CS/운영체제

[Chapter 3] 개념 이해를 위한 운영체제 [병행성 - 리눅스 병행성]

devrabbit22 2025. 2. 25. 16:26

스레드

  • 프로세스 내부에서 생성된 스래드들은 그 프로세스에게 할당된 모든 자원을 서로 공유하면서 병행적으로 실행한다.
  • 리눅스에서 전역 변수로 선언된 메모리 공간은 스래드들의 공유 메모리로 사용된다.
  • 따라서 스래드 함수 내부에서 전역 변수를 접근하는 부분이 임계 영역에 해당되기 때문에 공유 자원을 접근하는 임계 영역에 대한 스래드 간의 상호 배제 및 동기화 기법이 요구된다.
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>

int count = 0;

void *thread(void *unused){
int i, j;
for(i = 0; i < 10000; i++)
	for(j = 0; j < 10000; j++){
    	count++;
        count--;
        }
}

void main(){
	int n;
    pthread_t tid[10];
    for(n = 0; n < 10; n++)
    	pthread_create(&tid[n], NULL, &thread, NULL);
    for(n = 0; n<10; n++)
    	pthread_join(tid[n], NULL);
        
    printf("count = %d\n", count);
}

pthread_create()를 이용해 10개의 스래드를 생성한다. 10개의 스래드들은 전역 변수(count)의 값을 1씩 증가 및 감소시키는 동일한 스래드 함수(*thread())를 병행적으로 실행한다. 리눅스에서 전역 변수로 선언된 메모리 공간은 스래드들의 공유 메모리로 사용되기 때문에 전역 변수를 접근하는 부분(count++, count--)이 임계 영역에 해당된다. 부모 스래드가 종료되면 프로세스 자체가 종료된다. pthread_join()은 자식 스레드가 종료될 때까지 부모 스레드로 하여금 기다리게 하기 위함이다. 

이와 같이 임계 영역을 가지고 있는 10개의 스래드들이 병행적으로 실행될 때 스래드들의 결정성이 보장되지 못한다.

이 결정성을 보장하기 위해서는 동기화 기능이 요구된다.

 

 

이러한 임계 영역을 가지고 있는 스래드들이 병행적으로 실행될 떄 발생할 수 있는 경쟁 조건의 문제를 해결하기 위하여 리눅스에서는 스래드 뮤텍스(pthread_mutexes), 스래드 세마포어(pthread_ semaphore)의 기능을 제공하고 있다.

 

스래드 뮤텍스(pthread_mutexes)

뮤텍스는 "MUTual Exclusion locks"의 약자로써 병행 스래드들로 하여금 공유 자원에 대한 상호 배제적 접근을 허용하는 기법이다.

리눅스에서 스래드 뮤텍스를 사용하는 방법은 다음과 같다.

  1. 뮤텍스 타입 변수를 선언한다.
    pthread mutex_t mutex;
  2. 선언된 뮤텍스 타입의 변수의 속성을 초기화한다.
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutex_init(&mutex, &attr);
    속성을 기본값으로 초기화할 경우 NULL로 선언한다.
    pthread_mutex_init(&mutex, NULL);

혹은 다음과 같이 뮤텍스 타입 변수 선언 및 기본 속성 값으로 초기화할 수 있다.

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

    3. 뮤텍스 변수에 대한 잠금을 시도한다.
        pthread_mutex_lock(&mutex);

    4. 마지막으로 뮤텍스 변수의 잠금을 해제한다.
        pthread_mutex_unlock(&mutex);

아래 예제는 리눅스에서 제공하는 뮤텍스를 이용하여 임계 영역 (count++, count--)의 문제를 해결한 것이다.

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>

int count = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
void *thread(void *unused){
	int i, j;
    for(i = 0; i < 10000; i++)
    	for(j = 0; j < 10000; j++){
        	pthread_mutex_lock(&m);
            count++;
            count--;
            pthread_mutex_unlock(&m);
            }
}

