방문자 통계 (2018년 1월 17~31일)

안녕하세요? 문c 블로그입니다.

 

방문자 수가 작년과 올 해 이렇다 할 큰 변화가 없습니다.

현재까지 리눅스 커널에 대한 분석글을 올린 글 수는 275개이고, 그림은 1300개 정도되었습니다.

 

해마다 더 많은 분들이 리눅스 커널 분석을 도전하시길 바랍니다.

 

방문자 통계 참고) 2018년 1월 17~31일

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 구조체 포인터

 

참고

 

 

RCU(Read Copy Update) -4- (NOCB process)

 

RCU NO-CB (Offload RCU callback)

rcu의 cb를 처리하는 유형은 큰 흐름으로 다음과 같이 두 가지로 나뉜다.

  • cb 호출 – interrupt context에서 직접 처리
    • softirq에서 처리된다. 이러한 경우 보통 interrupt context에서 곧장 호출되어 처리되고 만일 softirq 처리 건 수가 많아져 지연되는 경우 softirqd 커널 스레드에서 호출되어 처리된다.
    • 디폴트 커널 설정이며 latency가 짧아 빠른 호출이 보장된다.
  • no-cb 호출 – 전용 rcu 커널 스레드에서 처리
    • 특정 rcu 커널 스레드에서 cb를 호출할 수 있도록 한다. 절전과 성능을 만족시키는 옵션이다.
    • CONFIG_RCU_NOCB_CPU 커널 옵션을 사용하고 다음 3가지 중 하나를 선택할 수 있다.
      • CONFIG_RCU_NOCB_CPU_NONE
        • 디폴트로 nocb 지정된 cpu는 없지만 “nocbs=” 커널 파라메터로 특정 cpu들을 nocb로 지정할 수 있다.
      • CONFIG_RCU_NOCB_CPU_ZERO
        • 디폴트로 cpu#0을 nocb 지정한다. 추가로 “nocbs=” 커널 파라메터를 사용하여 다른 cpu들을 nocb로 지정할 수 있다.
      • CONFIG_RCU_NOCB_CPU_ALL
        • 디폴트로 모든 cpu를 nocb로 지정한다.

 

Leader & Follower CPU

NO-CB용 콜백을 사용하는 경우 지정된 cpu에 nocb용 커널 스레드가 구성된다. grace period 관리 및 이에 수반되는 콜백 리스트의 cascade 관리를 위해 leader cpu를 지정하고 그 leader cpu가 처리해준 작업을 수행하는 follower cpu를 구성하여 처리한다.

  • Leader cpu가 하는 일
    • gp 관리
    • leader 및 follower에 속한 콜백 리스트의 이동
    • leader 콜백 호출
  • Follower cpu가 하는 일
    • follower 콜백 호출

 

NO-CB용 콜백 추가

__call_rcu_nocb()

kernel/rcu/tree_plugin.h

/*
 * This is a helper for __call_rcu(), which invokes this when the normal
 * callback queue is inoperable.  If this is not a no-CBs CPU, this
 * function returns failure back to __call_rcu(), which can complain
 * appropriately.
 *
 * Otherwise, this function queues the callback where the corresponding
 * "rcuo" kthread can find it.
 */
static bool __call_rcu_nocb(struct rcu_data *rdp, struct rcu_head *rhp,
                            bool lazy, unsigned long flags)
{

        if (!rcu_is_nocb_cpu(rdp->cpu))
                return false;
        __call_rcu_nocb_enqueue(rdp, rhp, &rhp->next, 1, lazy, flags);
        if (__is_kfree_rcu_offset((unsigned long)rhp->func))
                trace_rcu_kfree_callback(rdp->rsp->name, rhp,
                                         (unsigned long)rhp->func,
                                         -atomic_long_read(&rdp->nocb_q_count_lazy),
                                         -atomic_long_read(&rdp->nocb_q_count));
        else
                trace_rcu_callback(rdp->rsp->name, rhp,
                                   -atomic_long_read(&rdp->nocb_q_count_lazy),
                                   -atomic_long_read(&rdp->nocb_q_count));

        /*
         * If called from an extended quiescent state with interrupts
         * disabled, invoke the RCU core in order to allow the idle-entry
         * deferred-wakeup check to function.
         */
        if (irqs_disabled_flags(flags) &&
            !rcu_is_watching() &&
            cpu_online(smp_processor_id()))
                invoke_rcu_core();

        return true;
}

no-cb 지정된 cpu인 경우 rcu 커널 스레드(“rcuo”)에서 처리하도록 rcu cb를 큐잉한다.

  • 코드 라인 14~15에서 no-cb 지정된 cpu가 아니면 false를 반환한다.
  • 코드 라인 16에서 no-cb 큐에 rcu를 추가하고 rcu 커널 스레드를 호출하다.
  • 코드 라인 17~25에서 kfree용 rcu cb 및 함수 호출용 rcu cb 각각에 따른 trace 메시지 출력을 다르게 한다.
  • 코드 라인 32~35에서 irq disable이면서 확장 qs 상태에서 rcu 요청한 경우 softirq를 호출하여 rcu core를 호출한다.

 

