共计 3543 个字符,预计需要花费 9 分钟才能阅读完成。
导读 | 进程是操作系统种调度的实体,对进程拥有资源的描述称为进程控制块 (PCB, Process Contrl Block)。 |
内核里,通过 task_struct 结构体来描述一个进程,称为进程描述符 (process descriptor),它保存着支撑一个进程正常运行的所有信息。task_struct 结构体内容太多,这里只列出部分成员变量,感兴趣的读者可以去源码 include/linux/sched.h 头文件查看。
struct task_struct { | |
/* | |
* For reasons of header soup (see current_thread_info()), this | |
* must be the first element of task_struct. | |
*/ | |
struct thread_info thread_info; | |
volatile long state; | |
void *stack; | |
...... | |
struct mm_struct *mm; | |
...... | |
pid_t pid; | |
...... | |
struct task_struct *parent; | |
...... | |
char comm[TASK_COMM_LEN]; | |
...... | |
struct files_struct *files; | |
...... | |
struct signal_struct *signal; | |
} |
task_struct 中的主要信息分类:
1. 标示符:描述本进程的唯一标识符 pid,用来区别其他进程。
2. 状态:任务状态,退出代码,退出信号等
3. 优先级:相对于其他进程的优先级
4. 程序计数器:程序中即将被执行的下一条指令的地址
5. 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
6. 上下文数据:进程执行时处理器的寄存器中的数据
7.I/ O 状态信息:包括显示的 I / O 请求,分配的进程 I / O 设备和进程使用的文件列表
8. 记账信息:可能包括处理器时间总和,使用的时钟总和,时间限制,记帐号等
- struct thread_info thread_info: 进程被调度执行的信息
- volatile long state:- 1 是不运行的,= 0 是运行状态,>0 是停止状态。下面是几个比较重要的进程状态以及它们之间的转换流程。
- 鸿蒙官方战略合作共建——HarmonyOS 技术社区
- void *stack:指向内核栈的指针,内核通过 dup_task_struct 为每个进程都分配内核栈空间,并记录在此。
- struct mm_struct *mm: 与进程地址空间相关的信息。
- pid_t pid: 进程标识符
- char comm[TASK_COMM_LEN]: 进程的名称
- struct files_struct *files: 打开的文件表
- struct signal_struct *signal: 信号处理相关
接着看下 thread_info 结构:
struct thread_info { | |
unsigned long flags; /* low level flags */ | |
mm_segment_t addr_limit; /* address limit */ | |
u64 ttbr0; /* saved TTBR0_EL1 */ | |
union { | |
u64 preempt_count; /* 0 => preemptible, <0 => bug */ | |
struct { | |
u32 need_resched; | |
u32 count; | |
u32 count; | |
u32 need_resched; | |
} preempt; | |
}; | |
void *scs_base; | |
void *scs_sp; | |
}; |
接着再来看下内核栈的定义:
union thread_union { | |
struct task_struct task; | |
struct thread_info thread_info; | |
unsigned long stack[THREAD_SIZE/sizeof(long)]; | |
}; |
当 CONFIG_THREAD_INFO_IN_TASK 这个配置打开的时候,则 thread_union 结构中只存在 stask 成员了。
内核在启动的时候会在 head.S 里通过 __primary_switched 来做内核栈的初始化:
SYM_FUNC_START_LOCAL(__primary_switched) | |
adrp x4, init_thread_union | |
add sp, x4, | |
adr_l x5, init_task | |
msr sp_el0, x5 // Save thread_info |
将 init_thread_union 的地址保存到 x4,然后偏移 THREAD_SIZE 栈大小,用于初始化 sp。将 init_task 进程描述符地址赋值给 x5,并保存到 sp_el0。
下面再看下 init_thread_union 和 init_task 的定义:
extern union thread_union init_thread_union; | |
struct task_struct init_task | |
__aligned(L1_CACHE_BYTES) | |
= { | |
.thread_info = INIT_THREAD_INFO(init_task), | |
.stack_refcount = REFCOUNT_INIT(1), | |
..... | |
}; |
故这三者的关系可以通过下图描述:
内核中经常通过 current 宏来获得当前进程对应的 struct task_sturct 结构,我们借助 current,结合上面介绍的内容,看下具体的实现。
static __always_inline struct task_struct *get_current(void) | |
{ | |
unsigned long sp_el0; | |
asm ("mrs %0, sp_el0" : "=r" (sp_el0)); | |
return (struct task_struct *)sp_el0; | |
} | |
代码比较简单,可以看出通过读取用户空间栈指针寄存器 sp_el0 的值,然后将此值强转成 task_struct 结构就可以获得当前进程。(sp_el0 里存放的是 init_task,即 thread_info 地址,thread_info 又是在 task_sturct 的开始处,从而找到当前进程。)
