하하... 10주가 지난 지금에야 9주차 기록을 작성하고 있습니다. 😅😅
지난 주는 여러 이유로 주말이 후다닥 지나가버려서 얼른 작성해야지 하다 보니 벌써 시간이 이렇게 되었네유 ....
그럼 9주차 기록 시작하도록 하겠습니다. !!!
리눅스에서는 크게 두가지의 통신이 존재합니다 .
1. Kernel <----> process
2. Process <---> process [ 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 위에서 간략하게 커널과 프로세스의 통신을 확인 해보았다면 이제는 process간 통신에 대해서 알아보도록 합시다.
그 전에 프로세스에서 return()과 exit()의 차이를 먼저 짚고 넘어가겠습니다.
```c
int main(){
exit(status_code ); // 그냥 프로그램이 종료된다.
//상태를 부모 자식간,프로세스간에 넘길 수 있다.
return;
}
==========================================
int main(){
func();
return 0;
```
| 항목 | `return` | `exit()` |
| --- | --- | --- |
| 사용 위치 | 함수 내부에서만 사용 | 프로그램 어디서든 (어떤 함수 안에서도) 사용 가능 |
| 종료 대상 | 현재 함수만 종료 | 전체 프로세스(프로그램) 즉시 종료 |
| 반환값 전달 | 호출한 쪽(caller)에게 값 전달 | 운영체제에게 종료 상태 코드 전달 |
| 정리 작업 | 자동 변수 해제 + 스택 프레임 정리 | 등록된 종료 핸들러(`atexit`)만 실행 |
| 헤더 | 필요 없음 | `#include <stdlib.h>` 필요 |
| 함수 종류 | 언어 키워드 | 표준 라이브러리 함수 |
- thread의 개념이 등장하고 멀티 스레드 프로그램에서 한 스레드에서 exit()을 호출하면 프로그램 자체가 다 crash됩니다.
- -> 따라서 thread만 종료하기 위해서는 pthread_exit()을 사용할 수 있습니다.
- eixt() : 표준 입출력 루틴을 정리 + 열려 있는 파일 스트림은 fclose를 호출해 버퍼를 다 flush 한다.
- _exit() : 표준 입출력과 관련된 정리 작업을 수행하지 않는다 그 외는 exit과 동일 .
그러면 fork()를 통해 생성된 자식 프로세스가 종료되기 전에 부모 Process가 종료된다면 어떻게 될까요?
정상적인 경우라면 부모는 wait() 또는 waitpid()를 사용해서 자식의 종료를 기다려야 합니다.
하지만 부모가 wait() 없이 먼저 종료되면?
→ 자식 프로세스는 고아(Orphan) 프로세스가 됩니다.
리눅스 커널은 이 상황을 대비해 자동으로 처리해줍니다:
고아 프로세스는 init 프로세스(PID 1, systemd 등)에게 입양됩니다
좀비 프로세스는 정반대 상황에서 발생합니다.
자식 프로세스가 먼저 종료되었는데, 부모가 종료 상태를 회수하지 않은 경우
자식이 exit()로 종료됨
커널은 자식의 종료 상태(exit code, signal 등)를 보관
부모가 wait()나 waitpid()를 호출하지 않으면 이 정보가 계속 남아 있음
프로세스 테이블에 여전히 항목이 존재 → Zombie (Z) 상태
USER PID PPID STAT COMMAND
root 1234 1000 S ./parent
root 1235 1234 Z [child] <defunct>
ps aux 명령어로 확인해본다면 stat이 Z(zombie)이고 defunct 를 확인할 수 있습니다.
====>. fork()로 자식을 만들었다면 반드시 책임져야 합니다.
자식이 먼저 끝나면 wait()로 종료 상태를 회수하거나,SIGCHLD 시그널을 무시(signal(SIGCHLD, SIG_IGN))해서 커널이 자동 처리하게 만들어야합니다.
그렇지 않으면... 땅속에서 다시 일어나는 좀비 프로세스들이 당신의 시스템을 괴롭힐 거예요 🧟♂️"
다양한 프로세스 간 통신 기법
파이프
단방향 통신 : 주로 부모 자식 간에 사용된다.
예시
mom
.Text //mom과 son 공유
.DATA ( global var)
.STACK_mom
fd3 = open("file3"); //엄마 코드에서 오픈
fdt = {0,1,2 / 3 } ;
son = fork(); // 자식 생성
Son
.TEXT
.DATA_son
.STACK_son
=> open하지 않았지만 0,1,2에 접근 가능
메세지 큐
버퍼 형태의 오브젝트로 이를 통해 여러 태스크와 ISR은 메세지를 주고 받으며 데이터 통신과 동기화를 진행할 수 있다.
→ 원하는 수신자가 메세지를 읽어갈 때까지 메세지를 임시로 보관 가능 .
큐 컨트롤 블록 (QCB): 큐를 관리.
→ 메세지의 길이는 정해져 있어 버퍼로 나눠서 포인터를 연결할 수 있다. ⇒ 메세지의 길이가 매우 큰 경우
Shared Memory
프로세스 간에 메모리를 공유를 통해 메모리 복사 횟수를 줄여서 성능 향상을 이끌 수 있음
⇒ 커널과 프로세스 사이의 복사를 2회 없앤다. 속도 증가 .
단점 : 여러 프로세스에서 동시에 같은 메모리의 주소에 접근할 수 있기 때문에 동기화 기법 [상호 배제 기법] 이 별도로 필요하다 .
동기화 기법 : Semaphore ⭐️
세마포는 공유 메모리에 대한 상호 배제를 제공하여, 생산자와 소비자가 동시에 데이터를 수정하는 경쟁 조건(Race Condition)을 방지하는 뮤텍스 역할
상호 배제(Mutual Exclusion) 보장 — Critical Section 보호
for(ch='A'; ch<='Z'; ch++){
sem_wait(psem); // ← 공유 메모리 사용 시작 (잠금 획득)
*s++ = ch; // ← Critical Section: 공유 메모리에 쓰기
sem_post(psem); // ← 공유 메모리 사용 끝 (잠금 해제)
}→ 이 구조는 전형적인 뮤텍스(Mutex) 용도로 사용되는 이진 세마포(Binary Semaphore)입니다.
초기값이 1이기 때문에, 동시에 하나의 프로세스만 공유 메모리에 접근할 수 있어 경쟁 조건(Race Condition)을 방지할 수 있습니다.
9주차 이틀간 프로세스간 통신과 멀티 프로세스에 대해서 배웠습니다 .
추가적으로 멀티 스레드가 멀티 프로세스에 비해 어떤 장점을 지니는지, 어떻게 사용되는지 알아봅시다 .
Muti processing
P1 (j2) | P2 (j2)
장: secure / safe : thread에 비해
단: heavy -IPC ,context change
Multi Threading
T1
-----
T2
장: light
단: safe X => 하나가 죽으면 연쇄 kill 가능
문맥 교환에서의 차이
: cache => thread에서는 cache 활용(공유)이 가능
process에서는 cache의 이점을 활용할 수 없다.
cache의 종류
- DATA d-c
- TEXT i-c
- ADDR (TLB) : 가상 주소 -> 물리주소 변환시 table 활용 -> cache data 그대로 사용
thread <===== Unix POSIX (pthread)
linux " process oriented "
-==> IPC 구현이 간단하고 가벼운 프로세스 구현 -> thread로 발전
리눅스에서는 thread를 프로세스의 한 유형으로 구분한다.
간략하게 정리를 하면 이렇습니다.
스레드 도입 이유
해당 이유로 도입되었지만 장점만큼 단점 또한 존재합니다.
리눅스에서의 Pthread 사용상의 주의점을 하나 정리하고 넘어가겠습니다.
thread 생성 : pthread_create //parent
thread 종료 : pthread_exit // child 호출한 스레드를 종료한다. ⇒ 분리 상태가 아닐 경우 인자로 지정된 thread 에 join으로 합류한다
/*
thread inherit main thread schedule policy by default
@superuser @EXPLICIT //자식은 다른 권한을 제공할 수 있는가?
SCHED_FIFO under SCHED_NORMAL ok
SCHED_RR under SCHED_NORMAL ok
SCHED_FIFO under SCHED_RR ok
SCHED_RR under SCHED_FIFO ok
SCHED_NORMAL under SCHED_FIFO No
SCHED_NORMAL under SCHED_RR No
@IMPLICIT : parent의 권한을 다 가진다.
*/이때 superuser의 권한을 통해 main 우선순위 상속 지정 , main과 다른 priority 설정이 가능합니다. 하지만 main(parent_) 가 NORMAL 이 아닌 경우에는 explict이더라도 바꿀 수 없습니다. ⭐⭐thread의 경우 실제 구현을 하면서 차차 이해도를 높이도록 하겠습니다. ㅠㅠㅠㅠ
😅
드디어 ... 네트워크에 진입을 했습니다.
OSI 7계층에 대해서 간략하게 설명을 하고 지나갔지만 너무 속도가 빨랐던 탓일까 .. 혼자 복습할 시간을 가져야겠다고 생각했슴 ... ㅠ

출처: https://shlee0882.tistory.com/110
간략한 설명도 나와 있으니 한번 읽어보자 !
요 tcp , IP 프로토콜을 가장 중점적으로 배웠습니다 . 그러니 사용 예제와 결과 위주로 정리해보도록 하게씀돠 .(엄 근 진 )
IP protocol : network layer
서브네팅은 하나의 큰 IP 네트워크를 여러 개의 작은 네트워크(서브넷)로 나누는 기술입니다.
IPv4 주소가 부족하고 네트워크를 효율적으로 관리하기 위해 필수적입니다.
ip 주소의 호스트 비트를 쪼개어 호스트와 서브넷 영역으로 나눕니다 .
서브넷 마스크(Subnet Mask): IP 주소의 어느 부분이 네트워크이고 어느 부분이 호스트인지
구분하는 32비트 숫자.
예: 255.255.255.0
(이진: 11111111.11111111.11111111.00000000)
앞 24비트: 네트워크 부분
뒤 8비트: 호스트 부분
TCP vs UDP : transport layer
TCP : 핸드쉐이킹을 통한 연결 지향형(Connection-Oriented) 프로토콜 UDP : 신뢰성은 없지만 속도가 빠르기 때문에 일반적인 LAN 환경에서 많이 사용 소켓 : 프로세스간의 상호 양방향 통신 방식으로 소프트웨어로 작성된 통신 접속점
+ 일종의 통신을 위한 파일 디스크립터
int socket ( int domain, int type , int protocol)
소켓 함수
[TCP 와 UDP 에서 사용하는 함수가 차이가 있다.]
네트워크와 호스트의 엔디안이 다르기 때문에 함수 사용
=> 주로 네트워크에서는 Big endian 사용
=> linux에서는 Little endian 사용
변환 함수
device 1 byte
0x12 0x34 0x56 0x 78
(big) 12 > 78(little) 자릿 수가 다르기 때문
@buf
<A> <B>
0: 78 12
1: 56 34
2: 34 56
3: 12 78
little endian : A (little 부터 저장한다)
big endian : B
=======================
ARM : Little endian
Network : Big endian 사용
TCP는 다음을 보장한다.
기본 TCP 클라이언트 프로그래밍
socket() → connect() → send()/recv() → close()
주요 포인트
TCP 서버 프로그래밍의 단점
socket() → bind() → listen() → accept() → read()/write() → close()
문제점 (동시 접속 시)
단순 accept() 루프 서버는 한 클라이언트 처리 중 다른 클라이언트는 대기하게 됩니다.
클라이언트1 접속 → accept → read/write (처리 중)
클라이언트2 접속 시도 → 큐에 들어가지만, 클라이언트1 종료 전까지 accept() 호출 안 됨
→ 대기!
→ 동시 다수 클라이언트 처리 불가
동시 접속 처리 방법
| 방식 | 장점 | 단점 | 추천 상황 |
|---|---|---|---|
| 멀티 프로세스 (fork) | 안정적, 프로세스 격리 강함 | 프로세스 생성 비용 높음, IPC 복잡 | 보안/안정성 중요할 때 |
| 멀티 스레드 | 가볍고 통신 쉬움 | 스레드 동기화 복잡, 하나의 스레드 죽으면 위험 | 일반적인 웹 서버 등 |
| I/O 멀티플렉싱 | 단일 프로세스/스레드로 다수 처리 | 코드 복잡도 증가 | 클라이언트 수 많고 요구사항 단순할 때 가장 효율적 |
→ 대부분의 고성능 서버(nginx, Node.js 등)는 I/O 멀티플렉싱 기반
I/O 멀티플렉싱: select() 방식
select()는 여러 파일 디스크립터(소켓)를 동시에 감시할 수 있게 해줍니다.
핵심 동작 원리
장점
단점
하지만 select는 처리할 수 있는 파일 디스크립트 수의 제한과 속도가 늦다는 제한이 있음 → 이를 해결하기 위해서는 리눅스에서 epoll이라는 API를 제공한다. epoll에 관련해서는 다음에 별도로 학습 일지를 추가하도록 하겠습니다.
일반적으로 시스템 서비스는 데몬이라는 형태의 프로세스로 실행된다 .
daemon : 백그라운드로 실행되는 프로그램을 의미.
터미널을 사용할 수 없다 → 모니터와 키보드를 사용 할 수 없음을 의미
생성
umask()함수를 통해 데몬이 생성하는 파일의 접근 권한을 미리 설정 / 로 설정하고 열린 fd를 모두 닫음 stdout,stdin,stderr에 대해 사용할 수 없도록 처리하고 데몬과 관련된 처리 수행 ❔그렇다면 오류 발생시에는 어떻게 확인할 수 있을까 ?
syslog()함수를 사용하여 로그파일에 메세지를 기록 => /var/log/syslog에 위치한다.
⇒ 정해진 시간에 작업을 시작하고 싶은 경우 at 명령어를 사용할 수 있다.
네트워크 통신을 구현하면서 코드의 길이가 늘어나다보니 이해도가 낮아졌습니다.
소켓 통신을 위주로 라즈베리 파이 제어와 웹서버 통신을 한번 구현해보았?
음 제작된 예제 코드를 위주로 한번 실습을 해보았지만만 HTML 언어 구동 방식과 소켓 통신을 잘 모르다보니 아직까지는 어려움을 많이 느끼는 한 주였습니다.
그리고,, 주말에 바로 바로 정리하지 않고 일주일 뒤에 다시 마주하니 매우 깜깜 하군요,....
앞으로는 어떤 역경이 다가와도 미루지 않겠다 선서!.
독자 여러분과 다짐하면서 9주차 기록 끛!
맛집 리뷰 대신 올리는 자투리 모음집 ..
극히 주관적인 취향 반영.
운수 좋은 날 아침 . 10분 일찍 도착했더니 찾을 수 있었던 히든 피스랄까.. ㅎㅎ 다음에도 만나다오.
VEDA는 일상 생활에 큰 도움이 된다. 회로를 잠깐 공부했더니 인간 회로가 되어버리며 ..
휴식을 정말 잘 취하는 모습 보기 좋네요