[Raspberry Pi] 라즈베리 파이 사용하기 ③

예빈·2026년 1월 8일

Embedded/Linux

목록 보기
14/21


0️⃣ 들어가며

라즈베리파이와 리눅스 관련 3번째 글이다.
사실 배운 지는 한참 되었는데 정리할 내용이 엄청 밀려서
여기까지만 쓰고 당분간은 다른 내용의 포스팅을 해야 할 듯하다 😅


1️⃣ 학습 내용

4.5 리눅스 시스템 프로그래밍과 라즈베리 파이의 제어

✅ DHT11 센서

  • 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--+---+------+---------+-----+-----+

5. 프로세스와 스레드: 다중 처리

5.1 프로세스와 시그널

✅ 프로세스 기본 개념

  • active / passive process
    • passive process (프로그램)

      디스크에 저장된 실행 파일이나 스크립트처럼 아직 실행되지 않은 상태

      (예 : /bin/lsa.out 파일 자체)

    • active process (프로세스)

      실행 파일이 메모리에 적재되고, CPU·메모리·파일 같은 자원을 할당받아 실제로 실행 중인 상태

      보통 실행 중인 프로그램(program in execution)이라는 정의를 사용

  • user process / kernel process
    • 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 정보가 남아 있는 상태

  • 프로세스 상태 변경 흐름

    1. 생성

      fork() 호출 시 부모의 문맥을 복사해 자식 생성 → 새 task_struct와 PID 부여

    2. 실행/대기 전환

      CPU를 받으면 TASK_RUNNING → 실행

      I/O 요청, sleep, wait 등으로 인해 TASK_INTERRUPTIBLE / TASK_UNINTERRUPTIBLE로 전환

      이벤트 발생 또는 시그널/wake_up()으로 다시 TASK_RUNNING

    3. 종료 준비

      exit(status) 호출 → 리소스 회수, 상태 저장 → TASK_ZOMBIE로 전환

    4. 정리

      부모가 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 -lps -elps -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)의 상태 확인

    • 작업 제어 관련

      fgbg : 포그라운드/백그라운드로 작업 전환(쉘이 시그널과 터미널 제어)

      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를 받게

  • 프로세스 라이프사이클

    1. NEW

      생성 중 (fork() 호출 직후)

    2. READY(Ready Queue)

      준비 큐(READY-q)에 들어가 CPU를 기다림

      Ready Queue는 보통 FIFO 큐(Queue 자료구조)로 구현

      리눅스는 우선순위/스케줄링 정책별 여러 큐·트리로 관리

    3. RUNNING (CPU)

      스케줄러가 선택해 CPU에서 실행

    4. SLEEP / WAIT (Wait Queue)

      I/O 완료, 이벤트, 타이머 등을 기다리며 대기 (인터럽트 가능/불가능 WAIT-Q)

      조건이 충족되면 다시 READY 큐로 이동

    5. ZOMBIE

      exit()로 실행은 끝났지만, 부모의 wait()가 아직 호출되지 않아 PCB(task_struct)만 남은 상태

  • 스케줄링 정책과 우선순위

    1. 동적 스케줄링

      실행 중에 OS가 우선순위를 계속 조정하는 방식

      오래 실행 중인 태스크는 불리하게, 반대의 경우는 유리하게 만듦

      대표적으로 CFS가 있음

      • CFS(Completely Fair Scheduler)

        “완전 공평”하게 CPU 시간을 나누는 것

        각 태스크에 가상 실행 시간 vruntime을 유지

      • vruntime

        가상수행시간, 실제 실행 시간에 우선순위 가중치를 적용한 값

        더 많이 실행된 태스크는 vruntime이 커지고, 덜 실행된 태스크는 작음

        vruntime이 가장 작은 프로세스부터 우선 실행

      • nice 값과 PRI

        nice는 프로세스의 우선순위를 설정하는 명령어

        nice는 -20 ~ +19 사이의 값을 가지며, 음수 값을 주기 위해서는 관리자 권한 필요

        내부 우선순위는 보통 prio = nice + 숫자 형식으로 맵핑됨

        nice가 낮을수록 우선순위가 높아짐

    2. 정적 스케줄링

      우선순위를 한 번 정하면 실행 중에 변하지 않는 방식

      커널은 고정된 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 을 입력하면 지원되는 시그널 목록 확인 가능

    • 시그널의 역할
      1. 예외 처리 : 프로세스가 잘못된 동작을 했을 때 시스템이 프로세스의 동작을 제한
      2. 외부에서 동작 지시 : 다른 프로세스나 터미널 입력(ctrl+c 등)으로 동작 제한
      3. 자원 할당 예외
      4. 특정 이벤트 발생을 감지 : I/O 인터페이스에서 이벤트 발생을 통보
    • 시그널의 종류
      1. 커널 → 프로세스

        프로세스 예외 상황 시에 커널이 자동 발생시키는 시그널 (예: SIGSEGV )

      2. 프로세스 ↔ 프로세스 (IPC)

        하나의 프로세스가 다른 프로세스에게 신호를 보냄 (예: SIGUSR1/2 )

      3. 윈도우의 유사 개념 : 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)

    프로세스/스레드들이 공유 자원을 안전하게 사용하기 위한 메커니즘

    1. 배타적 사용 (Mutual Exclusion)

      한 번에 하나의 프로세스만 자원을 사용할 수 있음 (예: 화장실)

    2. 순서적 사용 (Ordering)

      특정 순서대로 자원 사용 (예: 이어달리기)

  • 시그널 보내기

    1. bash에서 kill 명령어 사용

      kill -SIGKILL 1234   # PID 1234에 SIGKILL 전송
    2. 프로그램에서 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

    SIGKILLSIGSTOP 은 핸들러로 가로채거나, 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)

2️⃣ 느낀 점

아무래도 제일 재미있었던 건 DHT11을 써서 했던 실습!
실제로 데이터가 들어오는 걸 눈으로 확인할 수 있어서 좋았다.
프로세스와 시그널은 사실 계속 쓰이는 개념이라 충분히 숙지하고 있어야 하지만
이번에 실습 과제를 하면서 보니 이론으로 보는 것과 제한된 시간 안에 코드에 적용하는 건 다르다는 걸 많이 느꼈다.
뒤에 남아 있는 관련 내용은 프로세스 간 통신, 네트워크 부분이고
당분간은 그 다음 내용인 드라이버에 관해 작성할 예정이다.

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

0개의 댓글