공유 메모리
-
공유 메모리는 프로세스와 프로세스 사이의 공유할 수 있는 메모리 공간을 지정하는 방법이다.
-
공유 메모리를 사용하려면 3가지 과정이 필요하다.
- 공유 메모리 객체를 만드는 과정
- 공유 메모리 객체의 크기를 설정하는 과정
- 공유 메모리 객체를 프로세스 메모리 영역과 맵핑하는 과정
#include <sys/mman.h> // 함수 정의
#include <sys/stat.h> // mode 매크로의 정의
#include <fcntl.h> // 0_로 시작하는 상수 매크로의 정의
int shm_open(const char *name, int oflag, mode_t mode);
-
shm_open()
함수는 특정 이름을 갖는 공유 메모리 객체를 생성한 후에 그 객체에 접근할 수 있는 파일 디스크립터를 반환한다. -
open()
함수와 마찬가지로, 만약 같은 이름을 갖는 객체가 이미 존재하면 그 객체를 지시하는 파일 디스크립터를 반환한다. -
두번째 매개변수로 사용하는
int
형oflag
는open
계열 함수에서 공통적으로 사용하는 인자로 객체의 읽기 쓰기 속성과open
이 실행될 때의 행동들을 정의한다.
매크로 이름 | 설명 |
---|---|
O_RDWR | 객체를 읽기 쓰기가 가능한 상태로 설정한다. |
O_RDONLY | 객체를 읽기만 가능한 상태로 설정한다. |
O_CREAT | 해당 이름의 객체가 존재하지 않을 경우 새로운 객체를 생성한다. |
O_EXCL | 해당 이름의 객체가 존재하면 에러를 발생시킨다. |
O_TRUNC | 해당 이름의 객체가 존재하면 사이즈 0으로 만든다. |
int ftruncate(int fd, off_t length);
- 생성한 공유 메모리의 크기를 지정하는 과정은
ftruncate()
함수를 통해서 이루어진다.
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t off_set);
- 맵핑 영역의 보호 수준을 지정하는
prot
매개변수의 사용을 위해서 다음과 같이 매크로 값이 정의되어 있다.
매크로 이름 | 설명 |
---|---|
PROT_READ | 데이터의 읽기 작업이 가능 |
PROT_WRITE | 데이터의 쓰기 작업이 가능 |
PROT_EXEC | 데이터의 실행이 가능 |
PROT_NONE | 데이터의 접근이 불가능 |
-
매개변수
flag
는 맵핑된 데이터의 처리방식에 대한 정보를 지정하며MAP_SHARED
값으로 지정하면 다른 프로세스와 맵핑된 영역을 공유하게 된다. -
공유 메모리 영역의 맵핑을 제거하려면
munmap
함수를 사용한다.
#include <sys/mman.h>
int munmap(void *addr, size_t len);
addr
: 맵핑을 제거할 시작 주소len
: 제거할 사이즈
공유 메모리 사용 예제
- 공유 메모리 영역을 생성한 후 정수형 카운터를 관리한다.
- 부모 프로세스와 자식 프로세스는 카운터 값을 각각 증가시킨다.
- 부모 프로세스와 자식 프로세스 간에 공유되는 정보를 확인한다.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
#define SM_NAME "/Test"
int main(int argc, char *argv[]) {
int shmfd;
int pid;
int status;
int *test_value;
shmfd = shm_open(SM_NAME, O_RDWR | O_CREAT, 0777);
if (shmfd == -1) {
fprintf(stderr, "open error\n");
exit(1);
}
if (ftruncate(shmfd, sizeof(int)) == -1) {
fprintf(stderr, "truncate error\n");
exit(1);
}
test_value = mmap(0, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0);
close(shmfd);
*test_value = 10;
printf("test value: %d\n", *test_value);
pid = fork();
if (pid == 0) {
*test_value = *test_value + 1;
printf("child process test value: %d\n", *test_value);
sleep(3);
*test_value = *test_value + 1;
printf("child process test value: %d\n", *test_value);
exit(1);
} else if (pid < 0) {
fprintf(stderr, "fork failure\n");
exit(1);
}
else {
sleep(1);
*test_value = *test_value + 5;
printf("parent process test value: %d\n", *test_value);
wait(&status);
}
shm_unlink(SM_NAME);
return 0;
}
-
결과를 보면, 공유메모리를 10으로 한 변수를 자식 프로세스에서 1증가시킨 후
sleep()
상태로 들어간다. -
그 후에 부모프로세스에서 공유 메모리 값을 5 증가시킨다.
-
그리고 나서, 자식 프로세스에서 깨어나서 다시 공유 메모리 값을 증가시킨것을 확인할 수 있다.
-
앞의 결과에서 부모 프로세스와 자식 프로세스 사이에서 공유 메모리 값이 공유된 다는 것을 확인할 수 있다.
세마포어
-
공유 메모리에서는 메모리 영역의 맵핑 이후에 운영체제로 데이터를 복사하기 위한 쓰기 작업, 운영체제로부터 데이터를 가져오기 위한 읽기 작업이 요구되지 않는다는 장점이 있다.
-
하지만 정보의 갱신 여부를 다른 프로세스에게 알리는 작업과 공유 메모리 영역에 대한 프로세스 간의 동기화 작업은 프로그래머의 몫으로 남게 된다.
-
이번에 프로세스가 공유하는 자원에 대한 동기화를 지원해주는 방법중에 하나인 세마포어에 대해서 알아볼 것이다.
크리티컬 섹션에 대한 접근 방법
-
프로세스 A와 프로세스 B가 하나의 카운터 변수를 공유하고 있다고 가정을 해보자.
-
카운터 변수를 1 증가시키는 방법은 다음과 같은 순서로 이루어진다.
- 현재 카운터 변수 값을 읽는다.
- 현재 카운터 변수의 값에 1을 더한 값을 계산한다.
- 카운터 변수의 값을 2)번 결과물로 대체한다.
-
프로세스 A가 카운터 변수의 값을 1 증가시키는 행위를 하는 도중에 프로세스 B가 카운터 변수의 값을 증가시키는 행위를 시도하면 프로세스 B는 A가 증가시킨 수치를 반영하지 못한채로 카운터 변수의 값을 읽어 올 것이다.
-
따라서, 프로세스 A, B가 한번씩 호출되어 총 2의 값이 증가해야하지만, 1의 값만 증가하는 현상이 발생할 수 있다.
-
이와 같이 프로세스 간에 공동으로 관리되어야 하는 자원에 대한 읽기/쓰기가 이루어지는 코드 부분을 크리티컬 섹션이라고 부른다.
-
이 예제에서는 카운터 변수의 값을 읽은 후에 1을 증가시킨 새로운 변수로 카운터 변수의 값을 변경하는 일련의 과정이 크리티컬 섹션에 대응된다.
- 카운터 변수값의 동기화를 위해 하나의 프로세스가 카운터 변수에 접근하여 값을 읽고 그 값을 변화시키기 전에 다른 프로세스가 해당 변수에 접근하는 것을 방지할 필요가 있다.
- 세마포어 값이 양수일 때만 크리티컬 섹션에 진입할 수 있다.
- 프로세스는 크리티컬 섹션에 진입하면서 세마포어 값을 0으로 변경한다.
- 프로세스는 크리티컬 섹션을 빠져나오면서 세마포어 값을 증가시킨다.
세마포어의 사용
- 세마포어를 사용하려면 객체를 세마포어 객체로 생성해야 한다. 아래 함수를 통해서 세마 포어를 생성할 수 있다.
<oflag에 O_CREAT가 설정된 경우>
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
<oflag에 OCREAT가 설정되지 않은 경우>
sem_t *sem_open(const char *name, int oflag);
-
크리티컬 섹션에 접근하려면, 지금 크리티컬 섹션을 수행하고 있는 프로세스가 있는지를 먼저 확인해야한다.
-
또한, 크리티컬 섹션에 진입한 프로세스는 다른 프로세스가 크리티컬 섹션에 들어오는 것을 방지하기 위해서 세마포어 값을 감소시켜야 한다. 이러한 기능을 하는 함수는
sem_wait
함수이다.
int sem_wait(sem_t *sem);
-
sem_wait
함수는 세마포어의 값을 확인하고 세마포어의 값이 0인 경우 그 값이 양수가 될 때까지 프로세스를 대기 상태로 전환시킨다. -
세마포어 값이 0이라는 것은 다른 프로세스가 이미 해당 자원을 사용하는 것을 뜻한다.
-
세마포어 값이 양수가 되면 비로소
sem_wait
함수 호출 이후의 코드가 실행된다. -
크리티컬 섹션에 진입한 프로세스는 크리티컬 섹션에 해당하는 코드를 실행한 후에 다시 다른 프로세스가 크리티컬 섹션에 진입할 수 있도록
sem_post
함수를 통해서 자원을 반납해야한다.
int sem_post(sem_t *sem);
세마포어와 공유 메모리
-
공유 메모리로 카운터 변수를 맵핑하여 사용한다고 생각을 해보자. 공유 메모리 영역에 대한 동기화는 전적으로 프로그래머에게 위임되어 있다.
-
따라서 아래 예제에서는 세마포어를 사용하여 프로세스 간에 동기화된 카운터를 사용할 수 있는 프로그램을 작성해보았다.
- 공유 메모리 영역을 한 후 정수형 카운터를 관리한다.
- 부모 프로세스와 자식 프로세스는 카운터 값을 각각 증가시킨다.
- 부모 프로세스와 자식 프로세스 간에 공유되는 정보를 확인한다.
- 부모 프로세스와 자식 프로세스가 공동으로 접근할 수 있는 공유 메모리의 동기화를 위해서 세마포어를 사용한다.
nclude <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#define SM_NAME "/Test"
#define SEM_NAME "/SemTest"
int main(int argc, char *argv[]) {
int shmfd, pid, status, i;
int *test_value;
sem_t *sem;
sem = sem_open(SEM_NAME, O_RDWR | O_CREAT, 0777, 1);
if (sem == SEM_FAILED) {
fprintf(stderr, "sem open error\n");
exit(1);
}
shmfd = shm_open(SM_NAME, O_RDWR | O_CREAT, 0777);
if (shmfd == -1) {
fprintf(stderr, "shm open error\n");
exit(1);
}
if (ftruncate(shmfd, sizeof(int)) == -1) {
fprintf(stderr, "truncate error\n");
exit(1);
}
test_value = mmap(0, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0);
close(shmfd);
*test_value = 10;
printf("test value: %d\n", *test_value);
pid = fork();
if (pid == 0) { // child process
for (i = 0; i < 10; i++) {
sem_wait(sem);
*test_value = *test_value + 1;
printf("child process test value: %d\n", *test_value);
sem_post(sem);
}
exit(1);
} else if (pid < 0) {
fprintf(stderr, "fork failure\n");
exit(1);
} else {
for (i = 0; i < 10; i++) {
sem_wait(sem);
*test_value = *test_value + 5;
printf("parent process test value: %d\n", *test_value);
sem_post(sem);
}
wait(&status);
}
shm_unlink(SM_NAME);
sem_unlink(SEM_NAME);
return 0;
}
- 부모 프로세스나 자식 프로세스에서 세마포어 값을 얻기 위해서
sem_wait
을 호출하고 세마 포어 값이 0으로 만들었다면sem_post
로 얻은 세마포어 값을 증가시키기전까지는 자식 프로세스에서 접근하지 못한다.
참고 문헌
>> Home