
라즈베리파이와 리눅스 관련 3번째 글이다.
사실 배운 지는 한참 되었는데 정리할 내용이 엄청 밀려서
여기까지만 쓰고 당분간은 다른 내용의 포스팅을 해야 할 듯하다 😅
DHT11 센서
온도와 습도를 동시에 측정하는 디지털 센서
하나의 데이터 핀을 통해 40비트 데이터 프레임으로 변환된 값을 전송함
온도 0~50도, 습도 20~90% 사이의 값을 측정할 수 있음
DHT11 핀의 구조
DHT11 모듈은 3핀(VCC, DATA, GND) 또는 4핀(VCC, DATA, NC, GND) 형태로 제공
VCC는 3.3V 또는 5V, GND는 공통 접지, DATA는 라즈베리파이 GPIO와 연결
연결 예시
DHT11 VCC → 라즈베리파이 3.3V (예: 핀 1)
DHT11 GND → 라즈베리파이 GND 핀 (예: 핀 39)
DHT11 DATA → 라즈베리파이 GPIO 핀 하나 (예: GPIO4, 핀 7)
라즈베리 파이 물리적 사양 확인하기
터미널에 pinout 입력하면 GPIO 핀 배치와 물리적 사양 확인 가능
,--------------------------------.
| oooooooooooooooooooo J8 +======
| 1ooooooooooooooooooo PoE | Net
| Wi 1o +======
| Fi Pi Model 4B V1.5 oo |
| ,----. +---+ +====
| |D| |SoC | |RAM| |USB3
| |S| | | | | +====
| |I| `----' +---+ |
| |C| +====
| |S| |USB2
| pwr |hd| |hd| |I||A| +====
`-| |---|m0|---|m1|----|V|-------'
https://pinout.xyz/ 에서 가독성 좋게 핀맵 확인할 수 있음

DHT11과 DHT22의 비교
| 센서 | 데이터 구조 (5바이트) | 습도 형식 | 온도 형식 | 체크섬 계산 |
|---|---|---|---|---|
| DHT11 | {A, 0, B, 0, S} | A (정수%) | B (정수°C) | A+B+0+0 |
| DHT22 | {A, a, B, b, S} | A.a (소수%) | B.b (소수°C) | A+a+B+b |
예시
DHT11 : {50, 0, 25, 0, 75} → 습도 50%, 온도 25°C, 체크섬 50+0+25+0=75
DHT22 : {45, 5, 23, 7, 80} → 습도 45.5%, 온도 23.7°C, 체크섬 45+5+23+7=80
DHT11의 통신 방식 개요
DHT는 단일 와이어(Single-Wire)를 통해 MCU와 센서가 데이터를 주고받는 방식
단일 버스(Single-bus) 방식으로, 별도의 클럭 신호 없이 하나의 DATA 핀으로 데이터 전송과 동기화
센서는 MSB(최상위 비트)부터 순서대로 데이터 전송
데이터 크기는 총 40비트로 구성되며, 8비트씩 5개의 필드로 구성됨
각 필드는 정수부(integer)와 소수부(decimal)로 나뉘어 정확한 값을 표현
습도 정수 → 습도 소수 → 온도 정수 → 온도 소수 → 체크섬 순으로 전송
[8bit] 습도 정수(RH integral) + [8bit] 습도 소수(RH decimal)
+ [8bit] 온도 정수(T integral) + [8bit] 온도 소수(T decimal)
+ [8bit] 체크섬(check sum)
DHT11 통신 과정
MCU(라즈베리파이)가 시작 신호를 보내면 센서가 깨어나서 40비트의 데이터로 응답
데이터 전송이 끝나면 다시 저전력 모드로 돌아가는 순환 구조
1단계: MCU 시작 신호 전송
라즈베리파이가 DATA 핀을 Low(0)로 18ms 이상 유지한 후 High(1)로 40us 정도 유지
이 신호를 받으면 DHT11이 저전력 대기 모드에서 작동 모드로 전환됨
2단계: DHT11 응답 신호
DHT11이 DATA 핀을 Low로 80us 유지 후 High로 80us 보내 ‘응답 완료’ 신호 전송
이 시점부터 40비트 데이터(습도+온도+체크섬)를 순서대로 전송
3단계: 데이터 수신 후 저전력 모드
40비트 전송이 끝나면 DHT11은 자동으로 DATA 핀을 High로 놓고 저전력 모드로 복귀
다음 읽기를 위해서는 MCU가 다시 시작 신호를 보내야 함
MCU의 시작 신호 전송
DATA 핀의 전압을 Low로 18ms 이상 유지한 후 High로 올려주는 간단한 과정
기본 상태
통신 시작 전 자유 상태
DATA 핀이 풀업 저항으로 인해 High(3.3V) 상태로 유지됨
1단계 - Low 신호 (최소 18ms)
MCU가 DATA 핀을 GPIO.LOW로 설정해 전압을 0V로 떨어뜨림
Low 구간이 18ms 이상 길어지면 DHT11이 ‘시작 신호’로 인식
2단계 - High 신호 (20-40us)
Low 후 바로 GPIO.HIGH로 전환해 핀을 다시 High로 만듦
이 High 상태에서 20~40마이크로초 대기하며 DHT11의 응답까지 기다림
DHT의 응답 신호
응답 시작 (80us Low)
시작 신호를 받은 DHT11이 DATA 핀을 Low로 80마이크로초 유지해 ‘응답 시작’을 알림
준비 신호 (80us High)
Low 후 High로 80us 유지하며 데이터 전송 준비 상태를 나타냄
이 시점부터 실제 40비트 데이터 전송이 시작됨
MCU는 Low 50us 후 High 펄스 길이를 측정해 비트를 판독
이때 40us 이상이면 '1', 미만이면 '0'으로 인식
데이터 비트의 전송 방식

