您的位置:首页 > 运维架构 > Linux

linux pid名字空间

2016-03-15 09:50 232 查看
最近看《深入理解linux内核架构》中pid名字空间一块,略有感悟。这里开门见山,直接来讲pid名字空间是如何实现的,以及如何使用。先看一幅图,图中描述了所使用的数据结构和他们的关系。



首先强调图中的几个比较容易搞混的字段。看task_struct中的struct pid_link *pids数组中,每个元素里有一个struct hlist_node node字段。也就是说,可以利用这个字段把task_struct加入到一个哈希表中。再看右边的struct pid结构中的struct hlist_head tasks[]数组,明显这个hlist_head,也就是表头,这个地方的每个数组元素都可以是一个链表。由于这里数据结构比较多,所以一定要注意是一个内结点,还是一个链表的头结点。

图中绿线(无箭头)表示头结点把中间结点链接成了一个链。而蓝色单箭头线表示,是只有一个指向目标结点的指针。蓝色双箭头线表示是双向链表。

为什么要搞这么复杂的结构呢?因为满足一些需求。

搞pid名字空间就是为了把一些进程隔离开来,让他们以为他们独占了整个系统,在虚拟化等中有应用。

还有,内核中经常需要由PID的数字值得到进程task_struct。(struct hlist_head *pid_hash的作用)。

需要由PID数字值得到进程组ID,会话ID。

当一个会话结束了,需要终止属于这个会话的所有进程。也就是要由会话ID找到所有属于这个会话的进程task_struct。(看看struct pid中有个SID字段,这里链接着属于这个PID的所有会话进程)。

linux用轻量级进程模拟线程,所以需要由线程组长ID找到所有属于这个进程的线程ID。

新创建一个进程是怎么影响名字空间的?

新建进程时的调用栈如下图。可以看出这是在创建内核线程。创建用户线程是调用fork()系统调用,这fork里也调用了do_fork(),也就是图中的do_fork(). do_fork中调用了copy_process函数,在这个函数中子进程复制了父进程的task_struct,其中就复制了task_struct中的struct pid_link pids[]数组。也就是说这时子进程与父进程有一样的PID,SID,GID。不过不用担心,这时子进程没有运行,在copy_process后边会重新给pids[PID].pid赋值。



在kernel/fork.c:copy_process函数中,依次调用了下列函数:

p = dup_task_struct(current);复制了父进程的PID,GID,SID。
if (pid != &init_struct_pid) {
retval = -ENOMEM;
pid = alloc_pid(p->nsproxy->pid_ns);//分配新的PID,包括在每个名字空间中看到的不同PID。
if (!pid)
goto bad_fork_cleanup_io;
if (clone_flags & CLONE_NEWPID) {
retval = pid_ns_prepare_proc(p->nsproxy->pid_ns);
if (retval < 0)
goto bad_fork_free_pid;
}
}
p->pid = pid_nr(pid);
p->tgid = p->pid;
if (clone_flags & CLONE_THREAD)
p->tgid = current->tgid;
//自己成了新的进程组长,但是在真正获取进程组ID时:task->group_leader->pids[PIDTYPE_PGID];
p->group_leader = p;
INIT_LIST_HEAD(&p->thread_group);
if (likely(p->pid)) {
list_add_tail(&p->sibling, &p->real_parent->children);
tracehook_finish_clone(p, clone_flags, trace);
if (thread_group_leader(p)) {
if (clone_flags & CLONE_NEWPID)
p->nsproxy->pid_ns->child_reaper = p;

p->signal->leader_pid = pid;
tty_kref_put(p->signal->tty);
p->signal->tty =tty_kref_get(current->signal->tty);
attach_pid(p, PIDTYPE_PGID, task_pgrp(current));

**//加入会话PID的task[PIDTYPE_SID]链表。**
attach_pid(p, PIDTYPE_SID, task_session(current));
list_add_tail_rcu(&p->tasks, &init_task.tasks);
__get_cpu_var(process_counts)++;
}
**//将自己加入struct pid的tasks[PIDTYPE_PID]链表。**
attach_pid(p, PIDTYPE_PID, pid);
nr_threads++;
}


