테스크 디스크립터
task_struct 구조체로, 프로세스의 속성 정보를 표현하는 가장 중요한 자료구조이다.
TCB Task Control Block 이라고 Context switch 시 TCB 를 참고하여 진행이 된다. TCB 는 프로세스의 정보를 표현하는 자료구조이다.
리눅스 커널에서 프로세스의 정보를 표현하는 자료구조가 바로 테스크 디스크립터 인 것이다.
리눅스 bash 를 통해 입력되는 ps 정보 또한 테스크 디스크립터 필드에 저장 된 값을 읽어서 표현된다.
리눅스 시스템에서 구동중인 프로세스 목록 또한 init_task.tasks 인 연결리스트에 접근해 출력 된다.
그럼 각 중요 항목들을 정리해보면,
프로세스 식별 필드
pid_t pid;
정수형인 프로세스 ID 값인 pid 이다.
char comm[TASK_COMM_LEN];
task_struct 의 comm 필드는 프로세스의 이름 정보를 가지고 있다. 참고로 set_task_comm() 함수를 통해 프로세스 이름 지정도 가능하다.
현재 실행 중인 테스크 디스크립터인 current 매크로와 프로세스 이름을 저장하는 comm 필드를 조합하여 디버깅 코드도 만들 수 있다.
p가 깨우려는 프로세스로 프로세스 이름이 kthread 인 경우 dump_stack 함수를 호출해 흐름을 커널 로그로 출력한다.
static void ttwu_do_wakeup(struct rq *rq, struct task_struct *p, int wake_flags,
struct rq_flags *rf)
{
check_preempt_curr(rq, p, wake_flags);
p->state = TASK_RUNNING;
trace_sched_wakeup(p);
if(!strcmp(p->comm, "kthreadd"))
{
printk("[+][%s] wakeup kthread process\n", current->comm);
dump_stack();
}
#ifdef CONFIG_SMP
if (p->sched_class->task_woken) {
/*
* Our task @p is fully woken up and running; so its safe to
* drop the rq->lock, hereafter rq is only used for statistics.
*/
rq_unpin_lock(rq, rf);
p->sched_class->task_woken(rq, p);
rq_repin_lock(rq, rf);
}
if (rq->idle_stamp) {
u64 delta = rq_clock(rq) - rq->idle_stamp;
u64 max = 2*rq->max_idle_balance_cost;
update_avg(&rq->avg_idle, delta);
if (rq->avg_idle > max)
rq->avg_idle = max;
rq->idle_stamp = 0;
}
#endif
프로세스 상태 저장
state, flags 가 있으며, state 는 실행 상태, flags 는 세부 동작상태와 속성 정보를 나타낸다.
volatile long state;
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
- TASK_RUNNING 은 CPU에서 실행 중이거나 런큐에 대기상태에 있음
- TASK_INTERRUPTIBLE 은 휴먼상태
- TASK_UNINTERRUPTIBLE 은 특정 조건에서 깨어나기 위해 휴면 상태로 진입한 상태
대부분은 interruptible 상태로 있으며, 런닝이나, 휴면상태가 너무 많으면 뭔가 문제가 있은 상태일 수 있다.
unsigned int flags;
#define PF_VCPU 0x00000001 /* I'm a virtual CPU */
#define PF_IDLE 0x00000002 /* I am an IDLE thread */
#define PF_EXITING 0x00000004 /* Getting shut down */
#define PF_IO_WORKER 0x00000010 /* Task is an IO worker */
#define PF_WQ_WORKER 0x00000020 /* I'm a workqueue worker */
#define PF_FORKNOEXEC 0x00000040 /* Forked but didn't exec */
#define PF_MCE_PROCESS 0x00000080 /* Process policy on mce errors */
#define PF_SUPERPRIV 0x00000100 /* Used super-user privileges */
#define PF_DUMPCORE 0x00000200 /* Dumped core */
#define PF_SIGNALED 0x00000400 /* Killed by a signal */
#define PF_MEMALLOC 0x00000800 /* Allocating memory */
#define PF_NPROC_EXCEEDED 0x00001000 /* set_user() noticed that RLIMIT_NPROC was exceeded */
#define PF_USED_MATH 0x00002000 /* If unset the fpu must be initialized before use */
#define PF_USED_ASYNC 0x00004000 /* Used async_schedule*(), used by module init */
#define PF_NOFREEZE 0x00008000 /* This thread should not be frozen */
#define PF_FROZEN 0x00010000 /* Frozen for system suspend */
#define PF_KSWAPD 0x00020000 /* I am kswapd */
#define PF_MEMALLOC_NOFS 0x00040000 /* All allocation requests will inherit GFP_NOFS */
#define PF_MEMALLOC_NOIO 0x00080000 /* All allocation requests will inherit GFP_NOIO */
#define PF_LOCAL_THROTTLE 0x00100000 /* Throttle writes only against the bdi I write to,
* I am cleaning dirty pages from some other bdi. */
#define PF_KTHREAD 0x00200000 /* I am a kernel thread */
#define PF_RANDOMIZE 0x00400000 /* Randomize virtual address space */
#define PF_SWAPWRITE 0x00800000 /* Allowed to write to swap */
#define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_mask */
#define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */
#define PF_MEMALLOC_NOCMA 0x10000000 /* All allocation request will have _GFP_MOVABLE cleared */
#define PF_FREEZER_SKIP 0x40000000 /* Freezer should not count it as freezable */
#define PF_SUSPEND_TASK 0x80000000 /* This thread called freeze_processes() and should not be frozen */
아이들 프로세스, 종료중상태,종료마무리 상태, 워커 스레드인 경우, 커널 스레드인 경우 등등
실제로 flags 를 보고 해당 상태에 따라 로직을 구성 할 수 있다.
int exit_state;
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010
#define EXIT_ZOMBIE 0x0020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
exit_code 필드는 프로세스의 종료 코드를 저장하는 필드이다. do_exit 에서 실제 인자로 exit_code 에 저장한다.
즉, signal kill 값이 저장된다.
프로세스 간 관계
struct task_struct *real_parent;
struct task_struct *parent;
struct list_head children;
struct list_head sibling;
*real_parent 에는 자신을 생성한 부모 프로세스, *parent 에는 생성 부모 종료 시 대리 부모 역할을 할 프로세스
children 에는 부모가 자식을 생성할 때 children 연결 리스트에 자식 프로세스를 등록
sibling 은 같은 부모 프로세스로 생성된 프로세스끼리 연결 리스트 주소를 저장
프로세스 연결 리스트
가장 중요한 task_struct 의 tasks 필드이다. 이 필드는 list_head 구조체로서 연결 리스트 타입이다.
커널에서 구동중인 모든 프로세스는 tasks 연결 리스트에 등록이 되어 있다.
struct task_struct init_task;
(list_head)init_task.tasks;
위와 같이 되어있는데, 새로 생성된 프로세스는 init_task 의 tasks에 연결 리스트로 등록이 된다.
더 자세히 설명하면, taskts 는 다음 프로세스의 list_head의 주소 값을 가지고 있다.
즉, init_task 를 통해 커널의 전체 프로세스 확인이 가능하다.
프로세스 실행 시각 정보
테스크 디스크립터에는 프로세스의 실행 시각 정보를 알 수 있는 아래 필드가 있다.
u64 utime, u64 stime;
struct sched_info;
sched_info.last_arrival;
utime 은 유저모드에서 프로세스가 실행한 시각을 나타낸다. account_user_time() 에서 바뀐다.
프로세스 관련 함수에서는 struct task_struct *p 인자로 사용되며, p->utime 으로 사용 한다.
stime 은 당연 커널 모드에서 프로세스가 실행한 시각을 저장한다. account_system_index_time() 에서 저장된다.
sched_info struct 의 last_arrival 은 프로세스 스케줄링 정보를 저장한다. 내용은 프로세스가 마지막에 CPU 에서 실행한 시각을 나타낸다.
실제 context_swtich 함수에서 sched_info_arrive 함수를 통해 rq 에 queue 마지막과 현재 queue 차이 delta 만큼을
더한 후 현재 rq 를 arrival 에 저장한다.
즉, 실제 컨텍스트 스위칭이 일어나기 직전에 sched_info_arrive 함수를 호출하여 프로세스의 실행 시간을 업데이트 한다.
'Embedded > Linux Kernel' 카테고리의 다른 글
[Linux Kernel] 테스크 디스크립터 매크로 함수 (0) | 2022.03.01 |
---|---|
[Linux Kernel] 스레드 자료구조 thread_info 간단 설명 (1) | 2022.02.27 |
[Linux Kernel] gcc 컴파일 옵션 (0) | 2022.02.16 |
[Linux Kernel] ftrace 를 통한 유저 레벨 프로세스 분석(POSIX exit 종료 방식) (0) | 2022.02.16 |
[Linux Kernel] 프로세스 소멸 과정 소스 분석 (0) | 2022.02.16 |