본문 바로가기
Ubuntu

IPC(Inter-Process-Communication) C언어 실습

by TYB 2024. 2. 19.
반응형

IPC(Inter-Process-Communication)

프로세스들은 상호간의 활동을 조정하기 위하여 프로세스간, 커널과 통신을 하기 위한 메커니즘임.

 

*IPC 기법 종류

1. Signal

    -초기 UNIX에서 사용했었고, 특정 이벤트를 종료할 때 사용

2. Pipe

    

3. Socket

    

4. Message Queue

    

5. Semaphore

    

6. Shared Memory(Kernel Memory)

    

 

멀티프로세스 기반의 다중접속 서버는 잘 안만듬. 이유는 하나의 프로세스마다 cpu에 많은 부담을 주게 되고 context는 굉장히 많은 메모리를 필요로 하므로 커널에도 부담이 됨. 프로세스 간 context swtiching 할 때도 굉장히 부하가 크고, 몇개 안될 때는 문제 안되겠지만 접속 수가 늘어날 수록 문제가 발생함.

그래서 다중접속 서버는 주로 멀티스레드로 구현을 하고, 대신 한정된 공유자원에 서로 다른 스레드가 동시에 접근하지 못하도록 세마포어, 뮤텍스 같은 deadlock을 막아주는 기술을 사용함. flag라고 생각하면 편함.

 

 

1. signal listen loop

ubuntu08@ubuntu08-VirtualBox:~/ipc$ vi sigloop.c
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

static void signal_hander(int signo) { // 시그널 핸들러 정의 
        printf("Catch SIGINT!, but no stop\n");
}

int main(void) {
        if (signal(SIGINT, signal_hander) == SIG_ERR) { // 핸들러 에러처리
                printf("Can't catch SIGINT! \n");
                exit(1);
        }
        for (;;) // 무한루프 
                pause();//process를 sleep상태로 둔다. interrupt에 의해서 깨어남.
        return 0;
}

pause와 sleep의 차이는 pause는 계속 잠들어 있어서 interrupt가 발생되야 깨는거고, sleep은 timeout이 있어서 시간이 지나면 인터럽트가 발생해서 깨어나는 거의 차이임.

ubuntu08@ubuntu08-VirtualBox:~/ipc$ gcc sigloop.c -o sigloop

가만히 냅두면 Sleep상태로 들어가고 ctrl c를 눌러서 interrupt( 2번) 발생시키면 sleep에서 깨어남
ctrl c로 프로세스가 종료가 안됨. ctrl z로 중단하고

 

중지 상태의 프로세스를 kill 하는 방법들이다.

ubuntu08@ubuntu08-VirtualBox:~/ipc$ sudo killall sigloop



ubuntu08@ubuntu08-VirtualBox:~/ipc$ sudo kill 8448



ubuntu08@ubuntu08-VirtualBox:~/ipc$ jobs

ubuntu08@ubuntu08-VirtualBox:~/ipc$ sudo kill %1

 

 

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

static void signal_hander(int signo) { // 시그널 핸들러 정의
        if(signo == SIGINT)
        printf("Catch SIGINT!, but no stop! signo:%d\n",signo);
        if(signo == SIGALRM)
        printf("Catch SIGALRM! signo:%d\n",signo);
}

int main(void) {
        if (signal(SIGINT, signal_hander) == SIG_ERR) { // 핸들러 에러처리
                printf("Can't catch SIGINT! \n");
                exit(1);
        }
        if (signal(SIGALRM, signal_hander) == SIG_ERR) { // 핸들러 에러처리
                printf("Can't catch SIGALRM! \n");
                exit(1);
        }
        for (;;) // 무한루프
                {
                        pause();
                        alarm(2);//2초 뒤에 알람을 울린다. 라는 뜻임.sighandler에 등록되어있으니까 handler를 호출한다는 뜻이기도 함.
                }
                                return 0;
}

 

sig int는 ctrl +c에 의해서 발생되는거고 번호는 2번,   sigalrm은 alarm에 의해서 발생되고 번호는 14번임.

