세션 및 제어 터미널 (Session and Controlling Terminal)
Ⅰ. 세션(Session)의 개념
1. 정의
세션(Session)은 하나 이상의 프로세스 그룹(Process Group)을 묶는 커널 수준의 논리적 그룹핑 단위이다. 세션은 SID (Session ID)로 식별되며, 세션 리더(Session Leader)의 PID와 동일하다. 하나의 세션은 최대 하나의 제어 터미널(Controlling Terminal)을 가질 수 있다.
비유: 세션은 "한 학교"이고, 프로세스 그룹은 "학교 안의 반"이며, 제어 터미널은 "교실"이다. 한 학교에 여러 반이 있고, 각 학교는 자신만의 교실을 가질 수 있다.
2. 세션 구조
세션 전체 구조도
+================================================================+
| Session (SID = 1000, Leader: login_shell PID=1000) |
| Controlling Terminal: /dev/pts/0 |
| |
| +----------------------------------------------------------+ |
| | Foreground Process Group (PGID = 5001) | |
| | [vim] PID=5001 PGID=5001 SID=1000 | |
| +----------------------------------------------------------+ |
| |
| +----------------------------------------------------------+ |
| | Background Process Group 1 (PGID = 3001) | |
| | [find] PID=3001 PGID=3001 SID=1000 | |
| | [xargs] PID=3002 PGID=3001 SID=1000 | |
| +----------------------------------------------------------+ |
| |
| +----------------------------------------------------------+ |
| | Background Process Group 2 (PGID = 4001) | |
| | [sleep] PID=4001 PGID=4001 SID=1000 | |
| +----------------------------------------------------------+ |
| |
| +----------------------------------------------------------+ |
| | Shell's Own Process Group (PGID = 1000) | |
| | [bash] PID=1000 PGID=1000 SID=1000 (Leader) | |
| +----------------------------------------------------------+ |
+================================================================+
※ 세션 리더(bash)는 PGID=1000 그룹에 속함
※ 세션 내 프로세스는 모두 동일한 SID를 가짐
3. SID (Session ID)
- SID (Session ID): 세션을 식별하는 정수값이다. 세션 리더의 PID와 동일하다.
- 세션 리더(Session Leader): SID와 PID가 동일한 프로세스.
setsid()를 호출하여 생성.
비유: SID는 "학교 번호"이고, 세션 리더는 "교장 선생님"이다. 교장이 자리를 비워도 학교(세션)는 계속 운영된다.
Ⅱ. setsid()와 세션 생성
1. setsid() 시스템 콜
| 함수 | 원형 | 설명 |
|---|---|---|
| setsid() | pid_t setsid(void) | 새 세션과 새 프로세스 그룹을 생성하고, 호출자를 리더로 만듦 |
| getsid() | pid_t getsid(pid_t pid) | 지정 PID의 SID 반환 |
setsid() 호출 전후 변화
[Before setsid()]
PID=2000 PGID=1000 SID=1000
세션 리더: bash (PID=1000)
프로세스 그룹 리더: bash (PGID=1000)
제어 터미널: /dev/pts/0 (세션 리더가 소유)
[After setsid()]
PID=2000 PGID=2000 SID=2000
세션 리더: 현재 프로세스 (PID=2000) <-- 새 세션
프로세스 그룹 리더: 현재 프로세스 (PGID=2000) <-- 새 그룹
제어 터미널: 없음 (Controlling Terminal 분리됨)
2. setsid()의 동작 규칙
setsid() 수행 과정
setsid() 호출
|
v
[1] 새 세션 생성 (SID = 호출자 PID)
|
v
[2] 호출자를 새 세션의 리더로 설정
호출자는 새 세션의 유일한 프로세스
|
v
[3] 새 프로세스 그룹 생성 (PGID = 호출자 PID)
호출자를 새 프로세스 그룹의 리더로 설정
|
v
[4] 기존 제어 터미널에서 분리
SIGHUP이 기존 제어 터미널에 전송되지 않음
새 제어 터미널이 없음
+--------------------------------------------------+
| 주의: 기존 프로세스 그룹의 리더가 setsid() 호출 |
| --> 오류 반환 (EPERM) |
| --> fork()로 자식를 만든 후 자식이 호출 |
+--------------------------------------------------+
3. 데몬(Daemon) 생성과 setsid()
데몬은 백그라운드에서 지속적으로 실행되는 프로세스로, 일반적으로 터미널과 분리되어야 한다. setsid()는 데몬이 터미널에서 독립하는 핵심 수단이다.
전통적 데몬 생성 절차 (Double Fork)
[fork()] --> 부모 종료 (터미널 반환)
|
v (자식)
[setsid()] --> 새 세션 생성, 터미널 분리
|
v
[fork()] --> 부모(세션 리더) 종료
|
v (손자)
--> 세션 리더가 아님 (터미미널 재획득 불가)
--> chdir("/") (작업 디렉토리를 루트로)
--> umask(0) (파일 권한 마스크 해제)
--> 표준 입출력을 /dev/null로 리다이렉트
--> 데몬 본체 실행
비유: 데몬은 "집을 떠나 독립적으로 사는 어른"이다. setsid()는 "새로운 주민등록번호(SID)를 받고 독립 가구를 구성하는 것"이고, double fork는 "본가와의 모든 연결을 끊는 것"이다.
Ⅲ. 제어 터미널 (Controlling Terminal)
1. 정의
제어 터미널(Controlling Terminal)은 세션과 연결된 터미널 장치이다. 세션의 포그라운드 프로세스 그룹은 제어 터미널의 입출력을 독점하며, 터미널에서 발생하는 시그널(SIGINT, SIGTSTP 등)은 포그라운드 프로세스 그룹 전체에 전달된다.
제어 터미널 연결 구조
+--------------------------------------------------------------+
| /dev/pts/0 (가상 터미널) |
| |
| 키보드 입력 |
| | |
| v |
| [터미널 드라이버] |
| | |
| +-- Ctrl+C (SIGINT) --> Foreground PG (PGID=5001) |
| | |
| +-- Ctrl+Z (SIGTSTP) --> Foreground PG (PGID=5001) |
| | |
| +-- Ctrl+\ (SIGQUIT) --> Foreground PG (PGID=5001) |
| | |
| +-- 터미널 종료(SIGHUP) --> 세션 리더(PGID=1000) |
| | |
| +-- 백그라운드 출력 --> SIGTTOU 전송 |
| | |
| +-- 백그라운드 입력 --> SIGTTIN 전송 |
| |
| Foreground Process Group (PGID=5001) |
| --> 터미널 stdin/stdout/stderr 독점 |
| |
| Background Process Groups (PGID=3001, 4001) |
| --> 터미널 입출력 금지 (시그널로 차단) |
+--------------------------------------------------------------+
2. 제어 터미널 할당 조건
제어 터미널 할당/해제 조건
+----------------------------------------------------------+
| 할당 (Acquisition) |
| - 세션 리더가 아닌 프로세스가 열린 터미널을 |
| 해당 세션의 제어 터미널로 지정 |
| - TIOCSCTTY ioctl()로 명시적 할당 |
| - 최초 터미널 접속 시 세션 리더가 자동 획득 |
+----------------------------------------------------------+
| 해제 (Release) |
| - 세션 리더 종료 |
| - setsid() 호출 (새 세션 생성, 기존 터미널 분리) |
| - TIOCNOTTY ioctl()로 명시적 해제 |
+----------------------------------------------------------+
비유: 제어 터미널은 "교실의 마이크"다. 포그라운드 그룹만 마이크를 쓸 수 있고, 백그라운드 그룹이 마이크를 쓰려고 하면 경고(SIGTTOU/SIGTTIN)가 울린다.
Ⅳ. 터미널 관련 시그널
1. 시그널 요약
| 시그널 | 번호 | 발생 원인 | 대상 |
|---|---|---|---|
| SIGINT | 2 | Ctrl+C | 포그라운드 프로세스 그룹 |
| SIGQUIT | 3 | Ctrl+\ (Core Dump) | 포그라운드 프로세스 그룹 |
| SIGTSTP | 20 | Ctrl+Z (일시 정지) | 포그라운드 프로세스 그룹 |
| SIGCONT | 18 | fg/bg 명령어 | 정지된 프로세스 |
| SIGHUP | 1 | 터미널 연결 끊김 | 세션 리더 및 자식 프로세스 |
| SIGTTIN | 21 | 백그라운드 프로세스의 터미널 입력 시도 | 해당 프로세스 |
| SIGTTOU | 22 | 백그라운드 프로세스의 터미널 출력 시도 | 해당 프로세스 |
2. 시그널 동작 상세
터미널 시그널 동작 흐름
[SIGINT: Ctrl+C]
사용자가 Ctrl+C 입력
--> 터미널 드라이버가 포그라운드 PGID의 모든 프로세스에 SIGINT 전송
--> 프로세스들이 종료됨 (기본 동작)
--> 셸이 새 프롬프트 표시
[SIGTSTP: Ctrl+Z]
사용자가 Ctrl+Z 입력
--> 터미널 드라이버가 포그라운드 PGID에 SIGTSTP 전송
--> 프로세스들이 일시 정지됨 (TASK_STOPPED)
--> 셸이 백그라운드로 이동시키고 jobs에 등록
--> fg 명령어로 SIGCONT 전송 후 포그라운드 복귀
[SIGHUP: 터미널 단절]
SSH 연결 끊김 / 터미널 닫힘
--> 세션 리더(bash)에 SIGHUP 전송
--> 세션 리더가 종료되면 자식 프로세스에도 SIGHUP 전달
--> 데몬은 setsid()로 터미널 분리 후 SIGHUP 무시
--> nohup 명령어로 SIGHUP 무시 설정 가능
[SIGTTIN: 백그라운드 입력]
백그라운드 프로세스가 터미널에서 읽기 시도
--> SIGTTIN 전송 (프로세스 정지)
--> fg로 포그라운드로 가져와야 읽기 가능
[SIGTTOU: 백그라운드 출력]
백그라운드 프로세스가 터미널에 쓰기 시도
--> TOSTOP이 설정된 경우 SIGTTOU 전송 (프로세스 정지)
--> TOSTOP 미설정 시 출력 허용 (기본)
3. SIGHUP과 데몬
SIGHUP 처리: 일반 프로세스 vs 데몬
[일반 프로세스 (터미미널 연결)]
SSH 종료 --> SIGHUP --> 프로세스 종료
|
nohup command &
|
nohup이 SIGHUP을 무시하도록 설정
--> SIGHUP 무시 --> 프로세스 계속 실행
--> 출력이 nohup.out에 기록됨
[데몬 (setsid()로 터미널 분리)]
setsid() --> 제어 터미널 없음
|
SIGHUP을 받을 대상이 아님
--> SSH 종료와 무관하게 계속 실행
|
데몬은 SIGHUP을 "설정 재로드" 시그널로 재사용
--> kill -HUP $(pidof nginx) --> 설정 재로드
비유: SIGHUP은 "전화선이 끊겼다는 알림"이다. 일반 프로세스는 전화가 끊기면 통화가 끝나지만, 데몬은 아예 전화기(터미널)를 없애서 끊길 일이 없다.
Ⅴ. 요약 및 기술사 출제 포인트
핵심 정리
세션 및 제어 터미널 요약도
[계층 구조]
세션(Session, SID)
└── 프로세스 그룹(Process Group, PGID)
└── 프로세스(Process, PID)
[세션의 특징]
1. SID = 세션 리더의 PID
2. 하나의 세션은 최대 하나의 제어 터미널 가짐
3. setsid()로 새 세션 생성 (제어 터미널 분리)
4. 데몬은 터미널 분리를 위해 setsid() 사용
[제어 터미널의 특징]
1. 포그라운드 프로세스 그룹만 입출력 독점
2. 백그라운드 입출력 시 SIGTTIN/SIGTTOU 발생
3. 터미널 단절 시 SIGHUP 전송
4. Ctrl+C/SIGINT, Ctrl+Z/SIGTSTP
[데몬 생성]
fork() -> setsid() -> fork() -> chdir("/") -> umask(0) -> 리다이렉트
지식 그래프
세션 및 제어 터미널
├── 세션 (Session)
│ ├── SID (Session ID)
│ ├── 세션 리더 (PID == SID)
│ ├── 세션 내 다중 프로세스 그룹
│ └── setsid() (새 세션 생성)
├── 제어 터미널 (Controlling Terminal)
│ ├── 세션과 1:1 연결
│ ├── 포그라운드 프로세스 그룹 (입출력 독점)
│ ├── 백그라운드 프로세스 그룹 (입출력 제한)
│ └── 할당/해제 (TIOCSCTTY, TIOCNOTTY)
├── 터미널 시그널
│ ├── SIGINT (Ctrl+C, 인터럽트)
│ ├── SIGTSTP (Ctrl+Z, 일시 정지)
│ ├── SIGQUIT (Ctrl+\, 코어 덤프 후 종료)
│ ├── SIGCONT (정지 해제)
│ ├── SIGHUP (터미널 단절, 설정 재로드)
│ ├── SIGTTIN (백그라운드 입력 차단)
│ └── SIGTTOU (백그라운드 출력 차단)
├── 데몬 (Daemon)
│ ├── setsid()로 터미널 분리
│ ├── Double Fork (세션 리더 방지)
│ ├── nohup (SIGHUP 무시)
│ └── 표준 입출력 리다이렉트 (/dev/null)
└── 작업 제어
├── fg (포그라운드 전환)
├── bg (백그라운드 재개)
├── jobs (작업 목록)
└── tcsetpgrp() (포그라운드 그룹 설정)
세 줄 설명 (어린이용)
- 세션은 여러 프로그램 그룹을 하나로 묶는 "큰 방"이고, 제어 터미널은 그 방의 "마이크"예요.
- 제일 앞에 있는 그룹만 마이크를 쓸 수 있고, 뒤에서 말하려고 하면 "시끄러!"라고 경고(SIGTTIN/SIGTTOU)를 받아요.
- 데몬은 setsid()라는 마법으로 혼자만의 방을 만들어서, 원래 방(터미널)이 닫혀도 계속 살아남아요.
약어 정리
| 약어 | Full Name |
|---|---|
| SID | Session ID |
| PGID | Process Group ID |
| PID | Process Identifier |
| SIGHUP | Signal Hang UP |
| SIGINT | Signal Interrupt |
| SIGTSTP | Signal Terminal Stop |
| SIGTTIN | Signal Terminal Input |
| SIGTTOU | Signal Terminal Output |
| SIGQUIT | Signal Quit |
| SIGCONT | Signal Continue |