리눅스 커널의 가장 중요한 객체로, 프로세스의 '주민등록등본'이자 '의료 기록 차트' 같은 느낌 (PCB)
리눅스 커널 소스(<linux/sched.h>)에 정의된 struct task_struct는 프로세스 제어 블록(PCB)의 리눅스 구조체입니다. 아래는 간략화된 실제 구조입니다.
struct task_struct {
// 프로세스 상태
volatile long state; // 실행중? 대기중? 종료?
// 프로세스 ID
pid_t pid; // 프로세스 ID
pid_t tgid; // 스레드 그룹 ID
// 스케줄링 정보
int prio; // 우선순위
unsigned int policy; // 스케줄링 정책
// 메모리 관리
struct mm_struct *mm; // 메모리 주소 공간
// 파일 시스템 정보
struct files_struct *files; // 열린 파일들
// CPU 레지스터 상태 (컨텍스트)
struct thread_struct thread;
// 부모-자식 프로세스 관계
struct task_struct *parent;
struct list_head children;
// ... 훨씬 더 많은 필드들
};
task_struct의 핵심 구성 요소커널은 이 구조체 하나로 프로세스의 모든 부분을 관리합니다.
| 구분 | 주요 필드 (변수명) | 설명 |
|---|---|---|
| 신원 (ID) | pid, tgid | 프로세스 ID, 스레드 그룹 ID. |
| 상태 (State) | state (or __state) | 실행 중(TASK_RUNNING), 대기 중, 좀비 등 현재 상태. |
| 스케줄링 | prio, policy, sched_class | 우선순위, 스케줄링 정책(FIFO, RR, CFS). |
| 메모리 | struct mm_struct *mm | 코드, 데이터, 힙, 스택이 있는 가상 메모리 정보. |
| 파일 시스템 | struct files_struct *files | 현재 열려 있는 파일 디스크립터(fd) 테이블. |
| 가계도 | parent, children, sibling | 부모, 자식, 형제 프로세스를 연결하는 이중 연결 리스트. |
| 시그널 | signal, sighand | 대기 중인 시그널, 시그널 핸들러 정보. |
모든 프로세스(테스크)는 원형 이중 연결 리스트로 묶여 있습니다.
for_each_process() 매크로를 통해 시스템의 모든 프로세스를 빠르게 탐색할 수 있습니다.init 프로세스를 조상으로 하여 트리 형태로도 연결됩니다.current커널 코드에서 가장 많이 보이는 매크로 중 하나로, 현재 CPU에서 실행 중인 프로세스의 task_struct 주소를 즉시 반환합니다.
// 현재 프로세스의 PID를 알고 싶을 때
printk(KERN_INFO "Current PID: %d\n", current->pid);
리눅스에서는 스레드도 task_struct로 관리됩니다. (Light Weight Process).
mm(메모리)과 files(파일) 구조체를 독점mm과 files 포인터를 공유

