PID 관리

 

PID 관리

PID(Process ID)는 Posix에서 정의한 것으로는 정확히 프로세스를 의미한다. 그러나 리눅스 커널 v2.6에 도입된 Posix 스레드(NTPL을 통한 pthread) 생성이 지원되면서부터 커널 소스내에서 사용하는 pid 값이 process id이지만 개념은 task id이다. 따라서 pid가 process id를 의미하는지 task id를 의미하는지 소스 주변 상황을 보면서 확인해야한다.(조심!!!)

 

ps 툴 등에서 언급하는 pid와 관련된 항목들을 알아본다.

  • LWP
    • thread id를 의미한다.
  • TID
    • LWP와 동일하다.
  • PID
    • posix.1에서 정의한 process id이다.
  • TGID
    • PID와 동일하다.
  • PGID
    • process 그룹 id이다. (시그널 공유)
    • command line 파이프라인을 통해 shell에서 새로운 child 프로세스를 fork할 때 동일 process group id를 사용한다.
    • process group id는 1개의 세션에만 허락된다.
  • SID
    • 세션 id이다.
    • 1 개 이상의 process group을 가질 수 있다.

 

다음 그림과 같이 세션에서 쉘 sh가 구동되었고 그 아래에서 ppd가 동작된 상황이다.

  • sh(shell)에서 ppd를 구동하는 경우 임시 쉘이 sh와 ppd 사이 중간에 잠깐 만들어지지만 생략하였다.
    • 중간에 만들어진 shell이 ppd에 대한 대표 process group id이다. (태스크와 pid가 발급되지만 생략)
    • 백그라운드에서 동작할 ppd를 호출한 후 중간에 생성된 shell이 사라지면서 대표 process group id가 ppd가 된다.

예) 쉘에서 ps 명령어로 각종 pid 값을 확인해보았다.

# ps -T -p 2189 -o lwp -o tid -o pid -o tgid -o pgid -o sid -o tty -o cmd
  LWP   TID   PID  TGID  PGID   SID TTY   CMD
 2189  2189  2189  2189  2189  2188 pts/2 ppd -d
 2190  2190  2189  2189  2189  2188 pts/2 ppd -d
 2191  2191  2189  2189  2189  2188 pts/2 ppd -d
 2192  2192  2189  2189  2189  2188 pts/2 ppd -d
 2193  2193  2189  2189  2189  2188 pts/2 ppd -d

 

리눅스에서 실제 pid 구현과 관련된 항목들을 알아본다. (t=task 디스크립터, pid=pid  디스크립터)

  • t->pid
    • 글로벌하게 unique한 task(thread) id를 의미하고 리눅스 내부에서 태스크 생성 시 마다 발급한다.
  • t->tgid
    • 글로벌하게 unique한 프로세스 id를 의미하고 프로세스 생성시 마다 발급한다.
    • posix.1에서 정의한 PID와 동일하다.
  • t->pids[PIDTYPE_PID]
    • .node 
      • 아래가 가리키는 pid 디스크립터의 tasks[PIDTYPE_PID] 리스트에 연결될 때 사용한다.
    • .pid
      • task(thread)에 해당하는 pid 디스크립터를 가리킨다.
  • t->pids[PIDTYPE_PGID]
    • .node
      • 아래가 가리키는 pid 디스크립터의 tasks[PIDTYPE_PGID] 리스트에 연결될 때 사용한다.
    • .pid
      • 프로세스(스레드 제외)가 소속된 process group id의 pid 디스크립터를 가리킨다.
  • t->pids[PIDTYPE_SID]
    • .node 
      • 아래가 가리키는 pid 디스크립터의 tasks[PIDTYPE_SID] 리스트에 연결될 때 사용한다.
    • .pid 
      • 프로세스(스레드 제외)가 소속된 session id의 pid 디스크립터를 가리킨다.

 

  • pid->tasks[PIDTYPE_PID]
    • task(thread) 하나가 담기는 리스트이다.
  • pids->tasks[PIDTYPE_PGID]
    • 동일한 프로세스 그룹 id를 사용하는 프로세스(스레드 제외)들이 담기는 리스트이다.
  • pids->tasks[PIDTYPE_SID]
    • 동일한 세션 id를 사용하는 프로세스(스레드 제외)들이 담기는 리스트이다.

 

초기에는 PIDTYPE_TGID가 있었지만 지금은 제거되어 사용되지 않는다. (커널 v2.6.x) 현재 스레드를 가진 대표 태스크는 t->thread_group 리스트에 하위 스레드들을 담고있다.

 

각 태스크는 글로벌 값인 pid, tgid 및 namespace 별로 virtual하게 관리되는 pids[]를 관리한다.

include/linux/sched.h

struct task_struct {
         ...
        pid_t pid;
        pid_t tgid;

