jun-wiki

View My GitHub Profile

Posts (Latest 10 updated) :
Read all
Contents:
  1. 8.5 시그널
    1. 8.5.1 시그널 용어
    2. 8.5.2 시그널 보내기
      1. Process Groups
      2. /bin/kill 프로그램으로 시그널 보내기
      3. 키보드로 시그널 보내기
      4. kill 함수로 시그널 보내기
      5. alarm 함수로 시그널 보내기
    3. 8.5.3 시그널의 수신
      1. 수신 절차
    4. 8.5.4 시그널 블록하기와 블록 해제하기
    5. 8.5.5 시그널 핸들러 작성하기 (Writing Signal Handlers)
      1. 안전한 시그널 처리 (Safe Signal Handling)
      2. 정확환 시그널 처리 (Correct Signal Handling)
      3. 호환성 있는 시그널 처리 (Portable Signal Handling)
    6. 8.5.6 치명적인 동시성 버그를 피하기 위해서 흐름을 동기화하기
      1. 문제(레이스)
      2. 순서 고정(부모)
      3. 자식 쪽
      4. 핸들러
      5. 핵심
    7. 8.5.7 명시적으로 시그널 대기하기
      1. 나쁜/부적절한 대안
      2. 권장 대안 : sigsuspend
      3. 요점

csapp 8장

그 중 .5 다

.5



8.5 시그널

Linux 시그널 : 예외적 제어 흐름으로 프로세스1와 커널2이 다른 프로세스를 중단하게 해준다

시그널은 시스템에서 어떤 종류의 이벤트가 발생했음을 프로세스에 알리는 작은 메시지로 리눅스 시스템에서 30가지 유형이 있다

저수준에서 일어나는 하드웨어 예외는 우리가 볼 수 없지만 시그널은 이를 보이게 해준다
그 상황에 걸맞는 간단한 시그널을 보내 말이다


8.5.1 시그널 용어

  1. 시그널 보내기

    커널이 목적지 프로세스의 컨텍스트3 안의 일부 상태를 갱신해 시그널을 보낸다


  • 보내는 상황

    • 시스템 이벤트 감지했을때 보낸다

    • kill함수를 호출해 명시적 요청을 받았을 때


자기 자신한테도 보낼 수 있다


  1. 시그널의 수신

    커널이 그 신호를 실제로 처리하도록 강제할 때에 시그널을 수신한다
    프로세서는 시그널을 무시하거나 종료하거나, 시그널 핸들러로 포착할 수도 있다


보냈지만 아직 수신되지 않은 시그널을 펜딩 시그널이라 한다
펜딩 시그널은 최대 하나만 존재 가능하다

이미 펜딩 시그널이 존재하면 그 이후의 시그널들은 대기 없이 바로 버려진다

이를 통해 특정 시그널들의 수신을 선택적으로 차단 가능하다



8.5.2 시그널 보내기

시그널을 보내기 위한 여러 메커니즘이 있는데 이 메커니즘들은 모두 프로세스 그룹(process group) 개념에 의존한다


Process Groups

모든 프로세스는 정확히 하나의 프로세스 그룹에 속하며, 이는 양의 정수인 프로세스 그룹 ID로 식별된다

기본적으로, 자식 프로세스는 부모와 같은 프로세스 그룹에 속한다


/bin/kill 프로그램으로 시그널 보내기

/bin/kill 프로그램은 임의의 시그널을 다른 프로세스에 보낸다

음수를 사용하면 프로세스가 아닌 프로세스 그룹 ID로 해석해 그 그룹의 모든 프로세스에 시그널을 보낸다


키보드로 시그널 보내기

잡(job): 셸4이 한 커맨드 라인으로 만든 프로세스 묶음이며, 각 잡은 하나의 프로세스 그룹(PGID) 으로 관리됨 (PGID는 보통 그룹 리더의 PID)

포그라운드/백그라운드: 한 번에 포그라운드 잡은 1개, 백그라운드 잡은 0개 이상 존재

파이프라인5 예시: ls | sort → 두 프로세스가 한 포그라운드 잡/그룹을 이룸

키보드 시그널은 TTY6의 포그라운드 프로세스 그룹 전체로 전달:

  • Ctrl+C → SIGINT: 기본 동작 종료.
  • Ctrl+Z → SIGTSTP: 기본 동작 일시정지(정지).


kill 함수로 시그널 보내기

프로세스는 (자기 자신을 포함한) 다른 프로세스에 시그널을 보내기 위해 kill 함수를 호출한다

pid값이 음수냐 양수냐 0이냐에 따라 시그널을 어디에 보낼지 정한다

  • 양수면 시그널을 pid프로세스에

  • 0이면 호출한 프로세스가 속한 프로세스 그룹의 모든 프로세스에(자신 포함)에 시그널을

  • 음수면 pid프로세스 그룹 전부의 프로세스에게 시그널을 보낸다