다음 그림은 9개의 cpu가 nocb를 사용하는 모습을 보여준다.

 

RCU NO-CB 설정

rcu_is_nocb_cpu()

kernel/rcu/tree_plugin.h

/* Is the specified CPU a no-CBs CPU? */
bool rcu_is_nocb_cpu(int cpu)
{
        if (have_rcu_nocb_mask)
                return cpumask_test_cpu(cpu, rcu_nocb_mask);
        return false;
}

요청한 cpu가 no-cb(rcu 스레드 사용)으로 설정되었는지 여부를 반환한다.

  • have_rcu_nocb_mask 변수는 rcu_nocb_setup() 함수가 호출되어 rcu_nocb_mask라는 cpu 마스크가 할당된 경우 true로 설정된다.
  • CONFIG_RCU_NOCB_CPU_ALL 커널 옵션을 사용하는 경우 항상 rcu 스레드에서 동작시키기 위해 true를 반환한다.
  • CONFIG_RCU_NOCB_CPU_ALL 및 CONFIG_RCU_NOCB_CPU 커널 옵션 둘 다 사용하지 않는 경우 항상 callback 처리하기 위해 false를 반환한다.

 

rcu_nocb_setup()

kernel/rcu/tree_plugin.h

/* Parse the boot-time rcu_nocb_mask CPU list from the kernel parameters. */
static int __init rcu_nocb_setup(char *str)
{
        alloc_bootmem_cpumask_var(&rcu_nocb_mask);
        have_rcu_nocb_mask = true;
        cpulist_parse(str, rcu_nocb_mask);
        return 1;
}
__setup("rcu_nocbs=", rcu_nocb_setup);

커널 파라메터 “rcu_nocbs=”에 cpu 리스트를 설정한다. 이렇게 설정된 cpu들은 rcu callback 처리를 rcu 스레드에서 처리할 수 있다.

  • 예) rcu_nocbs=3-6,8-10

 

Leader cpu와 Follower cpu 구성

rcu_organize_nocb_kthreads()

kernel/rcu/tree_plugin.h

/*
 * Initialize leader-follower relationships for all no-CBs CPU.
 */
static void __init rcu_organize_nocb_kthreads(struct rcu_state *rsp)
{
        int cpu;
        int ls = rcu_nocb_leader_stride;
        int nl = 0;  /* Next leader. */
        struct rcu_data *rdp;
        struct rcu_data *rdp_leader = NULL;  /* Suppress misguided gcc warn. */
        struct rcu_data *rdp_prev = NULL;

        if (!have_rcu_nocb_mask)
                return;
        if (ls == -1) {
                ls = int_sqrt(nr_cpu_ids);
                rcu_nocb_leader_stride = ls;
        }

        /*
         * Each pass through this loop sets up one rcu_data structure and
         * spawns one rcu_nocb_kthread().
         */
        for_each_cpu(cpu, rcu_nocb_mask) {
                rdp = per_cpu_ptr(rsp->rda, cpu);
                if (rdp->cpu >= nl) {
                        /* New leader, set up for followers & next leader. */
                        nl = DIV_ROUND_UP(rdp->cpu + 1, ls) * ls;
                        rdp->nocb_leader = rdp;
                        rdp_leader = rdp;
                } else {
                        /* Another follower, link to previous leader. */
                        rdp->nocb_leader = rdp_leader;
                        rdp_prev->nocb_next_follower = rdp;
                }
                rdp_prev = rdp;
        }
}

nocb 지정된 cpu에서 동작하는 스레드들에 대해 leader와 follower cpu로 나누어 구성한다.

  • 코드 라인 13~14에서 nocb 설정된 cpu가 없으면 함수를 빠져나간다.
  • 코드 라인 15~18에서 nocb로 구성된 cpu에 대해 leader cpu를 포함하여 follower cpu를 몇 개로 구성할지 여부를 알아온다. 만일 -1로 설정된 경우 sqrt(online cpu수) 결과값으로 사용한다.
    • “rcu_nocb_leader_stride=4″로 설정하는 경우 4개의 cpu마다 그룹을 나누어 첫번째는 leader cpu로 구성하고 나머지는 3개는 follower cpu로 구성한다.
    • y = int_sqrt(x) 함수에서 x 값의 rough한 루트 근사치를 구해온다.
      • x=1~3    y=1
      • x=4~8   y=2
      • x=9~15   y=3
      • x=16~24   y=4
      • x=25~35   y=5
      • x=36~48   y=6
      • x=49~63   y=7
      • x=64~80   y=8
      • x=81~99   y=9
      • x=100~120   y=10
      • x=121~143   y=11
      • x=144~168   y=12
      • x=169~195   y=13
      • x=196~224   y=14
      • x=225~255   y=15
  • 코드 라인 24~37에서 leader 및 follower cpu를 구성한다.
    • 예) “rcu_nocb_leader_stride=4”, “rcu_nocbs=6-11,  16-19″인 경우
      • 6=leader,     7=follower
      • 8=leader,     9, 10, 11=follower
      • 16=leader,    17, 18, 19=follower

 

