diff --git a/study/process/pid/Makefile b/study/process/kernel_thead/Makefile similarity index 86% rename from study/process/pid/Makefile rename to study/process/kernel_thead/Makefile index 89949a4..aa1be5b 100644 --- a/study/process/pid/Makefile +++ b/study/process/kernel_thead/Makefile @@ -1,6 +1,6 @@ -obj-m := get_pid.o +obj-m := kernel_thread.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build diff --git a/study/process/pid/get_pid.c b/study/process/kernel_thead/kernel_thread.c similarity index 59% rename from study/process/pid/get_pid.c rename to study/process/kernel_thead/kernel_thread.c index 0cc1caf..244f61e 100644 --- a/study/process/pid/get_pid.c +++ b/study/process/kernel_thead/kernel_thread.c @@ -30,49 +30,52 @@ int kthread_function(void *argc) return 0; } -void do_print_pid() +void print_kernel_thread_pid(void) { - pid_t res; + pid_t res = -1; + struct pid *ktpid = NULL; + struct task_struct *task = NULL; // create a kernel thread res = kernel_thread(kthread_function, NULL, CLONE_KERNEL); // get the pid of the kernel thread you create - struct pid* ktpid = find_get_pid(res); + ktpid = find_get_pid(res); // get the task info of the kernel thread - struct task_struct* task = pid_task(kpid, PIDTYPE_PID); + task = pid_task(ktpid, PIDTYPE_PID); - printk(KERN_ALERT "the state of the task is:%d\n", task->state); //显示任务当前所处的状态 + printk(KERN_ALERT "the state of the task is : %ld\n", task->state); //显示任务当前所处的状态 // get the real PID of the kernel thread - printk(KERN_ALERT "the pid of the task is:%d\n", task->pid); //显示任务的进程号 + printk(KERN_ALERT "the pid of the task is :%d\n", task->pid); //显示任务的进程号 // get the thread id of the kernel thread - printk(KERN_ALERT "the tgid of the task is:%d\n", task->tgid); + printk(KERN_ALERT "the tgid of the task is : %d\n", task->tgid); // 显示函数kernel_thread( )函数执行结果 - printk(KERN_ALERT "the kernel_thread result is:%d\n", res); + printk(KERN_ALERT "the kernel_thread result is : %d\n", res); printk(KERN_ALERT "out pid_task_init.\n"); +} + + +static int init_kernel_thread(void) +{ + print_kernel_thread_pid( ); + return 0; } - -static int init_print_pid(void) +static void exit_kernel_thread(void) { - -} - -static void exit_print_pid(void) -{ - printk(KERN_ALERT "GOOD BYE:print_pid!!\n"); + printk(KERN_ALERT "GOOD BYE:kernel_thread!!\n"); } -module_init(init_print_pidt); -module_exit(exit_print_pid); +module_init(init_kernel_thread); +module_exit(exit_kernel_thread); MODULE_AUTHOR("gatieme"); MODULE_LICENSE("GPL"); diff --git a/study/process/namespace/namespace.md b/study/process/namespace/namespace.md new file mode 100644 index 0000000..11204ed --- /dev/null +++ b/study/process/namespace/namespace.md @@ -0,0 +1,164 @@ +Linux Namespaces机制提供一种资源隔离方案。PID,IPC,Network等系统资源不再是全局性的,而是属于特定的Namespace。每个Namespace里面的资源对其他Namespace都是透明的。要创建新的Namespace,只需要在调用clone时指定相应的flag。Linux Namespaces机制为实现基于容器的虚拟化技术提供了很好的基础,LXC(Linux containers)就是利用这一特性实现了资源的隔离。不同Container内的进程属于不同的Namespace,彼此透明,互不干扰。下面我们就从clone系统调用的flag出发,来介绍各个Namespace。 +命名空间提供了虚拟化的一种轻量级形式,使得我们可以从不同的方面来查看运行系统的全局属性。该机制类似于Solaris中的zone或 FreeBSD中的jail。对该概念做一般概述之后,我将讨论命名空间框架所提供的基础设施。 + +#命名空间概念 +------- + +传统上,在Linux以及其他衍生的UNIX变体中,许多资源是全局管理的。 + +例如,系统中的所有进程按照惯例是通过PID标识的,这意味着内核必须管理一个**全局的PID列表**。而且,所有调用者通过uname系统调用返回的系统相关信息(包括系统名称和有关内核的一些信息)都是相同的。用户ID的管理方式类似,即各个用户是通过一个全局唯一的UID号标识。 + +全局ID使得内核可以有选择地允许或拒绝某些特权。虽然UID为0的root用户基本上允许做任何事,但其他用户ID则会受到限制。例如UID为n 的用户,不允许杀死属于用户m的进程(m≠ n)。但这不能防止用户看到彼此,即用户n可以看到另一个用户m也在计算机上活动。只要用户只能操纵他们自己的进程,这就没什么问题,因为没有理由不允许用户看到其他用户的进程。 + +但有些情况下,这种效果可能是不想要的。如果提供Web主机的供应商打算向用户提供Linux计算机的全部访问权限,包括root权限在内。传统上,这需要为每个用户准备一台计算机,代价太高。使用KVM或VMWare提供的虚拟化环境是一种解决问题的方法,但资源分配做得不是非常好。计算机的各个用户都需要一个独立的内核,以及一份完全安装好的配套的用户层应用。 + +**命名空间**提供了一种不同的解决方案,所需资源较少。在虚拟化的系统中,一台物理计算机可以运行多个内核,可能是并行的多个不同的操作系统。而命名空间则只使用一个内核在一台物理计算机上运作,前述的所有全局资源都通过命名空间抽象起来。这使得可以将一组进程放置到容器中,各个容器彼此隔离。隔离可以使容器的成员与其他容器毫无关系。但也可以通过允许容器进行一定的共享,来降低容器之间的分隔。例如,容器可以设置为使用自身的PID集合,但仍然与其他容器共享部分文件系统。 + +本质上,命名空间建立了系统的不同视图。此前的每一项全局资源都必须包装到容器数据结构中,只有资源和包含资源的命名空间构成的二元组仍然是全局唯一的。虽然在给定容器内部资源是自足的,但无法提供在容器外部具有唯一性的ID。 + +考虑系统上有3个不同命名空间的情况。命名空间可以组织为层次,我会在这里讨论这种情况。一个命名空间是父命名空间,衍生了两个子命名空间。假定容器用于虚拟主机配置中,其中的每个容器必须看起来像是单独的一台Linux计算机。因此其中每一个都有自身的init进程,PID为0,其他进程的PID 以递增次序分配。两个子命名空间都有PID为0的init进程,以及PID分别为2和3的两个进程。由于相同的PID在系统中出现多次,PID号不是全局唯一的。 + + + +虽然子容器不了解系统中的其他容器,但父容器知道子命名空间的存在,也可以看到其中执行的所有进程。图中子容器的进程映射到父容器中,PID为4到 9。尽管系统上有9个进程,但却需要15个PID来表示,因为一个进程可以关联到多个PID。至于哪个PID是"正确"的,则依赖于具体的上下文。 + +如果命名空间包含的是比较简单的量,也可以是非层次的,例如下文讨论的UTS命名空间。在这种情况下,父子命名空间之间没有联系。 +请注意,Linux系统对简单形式的命名空间的支持已经有很长一段时间了,主要是chroot系统调用。该方法可以将进程限制到文件系统的某一部分,因而是一种简单的命名空间机制。但真正的命名空间能够控制的功能远远超过文件系统视图。 + + +#Linux内核命名空间描述 +------- + +在Linux内核中提供了多个namespace,其中包括fs (mount), uts, network, sysvipc, 等。一个进程可以属于多个namesapce,既然namespace和进程相关,那么在task_struct结构体中就会包含和namespace相关联的变量。在task_struct 结构中有一个指向namespace结构体的指针nsproxy。 +```c +struct task_struct +{ +…….. +/* namespaces */ + struct nsproxy *nsproxy; +……. +} +``` + + +再看一下[nsproxy](http://lxr.free-electrons.com/source/include/linux/nsproxy.h#L29)是如何定义的,在[include/linux/nsproxy.h](http://lxr.free-electrons.com/source/include/linux/nsproxy.h)文件中,这里一共定义了5个各自的命名空间结构体,在该结构体中定义了5个指向各个类型namespace的指针,由于多个进程可以使用同一个namespace,所以nsproxy可以共享使用,count字段是该结构的引用计数。 + +```c +/* 'count' is the number of tasks holding a reference. + * The count for each namespace, then, will be the number + * of nsproxies pointing to it, not the number of tasks. + * The nsproxy is shared by tasks which share all namespaces. + * As soon as a single namespace is cloned or unshared, the + * nsproxy is copied +*/ +struct nsproxy +{ + atomic_t count; + struct uts_namespace *uts_ns; + struct ipc_namespace *ipc_ns; + struct mnt_namespace *mnt_ns; + struct pid_namespace *pid_ns_for_children; + struct net *net_ns; +}; +``` +1. UTS命名空间包含了运行内核的名称、版本、底层体系结构类型等信息。UTS是UNIX Timesharing System的简称。 + +2. 保存在struct ipc_namespace中的所有与进程间通信(IPC)有关的信息。 + +3. 已经装载的文件系统的视图,在struct mnt_namespace中给出。 + +4. 有关进程ID的信息,由struct pid_namespace提供。 + +5. struct net_ns包含所有网络相关的命名空间参数。 + +系统中有一个默认的`nsproxy`,[init_nsproxy](http://lxr.free-electrons.com/source/include/linux/init_task.h#L232),该结构在task初始化是也会被初始,定义在[include/linux/init_task.h](http://lxr.free-electrons.com/source/include/linux/init_task.h#L190) + +```c +#define INIT_TASK(tsk) \ +{ +…….. + .nsproxy = &init_nsproxy, +…….. +} +``` + +其中[init_nsproxy](http://lxr.free-electrons.com/source/kernel/nsproxy.c#L31)的定义为: + +``` +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 +}; +``` +对于.mnt_ns没有进行初始化,其余的namespace都进行了系统默认初始 + + +#命名空间的创建 +------- + +新的命名空间可以用下面两种方法创建。 + +1. 在用fork或clone系统调用创建新进程时,有特定的选项可以控制是与父进程共享命名空间,还是建立新的命名空间。 + +2. unshare系统调用将进程的某些部分从父进程分离,其中也包括命名空间。更多信息请参见手册页unshare(2)。 + +在进程已经使用上述的两种机制之一从父进程命名空间分离后,从该进程的角度来看,改变全局属性不会传播到父进程命名空间,而父进程的修改也不会传播到子进 程,至少对于简单的量是这样。而对于文件系统来说,情况就比较复杂,其中的共享机制非常强大,带来了大量的可能性。 + +命名空间的实现需要两个部分:每个子系统的命名空间结构,将此前所有的全局组件包装到命名空间中;将给定进程关联到所属各个命名空间的机制。 + +在用fork或clone系统调用创建新进程时,有特定的选项可以控制是与父进程共享命名空间,还是建立新的命名空间。这些选项如下 + +* CLONE_NEWPID 进程命名空间。空间内的PID 是独立分配的,意思就是命名空间内的虚拟 PID 可能会与命名空间外的 PID 相冲突,于是命名空间内的 PID 映射到命名空间外时会使用另外一个 PID。比如说,命名空间内第一个 PID 为1,而在命名空间外就是该 PID 已被 init 进程所使用。 + +* CLONE_NEWIPC 进程间通信(IPC)的命名空间,可以将 SystemV 的 IPC 和 POSIX 的消息队列独立出来。 + +* CLONE_NEWNET 网络命名空间,用于隔离网络资源(/proc/net、IP 地址、网卡、路由等)。后台进程可以运行在不同命名空间内的相同端口上,用户还可以虚拟出一块网卡。 + +* CLONE_NEWNS 挂载命名空间,进程运行时可以将挂载点与系统分离,使用这个功能时,我们可以达到 chroot 的功能,而在安全性方面比 chroot 更高。 + +* CLONE_NEWUTS UTS 命名空间,主要目的是独立出主机名和网络信息服务(NIS)。 + +* CLONE_NEWUSER 用户命名空间,同进程 ID 一样,用户 ID 和组 ID 在命名空间内外是不一样的,并且在不同命名空间内可以存在相同的 ID。 + + +##PID Namespace +------- + +当调用clone时,设定了CLONE_NEWPID,就会创建一个新的PID Namespace,clone出来的新进程将成为Namespace里的第一个进程。一个PID Namespace为进程提供了一个独立的PID环境,PID Namespace内的PID将从1开始,在Namespace内调用fork,vfork或clone都将产生一个在该Namespace内独立的PID。新创建的Namespace里的第一个进程在该Namespace内的PID将为1,就像一个独立的系统里的init进程一样。该Namespace内的孤儿进程都将以该进程为父进程,当该进程被结束时,该Namespace内所有的进程都会被结束。PID Namespace是层次性,新创建的Namespace将会是创建该Namespace的进程属于的Namespace的子Namespace。子Namespace中的进程对于父Namespace是可见的,一个进程将拥有不止一个PID,而是在所在的Namespace以及所有直系祖先Namespace中都将有一个PID。系统启动时,内核将创建一个默认的PID Namespace,该Namespace是所有以后创建的Namespace的祖先,因此系统所有的进程在该Namespace都是可见的。 + +##IPC Namespace +------- + +当调用clone时,设定了CLONE_NEWIPC,就会创建一个新的IPC Namespace,clone出来的进程将成为Namespace里的第一个进程。一个IPC Namespace有一组System V IPC objects 标识符构成,这标识符有IPC相关的系统调用创建。在一个IPC Namespace里面创建的IPC object对该Namespace内的所有进程可见,但是对其他Namespace不可见,这样就使得不同Namespace之间的进程不能直接通信,就像是在不同的系统里一样。当一个IPC Namespace被销毁,该Namespace内的所有IPC object会被内核自动销毁。 +PID Namespace和IPC Namespace可以组合起来一起使用,只需在调用clone时,同时指定CLONE_NEWPID和CLONE_NEWIPC,这样新创建的Namespace既是一个独立的PID空间又是一个独立的IPC空间。不同Namespace的进程彼此不可见,也不能互相通信,这样就实现了进程间的隔离。 + +##mount Namespace +------- + +当调用clone时,设定了CLONE_NEWNS,就会创建一个新的mount Namespace。每个进程都存在于一个mount Namespace里面,mount Namespace为进程提供了一个文件层次视图。如果不设定这个flag,子进程和父进程将共享一个mount Namespace,其后子进程调用mount或umount将会影响到所有该Namespace内的进程。如果子进程在一个独立的mount Namespace里面,就可以调用mount或umount建立一份新的文件层次视图。该flag配合pivot_root系统调用,可以为进程创建一个独立的目录空间。 + +##Network Namespace +------- + +当调用clone时,设定了CLONE_NEWNET,就会创建一个新的Network Namespace。一个Network Namespace为进程提供了一个完全独立的网络协议栈的视图。包括网络设备接口,IPv4和IPv6协议栈,IP路由表,防火墙规则,sockets等等。一个Network Namespace提供了一份独立的网络环境,就跟一个独立的系统一样。一个物理设备只能存在于一个Network Namespace中,可以从一个Namespace移动另一个Namespace中。虚拟网络设备(virtual network device)提供了一种类似管道的抽象,可以在不同的Namespace之间建立隧道。利用虚拟化网络设备,可以建立到其他Namespace中的物理设备的桥接。当一个Network Namespace被销毁时,物理设备会被自动移回init Network Namespace,即系统最开始的Namespace。 + +##UTS Namespace +------- + +当调用clone时,设定了CLONE_NEWUTS,就会创建一个新的UTS Namespace。一个UTS Namespace就是一组被uname返回的标识符。新的UTS Namespace中的标识符通过复制调用进程所属的Namespace的标识符来初始化。Clone出来的进程可以通过相关系统调用改变这些标识符,比如调用sethostname来改变该Namespace的hostname。这一改变对该Namespace内的所有进程可见。CLONE_NEWUTS和CLONE_NEWNET一起使用,可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。 +以上所有clone flag都可以一起使用,为进程提供了一个独立的运行环境。LXC正是通过clone时设定这些flag,为进程创建一个有独立PID,IPC,FS,Network,UTS空间的container。一个container就是一个虚拟的运行环境,对container里的进程是透明的,它会以为自己是直接在一个系统上运行的。一个container就像传统虚拟化技术里面的一台安装了OS的虚拟机,但是开销更小,部署更为便捷。 +Linux Namespaces机制本身就是为了实现 container based virtualizaiton开发的。它提供了一套轻量级、高效率的系统资源隔离方案,远比传统的虚拟化技术开销小,不过它也不是完美的,它为内核的开发带来了更多的复杂性,它在隔离性和容错性上跟传统的虚拟化技术比也还有差距。 + +##[user_namespace](http://lxr.free-electrons.com/source/include/linux/user_namespace.h#L25) +------- + +CLONE_NEWUSER指定子进程拥有新的用户空间 + diff --git a/study/process/pid/.get_pid.o.d b/study/process/pid/.get_pid.o.d deleted file mode 100644 index f1308da..0000000 --- a/study/process/pid/.get_pid.o.d +++ /dev/null @@ -1,178 +0,0 @@ -get_pid.o: \ - /home/gatieme/Work/GitHub/LDD-LinuxDeviceDrivers/study/process/pid/get_pid.c \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/include/linux/kconfig.h \ - include/linux/autoconf.h include/linux/init.h include/linux/compiler.h \ - include/linux/compiler-gcc.h include/linux/compiler-gcc4.h \ - include/linux/module.h include/linux/list.h include/linux/types.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/types.h \ - include/asm-generic/types.h include/asm-generic/int-ll64.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/bitsperlong.h \ - include/asm-generic/bitsperlong.h include/linux/posix_types.h \ - include/linux/stddef.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/posix_types.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/posix_types_64.h \ - include/linux/poison.h include/linux/prefetch.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/processor.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/processor-flags.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/vm86.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/ptrace.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/ptrace-abi.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/segment.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/cache.h \ - include/linux/linkage.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/linkage.h \ - include/linux/stringify.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/page_types.h \ - include/linux/const.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/page_64_types.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/math_emu.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/sigcontext.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/current.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/percpu.h \ - include/linux/kernel.h \ - /usr/lib/gcc/x86_64-redhat-linux/4.4.7/include/stdarg.h \ - include/linux/bitops.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/bitops.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/alternative.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/asm.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/cpufeature.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/required-features.h \ - include/asm-generic/bitops/sched.h include/asm-generic/bitops/hweight.h \ - include/asm-generic/bitops/fls64.h \ - include/asm-generic/bitops/ext2-non-atomic.h \ - include/asm-generic/bitops/le.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/byteorder.h \ - include/linux/byteorder/little_endian.h include/linux/swab.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/swab.h \ - include/linux/byteorder/generic.h include/asm-generic/bitops/minix.h \ - include/linux/log2.h include/linux/typecheck.h include/linux/ratelimit.h \ - include/linux/param.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/param.h \ - include/asm-generic/param.h include/linux/dynamic_debug.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/bug.h \ - include/asm-generic/bug.h include/asm-generic/percpu.h \ - include/linux/threads.h include/linux/percpu-defs.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/system.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/cmpxchg.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/cmpxchg_64.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/nops.h \ - include/linux/irqflags.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/irqflags.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/paravirt.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/pgtable_types.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/pgtable_64_types.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/paravirt_types.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/desc_defs.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/kmap_types.h \ - include/asm-generic/kmap_types.h include/linux/cpumask.h \ - include/linux/bitmap.h include/linux/string.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/string.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/string_64.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/page.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/page_64.h \ - include/asm-generic/memory_model.h include/asm-generic/getorder.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/msr.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/msr-index.h \ - include/linux/ioctl.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/ioctl.h \ - include/asm-generic/ioctl.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/errno.h \ - include/asm-generic/errno.h include/asm-generic/errno-base.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/cpumask.h \ - include/linux/personality.h include/linux/cache.h include/linux/math64.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/div64.h \ - include/asm-generic/div64.h include/linux/err.h include/linux/stat.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/stat.h \ - include/linux/time.h include/linux/seqlock.h include/linux/spinlock.h \ - include/linux/preempt.h include/linux/thread_info.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/thread_info.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/ftrace.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/atomic.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/atomic_64.h \ - include/asm-generic/atomic-long.h include/linux/bottom_half.h \ - include/linux/spinlock_types.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/spinlock_types.h \ - include/linux/lockdep.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/spinlock.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/rwlock.h \ - include/linux/spinlock_api_smp.h include/linux/kmod.h \ - include/linux/gfp.h include/linux/mmzone.h include/linux/wait.h \ - include/linux/numa.h include/linux/nodemask.h \ - include/linux/pageblock-flags.h include/linux/bounds.h \ - include/linux/memory_hotplug.h include/linux/notifier.h \ - include/linux/errno.h include/linux/mutex.h include/linux/rwsem.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/rwsem.h \ - include/linux/srcu.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/mmzone.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/mmzone_64.h \ - include/linux/mmdebug.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/smp.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/mpspec.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/mpspec_def.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/x86_init.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/bootparam.h \ - include/linux/screen_info.h include/linux/apm_bios.h include/linux/edd.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/e820.h \ - include/linux/ioport.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/ist.h \ - include/video/edid.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/apic.h \ - include/linux/delay.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/delay.h \ - include/linux/pm.h include/linux/workqueue.h include/linux/timer.h \ - include/linux/ktime.h include/linux/jiffies.h include/linux/timex.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/timex.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/tsc.h \ - include/linux/debugobjects.h include/linux/completion.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/apicdef.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/fixmap.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/acpi.h \ - include/acpi/pdc_intel.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/numa.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/numa_64.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/mmu.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/vsyscall.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/idle.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/io_apic.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/irq_vectors.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/sparsemem.h \ - include/linux/topology.h include/linux/smp.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/topology.h \ - include/asm-generic/topology.h include/linux/sysctl.h \ - include/linux/elf.h include/linux/elf-em.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/elf.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/user.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/user_64.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/auxvec.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/vdso.h \ - include/linux/kobject.h include/linux/sysfs.h include/linux/kref.h \ - include/linux/moduleparam.h include/linux/tracepoint.h \ - include/linux/rcupdate.h include/linux/rcutree.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/local.h \ - include/linux/percpu.h include/linux/slab.h include/linux/slab_def.h \ - include/linux/kmalloc_sizes.h include/linux/pfn.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/module.h \ - include/asm-generic/module.h include/trace/events/module.h \ - include/trace/define_trace.h include/linux/sched.h \ - include/linux/capability.h include/linux/rbtree.h \ - include/linux/mm_types.h include/linux/auxvec.h \ - include/linux/prio_tree.h include/linux/page-debug-flags.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/cputime.h \ - include/asm-generic/cputime.h include/linux/sem.h include/linux/ipc.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/ipcbuf.h \ - include/asm-generic/ipcbuf.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/sembuf.h \ - include/linux/signal.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/signal.h \ - include/asm-generic/signal-defs.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/siginfo.h \ - include/asm-generic/siginfo.h include/linux/path.h include/linux/pid.h \ - include/linux/proportions.h include/linux/percpu_counter.h \ - include/linux/seccomp.h include/linux/rculist.h include/linux/rtmutex.h \ - include/linux/plist.h include/linux/resource.h \ - /usr/src/kernels/2.6.32-573.26.1.el6.x86_64/arch/x86/include/asm/resource.h \ - include/asm-generic/resource.h include/linux/hrtimer.h \ - include/linux/task_io_accounting.h include/linux/latencytop.h \ - include/linux/cred.h include/linux/key.h include/linux/selinux.h \ - include/linux/percpu-rwsem.h include/linux/atomic.h include/linux/aio.h \ - include/linux/aio_abi.h include/linux/uio.h diff --git a/study/process/pid/PIDռ.png b/study/process/pid/PIDռ.png new file mode 100644 index 0000000..228b3b6 Binary files /dev/null and b/study/process/pid/PIDռ.png differ diff --git a/study/process/pid/pid.md b/study/process/pid/pid.md new file mode 100644 index 0000000..7d814c4 --- /dev/null +++ b/study/process/pid/pid.md @@ -0,0 +1,645 @@ +Linux 内核使用 task_struct 数据结构来关联所有与进程有关的数据和结构,Linux 内核所有涉及到进程和程序的所有算法都是围绕该数据结构建立的,是内核中最重要的数据结构之一。 + +该数据结构在内核文件[include/linux/sched.h](http://lxr.free-electrons.com/source/include/linux/sched.h#L1389)中定义,在目前最新的Linux-4.5(截至目前的日期为2016-05-11)的内核中,该数据结构足足有 380 行之多,在这里我不可能逐项去描述其表示的含义,本篇文章只关注该数据结构如何来组织和管理进程ID的。 + +#进程ID概述 +------- + +##进程ID类型 +------- + +要想了解内核如何来组织和管理进程ID,先要知道进程ID的类型: + +内核中进程ID的类型用[pid_type](http://lxr.free-electrons.com/source/include/linux/pid.h#L6)来描述,它被定义在[include/linux/pid.h](http://lxr.free-electrons.com/source/include/linux/pid.h)中 + +```c +enum pid_type +{ + PIDTYPE_PID, + PIDTYPE_PGID, + PIDTYPE_SID, + PIDTYPE_MAX +}; +``` + +* **PID** 内核唯一区分每个进程的标识 + + +pid是 Linux 中在其命名空间中唯一标识进程而分配给它的一个号码,称做进程ID号,简称PID。在使用 fork 或 clone 系统调用时产生的进程均会由内核分配一个新的唯一的PID值 + + +这个pid用于内核唯一的区分每个进程 + +>注意它并不是我们用户空间通过getpid( )所获取到的那个进程号,至于原因么,接着往下看 + +* **TGID** 线程组(轻量级进程组)的ID标识 + +在一个进程中,如果以CLONE_THREAD标志来调用clone建立的进程就是该进程的一个线程(即轻量级进程,Linux其实没有严格的进程概念),它们处于一个线程组, + + +该线程组的所有线程的ID叫做TGID。处于相同的线程组中的所有进程都有相同的TGID,但是由于他们是不同的进程,因此其pid各不相同;线程组组长(也叫主线程)的TGID与其PID相同;一个进程没有使用线程,则其TGID与PID也相同。 + + +* **PGID** + +另外,独立的进程可以组成进程组(使用setpgrp系统调用),进程组可以简化向所有组内进程发送信号的操作 + +例如用管道连接的进程处在同一进程组内。进程组ID叫做PGID,进程组内的所有进程都有相同的PGID,等于该组组长的PID。 + +* **SID** + +几个进程组可以合并成一个会话组(使用setsid系统调用),可以用于终端程序设计。会话组中所有进程都有相同的SID,保存在task_struct的session成员中 + + +##PID命名空间 +------- + +###pid命名空间概述 +------- + +命名空间是为操作系统层面的虚拟化机制提供支撑,目前实现的有六种不同的命名空间,分别为mount命名空间、UTS命名空间、IPC命名空间、用户命名空间、PID命名空间、网络命名空间。命名空间简单来说提供的是对全局资源的一种抽象,将资源放到不同的容器中(不同的命名空间),各容器彼此隔离。 + +>关于命名空间的详细信息,请参见 + +命名空间有的还有层次关系,如PID命名空间 + +![命名空间的层次关系图](./命名空间的层次关系图.jpg) + + + + +>在上图有四个命名空间,一个父命名空间衍生了两个子命名空间,其中的一个子命名空间又衍生了一个子命名空间。以PID命名空间为例,由于各个命名空间彼此隔离,所以每个命名空间都可以有 PID 号为 1 的进程;但又由于命名空间的层次性,父命名空间是知道子命名空间的存在,因此子命名空间要映射到父命名空间中去,因此上图中 level 1 中两个子命名空间的六个进程分别映射到其父命名空间的PID 号5~10。 + +###局部ID和全局ID +------- + + +命名空间增加了PID管理的复杂性。 + + +回想一下,PID命名空间按层次组织。在建立一个新的命名空间时,该命名空间中的所有PID对父命名空间都是可见的,但子命名空间无法看到父命名空间的PID。但这意味着某些进程具有多个PID,凡可以看到该进程的命名空间,都会为其分配一个PID。 这必须反映在数据结构中。我们必须区分**局部ID**和**全局ID** + + + +全局PID和TGID直接保存在[`task_struct`](http://lxr.free-electrons.com/source/include/linux/sched.h#L1389)中,分别是task_struct的`pid`和`tgid`成员: + + +* **全局ID** 在内核本身和初始命名空间中唯一的ID,在系统启动期间开始的 init 进程即属于该初始命名空间。系统中每个进程都对应了该命名空间的一个PID,叫全局ID,保证在整个系统中唯一。 + +* **局部ID** 对于属于某个特定的命名空间,它在其命名空间内分配的ID为局部ID,该ID也可以出现在其他的命名空间中。 + +```c + +struct task_struct +{ + //... + pid_t pid; + pid_t tgid; + //... +} +``` + +两项都是pid_t类型,该类型定义为__kernel_pid_t,后者由各个体系结构分别定义。通常定义为int,即可以同时使用232个不同的ID。 + +会话session和进程group组ID不是直接包含在task_struct本身中,但保存在用于信号处理的结构中。 + + + +>task_ struct->signal->__session表示全局SID, +> +>而全局PGID则保存在task_struct->signal->__pgrp。 +> +>辅助函数set_task_session和set_task_pgrp可用于修改这些值。 + + +除了这两个字段之外,内核还需要找一个办法来管理所有命名空间内部的局部量,以及其他ID(如TID和SID)。这需要几个相互连接的数据结构,以及许多辅助函数,并将在下文讨论。 + +下文我将使用ID指代提到的任何进程ID。在必要的情况下,我会明确地说明ID类型(例如,TGID,即线程组ID)。 + +一个小型的子系统称之为PID分配器(pid allocator)用于加速新ID的分配。此外,内核需要提供辅助函数,以实现通过ID及其类型查找进程的task_struct的功能,以及将ID的内核表示形式和用户空间可见的数值进行转换的功能。 + + + +###PID命名空间数据结构pid_namespace + +------- + + + +在介绍表示ID本身所需的数据结构之前,我需要讨论PID命名空间的表示方式。我们所需查看的代码如下所示: + +[pid_namespace](http://lxr.free-electrons.com/source/include/linux/pid_namespace.h#L24)的定义在[include/linux/pid_namespace.h](http://lxr.free-electrons.com/source/include/linux/pid_namespace.h#L24)中 + +命名空间的结构如下 + +```c +struct pid_namespace +{ + + struct kref kref; + struct pidmap pidmap[PIDMAP_ENTRIES]; + int last_pid; + struct task_struct *child_reaper; + struct kmem_cache *pid_cachep; + unsigned int level; + struct pid_namespace *parent; +}; +``` + +>我们这里只关心其中的child_reaper,level和parent这三个字段 + + +| 字段| 描述 | +| ------------- |:-------------:| +| kref | 表示指向pid_namespace的个数 | +| pidmap | pidmap结构体表示分配pid的位图。当需要分配一个新的pid时只需查找位图,找到bit为0的位置并置1,然后更新统计数据域(nr_free) | +| last_pid | 用于pidmap的分配。指向最后一个分配的pid的位置。(不是特别确定)| +| child_reaper | 指向的是当前命名空间的init进程,每个命名空间都有一个作用相当于全局init进程的进程 | +| pid_cachep | 域指向分配pid的slab的地址。| +| level | 代表当前命名空间的等级,初始命名空间的level为0,它的子命名空间level为1,依次递增,而且子命名空间对父命名空间是可见的。从给定的level设置,内核即可推断进程会关联到多少个ID。| +| parent | 指向父命名空间的指针 | + +![PID命名空间.png](./PID命名空间.png) + + +实际上PID分配器也需要依靠该结构的某些部分来连续生成唯一ID,但我们目前对此无需关注。我们上述代码中给出的下列成员更感兴趣。 + +每个PID命名空间都具有一个进程,其发挥的作用相当于全局的init进程。init的一个目的是对孤儿进程调用wait4,命名空间局部的init变体也必须完成该工作。 + +#pid结构描述 +------- + + +##pid与upid +------- + +PID的管理围绕两个数据结构展开: + +* [struct pid](http://lxr.free-electrons.com/source/include/linux/pid.h#L57)是内核对PID的内部表示, + +* [struct upid](http://lxr.free-electrons.com/source/include/linux/pid.h#L50)则表示特定的命名空间中可见的信息。 + + + +两个结构的定义在[include/linux/pid.h](include/linux/pid.h)中 + + + +```c +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 | 表示ID具体的值 | +| ns | 指向命名空间的指针 | +| pid_chain | 指向PID哈希列表的指针,用于关联对于的PID | + + +>所有的upid实例都保存在一个散列表中,稍后我们会看到该结构。 + +```c +struct pid +{ + atomic_t count; + /* 使用该pid的进程的列表, lists of tasks that use this pid */ + struct hlist_head tasks[PIDTYPE_MAX]; + int level; + struct upid numbers[1]; +}; +``` + +| 字段| 描述 | +| ------------- |:-------------:| +| count | 是指使用该PID的task的数目;| +| level | 表示可以看到该PID的命名空间的数目,也就是包含该进程的命名空间的深度 | +| tasks[PIDTYPE_MAX] | 是一个数组,每个数组项都是一个散列表头,分别对应以下三种类型 +| numbers[1] | 一个upid的实例数组,每个数组项代表一个命名空间,用来表示一个PID可以属于不同的命名空间,该元素放在末尾,可以向数组添加附加的项。| + +>tasks是一个数组,每个数组项都是一个散列表头,对应于一个ID类型,PIDTYPE_PID, PIDTYPE_PGID, PIDTYPE_SID( PIDTYPE_MAX表示ID类型的数目)这样做是必要的,因为一个ID可能用于几个进程。所有共享同一给定ID的task_struct实例,都通过该列表连接起来。 +> +>这个枚举常量PIDTYPE_MAX,正好是pid_type类型的数目,这里linux内核使用了一个小技巧来由编译器来自动生成id类型的数目 + + + +此外,还有两个结构我们需要说明,就是pidmap和pid_link + +* pidmap当需要分配一个新的pid时查找可使用pid的位图,其定义如下 + +* 而pid_link则是pid的哈希表存储结构 + +##pidmap用于分配pid的位图 +------- + +```c +struct pidmap +{ + atomic_t nr_free; + void *page; +}; +``` + +| 字段| 描述 | +| ------------- |:-------------:| +| nr_free | 表示还能分配的pid的数量 | +| page | 指向的是存放pid的物理页 | + + + +>pidmap[PIDMAP_ENTRIES]域表示该pid_namespace下pid已分配情况 + +##pid_link哈希表存储 +------- + +pids[PIDTYPE_MAX]指向了和该task_struct相关的pid结构体。 +pid_link的定义如下 +```c +struct pid_link +{ +struct hlist_node node; +struct pid *pid; +}; +``` + + + +##task_struct中进程ID相关数据结构 +------- + +##task_struct中的描述符信息 +------- +```c +struct task_struct +{ + //... + pid_t pid; + pid_t tgid; + struct task_struct *group_leader; + struct pid_link pids[PIDTYPE_MAX]; + struct nsproxy *nsproxy; + //... +}; +``` +| 字段| 描述 | +| ------------- |:-------------:| +| pid | 指该进程的进程描述符。在fork函数中对其进行赋值的 | +| tgid | 指该进程的线程描述符。在linux内核中对线程并没有做特殊的处理,还是由task_struct来管理。所以从内核的角度看, 用户态的线程本质上还是一个进程。对于同一个进程(用户态角度)中不同的线程其tgid是相同的,但是pid各不相同。 主线程即group_leader(主线程会创建其他所有的子线程)。如果是单线程进程(用户态角度),它的pid等于tgid。| +| group_leader | 除了在多线程的模式下指向主线程,还有一个用处, 当一些进程组成一个群组时(PIDTYPE_PGID), 该域指向该群组的leader | +| nsproxy | 指针指向namespace相关的域,通过nsproxy域可以知道该task_struct属于哪个pid_namespace | + +>对于用户态程序来说,调用getpid()函数其实返回的是tgid,因此线程组中的进程id应该是是一致的,但是他们pid不一致,这也是内核区分他们的标识 + + + +1. 多个task_struct可以共用一个PID + +2. 一个PID可以属于不同的命名空间 + +3. 当需要分配一个新的pid时候,只需要查找pidmap位图即可 +4. +那么最终,linux下进程命名空间和进程的关系结构如下: + +![进程命名空间和进程的关系结构](./进程命名空间和进程的关系结构.png) +可以看到,多个task_struct指向一个PID,同时PID的hash数组里安装不同的类型对task进行散列,并且一个PID会属于多个命名空间。 + + + +#内核是如何设计task_struct中进程ID相关数据结构的 +------- + +>本部内容较多的采用了[Linux 内核进程管理之进程ID](http://www.cnblogs.com/hazir/p/linux_kernel_pid.html) + +Linux 内核在设计管理ID的数据结构时,要充分考虑以下因素: + +1. 如何快速地根据进程的 task_struct、ID类型、命名空间找到局部ID + +2. 如何快速地根据局部ID、命名空间、ID类型找到对应进程的 task_struct + +3. 如何快速地给新进程在可见的命名空间内分配一个唯一的 PID + +如果将所有因素考虑到一起,将会很复杂,下面将会由简到繁设计该结构。 + +##一个PID对应一个task时的task_struct设计 +------- + +一个PID对应一个`task_struct`如果先不考虑进程之间的关系,不考虑命名空间,仅仅是一个PID号对应一个`task_struct`,那么我们可以设计这样的数据结构 + +```c +struct task_struct +{ + //... + struct pid_link pids; + //... +}; + +struct pid_link +{ + struct hlist_node node; + struct pid *pid; +}; + +struct pid +{ + struct hlist_head tasks; //指回 pid_link 的 node + int nr; //PID + struct hlist_node pid_chain; //pid hash 散列表结点 +}; +``` +每个进程的 task_struct 结构体中有一个指向 pid 结构体的指针,pid结构体包含了PID号。 + +结构示意图如图 + +![一个task_struct对应一个PID](./一个task_struct对应一个PID.png) + + +#如何快速地根据局部ID、命名空间、ID类型找到对应进程的 task_struct +------- +图中还有两个结构上面未提及: + +>* pid_hash[] +> +>这是一个hash表的结构,根据pid的nr值哈希到其某个表项,若有多个 pid 结构对应到同一个表项,这里解决冲突使用的是散列表法。 + +这样,就能解决开始提出的第2个问题了,根据PID值怎样快速地找到task_struct结构体: + +1. 首先通过 PID 计算 pid 挂接到哈希表 pid_hash[] 的表项 + +2. 遍历该表项,找到 pid 结构体中 nr 值与 PID 值相同的那个 pid + +3. 再通过该 pid 结构体的 tasks 指针找到 node + +4. 最后根据内核的 container_of 机制就能找到 task_struct 结构体 + +#如何快速地给新进程在可见的命名空间内分配一个唯一的 PID +------- + +>* pid_map +> +>这是一个位图,用来唯一分配PID值的结构,图中灰色表示已经分配过的值,在新建一个进程时,只需在其中找到一个为分配过的值赋给 pid 结构体的 nr,再将pid_map 中该值设为已分配标志。这也就解决了上面的**第3个问题——如何快速地分配一个全局的PID** + +至于上面的**第1个问题*就更加简单,已知 task_struct 结构体,根据其 pid_link 的 pid 指针找到 pid 结构体,取出其 nr 即为 PID 号。 + +##带进程ID类型的task_struct设计 +------- + +如果考虑进程之间有复杂的关系,如线程组、进程组、会话组,这些组均有组ID,分别为 TGID、PGID、SID,所以原来的 task_struct 中pid_link 指向一个 pid 结构体需要增加几项,用来指向到其组长的 pid 结构体,相应的 struct pid 原本只需要指回其 PID 所属进程的task_struct,现在要增加几项,用来链接那些以该 pid 为组长的所有进程组内进程。数据结构如下: + +>定义在http://lxr.free-electrons.com/source/include/linux/sched.h#L1389 + + +```c +enum pid_type +{ + PIDTYPE_PID, + PIDTYPE_PGID, + PIDTYPE_SID, + PIDTYPE_MAX +}; + +struct task_struct +{ + //... + pid_t pid; //PID + pid_t tgid; //thread group id + //.. + struct pid_link pids[PIDTYPE_MAX]; + struct task_struct *group_leader; // threadgroup leader + //... + struct pid_link pids[PIDTYPE_MAX]; + struct nsproxy *nsproxy; +}; + +struct pid_link +{ + struct hlist_node node; + struct pid *pid; +}; + +struct pid +{ + struct hlist_head tasks[PIDTYPE_MAX]; + int nr; //PID + struct hlist_node pid_chain; // pid hash 散列表结点 +}; +``` + +上面 ID 的类型 PIDTYPE_MAX 表示 ID 类型数目。之所以不包括线程组ID,是因为内核中已经有指向到线程组的 task_struct 指针 group_leader,线程组 ID 无非就是 group_leader 的PID。 + + +假如现在有三个进程A、B、C为同一个进程组,进程组长为A,这样的结构示意图如图 + +![增加ID类型的结构](./增加ID类型的结构.png) + + + +关于上图有几点需要说明: + +图中省去了 pid_hash 以及 pid_map 结构,因为第一种情况类似; + +进程B和C的进程组组长为A,那么 pids[PIDTYPE_PGID] 的 pid 指针指向进程A的 pid 结构体; + +进程A是进程B和C的组长,进程A的 pid 结构体的 tasks[PIDTYPE_PGID] 是一个散列表的头,它将所有以该pid 为组长的进程链接起来。 + +再次回顾本节的三个基本问题,在此结构上也很好去实现。 + +#进一步增加进程PID命名空间的task_struct设计 +------- + +若在第二种情形下再增加PID命名空间 + +一个进程就可能有多个PID值了,因为在每一个可见的命名空间内都会分配一个PID,这样就需要改变 pid 的结构了,如下: +```c +struct pid +{ + unsigned int level; + /* lists of tasks that use this pid */ + struct hlist_head tasks[PIDTYPE_MAX]; + struct upid numbers[1]; +}; + +struct upid +{ + int nr; + struct pid_namespace *ns; + struct hlist_node pid_chain; +}; +``` + +在 pid 结构体中增加了一个表示该进程所处的命名空间的层次level,以及一个可扩展的 upid 结构体。对于struct upid,表示在该命名空间所分配的进程的ID,ns指向是该ID所属的命名空间,pid_chain 表示在该命名空间的散列表。 + +举例来说,在level 2 的某个命名空间上新建了一个进程,分配给它的 pid 为45,映射到 level 1 的命名空间,分配给它的 pid 为 134;再映射到 level 0 的命名空间,分配给它的 pid 为289,对于这样的例子,如图4所示为其表示: + + +![增加PID命名空间之后的结构图](./增加PID命名空间之后的结构图.png) + + +图中关于如果分配唯一的 PID 没有画出,但也是比较简单,与前面两种情形不同的是,这里分配唯一的 PID 是有命名空间的容器的,在PID命名空间内必须唯一,但各个命名空间之间不需要唯一。 +至此,已经与 Linux 内核中数据结构相差不多了。 + +#进程ID管理函数 +------- + +有了上面的复杂的数据结构,再加上散列表等数据结构的操作,就可以写出我们前面所提到的三个问题的函数了: + +##获得局部ID +------- + +根据进程的 task_struct、ID类型、命名空间,可以很容易获得其在命名空间内的局部ID + +获得与task_struct 关联的pid结构体。辅助函数有 task_pid、task_tgid、task_pgrp和task_session,分别用来获取不同类型的ID的pid 实例,如获取 PID 的实例: + +```c +static inline struct pid *task_pid(struct task_struct *task) +{ + return task->pids[PIDTYPE_PID].pid; +} +``` + +获取线程组的ID,前面也说过,TGID不过是线程组组长的PID而已,所以: +``` +static inline struct pid *task_tgid(struct task_struct *task) +{ + return task->group_leader->pids[PIDTYPE_PID].pid; +} +``` + +而获得PGID和SID,首先需要找到该线程组组长的task_struct,再获得其相应的 pid: + +```c +static inline struct pid *task_pgrp(struct task_struct *task) +{ + return task->group_leader->pids[PIDTYPE_PGID].pid; +} + +static inline struct pid *task_session(struct task_struct *task) +{ + return task->group_leader->pids[PIDTYPE_SID].pid; +} +``` + +获得 pid 实例之后,再根据 pid 中的numbers 数组中 uid 信息,获得局部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; +} +``` +这里值得注意的是,由于PID命名空间的层次性,父命名空间能看到子命名空间的内容,反之则不能,因此,函数中需要确保当前命名空间的level 小于等于产生局部PID的命名空间的level。 + +除了这个函数之外,内核还封装了其他函数用来从 pid 实例获得 PID 值,如 pid_nr、pid_vnr 等。在此不介绍了。 +结合这两步,内核提供了更进一步的封装,提供以下函数: +```c +pid_t task_pid_nr_ns(struct task_struct *tsk, struct pid_namespace *ns); +pid_t task_tgid_nr_ns(struct task_struct *tsk, struct pid_namespace *ns); +pid_t task_pigd_nr_ns(struct task_struct *tsk, struct pid_namespace *ns); +pid_t task_session_nr_ns(struct task_struct *tsk, struct pid_namespace *ns); +``` +从函数名上就能推断函数的功能,其实不外于封装了上面的两步。 + +##查找进程task_struct +------- + +根据局部ID、以及命名空间,怎样获得进程的task_struct结构体呢?也是分两步: +获得 pid 实体。根据局部PID以及命名空间计算在 pid_hash 数组中的索引,然后遍历散列表找到所要的 upid, 再根据内核的 container_of 机制找到 pid 实例。代码如下: +``` +struct pid *find_pid_ns(int nr, struct pid_namespace *ns) +{ + struct hlist_node *elem; + struct upid *pnr; + //遍历散列表 + hlist_for_each_entry_rcu(pnr, elem, + &pid_hash[pid_hashfn(nr, ns)], pid_chain) //pid_hashfn() 获得hash的索引 + if (pnr->nr == nr && pnr->ns == ns) //比较 nr 与 ns 是否都相同 + return container_of(pnr, struct pid, //根据container_of机制取得pid 实体 + + numbers[ns->level]); + return NULL; +} +``` + +##根据ID类型取得task_struct 结构体 +------- +``` +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; +} +``` + +内核还提供其它函数用来实现上面两步: +```c +struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns); +struct task_struct *find_task_by_vpid(pid_t vnr); +struct task_struct *find_task_by_pid(pid_t vnr); +``` + +具体函数实现的功能也比较简单。 + +##生成唯一的PID +------- + +内核中使用下面两个函数来实现分配和回收PID的: +```c +static int alloc_pidmap(struct pid_namespace *pid_ns); +static void free_pidmap(struct upid *upid); +``` + +在这里我们不关注这两个函数的实现,反而应该关注分配的 PID 如何在多个命名空间中可见,这样需要在每个命名空间生成一个局部ID,函数 alloc_pid 为新建的进程分配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; + tmp = ns; + pid->level = ns->level; + // 初始化 pid->numbers[] 结构体 + for (i = ns->level; i >= 0; i--) + { + nr = alloc_pidmap(tmp); //分配一个局部ID + pid->numbers[i].nr = nr; + pid->numbers[i].ns = tmp; + tmp = tmp->parent; + } + // 初始化 pid->task[] 结构体 + for (type = 0; type < PIDTYPE_MAX; ++type) + INIT_HLIST_HEAD(&pid->tasks[type]); + + // 将每个命名空间经过哈希之后加入到散列表中 + upid = pid->numbers + ns->level; + for ( ; upid >= pid->numbers; --upid) + { + hlist_add_head_rcu(&upid->pid_chain, &pid_hash[pid_hashfn(upid->nr, upid->ns)]); + upid->ns->nr_hashed++; + } + + return pid; +} +``` diff --git a/study/process/pid/ռͽ̵Ĺϵṹ.png b/study/process/pid/ռͽ̵Ĺϵṹ.png new file mode 100644 index 0000000..f89248b Binary files /dev/null and b/study/process/pid/ռͽ̵Ĺϵṹ.png differ diff --git a/study/process/pid/ռIJιϵͼ.jpg b/study/process/pid/ռIJιϵͼ.jpg new file mode 100644 index 0000000..1060cf0 Binary files /dev/null and b/study/process/pid/ռIJιϵͼ.jpg differ diff --git a/study/process/pid/һtask_structӦһPID.png b/study/process/pid/һtask_structӦһPID.png new file mode 100644 index 0000000..77b9356 Binary files /dev/null and b/study/process/pid/һtask_structӦһPID.png differ diff --git a/study/process/pid/ID͵Ľṹ.png b/study/process/pid/ID͵Ľṹ.png new file mode 100644 index 0000000..f128fa0 Binary files /dev/null and b/study/process/pid/ID͵Ľṹ.png differ diff --git a/study/process/pid/PIDռ֮Ľṹͼ.png b/study/process/pid/PIDռ֮Ľṹͼ.png new file mode 100644 index 0000000..09f71da Binary files /dev/null and b/study/process/pid/PIDռ֮Ľṹͼ.png differ