Linux内核涉及到进程和程序的所有算法都围绕一个名为task_struct的数据结构建立,对于Linux内核把所有进程的进程描述符task_struct数据结构链成一个单链表(task_struct->tasks),该数据结构定义在include/sched.h中。这是系统的中主要的一个数据结构,其进程管理task_struct的结构图如下
1 任务ID
每一个任务都应用有一个ID,作为这个任务的唯一标识。pid是进程的ID,tgid是线程的group ID,group_leader是进程组的组长。
任何一个进程,如果只有主线程,那么pid是自己,tgid也是自己,group_leader 指向的还是自己。但是如果一个进程创建了其他线程,那么就会发生变化。线程有自己的pid,tgid就是进程的主线程的pid,group_leader指向是进程的主线程。
getpid系统调用返回当前进程的TGID,而不是线程的pid,因为一个多线程应用程序中所有的线程共享相同的pid
gettid()系统调用返回线程的PID
group_leader ,通过clone创建的所有线程的task_struct的group_leader 成员,会指向组长的task_struct实例
2 亲属关系
在Linux系统中,所有进程之间都有着直接或间接地联系,每个进程都有其父进程,也可能有零个或多个子进程。拥有同一父进程的所有进程具有兄弟关系。
成员 | 描述 |
---|---|
real_parent | 指向其父进程,如果创建它的父进程不再存在,则指向pid为1的Init进程 |
parent | 指向其父进程,当他终止时,必须向他的父进程发送信号,它的值通常与real_parent相同 |
children | 表示链表的头部,链表的所有元素都是它的子进程 |
sibling | 用于把它当前进程插入到兄弟链表中 |
3 任务状态
成员 | 描述 |
---|---|
state | 用于记录进程的状态 |
exit_code、exit_signal、exit_state | 用于存放进程退出值和终止信号,这样父进程就知道子进程的退出原因 |
flags | 用于描述进程属性的一些标志位 |
记录进程状态
在进程的生命周期中,我们定义了各个状态,linux内核也为进程定义了5中状态,其状态如下所示
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
-
TASK_RUNNING(可运行态或就绪态或者正在运行态):这个状态的名字是正在运行的意思,可是在Linux内核里不一定是指正在运行,所以很容易被人误解。
它的意思是指进程处于可运行状态,或正在运行,或在就绪队列中等待运行
-
TASK_INTERRUPTIBLE(可中断睡眠状态):进程进入睡眠状态(被阻塞)来等待某些条件的达成或者某些资源的就位,一旦条件达成或者资源到位,内核就可以把这个进程的状态设置成TASK_RUNNING并加其加到就绪队列。
这是一种浅睡眠的状态,也就是说,虽然在睡眠,等待 I/O 完成,但是这个时候一个信号来的时候,进程还是要被唤醒。只不过唤醒后,不是继续刚才的操作,而是进行信号处理。当然程序员可以根据自己的意愿,来写信号处理函数,例如收到某些信号,就放弃等待这个 I/O 操作完成,直接退出,也可也收到某些信息,继续等待。
-
TASK_UNINTERRUPTIBLE(不可中断睡眠态):进程在睡眠等待时不受干扰,不可被信号唤醒,只能死等I/O操作完成。一旦I/O操作因为特殊原因不能完成,这个时候,谁也叫不醒这个进程。
通过ps命令看到的被标记为D状态的进程,就属于不可中断态的进程,不可以发送SIGKILL信号使它终止,也有人把这个状态称为深度睡眠状态
-
TASK_STOPPED(终止态):在进程接收到 SIGSTOP、SIGTTIN、SIGTSTP 或者 SIGTTOU 信号之后进入该状态。
-
EXIT_ZOMBIE(僵尸态):进程已经消亡,但是task_struct数据结构还没有释放,每个进程在它的声明周期中都要经历这几个状态,子进程退出时,父进程可以通过wait()或者waitpid()来获取子进程的消亡原因。
4. 进程权限
unix是诞生于20世纪70年代的分时多任务多用户操作系统,当时的场景是许多用户同时登陆到一台主机,运行多个各自的进程。
linux使用一个整数来标记运行进程的用户,这个整数倍称为user id,简称uid。人通常被分组,比如这几个人在研发工作,被分到研发组,那几个人做销售工作,被分配到销售组。linux用另外一个整数来标记用户组,这个被称为group id,简称gid。
uid和gid是操作系统自主访问控制的基础。
成员 | 描述 |
---|---|
real_cred | 说明谁能操作我这个进程 |
cred | 说明这个进程能够操作谁 |
struct cred {
......
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
......
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
......
} __randomize_layout;
5 运行统计
在进程的运行过程中,会有一些统计量,具体你可以看下面的列表。这里面有进程在用户态和内核态消耗的时间、上下文切换的次数等等。
成员 | 描述 |
---|---|
utime | 用户态消耗的CPU时间 |
stime | 内核态消耗的CPU时间 |
nvcsw | 自愿上下文切换计数 |
nivcsw | 非资源上下文切换计数 |
start_time | 进程启动时间,不包括睡眠时间 |
real_start_time | 进程启动时间,包括睡眠时间 |
6 调度相关
进程的状态切换往往涉及调度,下面这些字段都是用于调度的。本次只是进行概要说明,后面进程调度会详细分析
成员 | 描述 |
---|---|
on_rq | 是否在运行队列上 |
prio | 用于保存动态优先级 |
static_prio | 用于保存静态优先级,可以通过nice系统调用来进行修改 |
normal_prio | 取决于静态优先级和调度策略 |
sched_class | 调度类 |
se | 普通进程的调用实体,每个进程都有其中之一的实体 |
policy | 调度策略 |
7 信号处理
主要定义了哪些信号被阻塞暂不处理(blocked),哪些信号尚等待处理(pending),哪些信号正在通过信号处理函数进行处理(sighand)。处理的结果可以是忽略,可以是结束进程等等。
成员 | 描述 |
---|---|
signal | 指向进程的信号描述符 |
sighand | 指向进程的信号处理程序描述符 |
blocked | 表示被阻塞信号的掩码,real_blocked表示临时掩码 |
pending | 存放私有挂起信号的数据结构 |
8 内存管理
每个进程都有自己独立的虚拟内存空间,这需要有一个数据结构来表示,就是 mm_struct,之前在内存管理章节中有学习,详细见linux内存管理笔记(三十)----进程虚拟地址
成员 | 描述 |
---|---|
mm | 程所拥有的用户空间内存描述符,内核线程无的mm为NULL |
active_mm | active_mm指向进程运行时所使用的内存描述符,对于普通进程而言,这两个指针变量的值相同。但是内核线程kernelthread是没有进程地址空间的,所以内核线程的tsk->mm域是空(NULL)。但是内核必须知道用户空间包含了什么,因此它的active_mm成员被初始化为前一个运行进程的active_mm值。 |
9 文件与文件系统
每个进程有一个文件系统的数据结构,还有一个打开文件的数据结构。这个我们放到文件系统那一节详细讲述。
成员 | 描述 |
---|---|
fs | 文件系统的数据结构 |
files | 表示进程当前打开的文件数据结构 |
10 内核栈
在程序在程序执行过程中,一旦调用到系统调用,就需要进入内核继续执行。那如何将用户态的执行和内核态的执行串起来呢?这就需要以下两个重要的成员变量:
成员 | 描述 |
---|---|
thread_info | 是task_struct的补充,存储于体系结构有关的内容 |
那如果一个当前在某个 CPU 上执行的进程,想知道自己的 task_struct 在哪里,又该怎么办呢?
struct thread_info {
unsigned long flags; /* low level flags */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
int preempt_count; /* 0 => preemptable, <0 => bug */
int cpu; /* cpu */
};
这里面有个成员变量 task 指向 task_struct,所以我们常用 current_thread_info()->task 来获取 task_struct,后面章节中会详细学习。
11 总结
通过这一节的学习,你会发现,一个进程的运行竟然要保存这么多信息,这些信息都可以通过命令行取出来,后面我们单独章节来学习怎么查看这些信息。
12 参考
趣谈Linux操作系统