void main(){
	int n;
    pthread_t tid[10];
    for(n = 0; n < 10; n++)
    	pthread_create(&tid[n], NULL, &pthread, NULL);
    for(n = 0; n < 10; n++)
    	pthread_join(tid[n], NULL);
    
    printf("count = %d\n", count);
}

스래드 함수의 for 루프 내부를 임계 영역으로 취급하여 뮤텍스를 사용하여 임계영역의 문제를 해결하였다. 그러나 첫 번째 스래드가 임계 영역에서 빠져 나오자마자 곧바로 다시 임계 영역에 들어가기 위해 pthread_mutex_lock()을 시도하기 때문에 두 번째 스래드는 첫 번째 스래드가 종료된 후에 임계 영역에 들어갈 수 있게 됨을 유의해야 한다.

 

스래드 세마포어

리눅스에서는 스래드를 위한 세마포어와 프로세스를 위한 세마포어를 제공하고 있다. 

 

리눅스에서 스래드 세마포어를 사용하는 방법

  1. 세마포어 타입 변수를 선언한다.
    sem_t s;
  2. 선언된 세마포어 타입의 변수 초기값을 부여한다.
    sem_init(&s, 0, 5); /*초기 값 = 5*/
  3. P, V 연산을 시도한다.
    sem_wait(&s);/*P연산*/
    sem_post(&s);/*V 연산*/

리눅스에서 제공하는 sem_wait()와 sem_post()는 스래드를 위한 P, V 연산에 각각 해당되며 세마포어 변수 S에 대한 sem_wait(S), sem_post(&S)는 아래와 같이 동작한다

sem_wait(S) : if(S == 0) then block();
	S:= S - 1