signal의 번호별 기능을 나타냄.

ubuntu08@ubuntu08-VirtualBox:~/ipc$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

 

2) SIGINT    14) SIGALRM 

실행 결과

 


 

1. signal kill

 

ubuntu08@ubuntu08-VirtualBox:~/ipc$ vi sigkill.c

 

#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv)
{
        int pid, result;
        int sig_num;
        if (argc != 3) {
                printf("usage %s [pid] [signum]\n", argv[0]);
                exit(1);
        }
        pid = atoi(argv[1]);
        sig_num = atoi(argv[2]);
        result = kill(pid, sig_num);//해당 pid에게 sig_num라는 signal을 보내겠다.
        if (result < 0) {
                perror("To send Signal is failed\n");
                exit(1);
        }
        return 0;
}
ubuntu08@ubuntu08-VirtualBox:~/ipc$ gcc sigkill.c -o sigkill

 

하는 방법은 

sigloop를 먼저 실행시켜놓은 상태에서 sigloop의 pid를 확인하고

sigkill의 argv로 pid랑 보내고 싶은 signal의 number를 넣어주면 됨.

./sigloop의 pid인 9580이었음. 참고하세용

자 그럼

sigint(ctrl+c)를 받으면 종료하도록 프로그램을 바꾸고 싶죠?

 

ubuntu08@ubuntu08-VirtualBox:~/ipc$ vi sigloop.c
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

static void signal_hander(int signo) { // 시그널 핸들러 정의
        if(signo == SIGINT)
        printf("Catch SIGINT!, but no stop! signo:%d\n",signo);
                exit(0);
        if(signo == SIGALRM)
        printf("Catch SIGALRM! signo:%d\n",signo);
}

int main(void) {
        if (signal(SIGINT, signal_hander) == SIG_ERR) { // 핸들러 에러처리
                printf("Can't catch SIGINT! \n");
                exit(1);
        }
        if (signal(SIGALRM, signal_hander) == SIG_ERR) { // 핸들러 에러처리
                printf("Can't catch SIGALRM! \n");
                exit(1);
        }
        for (;;) // 무한루프
                {
                        pause();
                        alarm(2);//2초 뒤에 알람을 울린다. 라는 뜻임.sighandler에 등록되어있으니까 handler를 호출한다는 뜻이기도 함.
                }
                                return 0;
}

 

종료되쥬?

b


 

6. shared memory

ubuntu08@ubuntu08-VirtualBox:~/ipc$ vi sharedmemory.c

 

커널의 공유메모리 사용

#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(void) {
        int shmid, pid;
        char *shmaddr_parent, *shmaddr_child; // 공유 메모리 영역을 할당할 포인터 변수
        shmid = shmget((key_t)1234, 10, IPC_CREAT|0664); // 키값 생성
        //  shmget(key값, 메모리 크기, flag)
        if(shmid == -1) {
                perror("shmget error\n");
                exit(1);
        }

        pid = fork(); // 자식 프로세스 생성
        if(pid > 0) { // 2.부모 프로세스
                wait(0); // 자식 프로세스의 exit() 호출까지 대기
                shmaddr_parent = (char *)shmat(shmid, (char *)NULL, 0);
                //shm attach 부모프로세스에서 공유메모리에 값을 넣어주는거임.
                
                printf("%s\n", shmaddr_parent); // 공유 메모리 값을 읽음(read)
                shmdt((char *)shmaddr_parent);
        }
        else { // 1.자식 프로세스
                shmaddr_child = (char *)shmat(shmid, (char *)NULL, 0); // 공유 메모리 키를 변수에 매핑
                strcpy((char *)shmaddr_child, "Hello Parent!"); // 공유 메모리에 쓰기(write)
                shmdt((char *)shmaddr_child); // 포인터 변수를 공유 메모리에서 해제
                //shm detach 부모프로세스에서 공유메모리의 값을 읽고 제거한다는 뜻임.
                exit(0);
        }
        shmctl(shmid, IPC_RMID, (struct shmid_ds *)NULL); // 공유메모리를 커널영역에서 삭제
        return 0;
}
ubuntu08@ubuntu08-VirtualBox:~/ipc$ gcc sharedmemory.c -o sharedmemory

 

