태스크 (Task - 리눅스 프로세스/스레드 통일 용어)

Ⅰ. 개요

1. 정의

**태스크(Task)**는 리눅스 커널에서 프로세스와 스레드를 구분 없이 부르는 통일된 용어이다. 리눅스 커널은 전통적인 UNIX의 프로세스와 스레드 구분을 없애고, 모든 실행 단위를 task_struct라는 하나의 자료구조로 관리한다.

리눅스의 통일 모델
┌─────────────────────────────────────────────────┐
│          리눅스 커널 관점                        │
│                                                 │
│  ┌───────────┐    ┌───────────┐    ┌─────────┐│
│  │task_struct│    │task_struct│    │task_    ││
│  │(프로세스) │    │(스레드)   │    │struct   ││
│  │TGID=PID  │    │TGID=부모  │    │(커널    ││
│  │          │    │ PID       │    │스레드)  ││
│  └───────────┘    └───────────┘    └─────────┘│
│       └──────────────┼──────────────┘         │
│                      │                         │
│                 모두 "태스크"                    │
│                 모두 동일 스케줄링 대상          │
│                                                 │
│  ★ 커널은 프로세스와 스레드를 구분하지 않음     │
└─────────────────────────────────────────────────┘

비유: 리눅스 커널에게 프로세스와 스레드는 "혼자 사는 집"과 "룸메이트와 함께 사는 집"의 차이다. 커널은 모두 같은 "집(task_struct)"으로 취급하며, 방을 공유하느냐(메모리 공유) 아니냐만 다를 뿐이다.

Ⅱ. task_struct 구조체

1. 핵심 필드

task_struct 주요 구성
┌──────────────────────────────────────────────────┐
│  struct task_struct {                            │
│      ┌─────────────────────┐                     │
│      │ 식별자               │                     │
│      │  pid, tgid, comm    │                     │
│      ├─────────────────────┤                     │
│      │ 스케줄링 정보         │                     │
│      │  state, prio,        │                     │
│      │  sched_class,        │                     │
│      │  sched_entity        │                     │
│      ├─────────────────────┤                     │
│      │ 메모리 관리           │                     │
│      │  mm (메모리 디스크립터)│                    │
│      │  active_mm           │                     │
│      ├─────────────────────┤                     │
│      │ 파일 관리             │                     │
│      │  files (파일 테이블)  │                     │
│      │  fs (파일시스템 정보) │                     │
│      ├─────────────────────┤                     │
│      │ 시그널               │                     │
│      │  signal, sighand     │                     │
│      ├─────────────────────┤                     │
│      │ 실행 컨텍스트         │                     │
│      │  thread (레지스터)   │                     │
│      │  stack               │                     │
│      └─────────────────────┘                     │
│  };                                             │
└──────────────────────────────────────────────────┘

2. 프로세스와 스레드의 task_struct 차이

┌───────────────┬──────────────────┬──────────────────┐
│               │    프로세스       │     스레드        │
├───────────────┼──────────────────┼──────────────────┤
│ task_struct   │      1개         │      1개 (각각)   │
│ pid           │   = tgid         │  = 고유 TID       │
│ tgid          │   = pid          │  = 리더의 PID     │
│ mm (메모리)   │   독립 소유      │  포인터 공유      │
│ files         │   독립 소유      │  포인터 공유      │
│ fs            │   독립 소유      │  포인터 공유      │
│ sighand       │   독립 소유      │  포인터 공유      │
│ stack         │   독립           │  독립 (각각)      │
└───────────────┴──────────────────┴──────────────────┘

Ⅲ. PID vs TGID

1. 식별자 체계

PID와 TGID의 관계
┌─────────────────────────────────────────────────┐
│                                                 │
│  프로세스 A (단일 스레드)                        │
│  ┌────────────────────┐                         │
│  │ PID = 1000         │                         │
│  │ TGID = 1000        │  ← PID == TGID          │
│  │ (리더 스레드)       │                         │
│  └────────────────────┘                         │
│                                                 │
│  프로세스 B (다중 스레드)                        │
│  ┌────────────────────┐                         │
│  │ PID=2000 TGID=2000 │  ← 메인 스레드 (리더)   │
│  ├────────────────────┤                         │
│  │ PID=2001 TGID=2000 │  ← 워커 스레드 1        │
│  ├────────────────────┤                         │
│  │ PID=2002 TGID=2000 │  ← 워커 스레드 2        │
│  └────────────────────┘                         │
│                                                 │
│  getpid()  → TGID 반환 (스레드 그룹 리더 PID)   │
│  gettid()  → 실제 PID (각 스레드 고유 ID)       │
│  kill(pid) → TGID로 전체 프로세스에 시그널       │
│  tgkill()  → 특정 TID로 특정 스레드에 시그널     │
└─────────────────────────────────────────────────┘