예시 코드 ```c #include <sys/types.h> #include

int kill(pid_t pid, int sig); /* Returns: 0 if OK, −1 on error */ ```


alarm 함수로 시그널 보내기

프로세스는 alarm 함수 호출 가능

alarm 호출은 보류중인 알람을 취소하고 취소된 알람의 남은 초를 반환

보류 중인 알람이 없었으면 0을 반환



8.5.3 시그널의 수신

수신 절차

  • 커널1이 사용자 모드로 돌아가기 전, 차단되지 않은 보류 중 시그널 집합을 점검

    • 비어있으면 다음 명령 실행

    • 비어있지 않으면 한 신호 k를 강제로 수신

      • 지정 동작 수행 후 중단 지점의 다음 명령


시그널 별 정의된 기본동작 존재

  • 프로세스 종료

  • 프로세스 종료 후 코어 덤프 남김

  • 프로세스가 SIGCONT 시그널에 의해 재시작될 때까지 정지

  • 프로세스가 시그널 무시


시그널 signum에 대한 동작 변경

  • signum 유형 시그널 무시

  • signum 유형의 시그널 동작 기본 동작으로 되돌리기

  • 그 외 사용자 핸들러 설치


SIGSTOP, SIGKILL은 포착/무시/변경 불가

반환값: 이전 핸들러 포인터 또는 SIG_ERR(오류)


핸들러 실행/복귀

  • 포착 시 핸들러(signum 인자 전달) 가 실행되어 시그널 유형을 구분 가능

  • 보통 return중단 지점으로 복귀하나, 일부 시스템에서는 중단된 시스템 콜이 즉시 오류로 반환될 수 있음


중첩(선점) 가능

  • 실행 중인 핸들러7 S가 또 다른 시그널 tT에 의해 중단될 수 있음

  • T 종료 후 S 재개

  • S 종료 후 메인 프로그램 재개



8.5.4 시그널 블록하기와 블록 해제하기

시그널 블록에 두 가지 메커니즘이 있다

  • 암묵적 차단 메커니즘

    • 기본적으로 커널1은 현재 핸들러7가 처리 중인 동일 유형 시그널은 보류 중(pending)이라도 차단
  • 명시적 차단 메커니즘

    • 응용 프로그램은 sigprocmask 함수와 그 보조 함수들로 선택한 시그널들을 명시적으로 차단/차단 해제 가능



8.5.5 시그널 핸들러 작성하기 (Writing Signal Handlers)

시그널 핸들러 작성하는 법 나온다

안전한 시그널 처리 (Safe Signal Handling)

  • G0 최소화: 핸들러는 작게—전역 플래그만 set하고 곧바로 return; 실제 처리는 메인 루프에서

  • G1 안전한 함수만: 핸들러에서는 async-signal-safe 함수만 호출(예: write, _exit, sigaction 등)
    printf/malloc/exit 금지
    → 필요하면 Sio(write 기반) 같은 안전 I/O 사용

  • G2 errno 보존: 핸들러 입구에서 int saved=errno;
    저장 → 출구에서 복원(핸들러가 _exit로 끝나면 불필요)

  • G3 공유 데이터 보호: 전역/공유 자료구조 접근은 일시적 시그널 차단(sigprocmask)으로 감싸서 중단 불가 구역 보장

  • G4 volatile: 핸들러와 메인이 공유하는 전역 변수는 volatile 로 선언 (레지스터 캐싱 방지)

  • G5 플래그는 sig_atomic_t: 단일 읽기/쓰기의 원자성을 보장하는 타입 사용
    volatile sig_atomic_t flag;
    (단, flag++ 같은 복합 갱신은 원자 아님)


정확환 시그널 처리 (Correct Signal Handling)

  • 신호는 큐잉되지 않음: 보류 집합은 타입당 1비트
    같은 타입이 차단 중에 여러 번 오면 중복은 버려짐
    → “도착 횟수 세기” 용도로 쓰지 말 것

  • SIGCHLD 예시: 자식이 3번 종료해도 핸들러가 실행 중이면 두 번째만 보류, 세 번째는 소실
    → 좀비 남음

    해법: 핸들러에서 while (waitpid(-1, NULL, …) > 0) 루프로 모든 자식을 한 번에 수거(보통 WNOHANG 옵션 권장)