        /* PID/PID hash table linkage. */
        struct pid_link pids[PIDTYPE_MAX];
       ...

 

namespace에 따라 pid가 각각 관리되는 모습을 보여준다.

 

다음 그림은 task 디스크립터와 pid 디스크립터가 연결된 모습을 보여준다.

  • task 디스크립터는 1:1로 pid 방향으로 연결된다.
  • pid 디스크립터에는 1:n으로 1개 이상의 task 디스크립터를 가진다.

 

다음 그림은 새로운 namespace에서 태스크가 fork되었을 때 upid의 연결관계를 보여준다.

 

APIs

pid 참조 관련

get_pid()

include/linux/pid.h

static inline struct pid *get_pid(struct pid *pid)
{
        if (pid)
                atomic_inc(&pid->count);
        return pid;
}

사용할 pid 디스크립터의 참조 카운터를 1 증가시킨다.

 

put_pid()

kernel/pid.c

void put_pid(struct pid *pid)
{
        struct pid_namespace *ns;

        if (!pid)
                return;

        ns = pid->numbers[pid->level].ns;
        if ((atomic_read(&pid->count) == 1) ||
             atomic_dec_and_test(&pid->count)) {
                kmem_cache_free(ns->pid_cachep, pid);
                put_pid_ns(ns);
        }
}
EXPORT_SYMBOL_GPL(put_pid);

사용한 pid 디스크립터의 참조 카운터를 1 감소시키고 0이되면 할당 해제한다.  pid namespace 카운터도 1 감소시키고 0이되면 할당 해제한다.  단 초기 namespace의 할당 해제는 하지 않는다.

 

task로 pid 조회

get_task_pid()

kernel/pid.c

struct pid *get_task_pid(struct task_struct *task, enum pid_type type)
{
        struct pid *pid;
        rcu_read_lock();
        if (type != PIDTYPE_PID)
                task = task->group_leader;
        pid = get_pid(task->pids[type].pid);
        rcu_read_unlock();
        return pid;
}
EXPORT_SYMBOL_GPL(get_task_pid);

태스크가 소속된 프로세스의 pid 디스크립터를 반환하되 pid 참조 카운터를 1 증가시킨다.

  • 해당 태스크의 pid를 가져오는 것이 아니라 프로세스의 pid를 가져오는 것임에 주의해야 한다.

 

task_pid()

linux/sched.h

static inline struct pid *task_pid(struct task_struct *task)
{
        return task->pids[PIDTYPE_PID].pid;
}

태스크에 해당하는 pid 디스크립터를 반환한다.

 

task_tgid()

linux/sched.h

static inline struct pid *task_tgid(struct task_struct *task)
{
        return task->group_leader->pids[PIDTYPE_PID].pid;
}

태스크가 소속된 프로세스, 즉 스레드 그룹리더에 해당하는 pid 디스크립터를 반환한다.

 

task_pgrp()

linux/sched.h

/*
 * Without tasklist or rcu lock it is not safe to dereference
 * the result of task_pgrp/task_session even if task == current,
 * we can race with another thread doing sys_setsid/sys_setpgid.
 */
static inline struct pid *task_pgrp(struct task_struct *task)
{
        return task->group_leader->pids[PIDTYPE_PGID].pid;
}

태스크가 소속된 프로세스 그룹 리더의 pid 디스크립터를 반환한다.

 

task_session()

linux/sched.h

static inline struct pid *task_session(struct task_struct *task)
{
        return task->group_leader->pids[PIDTYPE_SID].pid;
}

태스크가 소속된 세션 pid 디스크립터를 반환한다.

 

pid로 task 조회

get_pid_task()

kernel/pid.c

struct task_struct *get_pid_task(struct pid *pid, enum pid_type type)
{
        struct task_struct *result;
        rcu_read_lock();
        result = pid_task(pid, type);
        if (result)
                get_task_struct(result);
        rcu_read_unlock();
        return result;
}
EXPORT_SYMBOL_GPL(get_pid_task);

pid 디스크립터가 관리하는 요청한 pid 타입의 첫 번째 태스크를 반환하되 태스크의 참조 카운터를 1 증가시킨다.

 

pid_task()

kernel/pid.c

struct task_struct *pid_task(struct pid *pid, enum pid_type type)
{       
        struct task_struct *result = NULL;
        if (pid) {
                struct hlist_node *first;
                first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
                                              lockdep_tasklist_lock_is_held());
                if (first)
                        result = hlist_entry(first, struct task_struct, pids[(type)].node);
        }
        return result;
}
EXPORT_SYMBOL(pid_task);

pid 디스크립터가 관리하는 요청한 pid 타입의 첫 번째 태스크를 반환한다.

 

 

pid 디스크립터로 pid 번호 조회

pid_nr()

include/linux/pid.h

