티스토리 뷰

5-16 kernel/fork.c copy_process 3

clone 플래그에 따라 자원을 새로 할당하거나 공유


// clone 인 경우 부모것을 공유
if (clone_flags & CLONE_FILES) {
    atomic_inc(&oldf->count);
    goto out;
}

// clone 이 아닌경우 복사해서 새로 생성
newf = dup_fd(oldf, &error);
if (!newf)
    goto out;

5-17 kernel/fork.c copy_process 4

pid 구조체 & 정수형 pid 할당

// global pid
static inline pid_t pid_nr(struct pid *pid)
{
    pid_t nr = 0;
    if (pid)
        nr = pid->numbers[0].nr;
    return nr;
}

// init task 를 사용중인 경우
static inline bool is_child_reaper(struct pid *pid)
{
    return pid->numbers[pid->level].nr == 1;
}
// swapper process 인 경우 idle thread 를 생성하는데, 이때는 pid 를 할당하지 않음. (init_struct_pid 를 공유)
if (pid != &init_struct_pid) {
    pid = alloc_pid(p->nsproxy->pid_ns_for_children);
}

// global pid
p->pid = pid_nr(pid)

init_task_pid(p, PIDTYPE_PID, pid); // p->pids[PIDTYPE_PID].pid =pid

if (is_child_reaper(pid)) { // init task 가 사용중이라면
    ns_of_pid(pid)->child_reaper = p;
    p->signal->flags |= SIGNAL_UNKILLABLE;
}

if (thread_group_leader(p)) { // 새 프로세스 생성시
    attach_pid(p, PIDTYPE_SID);
    attach_pid(p, PIDTYPE_PID);
} else { // 이미 있는 쓰레드 그룹에 추가시 (쓰레드 생성)
    // 기존 쓰레드와 동일한걸 사용해서 처리 이미 clone 한듯하다
}
attach_pid(p, PIDTYPE_PGID); // pid 구조체와 task 들을 연결

5-18 kernel/fork.c copy_process 5

태스크 계층 구조 설정

if (clone_flags & CLONE_THREAD) { // 쓰레드 생성 중일때
    p->exit_signal = -1;
    p->group_leader = current->group_leader;
    p->tgid = current->tgid;
} else { // 쓰레드 생성이 아니라 메인 태스크 생성중일때 인듯하다 (유저 프로세스 & 커널 쓰레드)
    if (clone_flags & CLONE_PARENT)
        p->exit_signal = current->group_leader->exit_signal;
    else
        p->exit_signal = (clone_flags & CSIGNAL);
    p->group_leader = p;
    p->tgid = p->pid;
}

// 조부모를 실제 부모로 설정
if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
    p->real_parent = current->real_parent;
    p->parent_exec_id = current->parent_exec_id;
} else {
    p->real_parent = current;
    p->parent_exec_id = current->self_exec_id;
}

if (thread_group_leader(p)) { // 새 프로세스 생성시
    // children 리스트에 sibling 연결
    list_add_tail(&p->sibling, &p->real_parent->children);
    // 모든 task 가 연결되는 init_task.tasks  에 연결
    list_add_tail_rcu(&p->tasks, &init_task.tasks);
} else { // 이미 있는 쓰레드 그룹에 추가시 (쓰레드 생성)
    current->signal->nr_threads++;
    atomic_inc(&current->signal->live);
    atomic_inc(&current->signal->sigcnt);
    list_add_tail_rcu(&p->thread_group,
        &p->group_leader->thread_group);
    list_add_tail_rcu(&p->thread_node,
        &p->signal->thread_head);
}

5-19 kernel/fork.c copy_process 6

기타 정보 초기화

if ((clone_flags & (CLONE_VM|CLONE_VFORK)) == CLONE_VM) // VM : 쓰레드 생성, (VFORK 부모를 멈추고 부모것 사용?)
    p->sas_ss_sp = p->sas_ss_size = 0; // signal stack

// 태스크가 처리해야할 시그널이 있다면 fork 실패처리
if (signal_pending(current)) {
    goto bad_fork_cancel_cgroup;
}

5-20 kernel/fork.c dup_task_struct

task_struct 구조체 할당받고, 대부분 부모것을 상속

struct task_struct {
  void *stack;
}

// 왜 thread_info 를 stack 으로 사용한걸까..? 이렇게 사용중이라서 그런가보다
#define task_thread_info(task)    ((struct thread_info *)(task)->stack)
#define task_stack_page(task)    ((task)->stack)

