理论上:
理论上如下图所示,从一个用户栈到另一个的切换过程:
线程1用户栈通过int陷入内核栈
内核通过线程控制块寻找到(调度之后的)线程2线程控制块TCB
TCB2中有内核栈的指针,代表着内核栈切换成功
通过iret指令从内核栈回到用户空间;
以fork()为例看看实现:
首先大致梳理一下:
① int0x80(进入sys_call之前硬件自动push一堆东西到内核栈)
②之后在sys_call中(push一堆用户态信息的寄存器到内核栈)调用sys_fork完成fork工作
③fork之后进行schedule工作(如果切换进程则swith_to)
④最后ret_form_sys_call pop内核栈中一堆数据,调用iret回到用户态;
下面根据这个流程,仔细看看其中发生了什么;
-
fork系统调用陷入内核之前的过程:
①记录中断号NR_fork;②产生中断int,中断指令集完成内核栈初始化;③内核栈与用户栈相连,且存有int后面指令地址(PC指针);
-
system_call中(中断的入口和出口:相当于忽略sys_fork的头和尾巴)
①将当前用户态的寄存器中的内容全部压入内核栈中;
②调用sys_fork()完成创建进程的过程;
③在sys_fork结束之后,把当前执行进程的current.PCB给 %eax,如果阻塞就调用schedule如果时间片用光,也调用schedule;
④schedule结束后,调用ret_from_sys_call从内核中返回(此时如果调度,已完成切换内核栈过程);
⑤注意,右侧的*??2*是pushl %eax
的入栈;
-
详解2中的尾巴ret_from_sys_call
将内核栈中的数据都pop出去,并且跳回到中断发生的代码后面继续执行;
-
详解③中schedule结束后,switch_to的内核栈切换(线程切换)过程
linux 0.11中并未使用我们之前说的这种内存栈互相复制的方法;而是使用的tss段的整段复制;(TSS:task structure segment任务结构段,保存在PCB/TCB中)(TR寄存器是指向GDT表中tss段的指定寄存器;)
过程如下:(一条指令…)
①当前TR指向的原来的tss段保存当前cpu中的全部寄存器数据(快照);
②TR指向(入参next的PCB结构体)n中的tss段;
③将新的tss段全部复制(扣到)到cpu中!
注意:这样很慢,因此实验中要修改这里,完成内核栈的复制,弃用这种0.11方法
-
详解sys_fork()中的copy_process过程(不包含内存)
首先sys_fork中将需要传入给copy_process的参数,全部压入栈中!
接着就是调用copy_process的过程了:
①:申请内存空间—>给TCB/PCB
②:创建内核栈与PCB/TCB关联:将tss里面的内核栈变量与这段空间关联(tss在PCB中)
③:创建用户栈(与父进程使用同一个用户栈,这里面涉及内存管理,修改是创建)
④:修改新的tss,将cs:eip接下来的执行位置以及其他的寄存器放进去;(内核中的哦)
这里的tss就是承担的内核栈的作用;将两个进程在内核态分裂开来;