사용자 영역(User Space)에서는 task_struct에 직접 접근할 수 없으므로, /proc 파일시스템을 파싱(parsing)하여 구조체 내용을 확인하는 코드를 작성합니다.
task_struct는 커널 메모리에 존재하므로 일반 C 프로그램에서는 접근이 불가능합니다. 대신 커널이 해당 구조체의 정보를 보여주는 /proc/[PID]/stat 파일을 읽어, task_struct와 유사한 구조체에 담아보는 실습이 가장 효과적입니다.
파싱(parsing) : 분석하여 필요한 정보를 추출한다.
task_struct_view.c)리눅스 커널의 task_struct에서 핵심 필드 몇 가지를 뽑아 구조체로 정의하고, 내 프로세스의 정보를 읽어 채우는 예제입니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
// 커널의 task_struct를 흉내 낸 사용자 정의 구조체
typedef struct _MyTaskStruct {
pid_t pid; // 프로세스 ID
char comm[16]; // 프로세스 이름 (Command)
char state; // 프로세스 상태 (R:Running, S:Sleeping...)
pid_t ppid; // 부모 프로세스 ID
int priority; // 우선순위
unsigned long vsize; // 가상 메모리 크기
long rss; // 실제 메모리 사용량 (Page 단위)
} MyTaskStruct;
void get_process_info(MyTaskStruct *task) {
FILE *fp;
char path[256];
// 내 프로세스(/proc/self)의 상태 정보를 여는 경로
// 이 파일의 내용은 커널이 task_struct 값을 읽어서 생성해줌
sprintf(path, "/proc/%d/stat", getpid());
fp = fopen(path, "r");
if (fp == NULL) {
perror("fopen");
exit(1);
}
// /proc/[pid]/stat 파일 포맷 파싱
// (pid) (comm) (state) (ppid) ... 순서
fscanf(fp, "%d %s %c %d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %*d %*d %*d %*d %*d %*d %lu %ld",
&task->pid,
task->comm,
&task->state,
&task->ppid,
&task->priority, // 18번째 필드 근처 (정확한 위치는 man proc 참조 필요)
&task->vsize,
&task->rss);
fclose(fp);
// comm(이름)에 붙은 괄호() 제거
size_t len = strlen(task->comm);
if (len > 2) {
task->comm[len-1] = '\0'; // 닫는 괄호 제거
memmove(task->comm, task->comm+1, len-1); // 여는 괄호 제거
}
}
int main() {
MyTaskStruct my_task;
printf("=== Pseudo task_struct Viewer ===\n");
printf("내 PID: %d\n", getpid());
// 정보 가져오기
get_process_info(&my_task);
// 구조체 내용 출력
printf("\n[Process Descriptor Info]\n");
printf("PID : %d\n", my_task.pid);
printf("Name : %s\n", my_task.comm);
printf("State : %c (R=Running, S=Sleep)\n", my_task.state);
printf("Parent : %d\n", my_task.ppid);
printf("Priority : %d\n", my_task.priority);
printf("Virt Mem : %lu bytes\n", my_task.vsize);
printf("RSS Mem : %ld pages\n", my_task.rss);
return 0;
}
=== Pseudo task_struct Viewer ===
내 PID: 717130
[Process Descriptor Info]
PID : 717130
Name : practice1
State : R (R=Running, S=Sleep)
Parent : 717121
Priority : 0
Virt Mem : 2424832 bytes
RSS Mem : 33 pages
[1] + Done "/usr/bin/gdb" --interpreter=mi --tty=${DbgTerm} 0<"/tmp/Microsoft-MIEngine-In-pvj3ybuu.2xa" 1>"/tmp/Microsoft-MIEngine-Out-onspekwl.xh0"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
// 커널의 task_struct를 흉내 낸 사용자 정의 구조체
typedef struct _MyTaskStruct {
pid_t pid; // 프로세스 ID
char comm[16]; // 프로세스 이름 (Command)
char state; // 프로세스 상태 (R:Running, S:Sleeping...)
pid_t ppid; // 부모 프로세스 ID
int priority; // 우선순위
unsigned long vsize; // 가상 메모리 크기
long rss; // 실제 메모리 사용량 (Page 단위)
} MyTaskStruct;
void get_process_info(MyTaskStruct *task, pid_t pid) {
FILE *fp;
char path[256];
// 내 프로세스(/proc/self)의 상태 정보를 여는 경로
// 이 파일의 내용은 커널이 task_struct 값을 읽어서 생성해줌
sprintf(path, "/proc/%d/stat", pid);
fp = fopen(path, "r");
if (fp == NULL) {
perror("fopen");
exit(1);
}
// /proc/[pid]/stat 파일 포맷 파싱
// (pid) (comm) (state) (ppid) ... 순서
fscanf(fp, "%d %s %c %d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d %*d %*d %*d %*d %*d %*d %lu %ld",
&task->pid,
task->comm,
&task->state,
&task->ppid,
&task->priority, // 18번째 필드 근처 (정확한 위치는 man proc 참조 필요)
&task->vsize,
&task->rss);
fclose(fp);
// comm(이름)에 붙은 괄호() 제거
size_t len = strlen(task->comm);
if (len > 2) {
task->comm[len-1] = '\0'; // 닫는 괄호 제거
memmove(task->comm, task->comm+1, len-1); // 여는 괄호 제거
}
}
// 구조체 내용 출력
void print_task(MyTaskStruct task){
printf("\n[Process Descriptor Info]\n");
printf("PID : %d\n", task.pid);
printf("Name : %s\n", task.comm);
printf("State : %c (R=Running, S=Sleep)\n", task.state);
printf("Parent : %d\n", task.ppid);
printf("Priority : %d\n", task.priority);
printf("Virt Mem : %lu bytes\n", task.vsize);
printf("RSS Mem : %ld pages\n", task.rss);
}
void go_parant(MyTaskStruct task, pid_t my_pid){
pid_t cur_pid = my_pid;
while (cur_pid > 1)
{
// 정보 가져오기
get_process_info(&task, cur_pid);
print_task(task);
cur_pid = task.ppid;
}
// 정보 가져오기
get_process_info(&task, cur_pid);
print_task(task);
}
int main() {
MyTaskStruct my_task;
pid_t my_pid = getpid();
printf("=== Pseudo task_struct Viewer ===\n");
printf("내 PID: %d\n", my_pid);
go_parant(my_task, my_pid);
return 0;
}
=== Pseudo task_struct Viewer ===
내 PID: 721512
[Process Descriptor Info]
PID : 721512
Name : parents_process
State : (R=Running, S=Sleep)
Parent : 721501
Priority : 0
Virt Mem : 2424832 bytes
RSS Mem : 33 pages
[Process Descriptor Info]
PID : 721501
Name : gdb
State : S (R=Running, S=Sleep)
Parent : 721499
Priority : 0
Virt Mem : 373604352 bytes
RSS Mem : 2395 pages
[Process Descriptor Info]
PID : 721499
Name : sh
State : S (R=Running, S=Sleep)
Parent : 721179
Priority : 0
Virt Mem : 2539520 bytes
RSS Mem : 67 pages
[Process Descriptor Info]
PID : 721179
Name : bash
State : S (R=Running, S=Sleep)
Parent : 716293
Priority : 21
Virt Mem : 9207808 bytes
RSS Mem : 325 pages
[Process Descriptor Info]
PID : 716293
Name : node
State : S (R=Running, S=Sleep)
Parent : 716249
Priority : 48
Virt Mem : 1113718784 bytes
RSS Mem : 5397 pages
[Process Descriptor Info]
PID : 716249
Name : node
State : S (R=Running, S=Sleep)
Parent : 716245
Priority : 546
Virt Mem : 11844747264 bytes
RSS Mem : 7455 pages
[Process Descriptor Info]
PID : 716245
Name : sh
State : S (R=Running, S=Sleep)
Parent : 1
Priority : 0
Virt Mem : 2539520 bytes
RSS Mem : 67 pages
[Process Descriptor Info]
PID : 1
Name : systemd
State : S (R=Running, S=Sleep)
Parent : 0
Priority : 144324
Virt Mem : 26542080 bytes
RSS Mem : 915 pages
[1] + Done "/usr/bin/gdb" --interpreter=mi --tty=${DbgTerm} 0<"/tmp/Microsoft-MIEngine-In-gt2gj1va.wc3" 1>"/tmp/Microsoft-MIEngine-Out-4emzzton.0sl"
만약 진짜 task_struct에 접근하려면 커널 모듈(LKM)을 작성해야 합니다.
/* kernel_task_check.c */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h> // 실제 task_struct 정의 헤더
int init_module(void) {
// 'current' 매크로는 현재 실행 중인 프로세스(이 모듈을 로드한 insmod)의
// task_struct 포인터를 반환함.
struct task_struct *task = current;
printk(KERN_INFO "[Kernel] PID: %d\n", task->pid);
printk(KERN_INFO "[Kernel] Comm: %s\n", task->comm);
printk(KERN_INFO "[Kernel] Parent PID: %d\n", task->parent->pid);
return 0;
}
void cleanup_module(void) {
printk(KERN_INFO "[Kernel] Module removed.\n");
}
MODULE_LICENSE("GPL");
/proc 파일 시스템을 통해 task_struct 정보를 간접적으로 확인 (안전, 실무적).current 매크로를 통해 task_struct에 직접 접근 (드라이버 개발 시 사용).