고급 개발자로 가는 길

Embedded/Linux Kernel

[Linux Kernel] 테스크 디스크립터 자료구조

다크엔지니어 2022. 2. 22. 22:41
반응형

테스크 디스크립터

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 함수를 호출하여 프로세스의 실행 시간을 업데이트 한다.

 

 

 

 

반응형