공유 메모리(shared memory)를 사용한 프로세스간 통신 관련해 질문 드립니다.
유닉스 운영체제에서 공유 메모리를 사용해 프로세스간 통신을 구현하던 도중 몇 날 며칠을 고민해도 도저히 해결이 안되는 문제가 있어 이렇게 질문 드리게 되었습니다.
현재 유닉스 환경에서 하나의 서버 프로세스와 두개의 클라이언트 프로세스(편의상 클라이언트1, 클라이언트2라고 하겠습니다)가 존재하고 서버 프로세스는 두 번의 fork() 함수 호출을 통해 두 개의 자식 프로세스(편의상 자식 프로세스1, 자식 프로세스2라고 하겠습니다)를 생성합니다.
자식 프로세스 1은 클라이언트1과 공유 메모리를 통해 통신하고, 자식 프로세스 2는 클라이언트2와 공유 메모리를 통해 통신하며 이 두 개의 공유 메모리는 서로 다른 키값을 지정하여 <자식 프로세스1, 클라이언트1>과 <자식 프로세스2, 클라이언트2>는 서로 다른 공유 메모리를 통해 통신하는 상황입니다. 더불어 클라이언트와 자식 프로세스 간에 공유 메모리의 접근을 동기화하기 위해 서로 다른 이름의 Named 세마포어 2개를 사용하고 있습니다.
현재 발생하는 문제는 서버 프로세스를 먼저 실행하고 자식 프로세스1을 실행하면 정상적으로 작동이 되지만, 그 뒤에 이어서 자식 프로세스2를 실행하면 공유 메모리의 내용을 읽어오는 memcpy() 함수가 정상적으로 리턴되지 못하고 자식 프로세스2가 바로 종료되어 버립니다. 진행과정을 출력해본 결과 공유메모리를 detach하고 삭제하는 등의 정상적으로 종료되는 과정이 모두 수행되지 못하고 부모 프로세스의 자식 프로세스2에 대한 waitpid() 함수가 호출되어 자식 프로세스2가 공유 메모리를 읽어오는 memcpy()함수의 실행 도중 비정상적으로 종료되는 것으로 추측하고 있습니다.
제가 가장 혼란스러운 점은 자식 프로세스1과 자식 프로세스2의 코드가 동일하며 클라이언트1과 클라이언트2의 코드도 키값과 Named 세마포어의 이름을 제외한 모든 코드가 동일하다는 점입니다.
서버 프로세스와 클라이언트 프로세스의 코드를 첨부하오니 문제의 원인을 지적해주시면 정말 감사하겠습니다...
서버 프로세스 코드
#include <stdio.h> #include <string.h> #include <pthread.h> #include <sys/shm.h> #include <fcntl.h> #include <semaphore.h> #include <unistd.h> #include <sys/types.h> #define SHMKEY1 (key_t)60031 #define SHMKEY2 (key_t)60039 void readSharedMemory(void* arg); void decode(void); void printDecode(void); struct threadInfo { sem_t* sem; char* shmaddr; }; char readfromshm[100]; char decodetext[100]; sem_t a; sem_t b; sem_t c; sem_t d; int main(void) { pthread_t thread1,thread2,thread3; pid_t pid1; pid_t pid2; int shmid1, shmid2; char* shmaddr1, shmaddr2; sem_t* sem1; sem_t* sem2; struct threadInfo child1info; struct threadInfo child2info; shmid1 = shmget(SHMKEY1, 100, IPC_CREAT|0666);//공유 메모리 생성 및 접근 shmaddr1 = (char*)shmat(shmid1, 0, 0); //프로세스의 논리 주소 공간에 부착 shmid2 = shmget(SHMKEY2, 100, IPC_CREAT|0666); shmaddr2 = (char*)shmat(shmid2, 0, 0); sem1 = sem_open("/my_sem1", O_CREAT, 0666, 0); //named 세마포어 생성 sem2 = sem_open("/my_sem2", O_CREAT, 0666, 0); //named 세마포어 생성 child1info.sem = sem1; child1info.shmaddr = shmaddr1; child2info.sem = sem2; child2info.shmaddr = shmaddr2; sem_init(&a, 0, 1); sem_init(&b, 0, 0); sem_init(&c, 0, 1); sem_init(&d, 0, 0); if((pid1=fork())<0)//1번 자식 프로세스 생성 { perror("[Child1]fork error\n"); exit(1); } else if(pid1 == 0)//1번 자식 프로세스 영역 { printf("[Child1]1번 자식 프로세스 생성\n"); //자식프로세스 영역 if(pthread_create(&thread1, NULL, readSharedMemory, (void*)&child1info)) { perror("[Child1]쓰레드1 생성 실패\n"); exit(1); } if(pthread_create(&thread2, NULL, decode, NULL)) { perror("[Clild1]쓰레드2 생성 실패\n"); exit(1); } if(pthread_create(&thread3, NULL, printDecode, NULL)) { perror("[Child1]쓰레드3 생성 실패\n"); exit(1); } pthread_join(thread1, NULL); printf("[Child1]thread1 종료\n"); pthread_join(thread2, NULL); printf("[Child1]thread2 종료\n"); pthread_join(thread3, NULL); printf("[Child1]thread3 종료\n"); shmdt(shmaddr1); printf("[Child1]공유 메모리 detach\n"); shmctl(shmid1, IPC_RMID, NULL); printf("[Child1]공유 메모리 제거\n"); printf("[Child1]종료\n"); exit(0); printf("[Child1]종료되지 않았다면 출력\n"); } if((pid2=fork())<0)//2번 자식 프로세스 생성 { perror("[Child2]fork error\n"); exit(1); } else if(pid2 == 0)//2번 자식 프로세스 영역 { printf("[Child2]2번 자식 프로세스 생성\n"); if(pthread_create(&thread1, NULL, readSharedMemory, (void*)&child2info)) { perror("[Child2]쓰레드1 생성실패\n"); exit(1); } if(pthread_create(&thread2, NULL, decode, NULL)) { perror("[Child2]쓰레드2 생성 실패\n"); exit(1); } if(pthread_create(&thread3, NULL, printDecode, NULL)) { perror("[Child2] 쓰레드3 생성 실패\n"); exit(1); } pthread_join(thread1, NULL); printf("[Child2]thread1 종료\n"); pthread_join(thread2, NULL); printf("[Child2]thread2 종료\n"); pthread_join(thread3, NULL); printf("[Child2]thread3 종료\n"); shmdt(shmaddr2); printf("[Child2]공유 메모리 detach\n"); shmctl(shmid2, IPC_RMID, NULL); printf("[Child2]공유 메모리 제거\n"); exit(0); } if(pid1>0 && pid2>0)//부모 프로세스의 영역 { printf("[Parent] 1번 자식 프로세스의 종료를 wait\n"); waitpid(pid1,NULL,0); //1번 자식 프로세스의 종료를 기다림 printf("[Parnet] 1번 자식 프로세스 종료\n"); printf("[Parent] 2번 자식 프로세스 종료를 wait\n"); waitpid(pid2,NULL,0); //2번 자식 프로세스의 종료를 기다림 printf("[Parent] 2번 자식 프로세스 종료\n"); } printf("[Parent]부모 프로세스 종료\n"); exit(0); } void readSharedMemory(void* arg) { int shmid; char* shmaddr; sem_t* sem; struct threadInfo* info = (struct threadInfo*)arg; char end[]="EOF"; sem = info -> sem; shmaddr = info -> shmaddr; while(1) { sem_wait(sem); printf("[thread1]readSharedMemory : Named 세마포어 1 감소\n"); sem_wait(&a); printf("[thread1]readSharedMemory : 세마포어a 1감소\n"); printf("[thread1]공유 메모리에서 읽기 시작\n"); memcpy(readfromshm, shmaddr, 99);//공유 메모리에서 읽어서 readfromshm배열에 저장 printf("[thread1]공유 메모리에서 읽기 종료\n"); if(strcmp(readfromshm,"EOF")==0) { printf("[thread1]readSharedMemory : EOF를 읽어 쓰레드 종료\n"); sem_post(sem); sem_post(&b); printf("[thread1]공유 메모리에서읽기 종료\n"); break; } printf("[thread1]readSharedMemory : 공유 메모리에서 데이터 읽음 -> %s\n", readfromshm); memset(shmaddr, 0, 100); //공유 메모리 비움 printf("[thread1]readSharedMemory : 공유 메모리 비움\n"); sem_post(sem); printf("[thread1]readSharedMemory : Named 세마포어 1증가\n"); sem_post(&b); printf("[thread1]readSharedMemory : 세마포어 b 1증가\n"); } pthread_exit(NULL); } void decode(void) { while(1) { sem_wait(&b); printf("[thread2]decode : 세마포어 b 1감소\n"); sem_wait(&c); printf("[thread2]decode : 세마포어 c1 감소\n"); if(strcmp(readfromshm,"EOF")==0) { printf("[thread2] 복호화 종료\n"); strcpy(decodetext, "EOF"); sem_post(&a); sem_post(&d); break; } for(int i=0; i<strlen(readfromshm); i++) { decodetext[i]=readfromshm[i] + 1; //readfromshm에 저장된 암호화된 텍스트를 복호화해서 decodetext에 저장 } strcpy(readfromshm,""); //readfromshm 배열을 비워줌 printf("[thread2]decode : 세마포어 a 1증가\n"); sem_post(&a); printf("[thread2]decode : 세마포어 d 1증가\n"); sem_post(&d); } pthread_exit(NULL); } void printDecode(void) { while(1) { sem_wait(&d); printf("[thread3]print : 세마포어 d 1감소\n"); if(strcmp(decodetext,"EOF")==0) { printf("[thread3]복호화 결과 출력 종료\n"); sem_post(&c); break; } printf("[thread3]print : 복호화 결과 : %s\n", decodetext); strcpy(decodetext, ""); //decodetext 배열 비워줌 printf("[thread3]print : 세마포어 c 1증가\n"); sem_post(&c); } pthread_exit(NULL); }
클라이언트1 코드
#include <stdio.h> #include <string.h> #include <pthread.h> #include <semaphore.h> #include <sys/shm.h> #include <sys/stat.h> #include <fcntl.h> #define SHMKEY (key_t)60031 void readFile(void); void checkFile(void); void sharedMemory(void); char NewsText[100]; //.txt 파일에서 읽은 내용을 일차적으로 저장하는 배열 char CheckedText[100]; //비속어 삭제 처리를 한 결과를 저장하는 배열 sem_t a; sem_t b; sem_t c; sem_t d; int main() { pthread_t thread1, thread2, thread3; int err; sem_init(&a, 0, 1);//unnamed 세마포어 초기화 sem_init(&b, 0, 0);//unnamed 세마포어 초기화 sem_init(&c, 0, 1);//unnamed 세마포어 초기화 sem_init(&d, 0, 0);//unnamed 세마포어 초기화 err = pthread_create(&thread1, NULL, readFile, NULL);//thread1에 readFile 함수 할당 if(err != 0) { printf("ERROR code is %d\n", err); pthread_exit(NULL); } err = pthread_create(&thread2, NULL, checkFile, NULL);//thread2에 checkFile 함수 할당 if(err != 0) { printf("ERROR code is %d\n", err); pthread_exit(1); } err = pthread_create(&thread3, NULL, sharedMemory, NULL); //thread3에 sharedMemory 함수 할당,인자로 name 세마포어의 주소 전달 if(err != 0) { printf("ERROR code is %d\n", err); pthread_exit(1); } pthread_exit(NULL); } void readFile(void)//.txt 파일에서 한 문장씩 읽어서 NewsText 전역 배열에 저장 { FILE* fp; int ch; char* res; fp = fopen("a.txt", "r"); //file open if(fp == NULL) { printf("[client]ReadFile : 파일이 열리지 않았습니다.\n"); pthread_exit(1); } printf("[client]ReadFile : 파일이 열렸습니다.\n"); while(1) { sem_wait(&a); printf("[client]ReadFile : 세마포어 a 1감소\n"); res = fgets(NewsText, sizeof(NewsText), fp); //.txt 파일에서 한문장씩 읽어서 NewsText 배열에 저장 if(res == NULL) //문장을 다 읽으면 fgets 함수가 NULL을 반환 { strcpy(NewsText, "EOF"); sem_post(&b); break; //반복문 탈출 } NewsText[strlen(NewsText) - 1] = '\0'; //문장의 끝에 개행문자를 제거하고 종료 문자 설정 printf("[client]ReadFile : %s\n", NewsText); printf("[client]ReadFile : 세마포어 b 1증가\n"); sem_post(&b); } fclose(fp); pthread_exit(NULL); } void checkFile(void) { char* token = NULL; while(1) { sem_wait(&b); printf("[client]CheckFile : 세마포어 b 1 감소\n"); sem_wait(&c); printf("[client]CheckFile : 세마포어 c 1 감소\n"); if(strcmp(NewsText,"EOF")==0) { strcpy(CheckedText, "EOF"); sem_post(&a); sem_post(&d); break; } for(int i=0; i<strlen(NewsText); i++) { CheckedText[i] = NewsText[i] - 1; //NewsText에 저장된 텍스트를 암호화해서 CheckedText 배열에 저장 } CheckedText[strlen(CheckedText)]="\0"; //CheckedText에 종료 문자 삽입 printf("[client]CheckFile : CheckedText 배열 출력 -> %s\n", CheckedText); strcpy(NewsText,""); //NewsText 배열 비움 printf("[client]CheckFile : 세마포어d 1증가\n"); sem_post(&d); printf("[client]CheckFile : 세마포어a 1 증가\n"); sem_post(&a); // printf("[client]CheckFile : 세마포어d 1증가\n"); // sem_post(&d); } pthread_exit(NULL); } void sharedMemory(void) { char* shmaddr; sem_t *sem = sem_open("/my_sem1", 0); //named세마포어 open if(sem == SEM_FAILED)//예외 처리 { perror("sem_open error"); pthread_exit(1); } int shmid = shmget(SHMKEY, 0, 0);//서버가 생성한 공유 메모리에 접근하기 위해 size인수와 mode 인수를 0으로 설정 if(shmid == -1) { printf("[client]sharedMemory : shmget 오류\n"); pthread_exit(NULL); } shmaddr = (char*)shmat(shmid, 0, 0);//공유 메모리 구역을 프로세스의 논리 주소 공간에 부착 while(1) { sem_wait(&d); printf("[client]sharedMemory : 세마포어 d 1감소\n"); if(strcmp(CheckedText,"EOF")==0) { memcpy(shmaddr, CheckedText, strlen(CheckedText)); sem_post(sem); printf("[client]공유 메모리에 쓰기 끝\n"); sem_post(&c); sem_wait(sem); break; } memcpy(shmaddr, CheckedText, strlen(CheckedText)); printf("[clinet]sharedMemory : 공유 메모리 영역에 %s 쓰기 완료\n", CheckedText); strcpy(CheckedText, ""); //CheckedText 배열 비움 printf("[client]sharedMemory : Named 세마포어 1증가\n"); sem_post(sem); printf("[client]sharedMemory : 세마포어 c 1증가\n"); sem_post(&c); sleep(2); sem_wait(sem);//서버가 Named 세마포어의 해제를 기다림 printf("[client]sharedMemory : Named 세마포어 1감소\n"); } pthread_exit(NULL); }
클라이언트2 코드
#include <stdio.h> #include <string.h> #include <pthread.h> #include <semaphore.h> #include <sys/shm.h> #include <sys/stat.h> #include <fcntl.h> #define SHMKEY (key_t)60039 void readFile(void); void checkFile(void); void sharedMemory(void); char NewsText[100]; //.txt 파일에서 읽은 내용을 일차적으로 저장하는 배열 char CheckedText[100]; //비속어 삭제 처리를 한 결과를 저장하는 배열 sem_t a; sem_t b; sem_t c; sem_t d; int main() { pthread_t thread1, thread2, thread3; int err; sem_init(&a, 0, 1);//unnamed 세마포어 초기화 sem_init(&b, 0, 0);//unnamed 세마포어 초기화 sem_init(&c, 0, 1);//unnamed 세마포어 초기화 sem_init(&d, 0, 0);//unnamed 세마포어 초기화 err = pthread_create(&thread1, NULL, readFile, NULL);//thread1에 readFile 함수 할당 if(err != 0) { printf("ERROR code is %d\n", err); pthread_exit(NULL); } err = pthread_create(&thread2, NULL, checkFile, NULL);//thread2에 checkFile 함수 할당 if(err != 0) { printf("ERROR code is %d\n", err); pthread_exit(1); } err = pthread_create(&thread3, NULL, sharedMemory, NULL); //thread3에 sharedMemory 함수 할당,인자로 name 세마포어의 주소 전달 if(err != 0) { printf("ERROR code is %d\n", err); pthread_exit(1); } pthread_exit(NULL); } void readFile(void)//.txt 파일에서 한 문장씩 읽어서 NewsText 전역 배열에 저장 { FILE* fp; int ch; char* res; fp = fopen("b.txt", "r"); //file open if(fp == NULL) { printf("[client]ReadFile : 파일이 열리지 않았습니다.\n"); pthread_exit(1); } printf("[client]ReadFile : 파일이 열렸습니다.\n"); while(1) { sem_wait(&a); printf("[client]ReadFile : 세마포어 a 1감소\n"); res = fgets(NewsText, sizeof(NewsText), fp); //.txt 파일에서 한문장씩 읽어서 NewsText 배열에 저장 if(res == NULL) //문장을 다 읽으면 fgets 함수가 NULL을 반환 { strcpy(NewsText, "EOF"); sem_post(&b); break; //반복문 탈출 } NewsText[strlen(NewsText) - 1] = '\0'; //문장의 끝에 개행문자를 제거하고 종료 문자 설정 printf("[client]ReadFile : %s\n", NewsText); printf("[client]ReadFile : 세마포어 b 1증가\n"); sem_post(&b); } fclose(fp); pthread_exit(NULL); } void checkFile(void) { char* token = NULL; while(1) { sem_wait(&b); printf("[client]CheckFile : 세마포어 b 1 감소\n"); sem_wait(&c); printf("[client]CheckFile : 세마포어 c 1 감소\n"); if(strcmp(NewsText,"EOF")==0) { strcpy(CheckedText, "EOF"); sem_post(&a); sem_post(&d); break; } for(int i=0; i<strlen(NewsText); i++) { CheckedText[i] = NewsText[i] - 1; //NewsText에 저장된 텍스트를 암호화해서 CheckedText 배열에 저장 } CheckedText[strlen(CheckedText)]="\0"; //CheckedText에 종료 문자 삽입 printf("[client]CheckFile : CheckedText 배열 출력 -> %s\n", CheckedText); strcpy(NewsText,""); //NewsText 배열 비움 printf("[client]CheckFile : 세마포어d 1증가\n"); sem_post(&d); printf("[client]CheckFile : 세마포어a 1 증가\n"); sem_post(&a); // printf("[client]CheckFile : 세마포어d 1증가\n"); // sem_post(&d); } pthread_exit(NULL); } void sharedMemory(void) { char* shmaddr; sem_t *sem = sem_open("/my_sem2", 0); //named세마포어 open if(sem == SEM_FAILED)//예외 처리 { perror("sem_open error"); pthread_exit(1); } int shmid = shmget(SHMKEY, 0, 0);//서버가 생성한 공유 메모리에 접근하기 위해 size인수와 mode 인수를 0으로 설정 if(shmid == -1) { printf("[client]sharedMemory : shmget 오류\n"); pthread_exit(NULL); } shmaddr = (char*)shmat(shmid, 0, 0);//공유 메모리 구역을 프로세스의 논리 주소 공간에 부착 while(1) { sem_wait(&d); printf("[client]sharedMemory : 세마포어 d 1감소\n"); if(strcmp(CheckedText,"EOF")==0) { memcpy(shmaddr, CheckedText, strlen(CheckedText)); sem_post(sem); printf("[client]공유 메모리에 쓰기 끝\n"); sem_post(&c); sem_wait(sem); break; } memcpy(shmaddr, CheckedText, strlen(CheckedText)); printf("[clinet]sharedMemory : 공유 메모리 영역에 %s 쓰기 완료\n", CheckedText); strcpy(CheckedText, ""); //CheckedText 배열 비움 printf("[client]sharedMemory : Named 세마포어 1증가\n"); sem_post(sem); printf("[client]sharedMemory : 세마포어 c 1증가\n"); sem_post(&c); sleep(2); sem_wait(sem);//서버가 Named 세마포어의 해제를 기다림 printf("[client]sharedMemory : Named 세마포어 1감소\n"); } pthread_exit(NULL); }
클라이언트1이 읽는 a.txt파일과 클라이언트2가 읽는 b.txt파일을 첨부파일로 추가합니다
읽어주셔서 정말 감사합니다.
첨부 | 파일 크기 |
---|---|
클라이언트1이 읽는 txt 파일 | 63바이트 |
클라이언트2가 읽는 txt 파일 | 65바이트 |
보다 보니 서버 프로세스 main()에서main()
보다 보니 서버 프로세스 main()에서
이렇게 호출하고 있는데 main이 2번 호출(생성)되고 있습니다. 만약 의도하신 바가 아니면
이런 실행을 원하셨던 건가요?
자식 프로세스의 자식 프로세스가 아닌 부모 프로세스에서 2개의 자식 프로세스를 생성하려고 합니다.
부모 프로세스에서 2개의 자식 프로세스와 2개의 공유 메모리를 생성합니다. 자식 프로세스1이 1번 공유 메모리를 사용해 통신하다가 종료되면 자식 프로세스2가 2번 공유 메모리를 사용해 통신하려고 합니다. 여기서 자식 프로세스1이 1번 공유 메모리를 사용하고 정상적으로 종료되는 부분까지는 문제가 없으나 이후 자식 프로세스2가 2번 공유 메모리에 접근하려는 순간 자식 프로세스2가 비정상적으로 종료되는 문제가 발생합니다... 혹시 원인이 무엇인지 알려주신다면 정말 감사하겠습니다ㅠㅠ
fork()를 N번 하면 자식 프로세스가 N개 생기는
fork()를 N번 하면 자식 프로세스가 N개 생기는 것이 아니라 자식 프로세스가 또 자식 프로세스를 만들기 때문에 2^N 개만큼 프로세스가 생깁니다.
지금은 일단 이것밖에 안보이는데 여분으로 생기는 자식 프로세스는 바로 종료해서 없애서 부모 1개, 자식 2개로 맞추고 해보세요.
댓글 달기