一溪风月
一溪风月
Published on 2023-04-04 / 108 Visits
0
0

linux 进程管理

linux进程管理

linux 系统中运行着许多进程,有些进程还有不同的线程,不同进程或者线程的执行,都需要硬件资源。硬件资源是有限的,那么必须有一个完善的进程管理体系,才能解决好资源的管理调度问题。

在 linux 中,无论是进程,还是线程,在内核中统一称为 Task。由统一的结构 task_struct管理,这个结构十分复杂,下面来解析一下。

链表管理task

Snipaste_2023-04-04_10-59-20.png

linux 内核使用链表管理不同的 Task

进入 linux 内核中的以下目录:linux/include/linux ,打开文件:sched.htask_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_addlist_dellist_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。

Snipaste_2023-04-04_17-25-54.png

TASK_RUNNING 并不是说进程正在运行,而是表示进程处于一种时刻准备运行的状态。当处于这个状态的进程获得时间片的时候,就是在运行中;如果没有获得时间片,就说明被其他进程抢占了,在等待再次分配时间片。

在运行中的进程,一旦要进行一些 I/O 操作,需要等待 I/O 完毕,这个时候会释放 CPU,进入睡眠状态。

在 linux 中,有三种睡眠状态。

  1. TASK_INTERRUPTIBLE:可中断的睡眠状态。这是一种浅睡眠状态,也就是说,虽然在睡眠,等待 I/O 完成,但是这个时候一个信号来的时候,进程还是要被唤醒。只不过唤醒后,不是继续刚才的操作,而是进行信号处理。当然程序员可以根据自己的意愿,来写信号处理函数,例如收到某些信号,就放弃等待这个 I/O 操作完成,直接退出,或者收到某些信息,继续等待。

  2. TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。这是一种深度睡眠状态,不可被信号唤醒,只能死等 I/O 操作完成。一旦 I/O 操作因为特殊原因不能完成,这个时候,谁也叫不醒这个进程。包括 kill,因为 kill 本身也是一个信号,既然这个状态不可被信号唤醒,kill 信号也被忽略。除非重启电脑,没有其他办法。这是一件比较危险的事情,除非程序员极其有把握,不然还是不要设置 TASK_UNINTERRUPTIBLE。

  3. 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 */

Comment