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。
在kernel/fork.c:copy_process函数中,依次调用了下列函数:
每个进程有一个stuct pid结构,在这个结构中,tasks[]数组中有三个链表,第一个是PIDTYPE_PID链表,这个链表中只有一个进程x,也就是说每个进程有唯一一个struct PID.
第二个是PIDTYPE_PGID,这个链表中可能多于一个进程,多个进程可能属于同一个进程组,由此可见,组长进程退出后,这个struct结点不应该释放,直到进程组中所有进程退出。第三个是PIDTYPE_SID,这个与PGID一样,属于同一个会话的进程都在这个链表中。有了这些知识,上面的问题都可以迎刃而解了。
首先强调图中的几个比较容易搞混的字段。看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);
相关文章推荐
- Linux安装与基础配置
- linux命令(chown、find)
- linux配置java环境变量
- linux系统内SAMBA共享问题
- centos7 设置开机启动服务
- Red Hat Enterprise Linux Server 如何设置中文版本
- git 在Linux下搭建git服务器
- linux下samba的安装过程
- Install EPEL repo on CentOS 7 / RHEL 7
- Linux下VMware虚拟机的安装
- linux用户环境变量
- Linux下DNS服务器搭建详解
- linux build commands
- Netty4学习笔记(一) 基础篇
- Linux高级编程函数总结
- CentOS设置
- 运维系列之一 Linux的文件与目录权限解析
- 如何在Linux系统查找某文件夹中含有关键字的文件
- Linux常用命令
- solr在centos7下单机版部署