세마포어
what ?
<세마포어란?>
- 세마포어는 프로세스간 통신에서 발생하는 동기화문제를 해결하기 위해서 사용하는 프로세스간통신에 사용되는 방법중 하나이다.
- 공유데이터를 액세스하는 프로세스를 한순간에 하나로 제한하는 방법을 사용한다.
- 세마포어란,
"어떤 공유데이터(프로세스에서 같이 사용하는 변수를 예로 들수있다)에 대해 "현재 사용 가능한 데이터의 수""이다.
<이진 세마포어 S가 0일때와 1일때>
=> 세마포어의 정의 해석 : 세마포어가 0일때 현재 사요아능한 공유데이터에 대해 사용가능한 데이터의 수가 0이기에 접근할 수 없다는 것을 의미하고 1일때는사용가능한 것을 의미한다.
##이때의 세마포어는 S=0혹은 S=1로 사용불가능, 사용가능으로 두개의 값만 가질 수 있는 이진 세마포어##
##만약 공유데이터가(아래 사진의 예제에서 X변수) 두개 이상일때는 S를 이진세마포어가 아닌 카운트 세마포어를 사용한다## => 공유데이터의 수 N개라면 세마포어의 최고 값은 5개가 된다 = 최대 사용가능한 공유데이터 개수 5개
how? 어떻게 사용?
"크리티컬 영역"에서 공유데이터에 접근하기 전에 세마포어 값을 확인
S = 1 일때 => 사용가능 => S=0으로 만든후 공유데이터에 접근
S = 0 일때 => 사용불가능 (블록상태로) => S = 1 일때 깨어나 공유데이터에 접근가능
<semget()을 통한 세마포어 생성>
int semget(key_t key, int nsems, int semflg);
// key를 통한 세마포어 구분 -> 다른 프로세스가 해당 세마포어를 사용하기 이해 key값을 알아야함
// nsems : 세마포어 집합을 구성하는 멤버수
// semget()가 정상 수행되면 세마포어 객체와 객체 ID가 리턴
//flag값은 공유메모리처럼 실행하면 됨
//semget() 정상 수행시 생서되는 세마포어 객체 소개
struct semid_ds{
struct ipc_perm sem_perm;//ipc_perm구조체에는 key값과 uid,gid,mode등이 저장됨
time_t sem_otime; // 최근 세마포어의 조작(operation time)시간
time_t sem_ctime; //최근 병경시간(control method사용한 시간을 의미하는 듯)
struct sem *sem_base;//첫 세마포어의 포인터
struct wait_queue *eventn;
struct wait_queue *eventz;
struct sem_undo *undo;
ushort sem_nsens;//세마포어 넘버수
};
- 위의 코드 내용 중 semflg 종류
[이전에 공부한 공유메모리와 메시지 큐처럼 IPC_CREAT, IPC_EXCL를 사용할 수 있다]
<semop()을 통한 세마포어 operation(연산)>
세마포어의 값을 통해 크리티컬영역에서의 동기화문제를 해결할 것이기에 세마포어의 연산을 해줘야한다
//semop()을 통한 세마포어 값 연산
int semop(int semid, struct sembuf *operation, unsigned nsops);
//semid : semget()을 통해 생성된 ID
//구조체 operation : 세마포어 operation 구조체를 의미함
// nsops :두번째 인자가 몇개의 리스트를 가지고 있는지를 나타냄
struct sembuf{
short sem_num;//멤버 세마포어 번호로 0번이 첫 멤버 -> 몇번째 멤버 세마포어를 연산할지 구분
short sem_op;//세마포어 연산 내용 -> 한번에 사용하는 공유데이터 개수만큼 증가 혹은 감소
short sem_flg;//조작 플래그 : bitmask형태로 가지며 IPC_NOWAIT(넌블록)/SEM_UNDO(연산취소)등/ 0일때는 블록모드
}
- 위 코드에서 sembuf구조체에서 sem_flg값중 SEM_UNDO를 사용하는 경우
: 세마포어 연산 후 비정상적으로 프로세스가 종료될 경우 해당 세마포어의 값을 증가시킬 기회를 잃게 되기때문에 아예 세마포어 연산을 취소한다.
semctl()을 통한 세마포어 제어
//세마포어의 종료, 세마포어 값 읽기 및 설정, 특정 멤버 세마포어를 기다리는 프로세스 수 알기
int semctl(int semid, int memeber_index, int cmd, union semun semun semarg);
//member_index : 제어할 세마포어 멤버 번호
//cmd : 수행할 동작을 나타냄
// union semun : cmd종류에 따라 각각 다르게 사용될 수 있는 인자
union semun{
int val; // SETVAL을 위한 값
struct semid_ds *buf; //ICP_STAT, IPC_sET을 위한 버퍼
unsigned short int *array; // GETALL, SETALL을 위한 배열
struct seminfo *__bUf; //IPC_INFO를 위한 버퍼
void *___pad; // dummy
};
semctl() 사용을 위한 cmd종류
CMD종류 | CODE예시 | 설명 |
IPC_STAT | struct semid_ds semobj; //객체정의 union semun semarg; // semun 정의 semarg.buf = &semobj; //semun.buf를 semobj로 정의 semctl(semid,0,IPC_STAT, semarg);//semarg buf에 저장된 semogj 객체를 얻어옴 |
semid_ds 타입의 세마포어 객체에 대한 정보를 얻어오는 명령 union semun semarg인자에 세마포어 객체가 리턴 semarg.buf 포인터를 통해 세마포어 객체를 접근할 수 있음 IPC_STAT명령사용시, semctl()함수의 member_index인자를 사용하지 않음(0으로 설정) |
SETVAL | 2번 멤버 세마포어 값을 10으로 설정하는 code union semun semarg; unsigned short semvalue = 10; semarg.val = semvalue; semctl(semid, 2, SETVAL, semarg); |
세마포어 객체의 초기화시 필요 세마포어를 생성한 후에 공유데이터의 수를 설정해주는 작업 |
SETALL | unsigned short values = {3,14,1,25}; union semun semarg; semarg.array = values; semctl(semid, 0, SETALL, semarg); |
세마포어 집합 내의 모든 세마포어 값을 초기화 union semun semarg인자에 semarg.array에 초기값을 배열 형태로 넣어줌 all에 관여하기에 member_index는 사용되지 않음 |
GETVAL | 3번 멤버의 세마포어값을 얻는 code int n = semctl(setid, 3, GETVAL, 0); |
특정 멤버 세마포어 현재 값 확인명령 |
GETALL | 세마포어 집합의 멤버수가 총 3인경우 ->0~2까지 멤버가 존재 union semun semarg; unsinged short semvalues[3]; semarg.array = semvalues; //semvalues를 semarg의 array로 설정 semctl(semid, 0, GETALL, semarg); //semarg array에 모든 멤버에 대한 세마포어의 현재 값을 읽음 // semvalues에 저장됨 for(int i=0; i<3; i++) printf("%d번 멤버 세마포어의 값 = %d\n",i,semvalues[i]); |
모든 세마포어의 현재 값을 읽는데 사용 |
GETNCNT | semctl(semid, membet_index,GETNCNT,0); | 특정 멤버 세마포어를 기다리는 프로세스의 수를 union semun semarg없이 사용해 구함 |
GETPID | int pid = semctl(semid, member_index, GETPID,0); | 특정 멤버 세마포어에 대해 마지막으로 semop()을 사용해 세마포어를 계산을 수행한 PID를 알기 위해 사용 |
IPC_RMID | semctl(semid,0,IPC_RMID,0); | 세마포어 삭제 |
why?왜 사용?
공유메모리리에서는 두개의 프로세스가 동시에 access할 경우 shmbusyaccess를 통해 동기화문제가 발생해 같은 타이밍에 서로 공유메시지에 접근하는 문제가 발생한다. -> 메모리의 메시지를 잘못 사용할 수 있다는 문제점이 발생
<동기화문제>
ex) 프로세스 A와 프로세스 B가 있을 때
X값이 3인 상태에서 A -> B의 순서대로 차례로 실행했다면 : X값이 [3,4,4,3]이 될 것이다.
그러나, 동기화문제로 인해 A작업의 X++과정 이전에 B가 실행된다면 : X값이 [3,3,2,3]이 될 것이다.
뿐만 아니라, A작업의 X++과정 직후에 B가 실행된다면 : X값이 [3,4,3,3]이 될 것이다.
=> 보통 같은 변수로 여러 프로세스를 작업하려고 할 때 발생하며 이때 X를 같이 사용하기 때문이다.
=> 이러한 문제는 멀티프로세스 혹은 멀티스레드를 지원하는 운영체제에서는 언제나 발생이 가능하다.
:다음에 어떤 프로세스가 실행될지를 응용 프로그램에서 예측할 수 없기때문이다.
=> 동기화문제가 발생할 수 있는 영역 [critical region] 크리티컬 영역이라고 부르며 A와 B의 프로세스에서 변수 X를 ACCESS하는 세 문장이 크리티컬 영역이다.
따라서 동기화문제를 해결해줘야하고 이때 세마포어를 사용한다.
세마포어 사용예제 : pen_and_note.c 코드
만약 연필 3자루 노트 3권이 있을 때 각 프로세스는 연필한자루와 노트 한권을 동시에 사용하여 작업을 한다.
이때 프로세스는 총 10개이고 1개의 프로세를 통해 semget()후에 9개에서는 생성된 세마포어ID를 통해 사용할 수 있다.
pen_and_note.c 알고리즘
1) 세마포어 객체 생성을 통한 semid생성
semid = semget(mykey, 2, IPC_CREAT}mode);
=>작업에 관연하는 변수 2가 2개여서 nsems가 2 [operation작업시 sembuf에서 sem_nu에서 0번을 연필, 1번을 노트로 설정한다고 가정]
=> 입력받은 key값이 mykey
2) 세마포어 멤버별 값 초기화
3) 세마포어 값을 증가 또는 감소시키기 위한 sembuf구조체 정의
struct sembuf increase[]={{0,+1,SEM_UNDO},{ 1, +1,SEM_UNDO} }; // 연필과 노드를 하나의 작업시에 모두 1씩 증가
struct sembuf decrease[]={{0,-1,SEM_UNDO},{1,-1,SEM_UNDO}}; //연필과 노드를 하나의 작업이 끝나며 모두 1씩 감소
4) 세마포어 값 감소
semop(semid, &decrease[0],1);//0번이 연필일때. 연필 사용개수를 1번에 1개 줄임 -> 1번진행
semop(semid, &decrease[1],1);//1번이 노트일때. 노트 사용개수를 1번에 1개 줄임 -> 1번진행
5) 연필과 노트 사용
6) 세마포어 값 증가
semop(semid, increase[0],1); //0번이 연필일때. 연필 사용개수를 1번에 1개 늘림 -> 1번 진행
semop(semid, increase[1], 1);////1번이 노트일때. 노트 사용개수를 1번에 1개 늘림 -> 1번진행
이후 우분투를 사용해 pen_and_note에 대한 코드를 작성해 돌려보기로 하자.