다음 위로 이전 목차 리눅스 프로그래머를 위한 가이드

시스템 호출:semctl() (SYSTEM CALL:semctl())


  SYSTEM CALL: semctl();                                                          
  PROTOTYPE: int semctl ( int semid, int semnum, int cmd, union semun arg );
    RETURNS: positive integer on success 
             -1 on error: errno = EACCESS (permission denied)
                                  EFAULT (invalid address pointed to by arg argument)
                                  EIDRM (semaphore set was removed)
                                  EINVAL (set doesn't exist, or semid is invalid)
                                  EPERM (EUID has no privileges for cmd in arg)
                                  ERANGE (semaphore value out of range)
  NOTES: Performs control operations on a semaphore set

semctl 시스템 호출은 세마퍼 집합에서 통제 동작(control operation)을 수행하는데 사용된다. 이 호출은 메세지 큐에 대해 동작을 수행하는데 사용되는 msgctl 시스템 호출과 유사하다. 두개의 시스템 호출의 아규먼트 목록을 비교해보면, semctl의 목록이 msgctl과 조금 다름을 알 수 있다. 세마퍼는 실제로 단일 개체라기 보다는 집합으로 구현됨을 기억하라. 세마퍼 동작에 대해서 IPC 키를 넘겨야할 필요는 없을 뿐만아니라 목적지(target) 세마퍼는 당연히 집합안에 있어야 한다.

명령어를 지정하기위해 cmd 아규먼트를 사용하는 두 시스템 호출은 IPC 객체에서 수행된다. 남겨진 차이점은 두 호출의 마지막 아규먼트이다. msgctl에서 마지막 아규먼트는 커널에서 사용되는 내부 자료 구조의 복사본을 나타낸다. 큐의 소유권과 허가사항을 바꾸거나 지정하는 것은 물론이고 메세지 큐에 대한 정보를 조회하기위해 이 구조체를 사용했음을 상기하라. 세마퍼에서는 부가적인 동작 명령어를 지원하기위해 마지막 아규먼트로 보다 복잡한 자료 타입이 요구된다. union의 사용은 중요한 단계에서 많은 초보 세마퍼 프로그래머들을 당황하게 만든다. 이러한 혼동을 막기위해 이 구조체를 주의깊게 해부할 것 이다.

semctl()의 첫번째 아규먼트는 semget 호출에 의해 반환된 키값이다. 두번째 아규먼트(semnu)는 동작의 목표가 되는 세마퍼 번호이다. 중요한 것은, 이것이 '0'값으로 표현되는 집합내의 첫번째 세마퍼(또는 오직 한개만 있는 경우)를 가지고 세마퍼 집합의 인덱스로 간주된다는 것이다.

cmd 아규먼트는 집합에 대해서 수행되어지는 명령어를 나타낸다. 잘 알려진 IPC_STAT/IPC_SET 명령어는 세마퍼 집합에서 지정되는 부가적인 명령어의 풍부함과 함께 표현된다.:

IPC_STAT
집합에 대한 semid_ds 구조를 조회하고, semun union안의 buf 아규먼트의 주소지에 저장한다.
IPC_SET
집합에 대한 semid_ds 구조의 ipc_perm 멤버의 값을 지정한다. semum union의 buf 아규먼트로 부터 값을 가져온다.
IPC_RMID
커널로 부터 집합을 제거한다.
GETALL
집합으로 부터 모든 세마퍼의 값을 얻는데 사용된다. 정수값들이 union의 배열 멤버에 의해 지정된 unsigned short integer 배열에 저장된다.
GETCNT
자원에 대해 현재 기다리고 있는 프로세스의 수를 반환한다.
GETPID
마지막 semop 호출을 수행한 프로세스의 PID를 반환한다.
GETZCNT
100% 자원 활용을 위해 현재 기다리고 있는 프로세스의 수를 반환한다.
SETALL
집합안의 모든 세마퍼의 값을 union의 배열 멤버안에 포함된 매칭되는 값으로 지정한다.
SETVAL
집합안의 개별적인 세마퍼의 값을 union의 val 멤버의 값으로 지정한다.
arg 아규먼트는 semun 타입의 예를 나타낸다. 이 특별한 연합체(union)는 linux/sem.h에 다음과 같이 선언되어있다.:


        /* semctl 시스템 호출에 대한 아규먼트 */
        union semun {
                int val;                /* SETVAL을 위한 값 */
                struct semid_ds *buf;   /* IPC_STAT & IPC_SET을 위한 버퍼 */
                ushort *array;          /* GETALL & SETALL를 위한 배열 */
                struct seminfo *__buf;  /* IPC_INFO를 위한 버퍼 */
                void *__pad;
        };

val
SETVAL 명령어가 수행될 때 사용된다.세마퍼에 지정될 값을 지정한다.
buf
IPC_STAT/IPC_SET에서 사용된다. 커널안에서 사용되는 내부 세마퍼 자료 구조의 복사본을 나타낸다.
array
GETALL/SETALL 명령어에서 사용되는 포인터. 집합안에서 모든 세마퍼 값들을 조회하거나 지정하는데 사용되는 정수값들의 배열을 가리키고 있어야 한다.
남아있는 아규먼트인 _buf_pad는 커널안의 세마퍼 코드내에서 내부적으로 사용되며 응용프로그램 개발자에게는 거의 쓸모가 없다. 사실상, 이런 두개의 아규먼트는 리눅스 운영체제에서 지정되며 다른 유닉스 구현에서는 찾을 수 없다.

이런 특별한 시스템 호출을 모든 시스템 V IPC 호출을 이해하는데 가장 어려운 점으로 꼽을 수 있으므로,실제로 이러한 다양한 예를 검사할 것이다.

다음의 짧은 코드는 넘겨진 세마퍼의 값을 반환한다. 마지막 아규먼트(union)은 GETVAL 명령어가 사용될 때 무시된다.:


int get_sem_val( int sid, int semnum )
{
        return( semctl(sid, semnum, GETVAL, 0));
}

프린터 예제를 다시 살펴보기위해, 모두 다섯개의 프린터 상태가 필요하다고 가정해 보자:


        #define MAX_PRINTERS 5

        printer_usage()
        {
                int x;

                for(x=0; x<MAX_PRINTERS; x++)
                        printf("Printer %d: %d\n\r", x, get_sem_val( sid, x ));
        }

새 세마퍼 값을 초기화하는데 사용되는 다음의 함수를 살펴보자:


void init_semaphore( int sid, int semnum, int initval)
{
        union semun semopts;    

        semopts.val = initval;
        semctl( sid, semnum, SETVAL, semopts);
}

semctl의 첫번째 아규먼트는 연합체(union)의 포인터가 아니라 복사본임을 주목하라. 우리가 아규먼트처럼 연합체(union)의 재배하에 있는 동안, 이 시스템 호출이 사용될 때 오히려 일반적인 실수를 보여줄 수 있다.

msgtool 프로젝트로 부터 IPC_STAT와 IPC_SET 명령어가 큐상의 허가사항을 변경하는데 사용된다는 것을 기억하라. 이런 명령어들이 세마퍼 구현상에서 지원되는 반면에 그것들의 사용은 내부 자료 구조가 단일 개체보다는 연합체(union)의 멤버로 부터 복사되고 조회되는 것과는 다소 다르다. 이 코드안에서 버그를 찾을 수 있겠는가?


/* 요구되는 허가사항은 텍스트로 넘겨져야 한다. (ex: "660") */

void changemode(int sid, char *mode)
{
        int rc;
        struct semid_ds mysemds;

        /* 내부 자료 구조의 현재 값을 얻는다 */
        if((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1)
        {
                perror("semctl");
                exit(1);
        }
                
        printf("Old permissions were %o\n", semopts.buf->sem_perm.mode);
                
        /* 세마퍼의 허가사항을 바꾼다 */
        sscanf(mode, "%o", &semopts.buf->sem_perm.mode);

        /* 내부 자료 구조를 업데이트한다 */
        semctl(sid, 0, IPC_SET, semopts);

        printf("Updated...\n");
}

이 코드는 집합에 대한 내부 자료 구조의 국소적인 복사본을 만들고, 허가사항을 수정하고, 커널에 IPC_SET을 반환하고자 한다. 어쨌든, semctl의 첫번째 호출은 즉각 EFAULT를 반환하거나 마지막 아규먼트(union!)에 대한 잘못된 주소를 반환한다. 게다가, 그 호출로 부터 에러를 체크하지 않았다면, memory fault가 발생한다. 왜?

IPC_SET/IPC_STAT 명령어가 연합체(union)의 buf 멤버를 사용하고 이것은 semid_ds 타입의 포인터(pointer)임을 기억하라. 포인터의 포인터의 포인터의 포인터! buf 멤버는 우리의 함수가 적당히 일하기 위한 유효한 저장위치를 가리키고 있어야만 한다. 보완된 버전을 살펴보자:


void changemode(int sid, char *mode)
{
        int rc;
        struct semid_ds mysemds;

        /* 내부 자료 구조의 현재 값을 얻는다 */

        /* 먼저 우리의 국소적인 복사본을 가리킨다! */
        semopts.buf = &mysemds;

        /* 다시 한번 시도해 보자! */
        if((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1)
        {
                perror("semctl");
                exit(1);
        }
                
        printf("Old permissions were %o\n", semopts.buf->sem_perm.mode);
                
        /* 세마퍼의 허가사항을 바꾼다 */
        sscanf(mode, "%o", &semopts.buf->sem_perm.mode);

        /* 내부 자료 구조를 업데이트한다 */
        semctl(sid, 0, IPC_SET, semopts);

        printf("Updated...\n");
}


이전:시스템 호출:semop() (SYSTEM CALL:semop()) 다음:semtool:상호작용 세마퍼 조종자

Copyright (c) 1996,1997 by Euibeom.Hwang & SangEun.Oh All Rights Reserved

Email To:Webmaster , Another address
LAST UPDATE Nov 25,1997
Created Nov 25,1997