static inline void setup_thread_stack(struct task_struct *p, struct task_struct *org)
{
    *task_thread_info(p) = *task_thread_info(org);
    task_thread_info(p)->task = p;
}


tsk->stack = ti;

setup_thread_stack(tsk, orig); // 부모 구조체의 thread_info 내용으로 설정. 단 ti->task 는 내 task 로 설정

clear_tsk_need_resched(tsk);  // 부모가 resched 되어야 하는 설정이 되어있기 때문에, 나는 제거
atomic_set(&tsk->usage, 2); // 쓰레드가 종료되더라도, 스케줄러에 의해 1 감소 후 task_struct 감소하기때문에 2로 설정

5-21 kernel/sched/core.c sched_fork

fork 된 태스크의 스케줄링 정보 입력하기

get_cpu() // cpu 를 선점

// struct sched_entity {} 값 초기화

// prio 설정

static inline int rt_prio(int prio) // prio 를 가지고 어떤 task 인지도 파악 가능
{
    if (unlikely(prio < MAX_RT_PRIO))
        return 1;
    return 0;
}

put_cpu(); // cpu 선점 해제

5-22 kernel/sched/fair.c task_fork_fair

normal 태스크를 위한 초기화

// runqueue 시간을 동기화
// task 의 cpu 설정
// vruntime 을 current 가 있다면 동일하게 맞춰줌, 이후 조정 과정 거침
if (curr) se->vruntime = (u64)curr->vruntime

// 자식을 먼저 실행하는 옵션도 있음
if (sysctl_sched_child_runs_first && curr && entity_before(curr, se)) {
    swap(curr->vruntime, se->vruntime);
    resched_curr(rq);
}

5.6 태스크 종료하기

할일이 종료되거나 syscall 에 의해 태스크 종료
자원 해제 작업 진행

5.6.1 do_exit

5-23 kernel/exit.c do_exit

// 종료는 한번만 실행되어야함
// PF = Per process flags
if (unlikely(tsk->flags & PF_EXITING)) {
    pr_alert("Fixing recursive fault but reboot is needed!\n");  // 현재 종료중인 process 는 좀비가 되고 reboot 까지 남게됨
    tsk->flags |= PF_EXITPIDONE;
    set_current_state(TASK_UNINTERRUPTIBLE);
    schedule();
}

group_dead = atomic_dec_and_test(&tsk->signal->live); // 쓰레드 그룹내 쓰레드 개수를 1감소하고, 0이되면 group_dead 를 1로 반환

exit_mm, exit_files, exit_fs 등 ref 카운트를 1감소시키고 0이 되면 해당 내용을 해제

tsk->state = TASK_DEAD;
schedule(); // 다른 task 한태 양보하니까 context_switch 불림. 이때 TASK_DEAD 상태라면 task_struct 를 해제
// static struct rq *finish_task_switch(struct task_struct *prev) <-- kernel/sched/core.c

5.7 idle 쓰레드 (swapper)

swapper 는 부팅을 수행하며 시스템을 초기화 하는 역할의 태스크
초기화를 마무리하 면 idle 쓰레드로 변함. cpu 별로 1개씩 존재
idle 쓰레드는 cpu 가 실행할 task 가 없을때 실행됨

5.7.1 idle 쓰레드가 실행되는 과정

우선순위가 높은 class 부터 loop 를 돌며 Task 를 찾을때, 맨 끝가지 가면 idle 쓰레드를 리턴

5.7.2 idle 쓰레드가 하는 일

cpu 를 저전력 상태로 만들어 소모 전류를 줄이고자 함

5-24 kernel/sched/idle.c cpu_idle_loop

idle enter 하는 tick 끄기 (타이머가 발생하면 task 상태가 IDLE->RUNNING 으로 변경됨

#define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)0

static __always_inline bool need_resched(void)
{
    return unlikely(tif_need_resched());
}
static void cpu_idle_loop(void)
{
    // idle 쓰레드는 계속 while 문을 돌고있고, 아무 task 도 없을때 다시 이 while 문으로 돌아옴
    while (1) {
        // idle tick 끄기 : 주기적으로 tick 이 돌고있는데, 이를 비활성화.
        // idle 상태로 가기위해 RUNNING 을 만드는데 이를 IDLE 상태에서는 동작하지 않게하기 위함. (IDLE->RUNNING->IDLE-> ...)

        while (!need_resched) {
        }
        // 여기부터 resched 가 필요해진 상황
        // idle tick 켜기
    }
}
댓글