sem_post(S) : S : = S + 1;
if( (S == 1) && (waiting threads)
then wakeup(t);
  • 리눅스에서 세마포어 변수 값은 0 혹은 양수의 정수만을 허용한다. sem_wait(S)는 S 값을 검사한 후, 그 값이 -이면 대기 상태로 전환되고, 0이 아니면 S 값을 1 감소시킨다.
  • 반면 sem_post(S)는 S값을 1 증가시킨 후, S 값이 1이고 대기상태에서 기다리고 있는 스래드가 존재하면 대기 상태의 스래드들 중에서 하나의 스래드(t)가 준비 상태로 변환되도록 깨워준다.

세마포어를 이용하여 임계 영역(count++, count--)의 문제를 해결한 것이다.

#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#include<unistd.h>

sem_t s;
int count = 0;
void *thread(void *unused){
	int i, j;
    for(i =0; i<10000; i++)
    	for(j = 0; j<10000; j++){
        	sem_wait(&s);
            count++;
            count--;
            sem_post(&s);
        }
}

void main(){
	int n;
    sem_init(/7s, 0, 1);	// 세마포어 변수 초기값 = 1
    pthread_t tid[10];
    for(n = 0; n < 10; n++)
    	pthread_create(&tid[n], NULL, &thread, NULL);
    for(n = 0; n < 10; n++)
    	pthread_join(thd[n], NULL);
        
    printf("count = %d\n", count);
}

 

프로세스

리눅스에서 제공하고 있는 대표적인 IPC 기법과 동기화 기법

  • 파이프(pipe)
  • 메시지 큐(message queue)
  • 공유 메모리(shared memory)
  • 세마포어(semaphore)
  • 이들 중에서 파이프와 메시지 큐 기법은 운영체제 영역을 공유 메모리로 사용하며 공유 메모리 기법은 사용자 영역을 공유 메모리로 사용한다.
  • 따라서 파이프와 메시지 큐 기법에서는 별도의 동기화 기법이 요구되지 않지만 공유 메모리 기법에서는 사용자와 영역의 공유 메모리 접근에 대한 동기화를 위한 추가적인 기능이 요구되며 세마포어를 이용하여 공유 메모리의 동기화를 지원한다.
  • 프로세스 간 통신을 위한 기법들은 기본적으로 다음과 같은 과정을 거치게 된다.
  1. 공유 메모리 확보
  2. 공유 메모리 접근
  3. 공유 메모리 반납

파이프

  • 리눅스에서 파이프는 파일 시스템에서 제공하는 파일의 일종으로써 일반 파일처럼 취급된다.
  • 파이프를 이용한 IPC 기법은 개념적으로 두 프로세스 사이에서 하나의 파이프를 공유하면서 데이터를 FIFO(First-In First-Out) 방식으로 데이터를 주고 받는다. 즉, 한 프로세스는 파이프에 데이터를 쓰고, 다른 프로세스는 파이프로부터 데이터를 읽어가는 단방향통신이다.
  • 양방향 통신을 위해서는 두 개의 파이프가 필요하다. 프로세스가 파이프로부터 데이터를 읽으려고 할 때, 파이프가 비어있다면 다른 프로세스에 의해 파이프에 데이터가 쓰여 질 때까지 대기 상태로 변환하여 기다려야 한다.
  • 또한 프로세스가 파이프에 데이터를 쓰려고 할 때, 파이프가 꽉 차게 되면 다른 프로세스에 의해 파이프의 빈 공간이 생길 때까지 대기 상태로 변화하여 기다려야 한다. 이러한 동기화는 운영체제 내부에서 지원하고 있기 때문에 별도의 동기화 적업이 필요 없다.
  • 파이프에는 "이름 없는 파이프(unnamed pipe)"와 "이름 있는 파이프(named pipe)"가 있다.

 

이름 없는 파이프

리눅스에서 이름 없는 파이프는 보통 '파이프'라 일걷는다. 이름 없는 파이프는 시스템 호출 pipe()에 의해 생성되며 오직 부모와 자식 프로세스 사이의 IPC 기법에만 사용할 수 있다.

 

파이프 시스템 호출

  • 사용자 프로세스에서 호출한 시스템 호출 'pipe(pipefd)'이 정상적으로 수행되었을 때 커널 내부에 생성되는 파이프와 사용자 프로세스에게 반환되는 파이프 파일 디스크립터 pipefd[0]은 읽기 전용이고, pipefd[1]은 쓰기 전용이다.
  • 파이프에 대한 읽기, 쓰기, 닫기 연산은 보통 파일처럼 read(), write(), close() 시스템 호출을 사용하며 시스템 호출 시 파이프 파일 디스크립터를 사용한다.
  • 부모 프로세스는 pipe(pipefd) 시스템 호출을 통하여 커널 내부에 이름 없는 파이프를 생성한 후, fork() 시스템 호출함으로써 부모 프로세스와 자식 프로세스는 파이프 파일 디스크립터를 공유하게 된다.
  • 부모 프로세스와 자식 프로세스는 읽기 전용 파이프 파일 디스크립터(pipefd[0])와 쓰기 전용 파이프 파일 디스크립터(pipefd[1])를 각각 닫음으로써 단방향 통신 채널을 형성한다.
  • 양방향 통신 채널을 형성하고자 할 경우, 자식 프로세스로 하여금 쓰기 전용 파이프 파일 디스크립터(pipefd2[1])에 쓰고, 부모 프로세스는 읽기 전용 파이프 파일 디스크립터(pipefd2[0])에서 읽으면 된다.

이름 없는 파이프를 통한 IPC 기법의 기본적인 과정

[과정 - 1] 공유 메모리 확보

  1. 읽기/쓰기 전용 변수를 선언한다.
    int pipfd[2]; //pipefd[0] 읽기, pipefd[1] 쓰기
  2. 파이프 채널을 생성한다.
    pipe(pipefd)//커널 내부에 공유 메모리 확보
  3. 자식 프로세스를 생성한다.
    fork();//공유 메모리의 주소를 공유

[과정 - 2] 공유 메모리 사용 : read(), write() 함수를 이용하여 데이터를 수신/ 전송한다.

  1. read(pipefd[0], msgbuf, msg_length);//pipefd[0]가리키는 공유 메모리(pipe)로부터 msg_length 바이트의 데이터를 읽어 msgbuf에 읽어 온다
  2. write(pipefd[1], msgbuf, msg_length);//msgbuf에 저장된 데이터의 msg_length 바이트를 pipefd[1] 가리키는 공유메모리(pipe)에 쓴다.

[과정 - 3] 공유 메모리 반납: 부모 프로세스가 종료될 때 반납된다.

 

이름 없는 파이프를 이용한 두 프로세스간 IPC 기법

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>

int main(){
	int pid, fd[2], status, n;
    char *msg = "God bless you!!!";
    char msgbuf[100];
    
    pipe(fd);
    pid = fork();;
    if(pid > 0){
    	close(fd[0]);
        write(fd[1], msg, strlen(msg)+1);
        wait(&status);
    }
    else if(pid == 0){
    	close(fd[1]);
    	read(fd[0], msgbuf, strlen(msg)+1);
    	printf("$s\n", msgbuf);
    }
}
  • 파이프를 생성하여 부모 프로세스는 문자열을 커널 내부의 파이프 채널에 쓴 후 자식 프로세스의 종료를 기다린다. 또한 자식 프로세스는 커널 내부의 파이프 채널로부터 문자열을 읽어 들인 후 그 내용을 화면에 출력시킨다. 이때 부모 프로세스에 의한 문자열 쓰기 동작이 항상 먼저 이루어지도록 자식 프로세스로 하여금 읽기 동작 전에 1초간 기다리도록 한다,
  • 이름 없는 파이프에 의한 IPC 기법은 제어의 흐름이 매우 간결하고 크기에 제한 없이 파이프를 통한 데이터 전송이 가능한 장점이 있다. 그러나 이 기법의 결점은 오직 부모와 자식 사이에서만 사용할 수 있으며 파이프와 관련된 프로세스가 종료하면 사라지기 때문에 채널이 영구적이지 못하다는 점이다. 그러한 결점을 보완하기 위하여 이름 있는 파이프에 의한 IPC 기법이 도입되었다.

 

이름있는 파이프

  • 리눅스에서 이름 있는 파이프는 보통 파일처럼 파이프에 이름을 부여하여 사용되는 IPC 기법으로써 보통 파일과 구별하여 'FIFO 파일'이라고 부른다. 파이프에 고유의 이름을 부여함으로서 이름 없는 파이프의 결점을 보완하여 부모와 자식 관계가 아닌 임의의 서로 다른 드 프로세스 사이에서도 통신이 가능하며 FIFO 파일이 삭제되기 전까지 영구적으로 존재할 수 있다. 이름 있는 파이프의 기본적인 동작 원리는 이름 없는 파이프와 동일하며 mkfifo()시스템 호출에 의해 FIFO 파일을 생성하고 unlink()시스템 호출로 삭제한다. FIFO 파일에 대한 접근은 보통 파일과 동일한 방법으로 open(), read(), write(), close() 시스템 호출을 사용한다.

 

이름있는 파이프를 통한 IPC 기법의 기본적인 과정

[과정-1] 공유 메모리 사용

  1. FIFO 파일("fifo")을 읽기/쓰기용으로 연다.
    fd = open("fifo", O_RONLY);//읽기용
    fd = open("fifo", O_WONLY);//쓰기용
  2. read(), write() 함수를 이용하여 데이터를 수신. 전송한다.
    read(fd, msgbuf, msg_length);//메시지 수신
    write(fd, msgbuf, msg_length);//메시지 전송
  3. [과정 - 3] 공유 메모리 삭제: FIFO 파일("fifo")을 삭제한다. unlink("ifo)

이름 있는 파이프를 이용하여 부모와 자식 관계가 아닌 두 프로세스 간 IPC 기법

#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<unistd.h>
#include<string.h>

int main(){
	int fd;
    char *msg = "God bless you!!!";
    
    mkfifo("fifo", 0600);
    fd = open("fifo", O_WRONLY);
    write(fd, msg, strlen(msg)+1);
    close(fd);
    unlink("fifo");
}

예제 (a)

#include< stdio.h>
#include<fcntl.h>
#include<unistd.h>

int main(){
	int fd;
    char msgbuf[1024];
    
    sleep(2);
    fd = open("fifo", O_RDONLY);
    read(fd, msgbuf, 1024);
    printf("%s\n", msgbuf;
}

예제 (b)

(a)에서 mkfifo() 시스템 호출을 통해 이름있는 파이프인 fifo 파일을 생성한 후 문자열을 그 파일에 쓴다. 반면 b에서 fifo 파일로 부터 문자열을 읽어 들인 후 그 내용을 화면세 출력시킨다. 이때 주의할 점은 스신 프로세스에서 FIFO 채널을 열기(open)위해서는 FIFO 파일이 반드시 존재해야 한다. 따라서 송신 프로세스가 먼저 실행되어야 한다. 마지막으로 송신 프로세스는 FIFO파일을 삭제하여야 하기 때문에 수신 프로세스보다 먼저 종료되어서는 안된다.

 

메시지 큐

  • 메시지 큐는 커널 내부에 메시지 큐를 생성하고 이를 공유함으로써 데이터를 주고 받는 IPC 기법이다. 메시지는 msgget()시스템 호출에 의해 성성된다.
  • 사용자 프로세스에서 호출한 msgget()시스템 호출이 정상적으로 실행되었을 때 커널 내부에 생성되는 메시지 큐의 구조를 나타내고 있다.

  • 시스템 호출 msgget()는 새로운 메시지 큐를 생성한 후 이를 요청한 사용자 프로세스에게 메시지 큐 식별자(msgqid)를 알려준다.
  • 이 메시지 큐 식별자는 커널 내부에 존재하는 여러 개의 메시지 큐를 구별하는데 이용된다.
  • 사용자 프로세스는 msgsnd()와 msgrcv()시스템 호출을 이용하여 메시지 큐에 메시지를 송수신 한 후 msgctl()시스템 호출에 의해 제거한다.

 

메시지 큐를 통한 IPC 기법의 기본적이 과정

[과정 - 1] 공유 메모리 확보 : msgget()함수를 이용하여 메시지 큐를 생성한다.

msgqid = msgget(key, msgflg) //메시지 큐 생성

[과정 - 2] 공유 메모리 사용 : msgrcv(), msgsnd() 함수를 이용하여 데이터를 송수신한다

msgsnd(msgqid, *msgp, msgsz, msgflg);

msgrcv(msgqid, *msgp, msgsz, msgty, msgflg);

[과정 - 3] 공유 메모리 삭제 : msgctl() 함수를 이용하여 메시지 큐를 삭제한다.

msgctl(msgqid. IPC_RMID, 0);//메시지 큐 삭제

#include"msg.h"
#include<stdio.h>
#include<string.h>

int main(){
	int msgqid;
    Mesg msg;
    
    strcpy(msg.m_data, "God bless you !!!");
    msg.m_type = 10L;
    msgqid = msgget(1235L, 0600|IPC_CREAT);
    msgsnd(msgqid, &msg, 1024, 0);
}

예제(a)

#include "msg.h"
#include<stdio.h>

int main(){
	int msgqid;
    Mesg msg;
    
    msgqid = msgget(1235L, 0);
    msgrcv(msgqid, &msg, 1024, 10L, 0);
    msgctl(msgqid, IPC_RMID, 0);
}

 

예제(b)

//msg.h
#include<sys.types.h>
#include<sys.ipc.h>
#include<sys/msg.h>

typedef struct{
	long m_type;
    char m_data[1024];
} Mesg;

예제(c)

위 예제들은 이름 있는 파이프를 이용하여 부모와 자식 관계가 아닌 두프로세스 간 IPC 기법을 보여준다. a는 메시지 큐를 생성한 후 m_type = 10인 메시지를 송신하고 b는 동일한 메시지 큐로부터 m_type = 10인 메시지를 수신하고 메시지 큐를 삭제한다. 주의할 점은 메시지 큐를 공유하기 위해서는 동일한 키 값을 사용하여야 한다.

 

공유 메모리

공유 메모리는 사용자 영역에 공유 메모리를 생성하고 이를 공유함으로써 데이터를 주고 받는 IPC 기법이다. 공유 메모리는 shmget() 시스템 호출에 의해 생성하고 shmctl() 시스템 호출에 의해 제거된다. 일단 생성된 공유 메모리는 shmat() 시스템 호출에 의해 사용자 프로세스의 주소 공간에 연결(attach)되고 shmdt()시스템 호출에 의해 해제(detach)된다.

 

공유 메모리를 통한 IPC 기법의 기본적인 과정

[과정-1] 공유 메모리 확보 : 사용자 영역에 공유 메모리를 생성 한다.

shmid = shmget(key, size, shmflg)

//key 값에 해당한 size 크기의 공유 메모리 생성

[과정- 2] 공유 메모리 사용 : shmat()함수를 이용하여 공유 메모리의 주소 프로세스의 변수(local)에 연결한 후 변수에서 직접 읽기/쓰기 한다.

local = shmat(shmid, *shmaddr, shmflag);

//shmid 공유 메모리를 프로세스의 변수(local)에 연결

[과정 - 3] 공유 메모리 삭제 : 

1. shmdt() 함수를 이용하여 공유 메모리의 주소를 프로세스의 변수로부터 분리한다.

shmdt(local); // 공유 메모리를 local로부터 분리

2. shmctl()함수를 이용하여 공유 메모리를 삭제한다.

shmctl(shmid, IPC_RMID, 0); //공유 메모리 삭제

 

두 프로세스 사이의 공유 메모리 사용법

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/shm.h>
#include<sys/wait.h>

int main(){
	int n, pid, shmid, *count, status;
    shmid = shmget(IPC_PRIVATE, sizeof(*count), 0666);
    count = (int *)shamt(shmid, NULL, 0);
    *count = 0;
    pid = fork();
    	if(pid > 0){
        	for(n - 0; n<100000; n++)
            	*count - *count+1;
            wait(&status);
            printf("count = %d", *count);
            shmdt(count);
            shmctl(shmid, IPC_RMID, NULL);
        }
        else if(pid == 0){
        	for(n = 0; n<100000; n++)
            	*count = *count -1;
        }
}

shmget()함수를 이용하여 공유 메모리를 생성한 후 shmat() 함수를 이용하여 공유 메모리의 주소를 프로세스의 포인터 변수(*count)에 연결한 후 초기 값을 0으로 설정한다. 부모 프로세스는 공유 메모리의 값을 1씩 100,000번 증가시키고, 자식 프로세스는 공유 메모리의 값을 1씩 100,000번 감소시킨다. 결국 최종적으로 공유 메모리의 값은 0이 될 것으로 예상된다.

 

세마포어

  • 세마포어(semaphore)는 프로세스의 동기화와 상호 배제를 위한 도구로써 공유 메모리에 의한 IPC 기법과 함께 사용된다.
  • 리눅스에서는 프로세스를 위한 세마포어와 스래드를 위한 세마포어를 구별하여 서로 다른 시스템 호출에 의해 제공되고 있음을 유의해야 한다.

시스템 호출 semget()는 새로운 세마포어를 생성한 후, 이를 호출한 사용자 프로세스에게 세마포어 식별자(semid)를 알려준다. 커널 내부의 세마포어는 테이블 구조이며 테이블의 각 엔트리는 키(key) 값으로 구별되며 여러 개의 세마포어 즉, 세마포어 집합을 가질 수 있다.

 

세마포어를 이용한 프로세스 동기화 기법의 기본적인 과정

[과정-1] 세마포어 생성: semget()을 이용하여 세마포어를 생성 한다.

semid = semget(key, nsems, semflg)

//key 값으로 nsems 개의 세마포어를 생성

[과정-2] 세마포어 초기 값 설정과 P, V 연산:

  1. semctl() 함수를 이용하여 세마포어 배열 semval[snum]의 초기 값(val)을 부여한다.
    semctl(semid, snum, SETVAL, val);
  2. semop() 함수를 이용하여 P, V 연산을 수행한다.
    struct sembuf p_op = {0, -1, SEM_UNDO};

[과정 - 3] 세마포어 삭제 : semctl() 함수를 이용하여 세마포어를 삭제한다

semctl(semid, IPC_RMID, 0);

//세마포어(semid) 삭제(IPC_RMID)

세마포어를 이용함으로써 임계 영역의 문제가 해결됨을 보여준다.

#include<stdio.h>
#include<unistd.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<sys/wait.h>

int main(){
	int n, pid, shmid, semid, *count, status;
    struct sembuf p_op = {0, -1, SEM_UNDO},
    			  v_op = {0, 1, SEM_UNDO};
    shmid = shmget(IPC_PRIVATE, sizeof(*count}, 0666};
    count = (int *)shmat(shmid, NULL, 0);
    semid = semget(IPC_PRIVATE, 1, 0666);
    semctl(semid, 0, SETVAL, 1);
    *count = 0;
    pid = fork();
    if(pid > 0){
    	for(n = 0; n < 100000; n++){
        	semop(semid, &p_op, 1);//P연산
            *count = *count + 1;
            semop(semid, &v_op, 1);//V연산
         }
         wait(&status);
         printf("count = %d\n", *count);
         shmdt(count);
         shmctl(shmid, IPC_RMID, NULL);
         semctl(semid, 0, IPC_RMID, NULL);
     }
     else if(pid == 0){
     	for(n = 0; n<100000; n++){
        	semop(semid, &p_op, 1);	//P연산
        	*count - *count - 1;
        	semop(semid, &v_op, 1);	//V연산
        }
    }
}

결과는 프로세스의 결정성이 보장되고 있음을 확인할 수 있다.


Reference

개념이해를 위한 운영체제

https://speardragon.github.io/system/system%20programming/System-Programming-12%EC%9E%A5.-%ED%8C%8C%EC%9D%B4%ED%94%84/

 

[System Programming] 12-1장. 파이프

 

speardragon.github.io

https://codingsmu.tistory.com/62

 

[유닉스 시스템 프로그래밍] Ch09. 파이프

(본 강의 노트는 한빛 아카데미의 책을 기반으로 하고 있습니다) 학습목표 - 파이프를 이용한 IPC 기법을 이해한다 - 이름 없는 파이프를 이용해 통신 프로그램을 작성할 수 있다 - 이름 있는 파이

codingsmu.tistory.com

https://devraphy.tistory.com/173

 

20. 프로세스 - IPC 기법(메세지 큐)

1. 메세지 큐(Message Queue) 큐(Queue) 자료구조를 이용한 IPC 기법 기본적으로 FIFO 방식으로 데이터를 전송한다. a) 그림으로 보는 메세지 큐 기법 간단하게, A라는 프로세스에서 데이터를 insert하면 B라

devraphy.tistory.com

https://velog.io/@octo__/%EC%84%B8%EB%A7%88%ED%8F%AC%EC%96%B4Semaphore

 

세마포어(Semaphore)

에츠허르 다익스트라(Edsger Wybe Dijkstra)가 제안한 교착 상태(DeadLock)에 대한 해법으로 두개의 원자적(Atomic) 함수로 제어되는 정수 변수로 멀티프로그래밍 환경에서 공유자원에 대한 접근 제어 방

velog.io