Shared Memory
본래 서로 다른 프로세스간에는 memory를 공유할 수 없다. Shared Memory를 이용하면 가능하다.
shared memory는 client와 server사이에 데이터를 copy할 필요가없어 IPC에서 가장 빠르고,
server가 shared memory에 데이터를 저장하면, client는 서버의 작업이 끝날 때까지 access할 수 없다.
이는 shared memory를 synchronize하기 위해서 semaphore를 사용하기 때문에 server가 signal해야 client가 access할 수 있다.
share memory struct shmid_sd는 다음과 같은 구조이다.
struct shmid_ds {
struct ipc_perm shm_perm; /* see Section 15.6.2 */
size_t shm_segsz; /* size of segment in bytes */
pid_t shm_lpid; /* pid of last shmop() */
pid_t shm_cpid; /* pid of creator */
shmatt_t shm_nattch; /* number of current attaches */
time_t shm_atime; /* last-attach time */
time_t shm_dtime; /* last-detach time */
time_t shm_ctime; /* last-change time */
. . .
};
shm_perm에는 owner, creator의 uid / gid, mode가 들어간다.
shm_segsz에는 byte단위느 size가 들어간다.
shm_nattch는 attache한 프로세스의 개수이다.
shared memory를 프로세스에서 떼어내는 것을 detach라고 한다.
The shmget(2) system call
#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag);
// Returns: shared memory ID if OK, -1 on error
shmget은 다른 get system call과 사용법이 비슷하다.
The shmat (2) system call
#include <sys/shm.h>
void *shmat(int shmid, const void *addr, int flag);
// Returns: pointer to shared memory segment if OK, -1 on error
shmat은 return 값으로 shared memory segment의 시작주소 pointer를 반환한다.
위 그림과 같이 각각의 프로세스에서는 다른 주소라도 같은 shared memory region으로 사용할 수 있다.
The shmdt(2) system call
#include <sys/shm.h>
int shmdt(void *addr);
// Returns: 0 if OK, -1 on error
shmat을 통해서 얻은 shared memory의 시작주소 pointer를 addr로 넣으면, 해당 shared memory를 프로세스로부터 떼어낸다.
The shmctl(2) system call
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// Returns: 0 if OK, -1 on error
cmd에 해당하는 명령어를 통해 shmid_ds buf로 데이터를 가져온다.
cmd는 다음과 같다.
cmd | description |
IPC_STAT | copies shmid_ds to buf |
IPC_SET | set values of fields for shared memory segment shmid from values found in buf buf의 내용으로 shared memory에 설정 |
IPC_RMID | remove shared memory segment shmid and destroy corresponding shmid_ds shmid_ds와 일치하는 shared memroy segment를 지움 |
SHM_LOCK | Lock the shared memory segment in memory. disk에서 set out되지 않도록 lock 설정 |
SHM_UNLOCK | Unlock the shared memory segment. |
example - shmcopy
#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) 0x10 /* 공유 메모리 키 */
#define SHMKEY2 (key_t) 0x15 /* 공유 메모리 키 */
#define SEMKEY (key_t) 0x20 /* 세마포 키 */
/* 읽기와 쓰기를 위한 버퍼의 크기 */
#define SIZ 5*BUFSIZ
/* 데이터와 읽은 계수를 저장한다. */
struct databuf {
int d_nread;
char d_buf[SIZ];
};
typedef union _semun {
int val;
struct semid_ds *buf;
ushort *array;
} semun;
SHMKEY1, SHMKEY2가 두 개의 shared memory buf이고, 이 둘을 SEMKEY가 담고 있다.
#include "share_ex.h"
#define IFLAGS (IPC_CREAT |IPC_EXCL)
#define ERR ((struct databuf *)-1)
static int shmid1, shmid2, semid;
void getseg(struct databuf **p1, struct databuf **p2){
/* 공유 메모리 영역을 생성한다 */
if((shmid1=shmget(SHMKEY1,sizeof(struct databuf),0600|IFLAGS))==-1)fatal("shmget");
if((shmid2=shmget(SHMKEY2,sizeof(struct databuf),0600|IFLAGS))==-1)fatal("shmget");
/* 공유 메모리 영역을 부착한다. */
if ((*p1 = (struct databuf *) shmat (shmid1, 0, 0)) == ERR) fatal ("shmat");
if ((*p2 = (struct databuf *) shmat (shmid2, 0, 0)) == ERR) fatal ("shmat");
}
int getsem (void){ /* 세마포 집합을 얻는다 */
semun x; x.val = 0;
/* 두 개의 세마포를 갖는 집합을 생성한다 */
if ((semid = semget (SEMKEY, 2, 0600|IFLAGS)) == -1) fatal ("semget");
/* 초기값을 지정한다. */
if (semctl (semid, 0, SETVAL, x) == -1) fatal ("semctl");
if (semctl (semid, 1, SETVAL, x) == -1) fatal ("semctl");
return (semid);
}
void remobj (void) {/* 공유 메모리 식별자와 세마포 집합 식별자를 제거한다. */
if (shmctl (shmid1, IPC_RMID, NULL) == -1) fatal ("shmctl");
if (shmctl (shmid2, IPC_RMID, NULL) == -1) fatal ("shmctl");
if (semctl (semid, IPC_RMID, NULL) == -1) fatal ("semctl");
}
shmget으로 shared memory 영역을 생성하고, shmat을 통해 shared memory를 부착한다.
/* reader -- 화일 읽기를 처리한다. */
#include "share_ex.h"
/* 이들은 두 세마포를 위해 p()와 v()를 정의한다. */
struct sembuf p1 = {0, -1, 0}, p2 = {1, -1, 0};
struct sembuf v1 = {0, 1, 0}, v2 = {1, 1, 0};
void reader (int semid, struct databuf *buf1, struct databuf *buf2){
for(;;){
/* 버퍼 buf1으로 읽어들인다. */
buf1->d_nread = read(0, buf1->d_buf, SIZ);
/* 동기화 지점 */
semop (semid, &v1, 1);
semop (semid, &p2, 1);
/* writer가 수면하는 것을 피하기 위해 검사한다. */
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;
}
}
buf1의 read 실행 후 v1, p2를 실행하고, buf2의 read 실행 후 v1, p2를 실행한다.
#include "share_ex.h"
extern struct sembuf p1, p2; /* reader.c에 정의되어 있음. */
extern struct sembuf v1, v2; /* reader.c에 정의되어 있음. */
void writer (int semid, struct databuf *buf1,truct 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);
}
}
p1, v2를 실행 후 buf1의 write를 실행하고, p1, v2를 실행 후 buf2의 write를 실행한다.
#include "share_ex.h"
main(){
int semid;
pid_t pid;
struct databuf *buf1, *buf2;
/* 세마포 집합을 초기화한다. */
semid = getsem();
/* 공유 메모리 영역을 생성하고 부착한다. */
getseg (&buf1, &buf2);
switch (pid = fork()){
case -1:
fatal("fork");
case 0: /* 자식 */
writer (semid, buf1, buf2);
remobj ();
break;
default: /* 부모 */
reader (semid, buf1, buf2);
break;
}
exit(0);
}
fork로 child를 생성한다. 부모가 read, child가 write를 실행한다.
buf1, buf2의 pointer를 writer, reader 함수에 넘겨주고,
getseg를 통해 shared memory를 부착한다.
reader에서 buf1의 read가 끝났을 때 v1(singal)이 발생하여 writer의 p1(wait)에서 다음인 v2(signal)을 실행하고, reader가 p2(wait)에서 깨어나서 reader가 buf2의 read를, writer가 buf1의 write를 실행한다.
그 다음으로는 또 reader의 v1로 인해 writer의 p1가 깨어나고,
writer의 v2로 인해 reader의 p2가 깨어나서
각각 reader는 buf1의 read를, writer는 buf2의 write를 담당하게 된다.
'개발 · 컴퓨터공학' 카테고리의 다른 글
네트워크 프로그래밍 - Link State Routing (0) | 2021.12.01 |
---|---|
네트워크 프로그래밍 - Unicast Routing Protocols, Distance vector routing (0) | 2021.11.30 |
네트워크 프로그래밍 - ICMP(Error reporting messages, Query messages) (0) | 2021.11.25 |
네트워크 프로그래밍 - IP[Options, Checksum] (0) | 2021.11.24 |
네트워크 프로그래밍 - IP[Header, Datagrams, Fragmentation] (0) | 2021.11.23 |