/*
 * the helpers to get the pid's id seen from different namespaces
 *
 * pid_nr()    : global id, i.e. the id seen from the init namespace;
 * pid_vnr()   : virtual id, i.e. the id seen from the pid namespace of
 *               current.
 * pid_nr_ns() : id seen from the ns specified.
 *
 * see also task_xid_nr() etc in include/linux/sched.h
 */

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

pid 디스크립터에서 global pid 번호를 알아온다.

 

pid_vnr()

kernel/pid.c

pid_t pid_vnr(struct pid *pid)
{
        return pid_nr_ns(pid, task_active_pid_ns(current));
}
EXPORT_SYMBOL_GPL(pid_vnr);

pid 디스크립터에서 virtual pid 번호를 알아온다.

 

pid_nr_ns()

kernel/pid.c

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
{
        struct upid *upid;
        pid_t nr = 0;

        if (pid && ns->level <= pid->level) {
                upid = &pid->numbers[ns->level];
                if (upid->ns == ns)
                        nr = upid->nr;
        }
        return nr;
}
EXPORT_SYMBOL_GPL(pid_nr_ns);

pid 디스크립터 및 namespace로 virtual pid 번호를 알아온다.

 

 

pid 번호로 pid 디스크립터 조회

find_get_pid()

kernel/pid.c

struct pid *find_get_pid(pid_t nr)
{
        struct pid *pid;

        rcu_read_lock();
        pid = get_pid(find_vpid(nr));
        rcu_read_unlock();

        return pid;
}
EXPORT_SYMBOL_GPL(find_get_pid);

virtual pid 번호에 해당하는 pid 디스크립터를 반환하되 pid 디스크립터의 참조 카운터를 1 증가시킨다.

 

find_vpid()

kernel/pid.c

struct pid *find_vpid(int nr)
{
        return find_pid_ns(nr, task_active_pid_ns(current));
}
EXPORT_SYMBOL_GPL(find_vpid);

virtual pid 번호에 해당하는 pid 디스크립터를 반환한다.

 

find_pid_ns()

kernel/pid.c

struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
        struct upid *pnr;

        hlist_for_each_entry_rcu(pnr,
                        &pid_hash[pid_hashfn(nr, ns)], pid_chain)
                if (pnr->nr == nr && pnr->ns == ns)
                        return container_of(pnr, struct pid,
                                        numbers[ns->level]);

        return NULL;
}
EXPORT_SYMBOL_GPL(find_pid_ns);

pid 해시 리스트에서 요청한 virtual pid 번호 및 namespace에 매치되는 pid 디스크립터를 찾아 반환한다.

 

namespace 조회

task_active_pid_ns()

kernel/pid.c

struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)
{
        return ns_of_pid(task_pid(tsk));
}
EXPORT_SYMBOL_GPL(task_active_pid_ns);

task 디스크립터로 pid 디스크립터를 얻은 후 namespace를 찾아 반환한다.

 

ns_of_pid()

include/linux/pid.h

/*
 * ns_of_pid() returns the pid namespace in which the specified pid was
 * allocated.
 *
 * NOTE:
 *      ns_of_pid() is expected to be called for a process (task) that has
 *      an attached 'struct pid' (see attach_pid(), detach_pid()) i.e @pid
 *      is expected to be non-NULL. If @pid is NULL, caller should handle
 *      the resulting NULL pid-ns.
 */
static inline struct pid_namespace *ns_of_pid(struct pid *pid) 
{
        struct pid_namespace *ns = NULL;
        if (pid) 
                ns = pid->numbers[pid->level].ns;
        return ns;
}

pid 디스크립터로 namespace를 반환한다.

 

pid 할당과 해제

alloc_pid()

kernel/pid.c

struct pid *alloc_pid(struct pid_namespace *ns)
{
        struct pid *pid;
        enum pid_type type;
        int i, nr;
        struct pid_namespace *tmp;
        struct upid *upid;

        pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
        if (!pid)
                goto out;

        tmp = ns;
        pid->level = ns->level;
        for (i = ns->level; i >= 0; i--) {
                nr = alloc_pidmap(tmp);
                if (nr < 0)
                        goto out_free;

                pid->numbers[i].nr = nr;
                pid->numbers[i].ns = tmp;
                tmp = tmp->parent;
        }

        if (unlikely(is_child_reaper(pid))) {
                if (pid_ns_prepare_proc(ns))
                        goto out_free;
        }

        get_pid_ns(ns);
        atomic_set(&pid->count, 1);
        for (type = 0; type < PIDTYPE_MAX; ++type)
                INIT_HLIST_HEAD(&pid->tasks[type]);