RCU NO-CB 처리용 커널 스레드

rcu_nocb_kthread()

kernel/rcu/tree_plugin.h

/*
 * Per-rcu_data kthread, but only for no-CBs CPUs.  Each kthread invokes
 * callbacks queued by the corresponding no-CBs CPU, however, there is
 * an optional leader-follower relationship so that the grace-period
 * kthreads don't have to do quite so many wakeups.
 */
static int rcu_nocb_kthread(void *arg)
{
        int c, cl;
        struct rcu_head *list;
        struct rcu_head *next;
        struct rcu_head **tail;
        struct rcu_data *rdp = arg;

        /* Each pass through this loop invokes one batch of callbacks */
        for (;;) {
                /* Wait for callbacks. */
                if (rdp->nocb_leader == rdp)
                        nocb_leader_wait(rdp);
                else
                        nocb_follower_wait(rdp);

                /* Pull the ready-to-invoke callbacks onto local list. */
                list = ACCESS_ONCE(rdp->nocb_follower_head);
                BUG_ON(!list);
                trace_rcu_nocb_wake(rdp->rsp->name, rdp->cpu, "WokeNonEmpty");
                ACCESS_ONCE(rdp->nocb_follower_head) = NULL;
                tail = xchg(&rdp->nocb_follower_tail, &rdp->nocb_follower_head);

                /* Each pass through the following loop invokes a callback. */
                trace_rcu_batch_start(rdp->rsp->name,
                                      atomic_long_read(&rdp->nocb_q_count_lazy),
                                      atomic_long_read(&rdp->nocb_q_count), -1);
                c = cl = 0;
                while (list) {
                        next = list->next;
                        /* Wait for enqueuing to complete, if needed. */
                        while (next == NULL && &list->next != tail) {
                                trace_rcu_nocb_wake(rdp->rsp->name, rdp->cpu,
                                                    TPS("WaitQueue"));
                                schedule_timeout_interruptible(1);
                                trace_rcu_nocb_wake(rdp->rsp->name, rdp->cpu,
                                                    TPS("WokeQueue"));
                                next = list->next;
                        }
                        debug_rcu_head_unqueue(list);
                        local_bh_disable();
                        if (__rcu_reclaim(rdp->rsp->name, list))
                                cl++;
                        c++;
                        local_bh_enable();
                        list = next;
                }
                trace_rcu_batch_end(rdp->rsp->name, c, !!list, 0, 0, 1);
                smp_mb__before_atomic();  /* _add after CB invocation. */
                atomic_long_add(-c, &rdp->nocb_q_count);
                atomic_long_add(-cl, &rdp->nocb_q_count_lazy);
                rdp->n_nocbs_invoked += c;
        }
        return 0;
}

no-cb용 커널 스레드로 무한 루프를 돌며 gp를 대기한다. gp가 완료되고 대기하던 콜백들이 최종적으로 follower 리스트로 이동하면 이를 모두 호출하여 처리한다.

  • 코드 라인 16~21에서 무한 루프를 돌며 현재 스레드가 nocb_leader로서 동작하는 경우와 nocb_follower로 동작하는 것을 구분하여 콜백을 처리할 준비를 하기 위해 대기한다.
  • 코드 라인 24~28에서 콜백들을 호출하기 위해 follower 리스트에 담긴 콜백들을 모두 비워 로컬에 있는 list로 옮긴다.
  • 코드 라인 34~53에서 로컬 리스트로 가져온 콜백들을 모두 호출하여 수행한다.
  • 코드 라인 55~57에서 처리한 콜백 수 만큼 nocb_q_count 및 nocb_q_count_lazy를 갱신한다.
  • 코드 라인 48에서 콜백 호출한 횟 수인 n_nocbs_invoked 카운터를 갱신한다.

 

nocb_leader_wait()

kernel/rcu/tree_plugin.h

/*
 * Leaders come here to wait for additional callbacks to show up.
 * This function does not return until callbacks appear.
 */
