상세 컨텐츠

본문 제목

[한화비전 VEDA] 10주차 리뷰

VEDA

by 전산거북이 2026. 5. 31. 19:39

본문

10주차는 목요일에 면접이 있어 하루 참여하지는 못하였습니다.
아무튼 10주차 리뷰 시작하도록 하겠습니다.

프로세스 통신

프로세스간 통신을 하기 위해서는 IPC라는 방식이 필요합니다. IPC는 Inter Process Communication 의 약자로, message queue, shared memory, semaphore 가 존재합니다.

IPC 메소드통신 방향관련 프로세스특징

파이프 단방향 2개 프로세스간 PIPE 크기(약 4KB) 고려
메시지 큐 양방향 2개 이상 프로세스 짧은 메시지 교환(80KB 미만), queue방식. 커널이 간섭하느라 속도가 늦어질 수 있음
공유 메모리 양방향 2개 이상 프로세스 긴 메시지 교환 가능. 통신 시 충돌 문제 해결이 필요
세마포어 양방향 2개 이상 프로세스 주로 IPC 동기화 요소로 사용

위와 같은 특징이 존재합니다.

파이프

|로 사용하던 그것입니다.

#include <stdio.h>

FELE *popen(const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);

popen()과 pclose()로 C 코드 안에서 열고 닫을 수도 있습니다.
파이프는 named pipe와 unnamed pipe로 나뉘어 있습니다.
named 파이프는 파일로 파이프를 만들어내는 방식을 의미합니다.
이렇게 만든 파이프는 일반 파일처럼 read(), write() 등을 할 수 있습니다.

메시지 큐

같은 메시지 큐 아이디를 공유한다면, 프로세스 통신을 쉽게 할 수 있습니다. 파이프보다는 더 큰 내용을 송수신할 수 있으며, 파이프와는 달리 양방향 통신이 가능합니다. 또한, 같은 메시지 큐를 공유한다면 여러 프로세스까리도 통신할 수 있습니다. 누가 메시지를 전달받을지는 메시지 큐 앞에 붙는 msgtype을 통해 알 수 있습니다.

공유 메모리

shared memory는 메시지 큐보다 훨씬 큰 내용을 공유할 수 있습니다. 이름에서 알 수 있듯, 이는 메모리 공간을 할당하는 것이기 때문입니다.
공유메모리는 shmat()을 통해 붙이며, shmdt()를 통해 해제합니다. 그러나 해제를 하더라도 공유메모리 자체가 사라지는 것은 아닙니다. 단지 해당 프로세스에서 사용을 멈추는 것 뿐입니다.

세마포어

공유메모리는 동기화하지 않으면 충돌 문제가 날 수 있습니다.
간단하게 생각을 해 볼까요?

// process1
a = 3;
a += 3;

// process2
a++;

위와 같은 내용이 적혀있다고 가정해 봅시다. a는 공유 메모리에 저장되어있는 변수라고 하면, 어떤 문제가 발생할 수 있을까요?
process1에서 a를 3으로 세팅합니다. 이후 3을 더하면 process1에서는 a의 값은 6이 될 것입니다.
그러나 process2에서는 a에 값을 더합니다.
만약, process1에서 a = 3 이 실행된 뒤, process2 에서 a++을 하고나서 다시 process1에서 a += 3이 실행된다면, process1에서 실제로 받게 되는 a값은 7이 되어버립니다. process1에서 원하는 값이 아니게 되는것이죠.

이걸 막기 위해 동기화를 진행해야 하는데, 이 동기화를 시켜주는 것이 바로 semaphore입니다.
세마포어는 일종의 공유메모리 접근 티켓입니다. 몇 개의 프로세스가 공유메모리에 접근할 수 있는지를 알려주는 것이죠.
만약 semaphore가 1이면 앞으로 한 개의 프로세스만 공유메모리에 접근이 가능해집니다.

Thread

이번에는 프로세스와는 비슷하지만 다른, 쓰레드에 대해 알아보겠습니다.
프로세스가 프로그램이 실행되는 단위라고 하였지만, 더 작은 단위로 실행되긴 합니다. 그것이 바로 thread 입니다.
사실 컴퓨터가 처음 등장했을 때만 해도 Thread는 굳이 필요하지 않았다고 합니다. 그러나 web의 등장 이후 프로그램은 점점 더 무거워지는 등 결국 thread가 필요하게 되었다고 합니다.
아무튼 thread는 process와 조금 다릅니다. process를 새로 만들게 되면 자식 프로세스는 부모 프로세스와 별개로 작동을 하게 됩니다. 그러나 thread는 가지고 있는 대부분의 자원을 공유합니다. stack segment만 다르게 됩니다.

