상세 컨텐츠

본문 제목

UNIX - Shared Memory, system call[shmget, shmat, shmdt, shmctl]

Computer Science/UNIX

by 2021. 11. 29. 18:30

본문

반응형

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를 담당하게 된다.

반응형

관련글 더보기