Linux-aarch64-任务切换与内核栈
任务切换
task_struct->stack成员指向的内存空间就是内核栈
陷入内核时,用户任务上下文保存在其内核栈上:且保存的位置也是固定的,就在内核栈空间最上方预留的的pt_regs区域
- 参考《vectors.md》种entry_handler的定义:
mov x0, sp; bl el\el\ht\()_\regsize\()_\label\()_handler - 参考
kernel_entry宏的定义中,用户上下文的保存...; stp x2, x3, [sp, #16 * 1]; ... - 综合可知,用户任务上下文保存在当前内核栈指针的上方
- 因此,进程工作在用户态时,其内核栈指针应始终指向内核栈空间最高处-sizeof(pt_regs)处?这里可能会有些问题
- 刚创建该任务,而且还没有被调度运行时,应该是这样的
- 但是当该任务被调度运行后:
- 首先在内核态切换到该任务的内核栈:首次切换到内核栈初期【汇编码范围内】sp确实指向了
内核栈空间最高处-sizeof(pt_regs) - 接着执行部分内核态代码,此时会有入/出栈操作
- 这里要注意:首次进入内核态后,之后会向普通函数调用那样,借助
lr寄存器进行函数返回 - 注意到任务创建时,任务结构体中固定位置处【保存内核态上下文的结构】将pc = ret_from_fork,因此任务首次被调度执行 -> 首次进入内核态时执行的第一个比较重要的函数就是
ret_from_fork
- 这里要注意:首次进入内核态后,之后会向普通函数调用那样,借助
- 如果任务在内核态被抢占 -> 仍然要把内核态上下文保存到任务结构体的固定位置处?
- 之后该任务恢复后,依然继续之前内核态的执行
- 再之后需要从内核态返回用户空间:
ret_to_user/ret_to_fork: 这里面需要把内核栈上保存的用户空间上下文恢复 - 需要区分对待第一次返回到用户空间/第二次返回到用户空间:因为第一次返回时内核栈上没有保存的用户上下文?
- 这里还需要再深入看一下创建任务时,有没有为内核栈模拟保存的用户空间上下文!!!???❗️❗️
- 深入看一下
ret_from_fork
- 首先在内核态切换到该任务的内核栈:首次切换到内核栈初期【汇编码范围内】sp确实指向了
- 参考《vectors.md》种entry_handler的定义:
任务切换时,内核态上下文保存在任务结构体的固定位置处:THREAD_CPU_CONTEXT
- 新建任务时,要把其内核栈地址放在其任务结构体成员cpu_context.sp处;-> 再去看一下新建任务时,sp的赋值情况,就可以探索出内核栈的初始内存分配情况;
- 根据上面的叙述:
要把用户上下文保存在内核栈上方【高地址】位置处,因此在初始创建内核栈时,要注意这一点!!! - 要注意:创建任务时,其tsk->thread.cpu_context.sp要向内核栈空间最高地址 - 足够的空间容纳pt_regs:与后面的代码走读匹配了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32DEFINE(THREAD_CPU_CONTEXT, offsetof(struct task_struct, thread.cpu_context));
struct task_struct {
......
void *stack; // 通过查找,明确了这里为task的内核栈?栈顶(往下增长)
/* CPU-specific state of this task: */
// 这是一个架构相关的结构体
// 这个成员位于任务结构体的末尾
struct thread_struct thread;
/*
* WARNING: on x86, 'thread_struct' contains a variable-sized
* structure. It *MUST* be at the end of 'task_struct'.
*
* Do not put anything below here!
*/
};
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;
};
最终的寄存器、栈切换
1 | |
内核栈构建
1 | |
- 仅考虑栈向低地址增长;结合前面两个宏可以知道:
- tsk->stack 指向申请的内核栈空间的起始地址【低地址】
- childregs 指向该内核栈空间的最高地址 - sizeof(struct pt_regs)
p->thread.cpu_context.sp = (unsigned long)childregs这一行就使得新创建任务的内核栈地址处在内核栈空间的高位,且其上方有一个(struct pt_regs)空间,用于存储用户上下文
Linux-aarch64-任务切换与内核栈
http://example.com/2024/07/24/Linux-aarch64-任务切换与内核栈/