背景
一个任务既可以是一个进程,也可以是一个线程。简而言之,它指的是一系列共同达到某一目的的操作。
操作系统任务切换过程有两种,一种是基于TSS(任务状态段)的切换,这种切换需要使用一个长跳转指令,需要很多的mov,而且不能进行指令流水(分解成微指令),造成执行起来很慢。执行过程如下:使用TR(描述符表寄存器)找到GDT(全局描述符表)中的新tss描述符,之后使用描述符切换到新的tss。
另外一种是基于krlstack的切换。
通过堆栈实现任务切换可能要更快,而且采用堆栈的切换还可以使用指令流水的并行优化技术,同时又使得 CPU 的设计变得简单。所以无论是 Linux 还是 Windows,进程/线程的切换都没有使用 Intel 提供的这种 TSS 切换手段,而都是通过堆栈实现的。
本次实践项目就是将 Linux 0.11 中采用的 TSS 切换部分去掉,取而代之的是基于堆栈的切换程序。具体的说,就是将 Linux 0.11 中的 switch_to 实现去掉,写成一段基于堆栈切换的代码。
实验报告
问题1
movl tss,%ecx
addl $4096,%ebx
movl %ebx,ESP0(%ecx)
(1)为什么要加 4096?
因为页表结构是这样的:不要误会了,页表大小4KB

(2)为什么没有设置ss0?
SS0、SS1和SS2分别是0、1和2特权级的栈段选择子。
这里用不着特权级为0的内核段。
这个可以在《注释》或者《x86汇编语言 从实模式到保护模式》里面找到,课里面好像没讲啊。
问题2
*(--krnstack) = ebp;
*(--krnstack) = ecx;
*(--krnstack) = ebx;
*(--krnstack) = 0;
(1)子进程第一次执行时,eax=?为什么要等于这个数?哪里的工作让 eax 等于这样一个数?
这个eax,根据课程里面讲的内容是p_id,所以子进程eax=0;当使用copy_process()创建子进程的时候赋值的。
(2)这段代码中的 ebx 和 ecx 来自哪里,是什么含义,为什么要通过这些代码将其写到子进程的内核栈中?
父子的内核栈在初始化的时候完全一致,用户栈指向一个地方。通过copy_process()拷贝了参数。
(3)这段代码中的 ebp 来自哪里,是什么含义,为什么要做这样的设置?可以不设置吗?为什么?
ebp是用户栈地址,不设置就不能运行了。
问题3
为什么要在切换完 LDT 之后要重新设置 fs=0x17?而且为什么重设操作要出现在切换完 LDT 之后,出现在 LDT 之前又会怎么样?
cpu的段寄存器都存在两类值,一类是显式设置段描述符,另一类是隐式设置的段属性及段限长等值,这些值必须经由movl、lldt、lgdt等操作进行设置,而在设置了ldt后,要将fs显示设置一次才能保证段属性等值正确。

本文深入探讨了Linux内核中的任务切换机制,对比了基于TSS的任务切换与基于内核栈的任务切换,详细解析了switch_to函数的实现原理,并介绍了如何在Linux0.11中实现基于内核栈的任务切换。
最低0.47元/天 解锁文章
3000

被折叠的 条评论
为什么被折叠?



