linux进程管理
linux 系统中运行着许多进程,有些进程还有不同的线程,不同进程或者线程的执行,都需要硬件资源。硬件资源是有限的,那么必须有一个完善的进程管理体系,才能解决好资源的管理调度问题。
在 linux 中,无论是进程,还是线程,在内核中统一称为 Task。由统一的结构 task_struct
管理,这个结构十分复杂,下面来解析一下。
链表管理task
linux 内核使用链表管理不同的 Task
。
进入 linux 内核中的以下目录:linux/include/linux
,打开文件:sched.h
。task_stuct
就定义在这个文件中。
struct task_struct {
......
struct list_head tasks;
......
};
list_head
是 linux 提供的双向链表结构,在 linux/include/linux/types.h
中定义。
// types.h 文件中 list_head 的定义
struct list_head {
struct list_head *next, *prev;
};
linux 在 linux/include/linux/list.h
中提供了常用的操作链表的接口。如:list_add
、list_del
、list_replace
等等,感兴趣的可以深入研究。
任务ID
每个 task 都应该有一个 ID,作为这个任务的唯一标识,在 task_struct 中,以下字段标识任务id。
struct task_struct {
......
struct list_head tasks;
......
/* 新增 */
pid_t pid;
pid_t tgid;
......
struct task_struct *group_leader;
/* 新增 */
......
};
pid表示:process id;tgid表示:thread group id。
任何一个进程,如果只有主线程,那么 pid 是自己,tgid 是自己,group_leader 指向的也是自己。
如果一个进程创建了其他线程,那么每个线程有自己的 pid,tgid 就是进程主线程的 pid,group_leader 指向进程的主线程。
group_leader 是一个指针类型的变量,设计这个字段的目的是方便快速定位该线程对应的主线程,如果没有这个字段,就只能按照 tgid 一个一个找了。
信号处理
简单介绍一下关于信号处理的几个字段
struct task_struct {
......
struct list_head tasks;
......
pid_t pid;
pid_t tgid;
......
struct task_struct *group_leader;
......
/* 新增 */
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct __rcu *sighand;
sigset_t blocked;
sigset_t real_blocked;
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
/* 新增 */
......
};
这里定义了哪些信号被阻塞暂不处理(blocked),哪些信号尚等待处理(pending),哪些信号正在通过信号处理函数进行处理(sighand)。处理的结果可以是忽略,可以使结束进程等等。
task_struct
里面有一个 struct sigpending pending
。我们可以找到 struct signal_struct
的定义。在 linux/include/linux/sched/signal.h
文件中。
struct signal_struct {
......
struct sigpending shared_pending;
......
};
这个结构体也十分复杂,我们只看其中的一个字段:shared_pending
。pending 变量是本线程独有的信号,shared_pending 是线程组共享的信号。
信号处理函数默认使用用户态的函数栈,当然也可以开辟新的栈专门用于信号处理,这就是 sas_ss_xxx 这三个字段的作用。
任务状态
在 task_struct 中,涉及任务状态的是下面这几个变量。
struct task_struct {
/* 新增 */
unsigned int __state;
......
unsigned int flags;
......
int exit_state;
/* 新增 */
......
struct list_head tasks;
......
pid_t pid;
pid_t tgid;
......
struct task_struct *group_leader;
......
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct __rcu *sighand;
sigset_t blocked;
sigset_t real_blocked;
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
......
};
state 可以取的值定义在 sched.h
文件中。往前翻即可看见。
/* 新增 */
/* Used in tsk->state: */
#define TASK_RUNNING 0x00000000
#define TASK_INTERRUPTIBLE 0x00000001
#define TASK_UNINTERRUPTIBLE 0x00000002
#define __TASK_STOPPED 0x00000004
#define __TASK_TRACED 0x00000008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x00000010
#define EXIT_ZOMBIE 0x00000020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x00000040
#define TASK_DEAD 0x00000080
#define TASK_WAKEKILL 0x00000100
#define TASK_WAKING 0x00000200
#define TASK_NOLOAD 0x00000400
#define TASK_NEW 0x00000800
#define TASK_RTLOCK_WAIT 0x00001000
#define TASK_FREEZABLE 0x00002000
#define __TASK_FREEZABLE_UNSAFE (0x00004000 * IS_ENABLED(CONFIG_LOCKDEP))
#define TASK_FROZEN 0x00008000
#define TASK_STATE_MAX 0x00010000
......
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
......
/* 新增 */
struct task_struct {
unsigned int __state;
......
unsigned int flags;
......
int exit_state;
......
struct list_head tasks;
......
pid_t pid;
pid_t tgid;
......
struct task_struct *group_leader;
......
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct __rcu *sighand;
sigset_t blocked;
sigset_t real_blocked;
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
......
};
从定义的数值很容易看出来,state 是通过 bitset 的方式设置的,也就是说,当前是什么状态,哪一位就置 1。
TASK_RUNNING 并不是说进程正在运行,而是表示进程处于一种时刻准备运行的状态。当处于这个状态的进程获得时间片的时候,就是在运行中;如果没有获得时间片,就说明被其他进程抢占了,在等待再次分配时间片。
在运行中的进程,一旦要进行一些 I/O 操作,需要等待 I/O 完毕,这个时候会释放 CPU,进入睡眠状态。
在 linux 中,有三种睡眠状态。
TASK_INTERRUPTIBLE:可中断的睡眠状态。这是一种浅睡眠状态,也就是说,虽然在睡眠,等待 I/O 完成,但是这个时候一个信号来的时候,进程还是要被唤醒。只不过唤醒后,不是继续刚才的操作,而是进行信号处理。当然程序员可以根据自己的意愿,来写信号处理函数,例如收到某些信号,就放弃等待这个 I/O 操作完成,直接退出,或者收到某些信息,继续等待。
TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。这是一种深度睡眠状态,不可被信号唤醒,只能死等 I/O 操作完成。一旦 I/O 操作因为特殊原因不能完成,这个时候,谁也叫不醒这个进程。包括 kill,因为 kill 本身也是一个信号,既然这个状态不可被信号唤醒,kill 信号也被忽略。除非重启电脑,没有其他办法。这是一件比较危险的事情,除非程序员极其有把握,不然还是不要设置 TASK_UNINTERRUPTIBLE。
TASK_KILLABLE:可以终止的睡眠状态。进程处于这种状态中,它的运行原理类似 TASK_UNINTERRUPTIBLE,只不过可以响应致命信号。
从定义上看,TASK_WAKEKILL 用于在接受到致命信号时唤醒进程,而 TASK_KILLABLE 相当于这两位都设置了。
__TASK_STOPPED 是在进程接收到 SIGSTOP、SIGTTIN、SIGTSTP 或者 SIGTTOU 信号后进入的状态。
__TASK_TRACED 表示进程被 debugger 等进程监视,进程执行被调试程序所停止。当一个进程被另外的进程所监视,每一个信号都会让进程进入改状态。
一旦一个进程要结束,先进入的是 EXIT_ZOMBIE 状态,但是这个时候它的父进程还没有使用 wait() 等系统调用来获知它的终止信息,此时进程就变成了僵尸进程。
EXIT_DEAD 是进程的最终状态。
EXIT_ZOMBIE 和 EXIT_DEAD 也可以用于 exit_state。
上面的进程状态和进程的运行、调度有关系,还有其他的一些状态,我们称为标志。放在 flags 字段中,这些字段被定义为宏,以 PF 开头。
/* Used in tsk->state: */
#define TASK_RUNNING 0x00000000
#define TASK_INTERRUPTIBLE 0x00000001
#define TASK_UNINTERRUPTIBLE 0x00000002
#define __TASK_STOPPED 0x00000004
#define __TASK_TRACED 0x00000008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x00000010
#define EXIT_ZOMBIE 0x00000020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x00000040
#define TASK_DEAD 0x00000080
#define TASK_WAKEKILL 0x00000100
#define TASK_WAKING 0x00000200
#define TASK_NOLOAD 0x00000400
#define TASK_NEW 0x00000800
#define TASK_RTLOCK_WAIT 0x00001000
#define TASK_FREEZABLE 0x00002000
#define __TASK_FREEZABLE_UNSAFE (0x00004000 * IS_ENABLED(CONFIG_LOCKDEP))
#define TASK_FROZEN 0x00008000
#define TASK_STATE_MAX 0x00010000
......
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
......
struct task_struct {
unsigned int __state;
......
unsigned int flags;
......
int exit_state;
......
struct list_head tasks;
......
pid_t pid;
pid_t tgid;
......
struct task_struct *group_leader;
......
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct __rcu *sighand;
sigset_t blocked;
sigset_t real_blocked;
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
......
};
......
/* 新增 */
#define PF_VCPU 0x00000001 /* I'm a virtual CPU */
#define PF_EXITING 0x00000004 /* Getting shut down */
#define PF_FORKNOEXEC 0x00000040 /* Forked but didn't exec */
/* 新增 */
PF_VCPU:表示进程运行在虚拟 CPU 上。在函数 account_system_time 中,统计进程的系统运行时间,如果有这个 flag,就调用 account_guest_time,按照客户机的时间进行统计。
PF_EXITING:表示正在退出。当有这个 flag 的时候,在函数 find_alive_thread 中,找活着的进程,遇到有这个 flag 的,直接跳过。
PF_FORKNOEXEC:表示 fork 完了,还没有 exec。在 do_fork 函数里面调用 copy_process,这个时候把 flag 设置为 PF_FORKNOEXEC。当 exec 中调用了 load_elf_binary 的时候,又把这个 flag 去掉。
进程调度
/* Used in tsk->state: */
#define TASK_RUNNING 0x00000000
#define TASK_INTERRUPTIBLE 0x00000001
#define TASK_UNINTERRUPTIBLE 0x00000002
#define __TASK_STOPPED 0x00000004
#define __TASK_TRACED 0x00000008
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x00000010
#define EXIT_ZOMBIE 0x00000020
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED 0x00000040
#define TASK_DEAD 0x00000080
#define TASK_WAKEKILL 0x00000100
#define TASK_WAKING 0x00000200
#define TASK_NOLOAD 0x00000400
#define TASK_NEW 0x00000800
#define TASK_RTLOCK_WAIT 0x00001000
#define TASK_FREEZABLE 0x00002000
#define __TASK_FREEZABLE_UNSAFE (0x00004000 * IS_ENABLED(CONFIG_LOCKDEP))
#define TASK_FROZEN 0x00008000
#define TASK_STATE_MAX 0x00010000
......
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
......
struct task_struct {
unsigned int __state;
......
unsigned int flags;
......
int exit_state;
......
/* 新增 */
// 是否在运行队列上
int on_rq;
// 优先级
int prio;
int static_prio;
int normal_prio;
unsigned int rt_priority;
// 调度器实体
struct sched_entity se;
struct sched_rt_entity rt;
struct sched_dl_entity dl;
const struct sched_class *sched_class;
......
// 调度策略
unsigned int policy;
// 允许使用哪些 CPU
int nr_cpus_allowed;
cpumask_t cpus_mask;
struct sched_info sched_info;
/* 新增 */
......
struct list_head tasks;
......
pid_t pid;
pid_t tgid;
......
struct task_struct *group_leader;
......
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct __rcu *sighand;
sigset_t blocked;
sigset_t real_blocked;
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
......
};
......
#define PF_VCPU 0x00000001 /* I'm a virtual CPU */
#define PF_EXITING 0x00000004 /* Getting shut down */
#define PF_FORKNOEXEC 0x00000040 /* Forked but didn't exec */