static void nocb_leader_wait(struct rcu_data *my_rdp)
{
        bool firsttime = true;
        bool gotcbs;
        struct rcu_data *rdp;
        struct rcu_head **tail;

wait_again:

        /* Wait for callbacks to appear. */
        if (!rcu_nocb_poll) {
                trace_rcu_nocb_wake(my_rdp->rsp->name, my_rdp->cpu, "Sleep");
                wait_event_interruptible(my_rdp->nocb_wq,
                                !ACCESS_ONCE(my_rdp->nocb_leader_sleep));
                /* Memory barrier handled by smp_mb() calls below and repoll. */
        } else if (firsttime) {
                firsttime = false; /* Don't drown trace log with "Poll"! */
                trace_rcu_nocb_wake(my_rdp->rsp->name, my_rdp->cpu, "Poll");
        }

        /*
         * Each pass through the following loop checks a follower for CBs.
         * We are our own first follower.  Any CBs found are moved to
         * nocb_gp_head, where they await a grace period.
         */
        gotcbs = false;
        for (rdp = my_rdp; rdp; rdp = rdp->nocb_next_follower) {
                rdp->nocb_gp_head = ACCESS_ONCE(rdp->nocb_head);
                if (!rdp->nocb_gp_head)
                        continue;  /* No CBs here, try next follower. */

                /* Move callbacks to wait-for-GP list, which is empty. */
                ACCESS_ONCE(rdp->nocb_head) = NULL;
                rdp->nocb_gp_tail = xchg(&rdp->nocb_tail, &rdp->nocb_head);
                gotcbs = true;
        }

        /*
         * If there were no callbacks, sleep a bit, rescan after a
         * memory barrier, and go retry.
         */
        if (unlikely(!gotcbs)) {
                if (!rcu_nocb_poll)
                        trace_rcu_nocb_wake(my_rdp->rsp->name, my_rdp->cpu,
                                            "WokeEmpty");
                WARN_ON(signal_pending(current));
                schedule_timeout_interruptible(1);

                /* Rescan in case we were a victim of memory ordering. */
                my_rdp->nocb_leader_sleep = true;
                smp_mb();  /* Ensure _sleep true before scan. */
                for (rdp = my_rdp; rdp; rdp = rdp->nocb_next_follower)
                        if (ACCESS_ONCE(rdp->nocb_head)) {
                                /* Found CB, so short-circuit next wait. */
                                my_rdp->nocb_leader_sleep = false;
                                break;
                        }
                goto wait_again;
        }

leader 및 follower에 등록된 신규 콜백들을 follower_gp 리스트로 이동시킨다. gp가 만료된 후 follower_gp 리스트에 있는 콜백들을 follower 리스트로 옮기고 콜백들을 호출하도록 해당 follower의 nocb용 커널 스레드를 깨운다.

  • 코드 라인 15~23에서 “rcu_nocb_poll” 커널 파라메터 설정이 없는 경우 이 라인에서 대기한다.
  • 코드 라인 30~40에서 follower cpu들에 대해 nocb용 리스트를 nocb_gp 리스트로 옮긴다. 만일 등록된 콜백이 없으면 skip 한다.
  • 코드 라인 46~63에서 leader에 대응하는 follower cpu들 모두에 등록된 콜백이 없으면 leader는 1 tick 만큼 슬립한 후 leader에 등록한 콜백을 검사해서 여전히 없으면 이 함수 처음 wait_agail 레이블로 다시 이동한다.

 

        /* Wait for one grace period. */
        rcu_nocb_wait_gp(my_rdp);

        /*
         * We left ->nocb_leader_sleep unset to reduce cache thrashing.
         * We set it now, but recheck for new callbacks while
         * traversing our follower list.
         */
        my_rdp->nocb_leader_sleep = true;
        smp_mb(); /* Ensure _sleep true before scan of ->nocb_head. */

        /* Each pass through the following loop wakes a follower, if needed. */
        for (rdp = my_rdp; rdp; rdp = rdp->nocb_next_follower) {
                if (ACCESS_ONCE(rdp->nocb_head))
                        my_rdp->nocb_leader_sleep = false;/* No need to sleep.*/
                if (!rdp->nocb_gp_head)
                        continue; /* No CBs, so no need to wake follower. */

                /* Append callbacks to follower's "done" list. */
                tail = xchg(&rdp->nocb_follower_tail, rdp->nocb_gp_tail);
                *tail = rdp->nocb_gp_head;
                smp_mb__after_atomic(); /* Store *tail before wakeup. */
                if (rdp != my_rdp && tail == &rdp->nocb_follower_head) {
                        /*
                         * List was empty, wake up the follower.
                         * Memory barriers supplied by atomic_long_add().
                         */
                        wake_up(&rdp->nocb_wq);
                }
        }

        /* If we (the leader) don't have CBs, go wait some more. */
        if (!my_rdp->nocb_follower_head)
                goto wait_again;
}
  • 코드 라인 2에서 gp의 완료까지 대기한다.
  • 코드 라인 13~30에서 leader에 속한 follower cpu들을 순회하며 nocb_gp 리스트에 있는 콜백들을 nocb_follower 리스트로 옮기고 등록된 콜백들을 호출하라고 해당 nocb 커널 스레드를 깨운다.
  • 코드 라인 33~34에서 leader에 등록된 콜백이 없으면 함수 처음으로 돌아간다.

 

rcu_nocb_wait_gp()

kernel/rcu/tree_plugin.h

/*
 * If necessary, kick off a new grace period, and either way wait
 * for a subsequent grace period to complete.
 */
static void rcu_nocb_wait_gp(struct rcu_data *rdp)
{
        unsigned long c;
        bool d;
        unsigned long flags;
        bool needwake;
        struct rcu_node *rnp = rdp->mynode;

        raw_spin_lock_irqsave(&rnp->lock, flags);
        smp_mb__after_unlock_lock();
        needwake = rcu_start_future_gp(rnp, rdp, &c);
        raw_spin_unlock_irqrestore(&rnp->lock, flags);
        if (needwake)
                rcu_gp_kthread_wake(rdp->rsp);

        /*
         * Wait for the grace period.  Do so interruptibly to avoid messing
         * up the load average.
         */
        trace_rcu_future_gp(rnp, rdp, c, TPS("StartWait"));
        for (;;) {
                wait_event_interruptible(
                        rnp->nocb_gp_wq[c & 0x1],
                        (d = ULONG_CMP_GE(ACCESS_ONCE(rnp->completed), c)));
                if (likely(d))
                        break;
                WARN_ON(signal_pending(current));
                trace_rcu_future_gp(rnp, rdp, c, TPS("ResumeWait"));
        }
        trace_rcu_future_gp(rnp, rdp, c, TPS("EndWait"));
        smp_mb(); /* Ensure that CB invocation happens after GP end. */
}

새 gp가 완료되기를 기다린다.

  • 코드 라인 13~18에서 노드락을 걸고 새 gp의 completed 번호를 발급받아온다. 결과 값에 다라 gp 커널 스레드의 wakeup을 요청한다.
  • 코드 라인 25~33에서 발급받은 completed 번호로 홀짝 구분한 대기큐에서 요청 노드의 completed가 발급 받은 번호 이상이될 때까지 대기한다.

 

nocb_follower_wait()

kernel/rcu/tree_plugin.h

/*
 * Followers come here to wait for additional callbacks to show up.
 * This function does not return until callbacks appear.
 */
static void nocb_follower_wait(struct rcu_data *rdp)
{
        bool firsttime = true;

        for (;;) {
                if (!rcu_nocb_poll) {
                        trace_rcu_nocb_wake(rdp->rsp->name, rdp->cpu,
                                            "FollowerSleep");
                        wait_event_interruptible(rdp->nocb_wq,
                                                 ACCESS_ONCE(rdp->nocb_follower_head));
                } else if (firsttime) {
                        /* Don't drown trace log with "Poll"! */
                        firsttime = false;
                        trace_rcu_nocb_wake(rdp->rsp->name, rdp->cpu, "Poll");
                }
                if (smp_load_acquire(&rdp->nocb_follower_head)) {
                        /* ^^^ Ensure CB invocation follows _head test. */
                        return;
                }
                if (!rcu_nocb_poll)
                        trace_rcu_nocb_wake(rdp->rsp->name, rdp->cpu,
                                            "WokeEmpty");
                WARN_ON(signal_pending(current));
                schedule_timeout_interruptible(1);
        }
}

follower cpu는 이 곳에서 대기한다. leader가 gp 만료되면 깨워준다.

  • “rcu_nocb_poll” 커널 파라메터가 설정된 경우 1 tick 마다 폴링한다.

 

참고

 

Namespace

 

Namespace 관리 리소스

리눅스에서 namespace는 lightweight 가상화 솔루션이다. XEN이나 KVM 같은 가상화 솔루션들은 커널 인스턴스들을 생성하여 동작시키는 것에 반하여 리눅스의 namespace는 커널 인스턴스를 만들지 않고 기존의 리소스들을 필요한 만큼의 namespace로 분리하여 묶어 관리하는 방법으로 사용한다. 리눅스의 cgroup과 namespace를 사용하여 container를 생성하여 사용하는 LXC, 그리고 LXC를 사용하는 docker 솔루션 등이 구현되었다.  커널이 부팅된 후 관리 자원은 각각의 초기 디폴트 namespace에서 관리한다. 그런 후 사용자의 필요에 따라 namespace를 추가하여 자원들을 별도로 분리하여 관리할 수 있다. 관리 가능한 namespace 리소스들은 다음과 같다.

 

Namespace 초기화

리눅스에서 user namespace를 제외한 다른 리소스들의 구현은 nsproxy를 통해 연결된다.

struct nsproxy init_nsproxy = {
        .count                  = ATOMIC_INIT(1),
        .uts_ns                 = &init_uts_ns,
#if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
        .ipc_ns                 = &init_ipc_ns,
#endif
        .mnt_ns                 = NULL,
        .pid_ns_for_children    = &init_pid_ns,
#ifdef CONFIG_NET
        .net_ns                 = &init_net,
#endif
};

 

nsproxy_cache_init()

kernel/nsproxy.c

int __init nsproxy_cache_init(void)
{
        nsproxy_cachep = KMEM_CACHE(nsproxy, SLAB_PANIC);
        return 0;
}

nsproxy 구조체를 할당해줄 수 있는 kmem 슬랩 캐시를 준비한다.

 

새로운 Namespace 생성

kernel/nsproxy.c

SYSCALL_DEFINE2(setns, int, fd, int, nstype)
{
        struct task_struct *tsk = current;
        struct nsproxy *new_nsproxy;
        struct file *file;
        struct ns_common *ns;
        int err;

        file = proc_ns_fget(fd);
        if (IS_ERR(file))
                return PTR_ERR(file);

        err = -EINVAL;
        ns = get_proc_ns(file_inode(file));
        if (nstype && (ns->ops->type != nstype))
                goto out;

        new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs);
        if (IS_ERR(new_nsproxy)) {
                err = PTR_ERR(new_nsproxy);
                goto out;
        }

        err = ns->ops->install(new_nsproxy, ns);
        if (err) {
                free_nsproxy(new_nsproxy);
                goto out;
        }
        switch_task_namespaces(tsk, new_nsproxy);
out:
        fput(file);
        return err;
}

setns 라는 이름의 syscall을 호출하여 현재 태스크(스레드)를 새로 지정한 namespace에 연결시킨다.

  • 코드 라인 8~11에서 proc 인터페이스에 해당하는 파일 디스크립터로 file 구조체 포인터를 알아온다.
    • 예) “/proc/1/ns/mnt”
  • 코드 라인 13~16에서 file 디스크립터에 연결된 ns_common 구조체 포인터인 ns를 얻어온다. 만일 nstype이 지정된 경우 file 디스크립터의 namespace type과 다른 경우 에러를 반환한다.
  • 코드 라인 18~22에서 새로운 namespace에 현재 태스크를 지정한다.
  • 코드 라인 24~28에서 해당 리소스의 namespace의 install에 등록된 콜백 함수를 호출하여 설치한다.
    • pid: pidns_install()
    • ipc: ipcns_install()
    • net: netns_install()
    • user: userns_install()
    • mnt: mntns_install()
    • uts: utsns_install()
  • 코드 라인 29에서 태스크의 멤버 nsproxy 가 새로운 nsproxy로 연결하게 한다. 기존에 연결해두었던 nsproxy의 참조 카운터가 0이되면 해제(free)한다.

 

create_new_namespaces()

kernel/nsproxy.c

/*
 * Create new nsproxy and all of its the associated namespaces.
 * Return the newly created nsproxy.  Do not attach this to the task,
 * leave it to the caller to do proper locking and attach it to task.
 */
static struct nsproxy *create_new_namespaces(unsigned long flags,
        struct task_struct *tsk, struct user_namespace *user_ns,
        struct fs_struct *new_fs)
{
        struct nsproxy *new_nsp;
        int err;

        new_nsp = create_nsproxy();
        if (!new_nsp)
                return ERR_PTR(-ENOMEM);

        new_nsp->mnt_ns = copy_mnt_ns(flags, tsk->nsproxy->mnt_ns, user_ns, new_fs);
        if (IS_ERR(new_nsp->mnt_ns)) {
                err = PTR_ERR(new_nsp->mnt_ns);
                goto out_ns;
        }

        new_nsp->uts_ns = copy_utsname(flags, user_ns, tsk->nsproxy->uts_ns);
        if (IS_ERR(new_nsp->uts_ns)) {
                err = PTR_ERR(new_nsp->uts_ns);
                goto out_uts;
        }

        new_nsp->ipc_ns = copy_ipcs(flags, user_ns, tsk->nsproxy->ipc_ns);
        if (IS_ERR(new_nsp->ipc_ns)) {
                err = PTR_ERR(new_nsp->ipc_ns);
                goto out_ipc;
        }

        new_nsp->pid_ns_for_children =
                copy_pid_ns(flags, user_ns, tsk->nsproxy->pid_ns_for_children);
        if (IS_ERR(new_nsp->pid_ns_for_children)) {
                err = PTR_ERR(new_nsp->pid_ns_for_children);
                goto out_pid;
        }

        new_nsp->net_ns = copy_net_ns(flags, user_ns, tsk->nsproxy->net_ns);
        if (IS_ERR(new_nsp->net_ns)) {
                err = PTR_ERR(new_nsp->net_ns);
                goto out_net;
        }

        return new_nsp;

out_net:
        if (new_nsp->pid_ns_for_children)
                put_pid_ns(new_nsp->pid_ns_for_children);
out_pid:
        if (new_nsp->ipc_ns)
                put_ipc_ns(new_nsp->ipc_ns);
out_ipc:
        if (new_nsp->uts_ns)
                put_uts_ns(new_nsp->uts_ns);
out_uts:
        if (new_nsp->mnt_ns)
                put_mnt_ns(new_nsp->mnt_ns);
out_ns:
        kmem_cache_free(nsproxy_cachep, new_nsp);
        return ERR_PTR(err);
}

nsproxy를 새로 할당한 후 namespace들을 연결하고 nsproxy를 반환한다. 플래그에는 새롭게 생성할 namespace를 지정한 플래그 값을 사용할 수 있다.

  • 코드 라인 13~15에서 nsproxy 구조체를 새로 할당해온다.
  • 코드 라인 17~21에서 CLONE_NEWNS 플래그가 지정된 경우 새롭게 mnt_namespace 구조체를 할당하고, 플래그가 지정되지 않은 경우 기존 mnt_namespace를 알아온다. 그리고 요청한 태스크를 mnt_namespcae에 지정한다.
    • namespace 생성 플래그:
      • CLONE_NEWNS
      • CLONE_NEWUTS
      • CLONE_NEWIPC
      • CLONE_NEWUSER
      • CLONE_NEWPID
      • CLONE_NEWNET
  • 코드 라인 23~46에서 mnt namespace와 비슷한 방식으로 uts, ipc, pid, net namespace도 처리한다.
  • 코드 라인 48에서 새롭게 만들어진 rxproxy 구조체 포인터를 반환한다.

 