每个进程有一个stuct pid结构,在这个结构中,tasks[]数组中有三个链表,第一个是PIDTYPE_PID链表,这个链表中只有一个进程x,也就是说每个进程有唯一一个struct PID.

第二个是PIDTYPE_PGID,这个链表中可能多于一个进程,多个进程可能属于同一个进程组,由此可见,组长进程退出后,这个struct结点不应该释放,直到进程组中所有进程退出。第三个是PIDTYPE_SID,这个与PGID一样,属于同一个会话的进程都在这个链表中。有了这些知识,上面的问题都可以迎刃而解了。

分配struct pid

下面代码在kernel/pid.c中,在分配struct pid结点时调用。

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;
//从slab中获得struct pid结构
pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
if (!pid)
goto out;
tmp = ns;
for (i = ns->level; i >= 0; i--) {
nr = alloc_pidmap(tmp);//从pid名字空间的位图中分配一个数字PID号。
if (nr < 0)
goto out_free;

pid->numbers[i].nr = nr;
pid->numbers[i].ns = tmp;
tmp = tmp->parent;
}
get_pid_ns(ns);
pid->level = ns->level;
atomic_set(&pid->count, 1); //引用计数。计数为0就释放struct pid结构。
for (type = 0; type < PIDTYPE_MAX; ++type)
INIT_HLIST_HEAD(&pid->tasks[type]);

spin_lock_irq(&pidmap_lock);
for (i = ns->level; i >= 0; i--) {
upid = &pid->numbers[i];
hlist_add_head_rcu(&upid->pid_chain,
&pid_hash[pid_hashfn(upid->nr, upid->ns)]);
}
spin_unlock_irq(&pidmap_lock);

out:
return pid;

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

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

*
* attach_pid() must be called with the tasklist_lock write-held.
*/
void attach_pid(struct task_struct *task, enum pid_type type,
struct pid *pid)
{
struct pid_link *link;

link = &task->pids[type];
link->pid = pid;
hlist_add_head_rcu(&link->node, &pid->tasks[type]);
}


最初的struct pid是如何建立起来的?

一开始,内核用静态变量初始化了一个struct pid结构给swap(0号进程)使用。还静态初始化了一个pid名字空间。

这是在kernel/pid.ck上定义的。
struct pid_namespace init_pid_ns = {
.kref = {
.refcount       = ATOMIC_INIT(2),
},
.pidmap = {
[ 0 ... PIDMAP_ENTRIES-1] = { ATOMIC_INIT(BITS_PER_PAGE), NULL }
},
.last_pid = 0,
.level = 0,
.child_reaper = &init_task, //每个pid名字空间有一个init进程,用来收养孤儿进程
};
//全局pid_hash中。链接的是struct upid结构,哈希值是由nr(pid)和ns(pid所在名字空间)算出来的.由pid和ns就能得到struct upid结构,再用container_of得到struct pid结构,再从struct pid结构中tasks[]获取task_struct。
static struct hlist_head *pid_hash;
static unsigned int pidhash_shift = 4;
struct pid init_struct_pid = INIT_STRUCT_PID;//一个全局的初始pid结构。也是系统中最初始的PID。其他PID都是由alloc_pid函数从slab中分配的。

下面代码在include/linux/init_task.h中:
extern struct nsproxy init_nsproxy;//名字空间根结点,静态定义
#define INIT_NSPROXY(nsproxy) {                     \
.pid_ns     = **&init_pid_ns,**                 \
.count      = ATOMIC_INIT(1),               \
.uts_ns     = **&init_uts_ns,**                 \
.mnt_ns     = NULL,                     \
INIT_NET_NS(net_ns)                                             \
INIT_IPC_NS(ipc_ns)                     \
}

#define INIT_STRUCT_PID {                       \
.count      = ATOMIC_INIT(1),               \
.tasks      = {                     \
下边三个字段就是初始PID的初始化。可以看到0号进程是自己的组长,会话组长。
**{ .first = &init_task.pids[PIDTYPE_PID].node },       \
{ .first = &init_task.pids[PIDTYPE_PGID].node },    \
{ .first = &init_task.pids[PIDTYPE_SID].node },     \**
},                              \
.rcu        = RCU_HEAD_INIT,                \
.level      = 0,                        \
.numbers    = { {                       \
.nr     = 0,                    \
**.ns       = &init_pid_ns,**               \
.pid_chain  = { .next = NULL, .pprev = NULL },  \
}, }                                \
}