attach 하고 나서 바로 detach를 하니까 어떻게 확인할 방법이 없어요.

 

그래서 sleep을 넣어줘서 10초 동안 남겨주고 ipcs로 빠르게 확인해볼게여

#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(void) {
        int shmid, pid;
        char *shmaddr_parent, *shmaddr_child; // 공유 메모리 영역을 할당할 포인터 변수
        shmid = shmget((key_t)1234, 100, IPC_CREAT|0664); // 키값 생성
        if(shmid == -1) {
                perror("shmget error\n");
                exit(1);
        }

        pid = fork(); // 자식 프로세스 생성
        if(pid > 0) { // 2.부모 프로세스
                wait(0); // 자식 프로세스의 exit() 호출까지 대기
                shmaddr_parent = (char *)shmat(shmid, (char *)NULL, 0);
                printf("%s\n", shmaddr_parent); // 공유 메모리 값을 읽음(read)
                shmdt((char *)shmaddr_parent);
        }
        else { // 1.자식 프로세스
                shmaddr_child = (char *)shmat(shmid, (char *)NULL, 0); // 공유 메모리 키를 변수에 매핑
                strcpy((char *)shmaddr_child, "Hello Parent!"); // 공유 메모리에 쓰기(write)
                shmdt((char *)shmaddr_child); // 포인터 변수를 공유 메모리에서 해제
                sleep(10);
                exit(0);
        }
        shmctl(shmid, IPC_RMID, (struct shmid_ds *)NULL); // 공유메모리를 커널영역에서 삭제
        return 0;
}

100byte잘 써져 있죠?

 


2. pipe

병렬 통신 방식이고, 양방향 전이중 통신 방식

프로세스 간 통신에 제일 흔하게 사용됨. 데이터의 통로라고 생각하면 쉬움. 보통 받는거(rx) 보내는거(tx) 2개를 만듬.

그래서 process A와 process B가 IPC를 한다고 했을 때, pipe가 2개가 생긴다고 생각하면 됨. 1개의 pipe1는 A->B를 지원하고 pipe1는 B -> A  를 지원하는거임.

ubuntu08@ubuntu08-VirtualBox:~/ipc$ vi pipe.c

예제는 부모  프로세스 ->자식 프로세스 단방향 통신 예제임.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MSGSIZE 255

char* msg = "Hello Child Process!";
int main()
{
        char buf[255];
        int fd[2], pid, nbytes;
        if(pipe(fd) < 0) // pipe(fd)로 파이프 생성
                exit(1);
        pid = fork(); // 이 함수 실행 다음 코드부터 부모/자식 프로세스로 나뉨
        if (pid > 0) { // 부모 프로세스에는 자식프로세스의 pid값이 들어감
                printf("parent PID: %d, child PID: %d\n", getpid(), pid);
                write(fd[1], msg, MSGSIZE); // fd[1]에 write한다.
                exit(0);
        }
        else { // 자식프로세스에는 pid값이 0이 됨
                printf("child PID: %d\n", getpid());
                nbytes = read(fd[0], buf, MSGSIZE); // fd[0]을 읽음
                printf("%d %s\n", nbytes, buf);
                exit(0);
        }
        return 0;
}
ubuntu08@ubuntu08-VirtualBox:~/ipc$ gcc pipe.c -o pipe

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MSGSIZE 255

char* msg = "Hello Child Process!";
int main()
{
        char buf[255];
        int fd[2], pid, nbytes;
        if(pipe(fd) < 0) // pipe(fd)로 파이프 생성
                exit(1);
        pid = fork(); // 이 함수 실행 다음 코드부터 부모/자식 프로세스로 나뉨
        if (pid > 0) { // 부모 프로세스에는 자식프로세스의 pid값이 들어감
                printf("parent PID: %d, child PID: %d\n", getpid(), pid);
//                write(fd[1], msg, MSGSIZE); // fd[1]에 write한다.
                write(fd[1], msg, strlen(msg)); // fd[1]에 write한다.
                exit(0);
        }
        else { // 자식프로세스에는 pid값이 0이 됨
                printf("child PID: %d\n", getpid());
                nbytes = read(fd[0], buf, MSGSIZE); // fd[0]을 읽음
                printf("%d %s\n", nbytes, buf);
                exit(0);
        }
        return 0;
}

 

