IPC 방법 중 shared memory를 사용하는 방법에 대해서 알아보자.
shared memory에 대한 것은 다음 포스팅을 참고하면 좋다.
Shared Memory는 IPC 중 가장 빠른 방식이다. message passing 처럼 데이터를 kernel에 copy해야하는 일이 없기 때문이다.
하지만 운영체제가 IPC에 개입하지 않는 만큼, 동기화는 개발자가 직접 맞추어야 한다.
즉 데이터가 없을 때 데이터를 읽는 접근에 대한 것을 막아야 한다는 의미이다. 주로 semaphore를 사용해서 이를 처리한다.
Shared Memory 를 위한 syscall은 크게 4가지가 중요하다.
- shmget
: memory identifier를 가져온다. 이때 shared memory가 없으면 새로 생성한다.
이 syscall은 physical memory를 만드는 작업이다.
- shmat
: virtual memory에 physical memory를 연결한다.
- shmdt
: virtual memory와 physical memory의 연결을 해제한다.
shmdt는 shared memory가 사라지게 하는 것이 아니라 단지 연결을 끊는 것이다. 따라서 필요없어진 shared memory는 꼭 제거해야 한다.
- shmctnl
: shared memory에 대한 다양한 control을 담당한다.
예를 들면 삭제, fetch, lock 등등이다.
shared memory는 virtual memory management에 대한 이해를 기반으로 한다. 따라서 다음 포스팅을 참고하면 좋다.
page 단위로 구성한다면 다음과 같다.
이때 이 virtual address를 같은 physical address로 mapping하면 shared memory를 가진다.
shared library, 즉 dynamic linking도 이와 같은 원리이다.
System Call for Shared Memory
# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/shm.h>
int shmget(key_t key, int size, int flag);
함수 명세
return | 성공 시 shared memory ID, 실패 시 -1 |
description | physical memory에 shared memory를 생성한다. |
파라미터
key_t key | shared memory를 생성할 때의 key |
int size | shared memory의 크기 (이미 있는 shared memory를 불러오는 경우 0) |
int flag | shared memory의 mode (option) |
이렇게 생성한 shared memory를 process의 virtual memory에 붙여야 사용 가능하다.
# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/shm.h>
void *shmat(int shmid, void *addr, int flag);
함수 명세
return | 성공 시 shared memory segment의 virtual address에 대한 pointer, 실패 시 -1 |
description | virtual address를 shared memory와 연결한다. |
파라미터
int shmid | shared memory ID (physical memory), shmget의 return 값 |
void *addr | virtual address 시작 주소 설정 (0인 경우, kernel에 의해 첫번째로 가능한 주소가 연결된다.) |
int flag | shared memory mode (주로 0) - SHM_RND, SHM_RDONLY |
두번째 파라미터와 세번째 파라미터는 함께 쓰는 경우가 많다.
- addr != 0 and not SHM_RND
addr에 전달된 주소와 연결된다.
- addr != 0 and SHM_RND
(addr - (addr % SHMLBA))에 해당하는 주소와 연결된다.
- SHM_RDONLY
read-only mode로 연결된다.
# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/msg.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
함수 명세
return | 성공 시 0, 실패 시 -1 |
description | shared memory에 대한 여러 설정과 동작을 제어한다. |
파라미터
int shmid | shared memory ID (physical memory), shmget의 return 값 |
int cmd | shared memory control operations |
struct shmid_ds *buf | shared memory에 대한 메타정보 |
다음은 두번째 인자의 값으로 가능하다.
- IPC_STAT: shared memory에 shmid_ds 구조체를 fetch한다.
- IPC_SET: shmid_ds 구조체를 설정한다.
- IPC_RMID: system에서 shared memory segment를 지운다.
- SHM_LOCK: memory의 shared memory에 lock을 건다.
- SHM_UNLOCK: shared memory lock을 해제한다.
# include <sys/types.h>
# include <sys/ipc.h>
# include <sys/shm.h>
int shmdt(void *addr);
함수 명세
return | 성공 시 0, 실패 시 -1 |
description | shared memory의 연결을 해제한다. |
파라미터
void *addr | 연결된 shared memory의 시작 주소 |
shmat을 해서 얻은 virtual address pointer를 전달한다.
Example
shared memory 를 생성하고 메모리 주소를 출력해보자.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc, char *argv[])
{
int shmid;
char *ptr;
char *shmptr;
char Array[4000];
if ((ptr = (char *)malloc(10000)) == NULL)
exit(1);
if ((shmid = shmget(IPC_PRIVATE, 10000, SHM_R|SHM_W)) < 0)
{
free(ptr);
exit(1);
}
if ((shmptr = shmat(shmid, NULL, 0)) < 0)
{
free(ptr);
exit(1);
}
printf("Array from %p ~ %p\n", Array, Array + 4000);
printf("Stack: %p\n", &shmid);
printf("Heap from %p ~ %p\n", ptr, ptr + 10000);
printf("Shared Memory attached %p ~ %p\n", shmptr, shmptr + 4000);
if (shmdt(shmptr) < 0)
{
free(ptr);
exit(1);
}
if (shmctl(shmid, IPC_RMID, NULL) < 0)
{
free(ptr);
exit(1);
}
free(ptr);
return 0;
}
결과는 다음과 같다.
즉 shared memory는 heap과 stack의 중간쯤에 생성된다.
Example2
실제로 shared memory를 사용하여 IPC를 할때를 알아보자.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_KEY (0x900043)
#define SHM_SIZE 1024
#define SHM_MODE (SHM_R |SHM_W |IPC_CREAT)
int main(int argc, char *argv[]){
char *ptr;
char *pData
int shmid;
if((shmid = shmget(SHM_KEY, SHM_SIZE ,SHM_MODE))<0)
exit(1);
if((ptr = shmat(shmid, NULL, 0)) < 0)
exit(1);
pData = ptr;
printf("Received request: %s.....",pData);
sprintf(pData,"This is a reply from %d.",getpid());
if(shmdt(ptr) < 0)
exit(1);
if(shmctl(shmid, IPC_RMID, NULL) < 0)
exit(1);
return 0;
}
결국 shared memory도 메모리이기 때문에 데이터를 읽는 것과 똑같이 사용하면 된다.
따라서 shared memory를 가리킬 포인터로 ptr을 둔 다음, pData는 이 포인터를 가리키게 하여 pData로 데이터를 읽는다.
이때 메모리에 데이터가 없어도 프로세스는 waiting 상태에 진입하여 대기하지 않는다. 왜냐하면 운영체제가 sync를 맞추어주지 않기 때문이다. 따라서 signal 또는 shared memory 상에 플래그를 세우는 방식 등등을 사용해서 동기화를 직접 처리해야 한다.
'STUDY > linux' 카테고리의 다른 글
Synchronization in LINUX - POSIX Semaphore로 producer-consumer 해결하기 (0) | 2022.04.13 |
---|---|
IPC in LINUX - Message Passing (0) | 2022.04.02 |
Files and Directories in LINUX (0) | 2022.03.31 |
SIGNAL in LINUX (0) | 2022.03.29 |
Process and Threads in LINUX (0) | 2022.03.28 |