理论上:

理论上如下图所示,从一个用户栈到另一个的切换过程:
线程1用户栈通过int陷入内核栈
内核通过线程控制块寻找到(调度之后的)线程2线程控制块TCB
TCB2中有内核栈的指针,代表着内核栈切换成功
通过iret指令从内核栈回到用户空间;L12 内核级线程的代码实现(linux 0.11)

以fork()为例看看实现:

首先大致梳理一下:
① int0x80,硬件自动push一堆东西到内核栈
②之后在sys_call中,push一堆用户态信息的寄存器到内核栈,调用sys_fork完成fork工作
③fork之后进行schedule工作
④最后ret_form_sys_call pop内核栈中一堆数据,调用iret回到用户态;

下面根据这个流程,仔细看看其中发生了什么;

  1. fork系统调用陷入内核之前的过程:
    ①记录中断号NR_fork;②产生中断int,中断指令集完成内核栈初始化;③内核栈与用户栈相连,且存有int后面指令地址(PC指针);
    L12 内核级线程的代码实现(linux 0.11)L12 内核级线程的代码实现(linux 0.11)

  2. system_call中(中断的入口和出口:相当于忽略sys_fork的头和尾巴)
    ①将当前用户态的寄存器中的内容全部压入内核栈中
    ②调用sys_fork()完成创建进程的过程;
    ③在sys_fork结束之后,把当前执行进程的current.PCB给 %eax,如果阻塞就调用schedule如果时间片用光,也调用schedule;
    ④schedule结束后,调用ret_from_sys_call从内核中返回(此时如果调度,已完成切换内核栈过程);
    ⑤注意,右侧的*??2*是pushl %eax的入栈;
    L12 内核级线程的代码实现(linux 0.11)

  3. 详解2中的尾巴ret_from_sys_call
    将内核栈中的数据都pop出去,并且跳回到中断发生的代码后面继续执行;
    L12 内核级线程的代码实现(linux 0.11)

  4. 详解③中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方法
    L12 内核级线程的代码实现(linux 0.11)

  5. 详解sys_fork()中的copy_process过程(不包含内存)
    首先sys_fork中将需要传入给copy_process的参数,全部压入栈中!
    接着就是调用copy_process的过程了:
    ①:申请内存空间—>给TCB/PCB
    ②:创建内核栈与PCB/TCB关联:将tss里面的内核栈变量与这段空间关联(tss在PCB中)
    ③:创建用户栈(与父进程使用同一个用户栈,这里面涉及内存管理,修改是创建)
    ④:修改新的tss,将cs:eip接下来的执行位置以及其他的寄存器放进去;(内核中的哦)
    L12 内核级线程的代码实现(linux 0.11)
    L12 内核级线程的代码实现(linux 0.11)L12 内核级线程的代码实现(linux 0.11)
    这里的tss就是承担的内核栈的作用;将两个进程在内核态分裂开来;

相关文章: