环境
linux 4.19
current宏
定义在arch/arm64/include/asm/current.h:
#define current get_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;
}
从代码中可以看到,这个current宏是通过读取sp_el0的值来获取当前进程对应的struct task_struct。
在x86下,是把thread_info放在内核栈最底端,然后先要找到thread_info再找到struct task_struct。归根结底是因为x86寄存器太少,而像struct task_struct这么重要且经常使用的结构体,应该放到某个容易寻找的位置或者寄存器中最合适,方便快速访问。这是《Linux内核设计与实现》这本书中介绍的方法,通过地址偏移来获取thread_info。但是现在已经不这么做了,等会将介绍现在是如何做的。
在ARMv8中,可以通过sp_el0来存放当前进程struct task_struct的位置的,请看:ThreadInfo结构和内核栈的两种关系。看完这篇文章后,可以知道:
- 存在两种thread_info架构:①与x86一样,放在内核栈最底端,这样做可以通过地址偏移来获取到内核栈thread_info,然后再通过thread_info获取到struct task_struct;②开启CONFIG_THREAD_INFO_IN宏之后,thread_info放在struct task_struct中,struct task_struct
- ARMv8使用的是第二种架构,所以需要通过某个能够获取到的信息来保存struct task_struct的地址
- 在ARMv8中,使用sp_el0来保存当前进程的struct task_struct
- 这篇文章还提到了,进程切换时也会切换sp_el0,时刻保持sp_el0存储的是当前进程的struct task_struct地址。
可以看到,对于arm64来讲,current宏是从sp_el0寄存器中获取的。但是有一点不理解,就是sp_el0不是用户态的程序使用的吗?啥时候被换成用来保存struct task_struct了呢?这就涉及到两个问题:
- 何时更改sp_el0指向的地址?也就是说什么时候使sp_el0指向当前进程的struct task_struct?
- 在使用sp_el0保存struct task_struct之前,这个结构体保存在哪里?
首先说一说x86现在的做法,已经不再是之前那样了。现在在x86体系下,Linux内核定义了名为current_task的Per-CPU变量,每个CPU上当前运行的进程task_struct指针都保存current_task变量中。
为了解决ARMv8是怎么做的,需要先从第1个问题入手,就是什么时候sp_el0保存的内容进行切换。在用户态下是不能使用current宏的,而且用户态下sp_el0有自己的用途,就是指向进程地址空间的栈。所以sp_el0指向的地址的切换,一定是在用户态切换到内核态时完成的。
用户态切内核态就几种方法,同步异常、中断等等,这些都离不开中断向量表。关于ARMv8的系统调用/中断等,可以看一下我写的另外一篇博客:Linux内核学习之 – ARMv8架构的系统调用笔记。我们以系统调用el0_sync为例(当时分析系统调用的时候基于linux 4.19内核,现在5.15内核已经不叫这个函数名了):
el0_sync:
kernel_entry 0
mrs x25, esr_el1 // read the syndrome register, 寄存器esr_el1是在权限级EL1下可以访问的系统寄存器,该寄存器的相关状态就表明了异常发生的具体原因。
lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class, lsr: 逻辑右移指令,实现将寄存器进行右移操作, 將x25寄存器的值右移ESR_ELx_EC_SHIFT位后赋值给x24寄存器
cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state
b.eq el0_svc