create_nsproxy()

kernel/nsproxy.c

static inline struct nsproxy *create_nsproxy(void)
{
        struct nsproxy *nsproxy;

        nsproxy = kmem_cache_alloc(nsproxy_cachep, GFP_KERNEL);
        if (nsproxy)
                atomic_set(&nsproxy->count, 1);
        return nsproxy;
}

nsproxy 구조체를 할당해온다.

 

copy_pid_ns()

kernel/pid_namespace.c

struct pid_namespace *copy_pid_ns(unsigned long flags,
        struct user_namespace *user_ns, struct pid_namespace *old_ns)
{      
        if (!(flags & CLONE_NEWPID))
                return get_pid_ns(old_ns);
        if (task_active_pid_ns(current) != old_ns)
                return ERR_PTR(-EINVAL);
        return create_pid_namespace(user_ns, old_ns);
}

 

create_pid_namespace()

kernel/pid_namespace.c

static struct pid_namespace *create_pid_namespace(struct user_namespace *user_ns,
        struct pid_namespace *parent_pid_ns)
{
        struct pid_namespace *ns;
        unsigned int level = parent_pid_ns->level + 1;
        int i;
        int err;

        if (level > MAX_PID_NS_LEVEL) {
                err = -EINVAL;
                goto out;
        }

        err = -ENOMEM;
        ns = kmem_cache_zalloc(pid_ns_cachep, GFP_KERNEL);
        if (ns == NULL)
                goto out;

        ns->pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL);
        if (!ns->pidmap[0].page)
                goto out_free;

        ns->pid_cachep = create_pid_cachep(level + 1);
        if (ns->pid_cachep == NULL)
                goto out_free_map;

        err = ns_alloc_inum(&ns->ns);
        if (err)
                goto out_free_map;
        ns->ns.ops = &pidns_operations;

        kref_init(&ns->kref);
        ns->level = level;
        ns->parent = get_pid_ns(parent_pid_ns);
        ns->user_ns = get_user_ns(user_ns);
        ns->nr_hashed = PIDNS_HASH_ADDING;
        INIT_WORK(&ns->proc_work, proc_cleanup_work);

        set_bit(0, ns->pidmap[0].page);
        atomic_set(&ns->pidmap[0].nr_free, BITS_PER_PAGE - 1);

        for (i = 1; i < PIDMAP_ENTRIES; i++)
                atomic_set(&ns->pidmap[i].nr_free, BITS_PER_PAGE);

        return ns;

out_free_map:
        kfree(ns->pidmap[0].page);
out_free:
        kmem_cache_free(pid_ns_cachep, ns);
out:
        return ERR_PTR(err);
}

새로 만들어지는 child pid namespace는 최대 32단계까지 생성가능하다.  pid_namespace를 생성하고 초기화하고 내부 pidmap의 구성은 다음을 참고한다.

 

Namespace 예)

아래 <pid> 값은 해당 태스크의 pid 숫자로 치환되어야 한다.

root@jake-VirtualBox:/proc/<pid>/ns# ls /proc/1/ns/ -la
합계 0
dr-x--x--x 2 root root 0  1월 11 11:16 .
dr-xr-xr-x 9 root root 0  1월  8 14:06 ..
lrwxrwxrwx 1 root root 0  1월 11 11:16 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0  1월 11 11:16 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0  1월 11 11:16 net -> net:[4026531957]
lrwxrwxrwx 1 root root 0  1월 11 11:16 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0  1월 11 11:16 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0  1월 11 11:16 uts -> uts:[4026531838]

 

# unshare --help

Usage:
 unshare [options] <program> [<argument>...]

Run a program with some namespaces unshared from the parent.

Options:
 -m, --mount               unshare mounts namespace
 -u, --uts                 unshare UTS namespace (hostname etc)
 -i, --ipc                 unshare System V IPC namespace
 -n, --net                 unshare network namespace
 -p, --pid                 unshare pid namespace
 -U, --user                unshare user namespace
 -f, --fork                fork before launching <program>
     --mount-proc[=<dir>]  mount proc filesystem first (implies --mount)
 -r, --map-root-user       map current user to root (implies --user)
 -s, --setgroups allow|deny  control the setgroups syscall in user namespaces

 -h, --help     display this help and exit
 -V, --version  output version information and exit

 

참고

 

 

pidmap_init()

 

pidmap_init()

kernel/pid.c

