Linux操作系统引入了PCB(Process Control Block,进程控制块)结构。PCB是Linux操作系统识别进程的通道。
创建进程时,首先会创建PCB,根据PCB中的信息对进程实施有效管理。当进程终止后,Linux操作系统会释放对应的PCB资源。
PCB的数据结构是struct task_struct。包含四个方面,描述信息、控制信息、CPU上下文和资源管理。
task_struct位于源码<include/linux/sched.h>
首行的代码注释如下,sched.h用于定义task_struct结构体并提供主要的调度API,比如schedule(), wakeup等
/*
* Define 'struct task_struct' and provide the main scheduler
* APIs (schedule(), wakeup variants, etc.)
*/
task_struct总计有约700行代码。
1 描述信息
1.1 进程描述符
每个进程都有唯一的进程标识符pid。是一个32位正整型数。即单个系统内pid的上限为2的31次方*(2147483648),约21亿个PID
struct task_struct{
...(796行)
pid_t pid; //全局进程号
pid_t tgid; //全局线程组标识符
...
}
1.2 用户标识符
每个进程都属于系统中的某一个用户,因此在数据结构体中会包含用户uid信息,变量为kuid_t
struct task_struct{
...(940行)
kuid_t loginuid;
unsigned int sessionid;
...
}
kuid_t在<include/linux/uidgid.h>中有定义,为val变量,是一个无符号整数,代表用户标识号。
typedef struct {
uid_t val;
} kuid_t;
1.3 家族关系
进程并不会独立存在,互相之间存在依赖性,会形成依赖关系,主要为父子关系。除了0号进程之外,其他进程都有父进程。
父进程的父进程叫做祖先进程(隔代),同一个父进程中的多个子进程之间构成兄弟关系(同代)。
Linux系统启动时,0号进程进行内核初始化工作,并创建出1号进程。
1号进程完成用户空间初始化后成为init进程,为后续该系统内创建进程的共同祖先进程。
如下代码中real_parent和parent在正常情况下都指向父进程pid,但是在特殊情况下,比如因为程序缺陷,父进程在子进程结束前自行退出了,子进程就变成了孤儿进程,也就是僵尸进程,此时,该子进程就会被挂到init 1号进程下。这是real_parent不会变化,但是parent变成了1。
sibling用于指向兄弟进程。
children用于指向子进程。
struct task_struct{
...(809行)
/*
* 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; //通过进程描述符,指向线程组的组长
2控制信息
PCB中进程控制信息主要包括进程的状态信息、优先级信息和记账信息。
2.1 状态信息
进程的状态有就绪、运行、阻塞、终止等状态,其中在终止状态中又分为了僵尸和死亡两种状态。
内核代码中对进程状态有如下几种类型定义:
<include/linux/sched.h>
....(82行)
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000 //运行状态,int 0
#define TASK_INTERRUPTIBLE 0x0001 //阻塞状态(可中断) int 1
#define TASK_UNINTERRUPTIBLE 0x0002 //阻塞状态(不可中断) int 2
#define __TASK_STOPPED 0x0004 //终止状态 int 4
#define __TASK_TRACED 0x0008 // 跟踪状态 int 8
/* Used in tsk->exit_state: */
#define EXIT_DEAD 0x0010 //终止状态中的死亡状态,属于exit_state
#define EXIT_ZOMBIE 0x0020//终止状态中的僵尸状态,属于exit_state
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
在操作系统上可以看到的进程的状态描述:
static const char * const task_state_array[] = {
/* states in TASK_REPORT: */
"R (running)", /* 0x00 */
"S (sleeping)", /* 0x01 */
"D (disk sleep)", /* 0x02 */
"T (stopped)", /* 0x04 */
"t (tracing stop)", /* 0x08 */
"X (dead)", /* 0x10 */
"Z (zombie)", /* 0x20 */
"P (parked)", /* 0x40 */
/* states beyond TASK_REPORT: */
"I (idle)", /* 0x80 */
};
定义一个宏TASK_REPORT, 将所有进程的状态进行合并。
/* get_task_state(): */
#define TASK_REPORT (TASK_RUNNING | TASK_INTERRUPTIBLE | \
TASK_UNINTERRUPTIBLE | __TASK_STOPPED | \
__TASK_TRACED | EXIT_DEAD | EXIT_ZOMBIE | \
TASK_PARKED)
task_state_index为内联函数,用于计算给定任务结构体task_struct的状态索引,明确一个进程的具体状态。
根据给定任务的运行状态 (tsk->state) 和退出状态 (tsk->exit_state),计算出一个状态索引。通过按位操作和掩码 (TASK_REPORT),函数能够准确地表示任务的当前状态,并返回一个索引值,用于进一步的处理或决策。
static inline unsigned int task_state_index(struct task_struct *tsk)
{
unsigned int tsk_state = READ_ONCE(tsk->state);
unsigned int state = (tsk_state | tsk->exit_state) & TASK_REPORT;
BUILD_BUG_ON_NOT_POWER_OF_2(TASK_REPORT_MAX);
if (tsk_state == TASK_IDLE)
state = TASK_REPORT_IDLE;
return fls(state);
}
2.2 进程优先级信息
进程优先级用于确定进程被调度到CPU上执行的优先程度如内核代码定义分为动态优先级、静态优先级、普通优先级和试试优先级
struct task_struct {
......(732行)
int prio; //动态优先级
int static_prio; //静态优先级
int normal_prio; //普通优先级,取决于静态优先级和调度策略
unsigned int rt_priority; //realtime,实时优先级
静态优先级:数值越小,优先级越高,可用nice调整
动态优先级和普通优先级默认等于静态优先级,但动态优先级会被临时修改。同样是数值越小,优先级越高。
进程可分为实时进程和普通进程,实时优先级作用于实时进程,数值越大,优先级越高。
实时进程的调度优先于普通进程。
3 CPU上下文
CPU上下文是指进程执行到某时刻时CPU各寄存器中的数值,这些数值就代表着当前进程活动的状态信息。
什么是上下文切换?
在单CPU多进程并发的场景下,单位时间内一个CPU同一时刻只会执行一个进程的代码,比如在1秒中内,CPU的时间周期被切割成100个分片,同时有10个进程需要执行,且每个进程之间的优先级都是相同的情况下,每个进程被分配到的时间就在10个时间分片,同时这10个时间分片不一定是连续的。因此在CPU进行两个进程处理切换期间,需要保留上一个进程在CPU内寄存器的数值,同时要加载下一个进程在上一个时间片内执行的寄存器数值。这个过程叫做上下文切换。
进程切换时,CPU上下文保存调用为task_struct --> thread_struct --> cpu_context
结构体 thread_struct,用于存储特定于进程的状态信息,特别是在处理器上下文、浮点寄存器状态和调试信息方面的详细数据。以下是结构体的主要成员及其作用的解释:
<arch/arm64/include/asm/processor.h>
...
struct thread_struct {
struct cpu_context cpu_context; /* cpu context */
...
unsigned long fault_address; /* fault info */
unsigned long fault_code; /* 寄存器ESR_EL1 value */
struct debug_info debug; /* debugging */
...
}
对应引用cpu_context结构体,主要代码如下,包含了通用寄存器x19--x28, 栈帧寄存器FP,堆栈指针寄存器SP和程序计数器PC
<arch/arm64/include/asm/processor.h>
struct cpu_context {
unsigned long x19;
unsigned long x20;
unsigned long x21;
unsigned long x22;
unsigned long x23;
unsigned long x24;
unsigned long x25;
unsigned long x26;
unsigned long x27;
unsigned long x28;
unsigned long fp;
unsigned long sp;
unsigned long pc;
};
4 资源管理信息
PCB中包含资源管理信息,包含存储器、文件系统等等,多个代码调用关系如下
在sched.h中定义了对应的内存、文件、打开文件的结构体。
<include/linux/sched.h>
...
void *stack; //指向进程的内核栈
...
struct mm_struct *mm;
struct mm_struct *active_mm; //进程的用户控件描述符
struct fs_struct *fs; //进程相关联的文件系统信息
struct files_struct *files; //指向打开的文件列表
4.1 mm_types.h
mm_struct结构体记录了进程的内存布局
<include/linux/mm_types.h>
struct mm_struct { //内存描述符
struct {
struct vm_area_struct *mmap; /* list of VMAs */
struct rb_root mm_rb;
...
spinlock_t arg_lock; //自旋锁,保护如下字段
//内存空间中各段起始、结束地址
//包括堆、栈 、映射段、BSS段、代码段、数据段等等
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
...
4.2 fs_struct.h
fs_struct结构体记录与进程相关联的文件系统信息,包括当前目录和根目录。
<include/linux/fs_struct.h>
struct fs_struct {
int users;
spinlock_t lock; //自旋锁
seqcount_spinlock_t seq;
int umask;
int in_exec;
//根目录与当前目录
struct path root, pwd;
} __randomize_layout;
通过t内联函数get_fs_roo获取根目录信息
通过内联函数get_fs_pwd获取当前目录信息
//通过get_fs_root内联函数获取根目录信息
static inline void get_fs_root(struct fs_struct *fs, struct path *root)
{
spin_lock(&fs->lock);
*root = fs->root;
path_get(root);
spin_unlock(&fs->lock);
}
//通过内联函数get_fs_pwd获取当前目录信息
static inline void get_fs_pwd(struct fs_struct *fs, struct path *pwd)
{
spin_lock(&fs->lock);
*pwd = fs->pwd;
path_get(pwd);
spin_unlock(&fs->lock);
}
4.3 fdtable.h
files_struct结构是进程正打开的所有文件的列表(包含输入/输出设备也是以文件形式存在的)
<include/linux/fdtable.h>
struct files_struct {
atomic_t count; //引用计数器
bool resize_in_progress;
wait_queue_head_t resize_wait;
struct fdtable __rcu *fdt; //默认指向fdtab,用于动态申请内存
struct fdtable fdtab; //为fdt提供初始值
spinlock_t file_lock ____cacheline_aligned_in_smp;
unsigned int next_fd;
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
unsigned long full_fds_bits_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
struct files_cgroup *files_cgroup;
};