마지막 줄 앞에 있는 값이 255였는데 이는 255만큼 할당되어 있는 char buf의 내용을 MSGSIZE 255만큼 다 출력해서 그런 거임. 근데 그걸 strlen(msg)를 사용한 만큼만 write하도록 줄여주면 됨.

 

 


4. Message Queue

ubuntu08@ubuntu08-VirtualBox:~/ipc$ vi messagequeue.c

 

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>

// 메세지 타입을 설정하는 부분
typedef struct msgbuf { 
        long type;
        char text[50];
} MsgBuf;

int main(void) {
        int msgid, len;
        MsgBuf msg;
        key_t key = 1234;
        msgid = msgget(key, IPC_CREAT|0644); // 메세지 큐 생성 

        if(msgid == -1) { // 메세지 큐가 생성이 안된 경우
                perror("msgget");
                exit(1);
        }

        msg.type = 1;
        strcpy(msg.text, "Hello Message Queue!\n");

        if(msgsnd(msgid, (void *)&msg, 50, IPC_NOWAIT) == -1) { // 메세지 큐 전송 실패
                perror("msgsnd");
                exit(1);
        }
        return 0;
}

message 전송 후에 IPC_NOWAIT하면 기다리지 않고 바로 다음 동작을 하는거고 

다른 명령어를 넣어주면 보낸 후 받을 때 까지 대기하도록 블록 시켜주는 것도 있음.

ubuntu08@ubuntu08-VirtualBox:~/ipc$ gcc messagequeue.c -o messagequeue

 

한번 messagequeue 프로그램 실행 후 ipcs 쳐보면 Message Queue에 남아있음. 읽어갈 때 까지 유지됨.

 


 

4. Message Queue

메시지 queue의 id를 이용해서 queue에서 읽는거. 단방향임.

ubuntu08@ubuntu08-VirtualBox:~/ipc$ vi msqrcv.c

 

#include <sys/msg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef struct msgbuf { // 메세지 큐 전송 프로그램과 동일한 메세지 타입을 생성 
        long type;
        char text[50];
} MsgBuf;

int main(void) {
        MsgBuf msg;
        int msgid, len;
        key_t key = 1234; // 메세지 큐 전송 프로그램과 동일한 메세지 id 사용 
        if((msgid = msgget(key, IPC_CREAT|0644)) < 0) { // 메세지 키도 동일하게 생성
                perror("msgget");
                exit(1);
        }
        len = msgrcv(msgid, &msg, 50, 0, 0); // 위에서 입력한 키값을 가진 메세지 큐를 수신 
        printf("Received Message is [%d] %s\n", len, msg.text);
        return 0;
}

 

ubuntu08@ubuntu08-VirtualBox:~/ipc$ gcc msqrcv.c -o msqrcv

아까 messagequeue를 미리 실행시켜놨기 때문에 출력이 바로 되고 출력 됬으니, queue는 비워진거임.

message queue의 상태가 저렇게 message 0이고 used-byte가 0인 상태에서  ./msqrcv를 실행시키면 수신대기한다.

그 상태에서 ./messagequeue를 해주면 msqrcv 수신되면서 종료됨.

 


message queue, shared Memory, Semaphore 안에 있는 내용을 보는 거.

linux명령어 man은 헤더와 함수의 형태 정도만 볼 수 있는데,

 

리눅스 함수에 관련된 좀 더 자세한 메뉴얼을  한글로 보고 싶으면 아래 링크로 들어가면 됨.

 

조인씨 JOINC EDU

To be smarter and fancier, hang out with join communication 조인씨

www.joinc.co.kr

들어가보면 자세한 설명이 나옴.

반응형