리눅스 내부에서는 thread도 일종의 프로세스로 취급한다고 합니다. 그래서 다음과 같은 방법으로 thread의 실행 상태를 확인할 수 있습니다.

ps -eLf | grep 프로세스명

thread를 생성하기 위해서는 pthread_join()을 사용하면 됩니다. 만약 thread를 main thread와 분리해서 관리하고 싶다면 pthread_detach()를 사용할 수 있습니다만, 그렇게 되면 main thread에서 해당 쓰레드를 직접 통제하지 못하게 됩니다.

Thread 역시 자원을 공유할 수 있습니다. 일단 heap segment는 공유하니까요.
그런데 그렇다면 IPC에서 그랬듯 충돌 문제가 일어날 수 있지 않을까요? 이것은 어떻게 해결하면 좋을까요?
그것이 바로 mutex와 semaphore의 등장 배경입니다. 사실 둘은 IPC의 세마포어와 거의 비슷합니다. 다만, mutex는 단 1개만 통과 가능하다는 점이 세마포어와 조금 다르겠습니다. 세마포어는 갯수를 정할 수 있었습니다.

통신

기본적으로 통신을 하기 위해서는 TCP, UDP를 알아야 합니다.
OSI 7 Layer라고 불리는 것이 있습니다. 통신은 이 7계층에서 이루어집니다.
그 중 가장 접하기 쉬운 것이 바로 TCP/UDP일 것입니다.
TCP는 Transmission Control Protocol입니다. 말 그대로 통신 제어 프로토콜이죠.
UDP는 User Datagram Protocol입니다.

이 둘의 가장 큰 차이는 accept 수립 여부이지 않을까 싶습니다.
우선 TCP는 socket 생성 -> bind -> listen -> connect 요청 시 accept 의 순서로 통신이 시작됩니다.
그러나 UDP는 socket 생성 -> bind 까지는 동일하지만, 이후 listen을 하지 않습니다. client에서 해당 소켓을 통해 값을 보내면 그대로 받으면 되는 것입니다.

입출력 다중화

만약 recv() 를 사용하여 값을 받는다고 한다면, 안타깝지만 상대방이 데이터를 보내기 전까지는 아무것도 할 수 없습니다. 이를 blocking이라고 부릅니다.
이를 해결하기 위해, select() 함수와 poll() 함수가 등장하였습니다.
두 함수는 거의 유사하지만 조금 다릅니다. select() 함수가 조금 더 원시적인 방식입니다.

select()

#include <unistd.h>
#include <fcntl.h>

int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *tvptr);

위와 같은 방식으로 만들어집니다. 여기서 maxfd 에는 최대 파일 디스크립터의 번호를 넣어줍니다. 그렇게 하면 0번부터 해당 번호까지 모든 파일 디스크립터를 돌며 이벤트가 발생한 파일 디스크립터를 찾습니다.
이후, readfds에 설정되어있는 파일디스크립터 중에서 이벤트가 발생했다면 해당 fd_set에 설정해줍니다.
즉, 매번 select()를 사용할 때마다 초기화를 해 주어야 합니다.

poll()

앞선 부분에서 나온 방식은 아무래도 조금 비효율적입니다. 원하는 파일 디스크립터만 보면 되는 것이며, 굳이 fd_set을 재설정해줄 필요도 없을것 같습니다.
이럴 때 사용하기 좋은 것이 바로 poll() 입니다.

#include <poll.h>

int poll(struct pollfd *ufds, unsigned int nfds, int timeout)

struct pollfd {
	int fd;
	short events;
	short revents;
}

ufds에는 파일 디스크립터와 감지하고싶은 이벤트들을 넣어줍니다. 만약 이벤트가 감지되면 revents 값으로 반환됩니다.
다만, 검출하고자 하는 파일 디스크립터가 많으면 그만큼 많이 poll()을 해 주어야 합니다.

관련글 더보기

댓글 영역