minitalk과제는 posix 시스템의 신호를 이용해서 서로 다른 프로세스 간(클라이언트에서 서버로 문자열을 전달해야 한다.) 데이터를 전달하는 것을 구현하는 과제다. 이 때 허용되는 신호는 SIGUSR1, SIGUSR2 단 두가지 뿐이다. 즉, 해당 과제는 0과 1로만 통신하여 데이터를 전송하는 것으로 여겨야한다.

1. 데이터 전송 과정

1) SIGNAL - posix

posix 시스템에서 서로 다른 프로세스 끼리 통신할 수 있는 방법인 signal은 소프트웨어 인터럽트를 이용하여 다른 프로세스에게 간단한 값을 보낼 수 있는 방법이다. 그 값의 예로 ctrl + c는 SIGINT(터미널에서 프로세스에게 인터럽트)의 값을 보내고 ctrl + z는 SIGTSTP(프로세스를 임시 중지)를 전송한다. 다음은 신호의 값에 따른 의미를 정리한 표이다.

Signal Default Action Description
SIGABRT A 프로세스 중단
SIGALRM T 알람 신호 - alram 함수에서 사용 (real time)
SIGBUS A 정의되지 않은 메모리 객체에 접근
SIGCHLD I 자식 프로세스 종료, 중단 혹은 계속
SIGCONT C 정지하지 않으면 계속 실행
SIGFPE A 산술적 오류 ex) 0으로 나눗셈, 정수형 자료형에 float연산
SIGHUP T Hangup- ex) 터미널에서 접속이 끊어졌을을 때
SIGILL A Illegal instruction.
SIGINT T 터미널 인터럽트 신호
SIGKILL T 프로세스 강제 종료 (무시되거나 행동을 변경할 수 없음)
SIGPIPE T 사용자가 없는 파이프를 사용 ex) 이미 닫힌 소켓에 데이터 전송
SIGQUIT A 터미널 종료 신호
SIGSEGV A 잘못된 메모리 참조
SIGSTOP S 프로세스 강제 정지 신호 (무시되거나 행동을 변경할 수 없음)
SIGTERM T 종료 신호 (kill과 달리 무시, 또는 다른 행동으로 처리 될 수 있음)
SIGTSTP S 프로세스 정지
SIGTTIN S 백그라운드 프로세스 읽기 시도
SIGTTOU S 백그라운드 프로세스 쓰기 시도
SIGUSR1 T User-defined signal 1.
SIGUSR2 T User-defined signal 2.
SIGPOLL T Pollable event - ex) 소켓에 데이터가 전송이 완료되었는지 (특정 이벤트가 발생했는지)
SIGPROF T 프로파일링 타이머 신호 (sigalrm과 달리 현재 프로세스와 커널에서 실행된 cpu시간을 비교)
SIGSYS A 시스템 호출이 실패했을 때 발생
SIGTRAP A Trace/breakpoint trap. (일반적으로 디버깅 모드에서 사용)
SIGURG I out of band 데이터 도착 (대용량 데이터가 소켓에 도착하여 처리가 필요할 때)
SIGVTALRM T 알람 신호 (cpu time 측정)
SIGXCPU A cpu 시간 제한 초과
SIGXFSZ A 파일 크기 제한 초과

T : 프로세스 종료

A : 프로세스 종료, core 파일(종료 직전 메모리 상황을 저장) 생성과 같은 지정된 행동이 동반될 수 있음

I : 신호 무시

S : 프로세스 정지

C : 프로세스가 정지해 있다면 다시 실행, 그 외에는 무시

<aside> 💡 signal은 한 프로세스에 여러개의 같은 신호를 보낸다면 프로세스 핸들링 함수에서 처리가 끝나기 전까지 같은 신호를 모두 무시한다. (신호의 정보를 담은 siginfo_t가 overwritten 되지 않기 위함)

</aside>

<aside> 💡 signal을 한 프로세스에 여러개의 다른 신호를 보낸다면 queue에 저장되며 (이를 pending이라 한다) 어떤 순서로 보내질지는 정해지지 않는다.

</aside>

위에 서술한 signal의 특성 덕분에 usleep()없이 minitalk를 구현한다면 출력이 안되거나 문자열이 깨진 채로 출력되는 것을 볼 수 있었다. 이는 server에서 신호를 처리하기도 전에 client가 신호를 계속 보내서 신호의 순서가 바뀌거나 같은 신호가 무시되었기 때문이다. 이를 해결하는 가장 간단한 방법은 server에서 신호를 처리하기 충분한 시간을 두며 신호를 보내는 것이다. 그러나 이 방법은 server에 신호가 도착했는지 불확실하다. 따라서 usleep을 사용하는 대신 보너스 요구사항인 ack를 구현하기로 마음먹었다.

2. ack 구현

1) ack 란?

ack는 tcp에서 신뢰성 있는 패킷 전달을 위해 고안된 방법이다.

<aside> 💡 어려운 문제는 한번에 풀 수 없다. 따라서 저수준(물리)에서 고수준(어플리케이션)까지 코드를 쌓아 올린 것을 계층이라 하며 네트워크에서는 osi 7 혹은 tcp/ip 4 계층이라 부른다. osi 7계층에는 물리 - 데이터 링크 - 네트워크 - 전송 - 세션 - 표현 - 응용 계층으로 나뉘는데 tcp는 4계층인 전송 계층에 속한다.

ack는 acknowledgement의 약자로 데이터 송신측이 데이터 수신측으로 성공적으로 받았는지를 알 수 있는 방법이다. 이 때 수신자가 성공적으로 데이터를 받았다면 송신자에게 다음으로 받아야할 데이터의 번호를 ack로 보내고 수신자가 이를 받아서 다음 데이터를 전송한다. 만약 송신자에게 ack가 돌아오지 않았거나 nack로 문제가 있는 번호를 보낸다면 해당 데이터를 다시 전송한다.

이를 그림으로 그린다면 다음과 같다.