시스템 콜을 통한 IPC (Inter-Process Communication)

kenGwon·2024년 2월 19일
0

[Embedded Linux] BSP

목록 보기
20/36

한글판 linux man page

joinc은 사실상 "한글판 linux man 페이지"라고 보면 되겠다.


현재 커널에서 동작중인 IPC들을 확인하는 방법

$ ipcs
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 4          ubuntu     600        16384      1          dest
0x00000000 7          ubuntu     600        5505024    2          dest
0x00000000 10         ubuntu     600        524288     2          dest
0x00000000 12         ubuntu     600        1916928    2          dest

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

시그널을 통한 IPC

시그널은 가장 간단한 IPC 방법이다. 2바이트의 시그널코드(숫자)를 날리는 것이다.
SIGINT에 대한 시그널 핸들러를 구현해놓는다면, 그 어떤 타이밍에 [ctrl + c]를 눌러서 프로그램을 종료하던 간에 graceful한 종료를 보장할 수 있게 된다.

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);
        if (result < 0) {
                perror("To send Signal is failed\n");
                exit(1);
        }
        return 0;
}

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 %d\n", signo);
        // exit(0);
    }
    if (signo == SIGALRM)
        printf("Catch SIGALRM! %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();
        // sleep(2); // sleep()이랑 alarm()은 거의 같은 기능이기 때문에 같이 쓰지 않는게 좋다.
        alarm(2);
    }
    return 0;
}

무한루프로 빠져버린 sigloop를 종료하는 방법

// 첫번째 방법: PID 검색 후 종료
ubuntu@ubuntu14:~$ ps -ef | grep sigloop
ubuntu      9911    1947  0 16:26 pts/1    00:00:00 ./sigloop
ubuntu      9997    9987  0 16:26 pts/3    00:00:00 grep --color=auto sigloop
ubuntu@ubuntu14:~$ sudo kill -9 9911

// 두번째 방법: 프로세스 이름으로 종료
ubuntu@ubuntu14:~$ sudo killall sigloop

메시지 큐를 통한 IPC

메세지 큐는 말 그대로 Queue이기 때문에 FIFO이다.
버퍼라는 것은 통신속도가 다른 두 장치간에 임시 기억 장치다.

현재 메세지 큐를 확인하는 방법

아래는 아무것도 실행시키지 않은 상태이다.

ubuntu@ubuntu14:~/ipc$ ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 4          ubuntu     600        16384      1          dest
0x00000000 7          ubuntu     600        5505024    2          dest
0x00000000 10         ubuntu     600        524288     2          dest
0x00000000 12         ubuntu     600        1916928    2          dest

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

그리고 아래의 messagequeue.c를 컴파일하여 실행한 다음에 다시 ipcs명령을 찍어보면 방금 생성한 큐가 있는 것을 볼 수 있다.

ubuntu@ubuntu14:~/ipc$ ipcs

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages
0x000004d2 0          ubuntu     644        50           1

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 4          ubuntu     600        16384      1          dest
0x00000000 7          ubuntu     600        5505024    2          dest
0x00000000 10         ubuntu     600        524288     2          dest
0x00000000 12         ubuntu     600        1916928    2          dest

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

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;
}

위 코드에서 주목할 부분은 msgsnd(msgid, (void *)&msg, 50, IPC_NOWAIT)이다. IPC_NOWAIT를 주었기 때문에 상대방이 읽어가든 안읽어가든 그냥 큐에 써버리고 다음 코드로 넘어간다.
하지만 IPC_NOWAIT이 아니고 다른 '값'을 넣어줬다면 그 시간동안 큐에서 누군가가 읽어갈 때까지 블로킹 상태로 진입하여 대기하게 된다.

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);
        //msgctl(msgid, IPC_RMID, NULL); // 큐의 내용을 읽어들이면서 메시지 큐를 삭제

        return 0;
}

파이프를 통한 IPC

우리가 ls -l | grep kgh 이런식으로 썼던 것을 파이프라고 불렀던 이유는, 앞에 있는 ls라는 프로세스와 grep이라는 프로세스가 파이프 통신을 했기 때문이다.

파이프는 중간에 버퍼를 두는 느낌이다. 그것을 메모리로 사용할 수도 있고, 아니면 '네임드파이프'라고 해서 파일 형태로 저장될 수도 있다.

process1은 파이프에 write해야하고 process2는 파이프에서 read해야 하기 때문에 하나의 파이프에는 두개의 file descripter가 필요하다.

그리고 파이프는 기본적으로 단방향이기 때문에, 양방향 통신을 하려면 process1에서 process2로 데이터를 보내는 파이프 하나랑, process2에서 process1으로 데이터를 보내는 파이프 하나가 더 필요하다.

파이프통신에서 주의할 것은 "자신이 write한 값을 자신이 read해가지 않는것"이다.

pipe.c

이 예제는 부모프로세스와 자식프로세스의 '단방향 파이프 통신 예제'이다.

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

#define MSGSIZE 255

char* msg = "Hello Child Process!"; // 여기서 msg에 담기는 주소값은 read only영역의 H가 위치한 주소다.
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;
}

공유메모리를 통한 IPC

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); // 키값 생성
        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); // 포인터 변수를 공유 메모리에서 해제
                exit(0);
        }
        shmctl(shmid, IPC_RMID, (struct shmid_ds *)NULL); // 공유메모리를 커널영역에서 삭제
        return 0;
}
profile
스펀지맨

0개의 댓글