비트 시작: Low 50us (공통)
비트 끝 : High 길이로 0/1 구분
- '0' 비트: High 26~28us (짧음)
- '1' 비트: High 70us (김)
GPIO 상태 모니터링
/home/raspberry/WiringPi 를 빌드해서 gpio 를 설치하면 실시간 GPIO 상태 확인 가능
터미널에 gpio readall 입력
주요 항목 설명
BCM : Python RPi, GPIO에서 사용하는 번호 (DHT11 pin=4)
wPi : WiringPi 전용 번호
Physical : 실제 물리핀 번호 (pinout.xyz와 동일)
Mode : IN/OUT/ALTx (ALT0=I2C 등)
V : 현재 전압 상태 (1=High, 0=Low)
+-----+-----+---------+------+---+---Pi 4B--+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | ALT0 | 1 | 3 || 4 | | | 5v | | |
| 3 | 9 | SCL.1 | ALT0 | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | GPIO. 7 | IN | 1 | 7 || 8 | 1 | ALT5 | TxD | 15 | 14 |
| | | 0v | | | 9 || 10 | 1 | ALT5 | RxD | 16 | 15 |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 |
| 27 | 2 | GPIO. 2 | IN | 0 | 13 || 14 | | | 0v | | |
| 22 | 3 | GPIO. 3 | IN | 0 | 15 || 16 | 0 | IN | GPIO. 4 | 4 | 23 |
| | | 3.3v | | | 17 || 18 | 0 | IN | GPIO. 5 | 5 | 24 |
| 10 | 12 | MOSI | ALT0 | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | ALT0 | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 |
| 11 | 14 | SCLK | ALT0 | 0 | 23 || 24 | 1 | OUT | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | OUT | CE1 | 11 | 7 |
| 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 |
| 5 | 21 | GPIO.21 | IN | 1 | 29 || 30 | | | 0v | | |
| 6 | 22 | GPIO.22 | IN | 1 | 31 || 32 | 0 | IN | GPIO.26 | 26 | 12 |
| 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | |
| 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 |
| 26 | 25 | GPIO.25 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 |
| | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+---Pi 4B--+---+------+---------+-----+-----+
passive process (프로그램)
디스크에 저장된 실행 파일이나 스크립트처럼 아직 실행되지 않은 상태
(예 : /bin/ls, a.out 파일 자체)
active process (프로세스)
실행 파일이 메모리에 적재되고, CPU·메모리·파일 같은 자원을 할당받아 실제로 실행 중인 상태
보통 실행 중인 프로그램(program in execution)이라는 정의를 사용
user process (사용자 프로세스)
일반 애플리케이션, 쉘, 서버 프로그램 등 사용자 코드가 사용자 모드에서 실행되는 프로세스
파일 열기, 네트워크, 메모리 할당처럼 특권이 필요한 작업은 시스템 콜을 통해 커널에 요청
각 user process는 자기만의 유저 주소 공간과 스택을 가짐
kernel process / kernel thread (커널 프로세스/스레드)
OS 내부 작업을 담당하는 커널 코드가 커널 모드에서 실행되는 실행 단위
보통 사용자 영역이 아니라 커널 전용 주소 공간에 있고, 다른 프로세스와 공유되는 형태
프로세스 관리
프로세스 생성(fork), 상태를 바꾸며 실행/대기/종료, 종료 후 정리를 포함하는 전체 메커니즘
프로세스가 가진 정보들은 프로세스 제어 블록(PCB, task_struct )에 저장되어 관
프로세스의 내용
프로그램 코드 + 현재 활동 상태 : 텍스트 영역(명령어)와 현재 어디까지 실행했는지 정보
프로그램 카운터(PC) : 다음에 실행할 명령어 주소
레지스터 값 : 연산 중간 결과, 스택 포인터 등 CPU 레지스터들의 현재 값
스택(Stack) : 함수 호출/리턴, 지역 변수, 함수 인자 저장
전역 변수 영역(Data/BSS) : 프로세스 전체에서 공유되는 변수들
추상적인 프로세스 상태
생성(New) : 프로세스가 만들어짐
수행(Running) : CPU를 잡고 명령어를 실행 중
대기(Waiting/Blocked) : I/O, 이벤트, 시그널 등을 기다리며 잠든 상태
준비(Ready) : 실행할 준비는 되었지만 CPU를 아직 배정받지 못한 상태
종료(Terminated) : 실행을 마친 상태
리눅스의 프로세스 상태 (task_struct.state)
리눅스 커널의 include/linux/sched.h에서 struct task_struct의 state 필드로 관리되는 상태
TASK_RUNNING
실제로 CPU에서 실행 중이거나, 실행 대기(Ready) 큐에 올라와 있는 상태
TASK_INTERRUPTIBLE
어떤 조건(I/O 완료, 이벤트)을 기다리며 수면(sleep) 상태
시그널이 오면 깨울 수 있고, 조건이 만족되면 다시 TASK_RUNNING으로 전환
예 : 입력을 기다리는 프로세스
TASK_UNINTERRUPTIBLE
마찬가지로 sleep이지만, 시그널로는 깨지지 않음
커널이 명시적으로 wake_up() 같은 메커니즘으로 깨워야 함
중단되면 안 되는 I/O(디스크 블록 전송 등)에 사용
TASK_STOPPED / __TASK_STOPPED
SIGSTOP, 디버거(트레이스) 등에 의해 정지된 상태
TASK_ZOMBIE
프로세스가 exit()를 호출해 실행은 끝났지만, 부모가 아직 wait()를 호출하지 않아 task_struct가 남아 있는 상태
프로세스 컨트롤 블록이 해제되지 않아 프로세스 테이블에 task_struct 정보가 남아 있는 상태
프로세스 상태 변경 흐름
생성
fork() 호출 시 부모의 문맥을 복사해 자식 생성 → 새 task_struct와 PID 부여
실행/대기 전환
CPU를 받으면 TASK_RUNNING → 실행
I/O 요청, sleep, wait 등으로 인해 TASK_INTERRUPTIBLE / TASK_UNINTERRUPTIBLE로 전환
이벤트 발생 또는 시그널/wake_up()으로 다시 TASK_RUNNING
종료 준비
exit(status) 호출 → 리소스 회수, 상태 저장 → TASK_ZOMBIE로 전환
정리
부모가 wait(&status)를 호출하면 좀비 상태를 회수하고 PCB(task_struct) 제거 → 완전 종료
프로세스 생성 : fork / clone / copy_process
fork()
부모 프로세스가 자신의 복사본을 만들어 자식 프로세스를 생성하는 표준 시스템 콜
자식은 부모의 주소 공간, 파일 디스크립터, 레지스터 값 등을 복사
반환값(PID)은 다르게 받음 (부모는 자식 PID, 자식은 0)
리눅스 내부 흐름
사용자 공간 fork() → 커널의 clone() 시스템 콜 → _do_fork() → copy_process() 호출
copy_process()에서 새 task_struct와 커널 스택 생성 (dup_task_struct())
부모의 메모리/파일/시그널/스케줄링 정보 등을 자식에게 복사(또는 COW로 공유)
프로세스 동기화 : wait
wait(&status) 은 부모 프로세스가 자식이 종료될 때까지 블록(block)되어 기다리는 시스템 콜
자식이 exit(status)로 종료되면 커널은 자식 상태를 TASK_ZOMBIE로 두고, 부모에게 SIGCHLD 시그널 전달
부모가 wait(&status)를 호출하면 종료 상태를 넘겨주고, task_struct를 완전히 제거
wait를 호출하지 않는 경우 TASK_ZOMBIE 상태가 길게 유지
프로세스 종료 : exit
exit(status) 은 자식 프로세스 정상 종료 시 호출하는 함수
열린 파일, 메모리, 커널 자원을 회수하고 종료 코드와 정보를 PCB에 남김
TASK_ZOMBIE로 상태를 변경하고 부모에게 SIGCHLD 시그널을 보내 종료되었음을 알림
문맥(Context)
실행 중인 프로세스의 순간의 스냅샷
프로세스를 다시 이어서 실행하는 데 필요한 모든 실행 상태 정보
사용자 측면 (User context)
사용자 주소 공간 : 코드/데이터/힙/스택 등 프로세스 메모리 이미지
레지스터 측면 (CPU context)
프로그램 카운터(PC), 스택 포인터(SP), 상태 레지스터(CPSR), 범용 레지스터 등 현재 CPU 레지스터 값
시스템 측면 (Kernel context)
프로세스 상태(state), PID/TGID, 스케줄링 정보, 페이지 테이블(mm_struct), 커널 스택, 열린 파일, 시그널, 우선순위 등
리눅스에서 문맥 저장 위치
task_struct
프로세스/스레드의 PCB 역할을 하는 핵심 구조체
PID, 상태, 스케줄링 정보, 메모리 매핑, 파일 디스크립터 등 거의 모든 관리 정보를 포함
프로세스 관련 쉘 명
ps -l, ps -el, ps -L : PID, PPID, UID, STAT(상태), PRI(우선순위) 등 출력
top : 실시간 CPU/메모리 사용률 및 프로세스 상태 확인
pstree : 부모-자식 관계를 트리 구조로 표시
/proc/<pid>/status, /proc/<pid>/stat : 커널이 가진 task_struct 정보 일부를 텍스트로 노출 (State, PPid, Threads 등)
cat /proc/1/status : PID 1(init/systemd)의 상태 확인
작업 제어 관련
fg, bg : 포그라운드/백그라운드로 작업 전환(쉘이 시그널과 터미널 제어)
Ctrl+C : SIGINT로 프로세스 강제 종료 요청
Ctrl+Z : SIGTSTP로 프로세스 일시 정지(T 상태)
ps / /proc에서의 state 코드
R : Running / Runnable
실행 중이거나 run queue에 있어 언제든 실행될 수 있는 상태(TASK_RUNNING)
S : Sleeping
인터럽트 가능 수면, 일반적인 대기 상태(TASK_INTERRUPTIBLE)
D : Disk sleep (Uninterruptible sleep)
디스크 I/O 등으로 인한 인터럽트 불가능 수면 상태(TASK_UNINTERRUPTIBLE)
T : Traced / Stopped
SIGSTOP 또는 디버거 ptrace에 의해 중단된 상태(TASK_STOPPED)
Z : Zombie
종료되었지만 부모가 회수하지 않아 남아 있는 좀비(TASK_ZOMBIE, PCB/task_struct만 남음)
I : Idle
커널의 유휴 스레드 등, 일부 시스템에서 사용
프로세스 생성
bash의 명령에 의한 생성
bash가 먼저 fork() 로 자신을 복사한 자식 프로세스를 생성
자식이 exec() 계열로 새로운 프로그램 이미지를 덮어쓰기
프로그램 내 함수에 의한 생성
C 코드에서 직접 fork() 를 호출하여 자식을 생성
필요하면 자식 프로세스에서 execl() 로 다른 프로그램을 실행
프로세스 로드 함수
execl() 은 현재 프로세스가 실행 중인 프로그램을 다른 프로그램으로 교체하는 함수
이미 존재하는 프로세스 안에 다른 프로그램을 올리는 개념
현재 프로세스의 코드/데이터/스택/힙 등을 모두 새 프로그램으로 덮어 씀
프로세스 생성·대기·종료
fork()
부모 프로세스를 기반으로 거의 똑같은 자식 프로세스를 생성하는 함수
스택(함수 호출 상태, 지역 변수)과 데이터/전역 변수 영역, 힙, 파일 디스크립터, 환경 변수 등은 복사되어 전달
코드(text) 영역은 읽기 전용이므로 공유해서 사용
부모는 반환값으로 자식의 PID를 받고, 자식은 0을 받음
exec 계열
execl, execv, execve 등의 함수
현재 프로세스의 메모리 공간을 다른 프로그램 코드로 교체
wait(&status)
부모 프로세스가 자식 프로세스의 종료를 블록 상태로 기다리고 자식의 종료 코드 회수
wait를 호출해야 좀비 상태가 사라짐
exit(status)
프로세스가 자신의 실행을 끝내고 종료 상태 코드를 커널에 전달
부모 프로세스는 wait() 로 이 status를 받게
프로세스 라이프사이클
NEW
생성 중 (fork() 호출 직후)
READY(Ready Queue)
준비 큐(READY-q)에 들어가 CPU를 기다림
Ready Queue는 보통 FIFO 큐(Queue 자료구조)로 구현
리눅스는 우선순위/스케줄링 정책별 여러 큐·트리로 관리
RUNNING (CPU)
스케줄러가 선택해 CPU에서 실행
SLEEP / WAIT (Wait Queue)
I/O 완료, 이벤트, 타이머 등을 기다리며 대기 (인터럽트 가능/불가능 WAIT-Q)
조건이 충족되면 다시 READY 큐로 이동
ZOMBIE
exit()로 실행은 끝났지만, 부모의 wait()가 아직 호출되지 않아 PCB(task_struct)만 남은 상태
스케줄링 정책과 우선순위
동적 스케줄링
실행 중에 OS가 우선순위를 계속 조정하는 방식
오래 실행 중인 태스크는 불리하게, 반대의 경우는 유리하게 만듦
대표적으로 CFS가 있음
CFS(Completely Fair Scheduler)
“완전 공평”하게 CPU 시간을 나누는 것
각 태스크에 가상 실행 시간 vruntime을 유지
vruntime
가상수행시간, 실제 실행 시간에 우선순위 가중치를 적용한 값
더 많이 실행된 태스크는 vruntime이 커지고, 덜 실행된 태스크는 작음
vruntime이 가장 작은 프로세스부터 우선 실행
nice 값과 PRI
nice는 프로세스의 우선순위를 설정하는 명령어
nice는 -20 ~ +19 사이의 값을 가지며, 음수 값을 주기 위해서는 관리자 권한 필요
내부 우선순위는 보통 prio = nice + 숫자 형식으로 맵핑됨
nice가 낮을수록 우선순위가 높아짐
정적 스케줄링
우선순위를 한 번 정하면 실행 중에 변하지 않는 방식
커널은 고정된 priority 값만 보고 스케줄링
실시간 스케줄링 (RT: SCHED_FIFO, SCHED_RR)
우선순위가 높은 태스크가 CPU를 원할 때 마음껏 쓰는 구조
일반 CFS 태스크보다 항상 우선함
SCHED_FIFO (FIFO)
같은 우선순위 그룹 안에서는 먼저 READY가 된 태스크가 CPU를 선점당하지 않고 계속 사용
예: P1 먼저 준비되면 P1이 스스로 블록되거나 종료할 때까지 계속 실행, 그 뒤 P2 실행
SCHED_RR (Round Robin)
같은 우선순위 그룹 안에서 타임 슬라이스를 돌려가며 실행.
예: 타임 슬라이스 단위로 P1 → P2 → P1 → P2 …
프로세스 / 태스크와 OS 종류
GPos (일반 목적 OS)
리눅스, 윈도우 등 PC·서버용 OS
실행 단위를 보통 process(프로세스)라고 부름
RTOS (Real-Time OS)
FreeRTOS, VxWorks, Zephyr 등 임베디드·실시간 장치용 OS
실행 단위를 task(태스크)라고 부르는 경우가 많음
IPC / ITC
Inter Process(Task) Communication
프로세스 또는 태스크(실행 엔티티) → 스케줄러 → CPU 점유
이때 프로세스/태스크 사이에서 데이터를 주고받는 기법
그 중 시그널은 알림용 메커니즘
시그널의 기본 개념
커널을 통해 프로세스에게 전달되는 비동기적 이벤트(시간차에 의한 처리)
터미널에 kill -l 을 입력하면 지원되는 시그널 목록 확인 가능
ctrl+c 등)으로 동작 제한커널 → 프로세스
프로세스 예외 상황 시에 커널이 자동 발생시키는 시그널 (예: SIGSEGV )
프로세스 ↔ 프로세스 (IPC)
하나의 프로세스가 다른 프로세스에게 신호를 보냄 (예: SIGUSR1/2 )
윈도우의 유사 개념 : message
리눅스의 시그널과 유사한 역할을 윈도우 메시지가 수행
동기/비동기의 개념
동기적(synchronous)
공통된 기준을 맞춰 같은 타이밍에 움직이는 것
예: 정해진 시간에 동시에 시작하는 작업
비동기적(asynchronous)
별도의 공통된 시계 없이 한 쪽이 이벤트를 던지고 다른 쪽이 그때그때 받는 방식
예: 공을 던지고 받기
하드웨어 통신에서의 동기/비동기 예시
UART (Universal Asynchronous Receiver-Transmitter)
비동기 직렬 통신
Clock 선을 공유하지 않고 각자 보드레이트만 맞춰 통신
상대적으로 간단하지만 동기식에 비해 속도나 정확도가 제한적
USART (Universal Synchronous/Asynchronous Receiver-Transmitter)
동기 모드를 지원하는 통신으로, Clock 선을 공유함
비동기식보다 같은 시간 동안 보낼 수 있는 데이터의 양이 더 많음
Exception(동기적 예외)
프로세스 자신의 명령어(instuction)에 의해 발생
PC 위치에서 언제 발생할지 정확히 알 수 있음
int *p = 0; // NULL 포인터
*p = 1; // 0번지에 쓰기 시도 → SIGSEGV (Segmentation fault)
; 어셈블리
STR r1, [r2] ; r1=1, r2=0 → MMU가 Data Abort Exception 발생
↓
Exception Handler 호출
↓
SIGSEGV 시그널 → 프로세스 종료
PC → 문제 명령어 → Exception → Handler → 처리 후 복귀 또는 종료
Interrupt(비동기적 인터럽트)
외부(하드웨어 등)에서 발생
프로세스 실행 중 언제든 일어날 수 있어 예측이 불가능
i1 실행 ── i2 실행 ── i3 실행 ── 외부 인터럽트 발생
↓
IRQ Handler 호출
↓
인터럽트 처리
↓
i3 이어서 실행
PC → IRQ Handler → 처리 → PC로 복귀
프로세스 간 동기화 (Synchronize)
프로세스/스레드들이 공유 자원을 안전하게 사용하기 위한 메커니즘
배타적 사용 (Mutual Exclusion)
한 번에 하나의 프로세스만 자원을 사용할 수 있음 (예: 화장실)
순서적 사용 (Ordering)
특정 순서대로 자원 사용 (예: 이어달리기)
시그널 보내기
bash에서 kill 명령어 사용
kill -SIGKILL 1234 # PID 1234에 SIGKILL 전송
프로그램에서 kill() 시스템 콜
#include <signal.h>
#include <sys/types.h>
int kill(pid_t pid, int sig); // 성공: 0, 실패: -1
int send_signal(int pid)
{
int ret = kill(pid, SIGKILL); // PID에 SIGKILL 전송
printf("ret: %d\n", ret); // 0=성공, -1=실패
return ret;
}
시그널 기본 행동 종류
| 행동 | 의미 | 예시 시그널 |
|---|---|---|
| Term | 프로세스 종료 | SIGTERM, SIGINT |
| Ign | 무시(ignore) | SIGCHLD, SIGWINCH |
| Core | 종료 + 코어 덤프 생성 | SIGSEGV, SIGFPE |
| Stop | 프로세스 정지 | SIGSTOP, SIGTSTP |
SIGKILL 과 SIGSTOP 은 핸들러로 가로채거나, block하거나, 무시할 수 없음
시그널의 전달 과정
발생 : 키보드, kill 명령, 하드웨어 예외로부터 시그널 발생
커널 전달 : 현재 포그라운드 프로세스 그룹에 전달
인터럽트 : 실행 중인 프로세스가 작동 중이라면 인터럽트 → 유저 모드 복귀 시점에 시그널 확인
처리 : 사용자 시그널 핸들러를 실행하거나 기본 행동(Term
기본 행동이 없다면 프로세스 종료 요청/코어 덤프 생성
시그널 관련 API
signal()
시그널 처리를 설정하는 간단한 함수
시그널 번호와 핸들러 지정 가능
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
sigaction()
시그널 처리기를 설치하는 함수
시그널을 받았을 때 실행할 내용을 쌍·구조체를 사용하여 다양한 기능을 구현
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
struct sigaction
struct sigaction {
void (*sa_handler)(int); // 기본 핸들러
void (*sa_sigaction)(int, siginfo_t*, void*); // 상세 정보 핸들러
sigset_t sa_mask; // 핸들러 실행 중 블록할 시그널 집합
int sa_flags; // 동작 플래그 (SA_RESTART 등)
void (*sa_restorer)(void); // 레거시
};
시그널 그룹
시그널 집합의 처리를 위한 구조체
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t;
시그널 그룹 관리 API
시그널 집합인 sigset_t 관리를 위한 함수들
sigemptyset(&set); // 빈 집합
sigaddset(&set, SIGINT); // SIGINT 추가
sigdelset(&set, SIGUSR1); // SIGUSR1 제거
sigismember(&set, SIGINT); // 포함 여부 확인 (1/0)
아무래도 제일 재미있었던 건 DHT11을 써서 했던 실습!
실제로 데이터가 들어오는 걸 눈으로 확인할 수 있어서 좋았다.
프로세스와 시그널은 사실 계속 쓰이는 개념이라 충분히 숙지하고 있어야 하지만
이번에 실습 과제를 하면서 보니 이론으로 보는 것과 제한된 시간 안에 코드에 적용하는 건 다르다는 걸 많이 느꼈다.
뒤에 남아 있는 관련 내용은 프로세스 간 통신, 네트워크 부분이고
당분간은 그 다음 내용인 드라이버에 관해 작성할 예정이다.

그리고 12월의 챌린저가 되었다!
킹메이커 여러분께 개큰감사를 🙇♀️
내 블로그 보고 시험공부하는 사람들은 인간적으로 따봉 눌러줘라