关注公众号:百万年薪工程师成长手札
进程的关键属性
pid
该字段保存了进程唯一的标识符,称为pid。Linux的PID是pid_t 整数类型。通过/proc/sys/kernel/pid_max接口制定,默认值为32768,最高可设置为2的22次方(PID_MAX_LIMIT, 约400万)
配置pid的linux源码在linux/include/threads.h下
/*
* This controls the default maximum pid allocated to a process
*/
#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
从PID_MAX_DEFAULT 可以看出如果编译内核时设置了CONFIG_BASE_SMALL选项,则默认值为0x1000,转化成十进制为4096,否则默认值为0x8000,转化为十进制为32768
#define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : \
(sizeof(long) > 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT))
PID_MAX_LIMIT则是限制pid的上限,如果编译内核时设置了CONFIG_BASE_SMALL选项,则最大值就是 8 * PAGE_SIZE个大小,否则就看long数据类型的的大小,如果大于4,则最大可以设置4 * 1024 * 1024个(4194304个),否则最大值只能设置PID_MAX_DEFAULT。
从实际操作来看,符合代码的设置,pid_max最高不能超过4194304个。一般一个虚拟机,400w个pid也足够用了,一个虚拟机一般也就4C-8C,如果超过了400w个pid,可能就是应用的bug了,有进程或者线程泄漏。
另外在源码注释中有一段描述
[NOTE: PID/TIDs are limited to 2^29 ~= 500+ million, see futex.h.]
我把futex.h翻了半天,顺便百度了几次,也没有看到类似的代码段,不过从400万突破到500万,在大多数场景下意义不大,那就暂时不研究了。等以后C语言学的精通了,回过头来再研究。
pid的管理采用的是bitmap位图管理,该位图允许内核跟踪PID的使用情况,并为新进程分配唯一的pid。在位图中,值为1表示该pid已经被分配,值为0表示pid为空闲可分配。
tgid
该字段保存了线程组id。假设创建了一个新进程,他的pid和tgid是一样的。当进程产生一个新的线程时,新的子进程将获得唯一的pid,但是继承了父线程的tgid,因为属于同一个线程组。tgid用于支持多线程进程。
thread info
该字段保存了处理器特定的状态信息。并且他是任务结构体的关键元素
flags
该标志字段记录了进程响应的各种属性。该字段中的每一位对应于一个进程生命周期中的各个阶段。每个进程标志定义在中
/*
* Per process flags
*/
#define PF_IDLE 0x00000002 /* I am an IDLE thread */
#define PF_EXITING 0x00000004 /* Getting shut down */
#define PF_VCPU 0x00000010 /* I'm a virtual CPU */
#define PF_WQ_WORKER 0x00000020 /* I'm a workqueue worker */
#define PF_FORKNOEXEC 0x00000040 /* Forked but didn't exec */
#define PF_MCE_PROCESS 0x00000080 /* Process policy on mce errors */
#define PF_SUPERPRIV 0x00000100 /* Used super-user privileges */
#define PF_DUMPCORE 0x00000200 /* Dumped core */
#define PF_SIGNALED 0x00000400 /* Killed by a signal */
#define PF_MEMALLOC 0x00000800 /* Allocating memory */
#define PF_NPROC_EXCEEDED 0x00001000 /* set_user() noticed that RLIMIT_NPROC was exceeded */
#define PF_USED_MATH 0x00002000 /* If unset the fpu must be initialized before use */
#define PF_USED_ASYNC 0x00004000 /* Used async_schedule*(), used by module init */
#define PF_NOFREEZE 0x00008000 /* This thread should not be frozen */
#define PF_FROZEN 0x00010000 /* Frozen for system suspend */
#define PF_KSWAPD 0x00020000 /* I am kswapd */
#define PF_MEMALLOC_NOFS 0x00040000 /* All allocation requests will inherit GFP_NOFS */
#define PF_MEMALLOC_NOIO 0x00080000 /* All allocation requests will inherit GFP_NOIO */
#define PF_LESS_THROTTLE 0x00100000 /* Throttle me less: I clean memory */
#define PF_KTHREAD 0x00200000 /* I am a kernel thread */
#define PF_RANDOMIZE 0x00400000 /* Randomize virtual address space */
#define PF_SWAPWRITE 0x00800000 /* Allowed to write to swap */
#define PF_MEMSTALL 0x01000000 /* Stalled due to lack of memory */
#define PF_UMH 0x02000000 /* I'm an Usermodehelper process */
#define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_mask */
#define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */
#define PF_MEMALLOC_NOCMA 0x10000000 /* All allocation request will have _GFP_MOVABLE cleared */
#define PF_IO_WORKER 0x20000000 /* Task is an IO worker */
#define PF_FREEZER_SKIP 0x40000000 /* Freezer should not count it as freezable */
#define PF_SUSPEND_TASK 0x80000000 /* This thread called freeze_processes() and should not be frozen */
exit_code和exit_sigal
这些字段保存了任务的退出值和导致终止的信号的详细信息。这些字段将由父进程在子进程终止时通过wait()访问
comm
该字段保存了用于启动进程的二进制可执行文件的名称
ptrace
当使用ptrace()系统调用使进程转为跟踪模式时,将启用并设置该字段
进程关系
real_parent和parent
指向付任务结构体的指针。对于正常的进程,两个指针都指向同一个task_struct。他们的区别仅在于使用posix线程实现的多线程进程。对于这种,real_parent指向父线程任务结构体,parent指向收到SIGCHLD信号的进程任务结构体
children
指向子任务结构体链表的指针
sibling
指向兄弟任务结构体链表的指针
group_leader
指向进程组组长的任务结构体
调度属性
所有相互竞争的进程都必有公平的CPU时间,需要基于时间片和进程优先级来调度
prio和static_prio
prio确认调度进程的优先级。如果进程被分配了试试调度策略,则此字段保存进程的静态优先级,范围为1--99(由sched_setscheduler()指定)。对于正常进程,这个字段保存了由nice值得来的动态优先级。
se、rt和dl
每个任务都属于调度实体(任务组),因为调度是在每个实体级别上完成的。se用于所有正常进程,rt用于实时进程,dl用于截止期进程。
policy
该字段保存了和进程调度策略相关的信息,有助于确定进程优先级。
cpus_allowed
该字段指定了进程的CPU掩码。在多处理系统中,进程允许在哪个CPU上进行调度。(传统CPU使用4位掩码,如0000, 0001, 如果有超线程技术,则使用8位掩码)。
rt_priority
该字段用于指定实时调度策略的进程优先级。但对于非实时进程,该字段未被使用
内核栈
在基于多核硬件的当代计算平台上,可以同时并行运行应用程序。在请求同一个进程时,可以同时启动多个进程的内核模式切换。为了能够处理这种情况,内核服务被设计为可重入的,允许多个进程介入并使用所需的服务。这就要求请求进程维护他自己的私有内核栈,来跟踪内核函数调用顺序,存储内核函数的本地数据等等。
内核栈直接映射到物理内存,强制排列在物理上处于连续的区域中。在64位系统上为16KB内核栈
Linux线程支持
一个进程中的执行流被称为线程(thread),多线程意味着一个进程中存在多个执行上下文流。实现真正并发,公平的多任务处理。
Linux不直接支持用户级线程,它通过一个替代API枚举并成为轻量级进程(Light Weight Process, LWP)的特殊进程,该进程可以与父进程共享一组配置资源,例如动态内存分配、全局数据、打开文件、信号处理等。
内核线程
为满足运行后台操作的需要,内核会创建线程,内核线程没有地址空间,只在内核模式下运行,不具有交互性。内核子系统使用kthread线程周期性运行和进行异步操作。
所有的内核线程都是PID 2(kthreadd)的后代,
PPID为2
kthread是一个永久运行的线程,它会查看名为kthread_create_list的链表,用来获取要创建的新kthreads线程的数据
命名空间和cgroup
基于cgroup和命令空间的框架,可以给客户端提供高效和有效的隔离和资源管理
挂载命名空间
使文件系统挂载点的集合限制在一个进程的命名空间内可见,不同挂载命名空间中的进程组拥有不同的视图
UTS命令空间
一个uts命名空间能够隔离系统的主机和域名
IPC命名空间
将进程与使用system v和posix消息队列区分开来,防止一个进程从IPC命名空间访问另一个进程资源
PID命名空间
PID命名空间允许进程使用其自己的根进程。
网络命名空间
提供了网络协议服务和接口的抽象化和虚拟化。每个网络命名空间有自己的网络设备实例,可以使用单独的网络地址进行配置
用户命名空间
允许进程在命名空间内外使用唯一的用户id和组id。
cgroup命名空间
cgroup命名空间虚拟化/proc/self/cgroup问价的内容。