void __init pidmap_init(void)
{
        /* Veryify no one has done anything silly */
        BUILD_BUG_ON(PID_MAX_LIMIT >= PIDNS_HASH_ADDING);

        /* bump default and minimum pid_max based on number of cpus */
        pid_max = min(pid_max_max, max_t(int, pid_max,
                                PIDS_PER_CPU_DEFAULT * num_possible_cpus()));
        pid_max_min = max_t(int, pid_max_min,
                                PIDS_PER_CPU_MIN * num_possible_cpus());
        pr_info("pid_max: default: %u minimum: %u\n", pid_max, pid_max_min);

        init_pid_ns.pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL);
        /* Reserve PID 0. We never call free_pidmap(0) */
        set_bit(0, init_pid_ns.pidmap[0].page);
        atomic_dec(&init_pid_ns.pidmap[0].nr_free);

        init_pid_ns.pid_cachep = KMEM_CACHE(pid,
                        SLAB_HWCACHE_ALIGN | SLAB_PANIC);
}

pid를 관리하기 위해 possible cpu 수에 맞추어 pid_max와 pid_max_min을 산출한다. 그 후 첫 번째 pidmap 배열을 할당하고 pid 0번을 사용상태로 설정한다.

  • 코드 라인 7~8에서 1024 * possible cpu 수를 곱한 값(최소 32K부터 시작)과 pid_max_max(32bit=32K, 64bit=4M) 값 둘 중 작은 값으로 pid_max를 산출한다.
    • rpi2: pid_max=32K
  • 코드 라인 9~10에서 8 * possible cpu 수를 곱한 값(최소 301부터 시작)으로 pid_max_min을 대입한다.
    • rpi2: pid_max_min=301
  • 코드 라인 11에서 pid 관련 정보를 출력한다.
    • rpi2: “pid_max: default: 32768 minimum: 301
  • 코드 라인 13에서 init_pid_ns의 pidmap 배열에서 첫 페이지는 무조건 필요하므로 1개 페이지를 할당한다.
  • 코드 라인 15~16에서 pid 0번에 해당하는 비트를 설정하고, nr_free 수에서 1을 감소 시킨다.
  • 코드 라인 18~19에서 pid 구조체 할당 시 사용할 목적의 kmem 슬랩 캐시를 준비한다.

 

PID_MAX_DEFAULT 값은 다음과 같다. (기본적으로는 32K 개의 pid 수를 사용한다.)

  • small foot print 커널
    • 4K
  • 32bit 또는 64bit 커널
    • 32K

 

PID_MAX_LIMIT  값을 알아보면 다음과 같다. (64비트 시스템에서 4M개의 pid 수를 사용할 수 있음을 알 수 있다)

  • small foot print 커널
    • PAGE_SIZE(4K) x 8 = 32K
  • 32bit 커널
    • 32K
  • 64bit 커널
    • 4M

include/linux/threads.h

/*
 * This controls the default maximum pid allocated to a process
 */
#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)

/*
 * A maximum of 4 million PIDs should be enough for a while.
 * [NOTE: PID/TIDs are limited to 2^29 ~= 500+ million, see futex.h.]
 */
#define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : \
        (sizeof(long) > 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT))

 

컴파일 타임에 pid 관련 전역 변수의 초기값은 다음과 같다.

include/linux/threads.h

int pid_max = PID_MAX_DEFAULT;

#define RESERVED_PIDS           300

int pid_max_min = RESERVED_PIDS + 1;
int pid_max_max = PID_MAX_LIMIT;

 

cpu별 pid min 값은 1024개이고 pid max는 32K이다. (32개의 cpu 기준)

include/linux/threads.h

/*
 * Define a minimum number of pids per cpu.  Heuristically based
 * on original pid max of 32k for 32 cpus.  Also, increase the
 * minimum settable value for pid_max on the running system based
 * on similar defaults.  See kernel/pid.c:pidmap_init() for details.
 */
#define PIDS_PER_CPU_DEFAULT    1024
#define PIDS_PER_CPU_MIN        8

 

각 pid 번호의 사용 유무는 1bit를 사용한다. 1개의 페이지로 관리할 수 있는 pid 수는 PAGE_SIZE x 8 bit 이다. 관리할 최대 pid 수가 큰 경우 여러 페이지를 사용할 수 있는데 pidmap을 배열로 만들고 각 배열 인덱스는 1 개의 페이지를 관리한다. 예를 들어 1개 페이지 사이즈가 4K라고 가정할 때, 1개의 페이지 사이즈를 사용하여 pid에 대한 맵을 사용하면 1 PAGE x 8 bit = 32K 만큼의 pid를 관리할 수 있다. 따라서 pidmap 배열에 사용할 엔트리 수는 최대 pid 수(PID_MAX_LIMIT)를 32K 단위로 나누어 올림 처리한 수로 사용한다.

  • 4K 페이지, 32bit 시스템에서 최대 32K 개의 pid는 1개 페이지로 처리할 수 있어 pidmap[1]을 사용한다.
  • 4K 페이지, 64bit 시스템에서 최대 4M 개의 pid는 128개 페이지로 처리해야 하므로 pidmap[128]이 필요하다.

include/linux/pid_namespace.h

#define BITS_PER_PAGE           (PAGE_SIZE * 8)
#define BITS_PER_PAGE_MASK      (BITS_PER_PAGE-1)
#define PIDMAP_ENTRIES          ((PID_MAX_LIMIT+BITS_PER_PAGE-1)/BITS_PER_PAGE)

 

 

참조