[Unix Programming] Shared Memory
Shared Memory는 여러 process가 동시에 접근할 수 있는 메모리로, 두 개 이상의 process가 물리적 메모리의 일부를 공유한다. 여기서 주의할 점은 아무 process가 접근할 수 있는 것이 아니라, 잘 제어된 함수를 통해 요청한 process만 접근이 가능하다.
Shared Memory는 앞에서 봤던 IPC 기법들 중에서 가장 빠르고 효율적이다.
Shared Memory 기법의 사용 방법에 대해 간략히 살펴보면,
먼저 shmget 함수를 통해 접근 가능한 메모리 공간 할당을 요청한다.
그다음 shmat 함수를 통해 논리 메모리에 shmget을 통해 얻은 물리 메모리를 mapping 시켜준다.
마지막으로 사용하지 않으면 shmdt 함수를 호출하여 논리 메모리를 통해 물리 메모리에 접근하지 못하도록 한다.
다음은 shmget 함수이다.
#include <sys/shm.h>
int shmget(key_t key, size_t size, int permflags);
key는 앞에서 봤던 것과 마찬가지로 unique 해야 하고, 같은 메모리를 공유하려는 process들은 같은 key값을 사용해야 한다.
size는 메모리의 최소 크기를 지정하는 요소이다. 새 shared memory를 할당받는다면 이 값을 명시하고 이미 존재하는 메모리면 0을 할당한다.
permflags는 전과 마찬가지로 접근 허가 flag들이다.
return 값은 마찬가지로 shared memory의 id값이다.
다음은 shmat 함수이다. 이 함수는 shmget에 의해 생성된 메모리 영역을 자신의 논리적 자료 공간에 부착시킨다.
#include <sys/shm.h>
int *shmat(int shmid, const void *daddr, int shmflags);
shmid는 shmget에서 생성한 shared memoey id 값이 들어간다.
daddr는 자신의 논리적인 자료 공간을 의미한다. 이 값이 NULL일 경우, segment가 가용한 첫 번째 주소에 attached 되고, NULL이 아닐 경우, 해당 segment에 있는 주소 근처에 attached 된다.
마지막으로, shmat 함수는 논리적인 자료 공간을 사용할 수 있는 pointer를 return 한다.
shmflags
- SHM_RDONLY: read only
- SHM_RND: 페이지 경계에 맞춘다. (속도가 느려지는 것을 방지할 수 있다)
다음은 shmdt 함수이다. 이 함수는 shared memory를 process와 떼어낸다.
#include <sys/shm.h>
int shmdt(const void *shmaddr);
shmaddr는 논리적인 자료 공간, 즉 shamt에서 전달받은 pointer를 전달하여 수행한다.
성공 시 0, 실패 시 -1을 return 한다.
다음은 shmctl 함수이다. 이 함수는 shared memory를 제어하는 함수이다.
#include <sys/shm.h>
int shmctl(int shmid, int command, struct shmid_ds *shm_stat);
구조와 목적은 message passing에서 본 것과 동일하다.
shmid는 shared memory의 id를 뜻한다.
command를 통해 함수의 목적을 설정할 수 있으며, shmid_ds 구조체에는 shared memory의 상태정보가 들어간다.
command
- IPC_STAT: shared memory의 상태정보를 shm_stat에 저장
- IPC_SET: shared memory에 대한 제어변수들의 값을 지정 (저
- IPC_RMID: shared memory의 삭제
shmid_ds (unix 버전에 따라 member variables 가 차이를 보일 수 있다)
- shm_perm: 접근권한(공유메모리는 여러 개의 process가 동시에 접근 가능하므로, 파일과 같이 그 접근권한을 분명히 명시해줘야 한다.)
- shm_segsz: 할당된 memory의 byte 크기
- shm_atime: 가장 최근의 process가 segment를 attach한 시간
- shm_dtime: 가장 최근의 process가 segment를 detach한 시간
- shm_ctime: 마지막으로 이 구조체가 변경된 시간
- shm_cpid: 이 구조체를 생성한 process의 id
- shm_lpid: 마지막으로 작동을 수행한 process의 id
- shm_nattch: 현재 접근 중인 process의 수
다음은 shared memory의 예제 코드이다.
/* share_ex.h */
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#define SHMKEY1 (key_t)0x7010 /* 공유 메모리 키 */
#define SHMKEY2 (key_t)0x7015 /* 공유 메모리 키 */
#define SEMKEY (key_t)0x7020 /* 세마포 키 */
#define IFLAGS (IPC_CREAT|IPC_EXCL)
#define ERR ((struct databuf *)-1)
#define SIZ 80 /* 읽기와 쓰기를 위한 버퍼의 크기 */
/* 데이터와 읽은 계수를 저장한다. */
struct databuf {
int d_nread;
char d_buf[SIZ];
};
void writer(int semid, struct databuf *buf1, struct databuf *buf2);
void reader(int semid, struct databuf *buf1, struct databuf *buf2);
void sig_handler(int signo);
static int shmid1, shmid2, semid;
struct sembuf p1={0, -1, 0}, p2={1, -1, 0};
struct sembuf v1={0, 1, 0}, v2={1, 1, 0};
typedef union _semun {
int val;
struct semid_ds *buf;
ushort *array;
} semun;
void getseg(struct databuf **p1, struct databuf **p2) {
if ((shmid = shmget(SHMKEY1, sizeof(struct databuf), 0600|IFLAGS)) == -1) {
exit(1);
}
if ((shmid = shmget(SHMKEY2, sizeof(struct databuf), 0600|IFLAGS)) == -1) {
exit(2);
}
if ((*p1 = (struct databuf *)shmat(shmid1, 0, 0)) == ERR) {
exit(3);
}
if ((*p2 = (struct databuf *)shmat(shmid2, 0, 0)) == ERR) {
exit(4);
}
}
int getsem(void) {
semun x;
x.val = 0;
if ((semid = semget(SEMKEY, 2, 0600|IFLAGS)) == -1) {
exit(5);
}
if (semctl(semid, 0, SETVAL, x) == -1) {
exit(6);
}
if (semctl(semid, 1, SETVAL, x) == -1) {
exit(7);
}
return (semid);
}
void remobj(void) {
if (shmctl(shmid1, IPC_RMID, NULL) == -1) {
exit(9);
}
if (shmctl(shmid2, IPC_RMID, NULL) == -1) {
exit(10);
}
if (shmctl(semid, IPC_RMID, NULL) == -1) {
exit(11);
}
}
int main(void) {
int semid;
pid_t pid;
struct databuf *buf1, *buf2;
signal(SIGINT, sig_handler);
semid = getsem();
getseg(&buf1, &buf2);
switch (pid = fork()) {
case -1:
exit(12);
case 0:
writer(semid, buf1, buf2);
remobj();
break;
default:
reader(semid, buf1, buf2);
break;
}
exit(0);
}
void reader(int semid, struct databuf *buf1, struct databuf *buf2) {
for (;;) {
buf1->d_nread = read(0, buf1->d_buf, SIZ);
semop(semid, &v1, 1);
semop(semid, &p2, 1);
if (buf1->d_nread <= 0) {
return;
}
buf2->d_nread = read(0, buf2->d_buf, SIZ);
semop(semid, &v1, 1);
semop(semid, &p2, 1);
if (buf2->d_nread <= 0) {
return;
}
}
}
void writer(int semid, struct databuf *buf1, struct databuf *buf2) {
for (;;) {
semop(semid, &p1, 1);
semop(semid, &v2, 1);
if (buf1->d_nread <= 0) {
return;
}
write(1, buf1->d_buf, buf1->d_nread);
semop(semid, &p1, 1);
semop(semid, &v2, 1);
if (buf2->d_nread <= 0) {
return;
}
write(1, buf2->d_buf, buf2->d_nread);
}
}
void sig_handler(int signo) {
printf("graceful exit \n");
if (shmctl(shmid1, IPC_RMID, NULL) == -1) {
exit(13);
}
if (shmctl(shmid2, IPC_RMID, NULL) == -1) {
exit(14);
}
if (shmctl(semid, IPC_RMID, NULL) == -1) {
exit(15);
}
exit(16);
}
위 예제를 실행시키고 message를 입력하면 밑에서 동일한 message를 출력한다.
종료시킬땐 Ctrl+C로 인터럽트 시켜 종료시키면 된다.
위 예제 같은 경우, read함수를 통해 stdin, 즉 키보드에서 입력 받은 값을 메모리에 넣도록 되어 있다.
만약 변수를 통해 값을 부여하고 싶으면, shmat을 통해 void* 형태로 반환받은 논리 메모리 공간 address를 데이터를 작성할 변수에 형변환 시켜 참조하게 한 뒤, 해당 변수에 값을 넣으면 된다.
위와 같은 형태도 race condition 상황을 방지하기 위해 semaphore와 같은 synchronization 기법을 사용하여 처리하도록 한다.
'Computer Science > OS' 카테고리의 다른 글
[OS] 컴퓨터는 현재시간을 어떻게 알까? (0) | 2024.07.31 |
---|---|
[Unix Programming] ipcs & ipcrm (0) | 2021.12.13 |
[Unix Programming] Semaphore (0) | 2021.12.04 |
[Unix Programming] Message Passing (0) | 2021.12.04 |
[Unix Programming] IPC 기본 개념 (0) | 2021.12.03 |
댓글
이 글 공유하기
다른 글
-
[OS] 컴퓨터는 현재시간을 어떻게 알까?
[OS] 컴퓨터는 현재시간을 어떻게 알까?
2024.07.31 -
[Unix Programming] ipcs & ipcrm
[Unix Programming] ipcs & ipcrm
2021.12.13 -
[Unix Programming] Semaphore
[Unix Programming] Semaphore
2021.12.04 -
[Unix Programming] Message Passing
[Unix Programming] Message Passing
2021.12.04