#define INIT_PID_LINK(type)                     \
{                               \
.node = {                       \
.next = NULL,                   \
.pprev = &init_struct_pid.tasks[type].first,    \
},                          \
.pid = &init_struct_pid,                \
}

/*
*  INIT_TASK is used to set up the first task table, touch at
* your own risk!. Base=0, limit=0x1fffff (=2MB)
*/
#define INIT_TASK(tsk)  \初始化第一个进程
{                                   \
.state      = 0,                        \
.stack      = &init_thread_info,                \
.usage      = ATOMIC_INIT(2),               \
.flags      = PF_KTHREAD,                   \
.lock_depth = -1,                       \
.prio       = MAX_PRIO-20,                  \
.static_prio    = MAX_PRIO-20,                  \
.normal_prio    = MAX_PRIO-20,                  \
.policy     = SCHED_NORMAL,                 \
.cpus_allowed   = CPU_MASK_ALL,                 \
.mm     = NULL,                     \
.active_mm  = &init_mm,                 \
.se     = {                     \
.group_node     = LIST_HEAD_INIT(tsk.se.group_node),    \
},                              \
.rt     = {                     \
.run_list   = LIST_HEAD_INIT(tsk.rt.run_list),  \
.time_slice = HZ,                   \
.nr_cpus_allowed = NR_CPUS,             \
},                              \
.tasks      = LIST_HEAD_INIT(tsk.tasks),            \
.pushable_tasks = PLIST_NODE_INIT(tsk.pushable_tasks, MAX_PRIO), \
.ptraced    = LIST_HEAD_INIT(tsk.ptraced),          \
.ptrace_entry   = LIST_HEAD_INIT(tsk.ptrace_entry),     \
.real_parent    = &tsk,                     \
.parent     = &tsk,                     \
.children   = LIST_HEAD_INIT(tsk.children),         \
.sibling    = LIST_HEAD_INIT(tsk.sibling),          \
**.group_leader = &tsk,**                       \
.real_cred  = &init_cred,                   \
.cred       = &init_cred,                   \
.cred_guard_mutex =                     \
__MUTEX_INITIALIZER(tsk.cred_guard_mutex),     \
.comm       = "swapper",                    \
.thread     = INIT_THREAD,                  \
.fs     = &init_fs,                 \
.files      = &init_files,                  \
.signal     = &init_signals,                \
.sighand    = &init_sighand,                \
.nsproxy    = &init_nsproxy,                \
.pending    = {                     \
.list = LIST_HEAD_INIT(tsk.pending.list),       \
.signal = {{0}}},                   \
.blocked    = {{0}},                    \
.alloc_lock = __SPIN_LOCK_UNLOCKED(tsk.alloc_lock),     \
.journal_info   = NULL,                     \
.cpu_timers = INIT_CPU_TIMERS(tsk.cpu_timers),      \
.fs_excl    = ATOMIC_INIT(0),               \
.pi_lock    = __SPIN_LOCK_UNLOCKED(tsk.pi_lock),        \
.timer_slack_ns = 50000, /* 50 usec default slack */        \
**将自己加入struct pid的tasks字段中。**
.pids = {                           \
[PIDTYPE_PID]  = INIT_PID_LINK(PIDTYPE_PID),        \
[PIDTYPE_PGID] = INIT_PID_LINK(PIDTYPE_PGID),       \
[PIDTYPE_SID]  = INIT_PID_LINK(PIDTYPE_SID),        \
},                              \
.dirties = INIT_PROP_LOCAL_SINGLE(dirties),         \
INIT_IDS                            \
INIT_PERF_EVENTS(tsk)                       \
INIT_TRACE_IRQFLAGS                     \
INIT_LOCKDEP                            \
INIT_FTRACE_GRAPH                       \
INIT_TRACE_RECURSION                        \
INIT_TASK_RCU_PREEMPT(tsk)                  \
}

下面代码在arch/x86/kernel/init_task.c中:
/*
* Initial task structure.
*
* All other task structs will be allocated on slabs in fork.c
*/
struct task_struct init_task = INIT_TASK(init_task);
EXPORT_SYMBOL(init_task);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: