linux的进程与线程,task_struct结构体
linux 的进程与线程在源码中都对应到同一个结构体task_struct
, 它位于include/linux/sched.h
中, 他有很多很多成员, 下面我们分析一下主要成员及其作用。
1. 任务列表
有了任务列表, 操作系统就能知道当前任务的下一个任务和上一个任务了。
为什么是双向链表呢?因为可以高效的删除, 并且可以反向遍历。
至于为什么不是数组? 因为要频繁的创建删除, 数组咋行呢。
struct list_head tasks; // 双向链表, 维护任务列表
2. 任务ID
这里的pid_t类型是一个int, 为什么有一个pid还需要有一个tgid呢, 这是因为进程和线程在操作系统眼里都是一个task_struct, 但是总归要区分不是, pid就是这个task_struct的id, tgid则是这个task_struct所属于的group id。
换言之:
- task_struct是进程, pid == tgid
- task_struct是线程, pid != tgid
那为什么需要一个group_leader呢?可以注意到它是一个指针, 自然是为了快速访问这个task_struct的老大了!
pid_t pid; // process ID
pid_t tgid; // thread group ID
struct task_struct *group_leader;
3. 信号处理
上面提到,一个task_struct是有leader的,当操作系统要给一个小组发信号,当然是要发给leader,再由leader转发给小组的每个人,那这些信号是怎么处理的呢?
这里定义了哪些信号被阻塞暂不处理(blocked),哪些信号尚等待处理(pending),哪些信号正在通过信号处理函数进行处理(sighand)。处理的结果可以是忽略,可以是结束进程等等。
这里real_blocked在kvm_main文件和signal文件的do_sigtimedwait里都用到了,但是具体作用没理解...
信号处理函数默认使用用户态的函数栈,当然也可以开辟新的栈专门用于信号处理,这就是 sas_ss_xxx 这三个变量的作用。
/* 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;
4. 任务状态
主要是由 task->state 和 task->exit_state 两块组成,他们都是一个个bit,定义如下:
/*
* Task state bitmask. NOTE! These bits are also
* encoded in fs/proc/array.c: get_task_state().
*
* We have two separate sets of flags: task->state
* is about runnability, while task->exit_state are
* about the task exiting. Confusing, but this way
* modifying one set can't modify the other one by
* mistake.
*/
/* 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_ANY (TASK_STATE_MAX-1)
/*
* DO NOT ADD ANY NEW USERS !
*/
#define TASK_FREEZABLE_UNSAFE (TASK_FREEZABLE | __TASK_FREEZABLE_UNSAFE)
/* Convenience macros for the sake of set_current_state: */
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED __TASK_TRACED
#define TASK_IDLE (TASK_UNINTERRUPTIBLE | TASK_NOLOAD)
/* Convenience macros for the sake of wake_up(): */
#define TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
/* get_task_state(): */
#define TASK_REPORT (TASK_RUNNING | TASK_INTERRUPTIBLE | \
TASK_UNINTERRUPTIBLE | __TASK_STOPPED | \
__TASK_TRACED | EXIT_DEAD | EXIT_ZOMBIE | \
TASK_PARKED)
#define task_is_running(task) (READ_ONCE((task)->__state) == TASK_RUNNING)
#define task_is_traced(task) ((READ_ONCE(task->jobctl) & JOBCTL_TRACED) != 0)
#define task_is_stopped(task) ((READ_ONCE(task->jobctl) & JOBCTL_STOPPED) != 0)
#define task_is_stopped_or_traced(task) ((READ_ONCE(task->jobctl) & (JOBCTL_STOPPED | JOBCTL_TRACED)) != 0)
贴一张极客时间里图:
5. 进程调度
调度有很多策略,这里写不下,我们先简单知道task_struct里有进程调度的策略、优先级等信息
//是否在运行队列上
int on_rq;
//优先级
int prio;
int static_prio;
int normal_prio;
unsigned int rt_priority;
//调度器类
const struct sched_class *sched_class;
//调度实体
struct sched_entity se;
struct sched_rt_entity rt;
struct sched_dl_entity dl;
//调度策略
unsigned int policy;
//可以使用哪些CPU
int nr_cpus_allowed;
cpumask_t cpus_allowed;
struct sched_info sched_info;
6. 运行统计
u64 utime; //用户态消耗的CPU时间
u64 stime; //内核态消耗的CPU时间
unsigned long nvcsw; //自愿(voluntary)上下文切换计数
unsigned long nivcsw; //非自愿(involuntary)上下文切换计数
u64 start_time; //进程启动时间,不包含睡眠时间
u64 real_start_time; //进程启动时间,包含睡眠时间
7. 进程亲缘关系
通常情况下,real_parent 和 parent 是一样的,但是也会有另外的情况存在。例如,bash 创建一个进程,那进程的 parent 和 real_parent 就都是 bash。如果在 bash 上使用 GDB 来 debug 一个进程,这个时候 GDB 是 parent,bash 是这个进程的 real_parent。
这里会发现怎么有group_leader?上面不是说过了吗?
其实group_leader和parent是俩概念,group_leader是小组领导,如果系统发送一个信号,领导要分发出去,但是这个领导可不一定是parent:
举个例子
假如有一个shell进程(pid=100),启动了两个进程A(pid=200)和B(pid=300),这两个进程又都分别fork了子进程C(pid=201)和D(pid=301)
那么A和C就是一组,B和D是一组。A的group_leader是A,但是A的parent是shell
/*
* Pointers to the (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/
/* Real parent process: */
struct task_struct __rcu *real_parent;
/* Recipient of SIGCHLD, wait4() reports: */
struct task_struct __rcu *parent;
/*
* Children/sibling form the list of natural children:
*/
struct list_head children;
struct list_head sibling;
struct task_struct *group_leader;
8. 进程权限
/* Process credentials: */
/* Tracer's credentials at attach: */
const struct cred __rcu *ptracer_cred;
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
9. 内存管理
内存布局很复杂,需要专门讲一下
struct mm_struct *mm;
struct mm_struct *active_mm;
10. 文件系统
/* Filesystem information: */
struct fs_struct *fs;
/* Open file information: */
struct files_struct *files;
11. 内核栈
存储了这样一个指针,它指向了内核栈的地址,内核栈的最高地址存储的是pt_regs(存储了寄存器变量),最低地址是thread_info,thread_info是针对不同cpu有不同的信息
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
#endif
void *stack;