        upid = pid->numbers + ns->level;
        spin_lock_irq(&pidmap_lock);
        if (!(ns->nr_hashed & PIDNS_HASH_ADDING))
                goto out_unlock;
        for ( ; upid >= pid->numbers; --upid) {
                hlist_add_head_rcu(&upid->pid_chain,
                                &pid_hash[pid_hashfn(upid->nr, upid->ns)]);
                upid->ns->nr_hashed++;
        }
        spin_unlock_irq(&pidmap_lock);

out:
        return pid;

out_unlock:
        spin_unlock_irq(&pidmap_lock);
        put_pid_ns(ns);

out_free:
        while (++i <= ns->level)
                free_pidmap(pid->numbers + i);

        kmem_cache_free(ns->pid_cachep, pid);
        pid = NULL;
        goto out;
}

 

free_pid()

kernel/pid.c

void free_pid(struct pid *pid)
{
        /* We can be called with write_lock_irq(&tasklist_lock) held */
        int i;
        unsigned long flags;

        spin_lock_irqsave(&pidmap_lock, flags);
        for (i = 0; i <= pid->level; i++) {
                struct upid *upid = pid->numbers + i;
                struct pid_namespace *ns = upid->ns;
                hlist_del_rcu(&upid->pid_chain);
                switch(--ns->nr_hashed) {
                case 2:
                case 1:
                        /* When all that is left in the pid namespace
                         * is the reaper wake up the reaper.  The reaper
                         * may be sleeping in zap_pid_ns_processes().
                         */
                        wake_up_process(ns->child_reaper);
                        break;
                case PIDNS_HASH_ADDING:
                        /* Handle a fork failure of the first process */
                        WARN_ON(ns->child_reaper);
                        ns->nr_hashed = 0;
                        /* fall through */
                case 0:
                        schedule_work(&ns->proc_work);
                        break;
                }
        }
        spin_unlock_irqrestore(&pidmap_lock, flags);

        for (i = 0; i <= pid->level; i++)
                free_pidmap(pid->numbers + i);

        call_rcu(&pid->rcu, delayed_put_pid);
}

 

 

 

구조체

pid 구조체

include/linux/pid.h

/*
 * What is struct pid?
 *
 * A struct pid is the kernel's internal notion of a process identifier.
 * It refers to individual tasks, process groups, and sessions.  While
 * there are processes attached to it the struct pid lives in a hash
 * table, so it and then the processes that it refers to can be found
 * quickly from the numeric pid value.  The attached processes may be
 * quickly accessed by following pointers from struct pid.
 *
 * Storing pid_t values in the kernel and referring to them later has a
 * problem.  The process originally with that pid may have exited and the
 * pid allocator wrapped, and another process could have come along
 * and been assigned that pid.
 *
 * Referring to user space processes by holding a reference to struct
 * task_struct has a problem.  When the user space process exits
 * the now useless task_struct is still kept.  A task_struct plus a
 * stack consumes around 10K of low kernel memory.  More precisely
 * this is THREAD_SIZE + sizeof(struct task_struct).  By comparison
 * a struct pid is about 64 bytes.
 *
 * Holding a reference to struct pid solves both of these problems.
 * It is small so holding a reference does not consume a lot of
 * resources, and since a new struct pid is allocated when the numeric pid
 * value is reused (when pids wrap around) we don't mistakenly refer to new
 * processes.
 */
struct pid
{
        atomic_t count;
        unsigned int level;
        /* lists of tasks that use this pid */
        struct hlist_head tasks[PIDTYPE_MAX];
        struct rcu_head rcu;
        struct upid numbers[1];
};
  • count
    • 참조카운터
  • level
    • namespace 레벨 (init=0)
  • tasks[]
    • 태스크들이 연결되는 리스트
  • rcu
    • pid 소멸 시 rcu 방법으로 사용하기 위한 rcu 노드
    • pid 해시 태에블에서 pid를 검색할 때 rcu_read_lock()을 사용하여 접근한다.
  • numbers[]
    • namespace 별로 관리하기 위해 upid 구조체가 사용된다.
    • namespace 레벨에 따라 배열이 조정된다.
    • numbers[0].nr에는 항상 init namespace의 global pid 값이 사용된다.

 

upid 구조체

include/linux/pid.h

/*
 * struct upid is used to get the id of the struct pid, as it is
 * seen in particular namespace. Later the struct pid is found with
 * find_pid_ns() using the int nr and struct pid_namespace *ns.
 */
struct upid {
        /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
        int nr;
        struct pid_namespace *ns;
        struct hlist_node pid_chain;
};
  • nr
    • virtual pid 번호로 virtual 태스크 id와 동일하다.
  • *ns
    • namespcae를 가리키는 pid_namespace 구조체 포인터
  • pid_chain

 

pid_link 구조체

struct pid_link
{
        struct hlist_node node;
        struct pid *pid;
};
  • node
    • 태스크가 pid 해시 리스트에 연결할 때 사용
  • *pid
    • pid 구조체 포인터

 

참고

 

 

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다