suminworld

system

[시스템 프로그래밍] myshell.c 코드 분석 - 2

숨usm 2025. 9. 12. 05:03

https://github.com/sumin-world/suminworld-system-lab/tree/main/shell

 

suminworld-system-lab/shell at main · sumin-world/suminworld-system-lab

System programming & networking lab (C, Linux, OSTEP practice) - sumin-world/suminworld-system-lab

github.com

전체 코드는 위에서 확인 가능합니다!

Foreground / Background 프로세스와 시그널 처리

Foreground (전경)

  • 현재 사용자와 상호작용하고 있는 프로그램
  • 키보드 입력을 받는 프로그램
  • 터미널에서 "지금 실행 중"인 상태

Background (배경)

  • 뒤에서 조용히 실행되는 프로그램
  • 키보드 입력을 받지 않음
  • & 기호로 실행하거나, Ctrl+Z로 보낸 프로그램들

실행 예시

myshell$ ls              # ls가 foreground에서 실행
myshell$ sleep 100 &     # sleep이 background에서 실행 (&때문에)
myshell$ vim file.txt    # vim이 foreground에서 실행
# Ctrl+Z 누르면
[1]+ Stopped vim file.txt
myshell$                 # vim이 background로 이동, 쉘이 다시 foreground

프로세스 상태 전환 (Job Control)

        ┌──────────────┐
        │  Foreground  │
        │ (쉘이 기다림)│
        └──────┬───────┘
               │
     Ctrl+Z    │   종료/완료
(SIGTSTP 신호) │
               ▼
        ┌──────────────┐
        │   Stopped    │
        │ (일시정지)   │
        └──────┬───────┘
       fg │    │ bg
 (다시 전면) │ (백그라운드 실행)
             ▼
        ┌──────────────┐
        │ Background   │
        │ (쉘은 자유)  │
        └──────────────┘

상태별 설명

  • Foreground: 프로그램이 화면(터미널)을 점유하고, 쉘은 입력을 기다리지 않음
  • Stopped: Ctrl+Z로 멈춘 상태 (쉘 프롬프트 돌아옴, 프로그램은 메모리 안에 그대로 있음)
  • Background: & 또는 bg로 실행됨. 프로그램은 뒤에서 계속 돌아가고, 쉘은 입력을 즉시 받을 수 있음

fg_pgid 변수의 역할

fg_pgid는 foreground process group ID의 줄임말로, 지금 터미널을 점유하고 있는 프로세스 그룹의 ID를 저장합니다.

상태 해석

  • fg_pgid = 0: foreground에 아무 프로그램도 없음 (쉘만 터미널 사용)
  • fg_pgid > 0: 어떤 프로그램이 foreground에서 실행 중

왜 "그룹 ID"일까?

단순히 프로세스 하나만 실행되는 게 아니라, vim | grep abc | less 같은 파이프라인은 여러 프로세스로 이루어집니다. 이들을 묶어서 하나의 프로세스 그룹으로 관리해야 하기 때문입니다.

시그널 처리 설정 코드 분석

기본 구조

struct sigaction sa_int = {0}, sa_tstp = {0}, sa_chld = {0};

이 코드는 각각 다른 시그널을 처리할 "규칙"을 담는 구조체를 만든 것입니다:

  • sa_int → SIGINT (Ctrl+C) 처리용
  • sa_tstp → SIGTSTP (Ctrl+Z) 처리용
  • sa_chld → SIGCHLD (자식 프로세스 종료 알림) 처리용

시그널 핸들러 등록

sa_int.sa_handler = sigint_handler;
sigemptyset(&sa_int.sa_mask);
sa_int.sa_flags = SA_RESTART;
sigaction(SIGINT, &sa_int, NULL);

각 줄의 의미:

  1. sa_int.sa_handler = sigint_handler;
    • "만약 SIGINT가 오면 → sigint_handler()라는 함수를 실행해라"
  2. sigemptyset(&sa_int.sa_mask);
    • 시그널을 처리하는 동안 추가로 막을 시그널이 없음을 지정
  3. sa_int.sa_flags = SA_RESTART;
    • 시그널 처리 중에 read(), write() 같은 시스템 콜이 중단되지 않고 자동으로 재시도되게 함
  4. sigaction(SIGINT, &sa_int, NULL);
    • "앞으로 SIGINT(=Ctrl+C) 들어오면 sa_int 규칙대로 처리해라" 등록

struct sigaction 구조체 이해

구조체 정의 (리눅스 glibc 기준)

struct sigaction {
    void (*sa_handler)(int);    // 시그널 오면 실행할 함수
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;           // 처리 중 block할 시그널 집합
    int sa_flags;               // 옵션 플래그
    void (*sa_restorer)(void);  // (거의 안 씀, 옛날용)
};

핵심: sa_handler

  • void (*sa_handler)(int); → 함수 포인터
  • 의미: "인자로 int 하나를 받는, 반환형이 void인 함수의 주소"
  • 이 필드에 시그널 처리 함수를 등록

구조체 사용 예시

// 시그널 핸들러 함수 정의
void sigint_handler(int sig) {
    printf("Ctrl+C 눌렀네! (시그널 번호=%d)\n", sig);
}

// 구조체에 핸들러 등록
struct sigaction sa_int = {0};
sa_int.sa_handler = sigint_handler;

주요 시그널 비교

시그널 단축키 기본 동작 결과

SIGINT Ctrl + C 프로세스 종료 프로그램 죽음
SIGTSTP Ctrl + Z 프로세스 일시정지 프로그램 멈춤, 다시 실행 가능

핵심 차이점

  • Ctrl+C (SIGINT) = 완전 중단 (죽임)
  • Ctrl+Z (SIGTSTP) = 일시정지 (살려둠)

SIGINT ≠ 터미널 종료

SIGINT는 터미널 자체를 종료하는 게 아니라, 현재 foreground에 있는 프로세스 그룹에 시그널을 보내는 것입니다.

myshell$ cat hello    # (지금 cat은 입력 대기중)
^C                    # Ctrl+C 누름
myshell$             # cat은 종료되었고, 쉘은 계속 살아있음

마무리

이 시그널 처리 메커니즘을 통해 쉘은:

  1. 사용자의 키보드 입력(Ctrl+C, Ctrl+Z)을 감지
  2. 적절한 프로세스 그룹에 시그널 전달
  3. Foreground/Background 프로세스 상태 관리
  4. Job control 기능 제공

-- 2025.09.12.(금) 05:02

init_shell() 함수 한번 더 정리하기

print_prompt() 함수로 넘어가기