2. 스레드 그룹 리더

  • 스레드 그룹의 첫 번째 태스크가 리더
  • 리더의 PID가 TGID가 된다
  • 리더가 종료되어도 그룹은 유지 (다른 스레드 존재 시)

Ⅳ. /proc 파일시스템과 태스크

1. /proc/[pid]/task/ 구조

/proc에서 스레드 확인
┌─────────────────────────────────────────────────┐
│                                                 │
│  /proc/2000/              ← 프로세스 (TGID)     │
│  ├── task/                ← 스레드 목록          │
│  │   ├── 2000/            ← 메인 스레드          │
│  │   │   ├── status       ← 개별 상태            │
│  │   │   ├── regs         ← 레지스터             │
│  │   │   └── comm         ← 스레드 이름          │
│  │   ├── 2001/            ← 워커 스레드 1        │
│  │   └── 2002/            ← 워커 스레드 2        │
│  ├── status               ← 프로세스 전체 상태   │
│  ├── maps                 ← 메모리 맵 (공유)     │
│  └── fd/                  ← 파일 디스크립터      │
│                                                 │
│  확인 명령어:                                   │
│  $ ls /proc/2000/task/     ← 2000 2001 2002     │
│  $ cat /proc/2000/task/2001/comm  ← 스레드명     │
└─────────────────────────────────────────────────┘

Ⅴ. 스케줄러와 태스크

1. 균등한 스케줄링

스케줄러의 태스크 처리
┌─────────────────────────────────────────────────┐
│                                                 │
│  스케줄러 (CFS, RT, Deadline)                   │
│       │                                         │
│       ▼                                         │
│  ┌─────────────────────────────────┐           │
│  │         Run Queue               │           │
│  │                                 │           │
│  │  [Task A] [Task B] [Task C]     │           │
│  │   프로세스   스레드    프로세스   │           │
│  │   (TGID)    (TID)    (TGID)     │           │
│  │                                 │           │
│  │  ★ 구분 없이 모두 동일한 태스크  │           │
│  │  ★ vruntime 기준 공정 스케줄링   │           │
│  └─────────────────────────────────┘           │
└─────────────────────────────────────────────────┘
  • 커널 스케줄러는 모든 task_struct를 동등한 스케줄링 단위로 취급
  • 프로세스와 스레드 간에 우선순위 차이 없음
  • CFS(Completely Fair Scheduler)가 가상 실행 시간(vruntime)으로 공정 분배

Ⅴ. 지식 그래프 및 요약

1. 지식 그래프

[태스크 (Task)]
├── [자료구조]
│   ├── task_struct ─── 모든 실행 단위의 커널 표현
│   └── thread_info ─── 저수준 아키텍처 정보
├── [식별자]
│   ├── PID ──────── 각 태스크의 고유 번호
│   ├── TGID ─────── 스레드 그룹 리더의 PID
│   └── getpid() ─── TGID 반환, gettid() ─ PID 반환
├── [자원 공유 여부에 따른 분류]
│   ├── 프로세스 ─── mm, files, fs 독립 소유
│   ├── 스레드 ────── mm, files, fs 포인터 공유
│   └── 커널 스레드 ── mm 없음 (커널 공간만 사용)
├── [관찰 방법]
│   ├── /proc/[pid]/task/ ── 스레드 목록
│   └── ps -eLf ─────── 모든 스레드 표시
└── [스케줄링]
    ├── CFS ──────── 가상 실행 시간 기준 공정 스케줄링
    ├── RT 스케줄러 ── 실시간 우선순위 기반
    └── 구분 없이 모든 태스크를 동등 취급

2. 준말

  • PID: Process ID (프로세스 식별자)
  • TGID: Thread Group ID (스레드 그룹 식별자)
  • TID: Thread ID (스레드 식별자, 리눅스에서는 PID와 동일)
  • CFS: Completely Fair Scheduler (완전 공정 스케줄러)

3. 어린이를 위한 3줄 설명

리눅스에서는 프로세스도 스레드도 모두 "태스크"라고 불러요. 각 태스크는 자기만의 번호표(PID)를 가지고, 같은 그룹에 속한 스레드들은 그룹 번호(TGID)도 같이 가져요. 커널은 모든 태스크를 똑같이 취급해서 차례대로 실행 기회를 줘요.