호환성 있는 시그널 처리 (Portable Signal Handling)

  • signal() 의미론 차이: 일부 시스템은 핸들러 1회 실행 뒤 자동 기본값 복구

  • 느린 시스템 콜: 일부 시스템에서 시그널로 중단 후 즉시 EINTR로 실패(자동 재시작 없음)

  • sigaction 사용 권장: 의미론을 명시. 실전에서는 래퍼 Signal 을 써서

    • 현재 처리 유형만 차단,

    • 가능하면 SA_RESTART 로 중단된 시스템 콜 자동 재시작,

    • 핸들러는 재설치 없이 지속되도록 설정



8.5.6 치명적인 동시성 버그를 피하기 위해서 흐름을 동기화하기

동시성은 상당히 중요하고 어렵기에 12장에서 제대로 다루고 지금은
예외적 제외흐름과 함께하는 겉핥기 정도다

예시로 셸과 같은 자식을 계속 만들고 잡 리스트를 관리하려다, 시그널 타이밍 때문에 deletejobaddjob보다 먼저 실행될 수 있는 레이스를 보여준다

  • 레이스 : 동기화 오류로 공유 자원에 여러 프로세스나 스레드가 접근하려 할때 발생한다

  • addjob, deletejob 함수는 잡 리스트에 항목을 추가/삭제 한다


그리고 이러한 레이스 오류가 생기는 이유와 해결법 나온다



문제(레이스)

자식이 매우 빨리 종료하면 SIGCHLD가 먼저 도착해 핸들러의 deleteadd보다 앞섬
→ 잡 리스트에 유령 항목 남음

순서 고정(부모)

fork SIGCHLD 차단
→ fork
addjob 먼저
→ 차단 해제(보류된 신호는 해제 후 처리)

자식 쪽

부모의 차단 상태 상속execSIGCHLD 해제

핸들러

호출될 때마다 모든 종료 자식을 루프로 수거, 공유 리스트 수정 구간만 잠시 차단, errno 저장/복원, 본문은 짧고 단순

핵심

“add → delete” 순서를 차단/해제로 강제하고,

비큐잉 신호 특성을 감안해 루프 수거로 정확성 확보



8.5.7 명시적으로 시그널 대기하기

명시적으로 시그널을 기다려야 할 때가 있다

바로
전면 작업을 생성할 때, 커널1은 이 작업이 종료되고 SIGHLD 핸들러에 의해 삭제될 때까지 기다려야만 한다

이에 대해서도 대안이 있다

나쁜/부적절한 대안

  • 스핀 루프 :

    • 신호를 기다릴 때 잠들지 않고 계속 플래그 확인

    • 자원을 많이 먹어 비효율적


  • pause를 섞음 :

    • 조건 검사 마지막 루프가 됨 while (pid == 0)가 참

    • pause()를 호출직전에 핸들러가 pid ≠ 0으로 갱신하고 복귀

    • pause()는 이미 호출되었지만 더받을 신호가 없음

    • 무기한 대기


  • sleep/nanosleep 섞기 :

    • 얼마만큼 잘지 근거가 없음

      • 짧으면 짧은대로 자원낭비

      • 길면 긴대로 시간낭비


권장 대안 : sigsuspend

  1. SIGCHLD를 차단한 채로 자식 생성

  2. 전역 플래그(예: pid = 0) 초기화

  3. sigsuspend잠깐 SIGCHLD만 허용하고 잠듦

  4. 자식이 끝나면 SIGCHLD 핸들러7가 플래그 갱신
    → sigsuspend가 깨고 돌아옴

  5. 원래 차단 상태가 자동 복원
    → 필요한 작업 하고 다음 라운드로


요점

sigsuspend항상 -1로 반환(보통 errno=EINTR) → 정상, 깨어났다는 뜻

핵심은 “SIGCHLD만 잠깐 허용하고 안전하게 잠들기” 로,
조건 확인 ↔ 잠들기 사이 레이스가 없다는 것






  1. 커널: 하드웨어와 응용 사이에서 자원·보안·스케줄링을 관리하는 운영체제의 핵심  2 3 4

  2. 프로세스: 실행 중인 프로그램으로, 독립된 메모리와 상태를 가진 수행 단위 

  3. 컨텍스트: 프로세스를 중단·재개하기 위해 커널이 보관하는 실행 상태의 묶음(레지스터, PC/SP, 시그널 마스크·대기 목록, 메모리/열린 파일 등) 

  4. 셸(Shell): 사용자 명령을 해석해 프로그램을 실행하고 입출력·잡 제어를 중개하는 명령줄 인터프리터 

  5. 파이프라인(Pipeline): 프로세스들을 연결해 앞의 표준출력을 다음의 표준입력으로 넘기는 실행 구조 

  6. TTY: 터미널 장치(제어 터미널)로, 키보드/화면 I/O와 포그라운드 프로세스 그룹을 커널이 관리하는 인터페이스 

  7. 핸들러(handler): 어떤 이벤트가 발생했을 때 자동으로 호출되도록 연결된 함수  2 3