Linux的内存啥样的?
首先要了解Linux的段页式内存结构是什么样的,用户态和内核态切换访问涉及的内存肯定是不一样的:
可以看到内核空间其实就是在虚拟空间的高地址部分
对于用户态来说,他实际上是持有内核空间的页表的,但是不能访问,每次fork都可以直接进行复制,保持各个线程都拥有同一份内核空间的映射关系。
内核中采用了一个叫做内存描述符的 mm_struct 结构体来表示进程虚拟内存空间的全部信息
那么所谓内核栈就是:在Linux中每个进程有两个栈,分别用于用户态和内核态的进程执行(类似于内核页表与用户页表),其中的内核栈就是用于内核态的栈,它和进程的thread_info结构一起放在两个连续的页框大小的空间内。
当用户态线程system call时 在哪里获取到该线程的内核栈地址呢?
TSS (Task Status Segment)
TSS(task status segment)就是一个段内存。TSS中存储了相关信息,例如内核栈的地址。tss的内存地址存放在tr寄存器中。
tss保存不同特权级别下任务所使用的寄存器,特别重要的是esp,因为比如中断后,涉及特权级切换时(一个任务切换),首先要切换栈,这个栈显然是内核栈,那么如何找到该栈的地址呢,这需要从tss段中得到,这样后续的执行才有所依托(在x86机器上,c语言的函数调用是通过栈实现的)。只要涉及低特权环到高特权环的任务切换,都需要找到高特权环对应的栈,因此需要esp2,esp1,esp0起码三个esp,然而linux只使用esp0。
linux如何切换?
linux没有为每一个进程都准备一个tss段,而是每一个cpu使用一个tss段,tr寄存器保存该段。进程切换时,只更新唯一tss段中的esp0字段,esp0保存新进程的内核栈地址。
linux的tss段中只使用esp0和iomap等字段,不用它来保存寄存器,在一个用户进程被中断进入ring0的时候,tss中取出esp0,然后切到esp0,其它的寄存器则保存在esp0指示的内核栈上,而不保存在tss中。
结果,linux中每一个cpu只有一个tss段,tr寄存器永远指向它。符合x86处理器的使用规范,但不遵循intel的建议,这样的后果是开销更小了,因为不必切换tr寄存器了。
CPU切换过程过程
读取tr寄存器,访问TSS段
从TSS段中的esp0获取进程内核栈的栈顶指针
由控制单元在内核栈中保存当前eflags,cs,ss,eip,esp寄存器的值。
由SAVE_ALL保存其寄存器的值到内核栈
把内核代码选择符写入CS寄存器,内核栈指针写入ESP寄存器,把内核入口点的线性地址写入EIP寄存器
此时,CPU已经切换到内核态,根据EIP中的值开始执行内核入口点的第一条指令。
参考文章
https://www.cnblogs.com/long123king/p/3501853.html
https://blog.betamao.me/posts/2021/linux-task-manage/
https://liujunming.top/2020/01/18/%E6%B5%85%E8%B0%88tss/