写在前面:
建议看原课件梳理!!!
一、内核级线程具体实现
例(在这里printf("A")和printf("B")变为 exec(cmd1)和exec(cmd2)):
1. 正在执行int 0x80
SS:SP、EFLAGS和 ret = 100都是通过 int0x80 硬件自动压栈
2.中断处理:system_call
3.sys_fork:CreateThread
4.copy_process:在执行子程序前的准备工作
- 申请内存空间;
- 创建TCB(TSS:TASK_STRUCT);
- 创建内核栈和用户栈
- 关联栈和TCB
- fork和父进程共用用户栈
- fork创建自己的内核栈
TASK_STRUCT(TSS结构):
- 通过sys_fork函数,执行子程序exec(cmd1)的准备工作已经完成,此时父进程未堵塞,执行:
push %eax
cmpl $0,counter(%eax)
ret_from_sys_call
未跳转执行子程序,ret_from_sys_call是恢复父进程执行现场,并通过iret回到用户栈,即int 0x80后面的执行程序!
- 返回到父进程 100:mov %eax,res(即ret=100)处,此时res!=0,执行父进程;若res=0,即%eax被置为0时,执行子程序;
- 继续创建子程序exec(cmd2)执行前的准备工作(TSS和内核栈),放入就绪队列
至此,exec(cmd1)和exec(cmd2)的TSS被初始化完成(即sys_fork执行完毕)
然后继续向下执行:
push %eax
mov1 _current,%eax
cmpl $0,state(%eax)
jne reschedule
cmpl $0,counter(%eax)
jne reschedule
ret_from_sys_call
假设此时父进程发生堵塞(wait()函数),则要执行子进程,即执行 jne reschedule
5.进入子进程,父进程等待
- 将 $ret_from_sys_call压栈,方便后续从内核栈回到用户栈,恢复父进程执行现场
- 跳到schedule函数
- next = i //涉及到调度算法,即从就绪队列选择一个程序执行(exec(cmd1)和exec(cmd2))
- switch_to(next) //线程切换,即父进程、exec(cmd1)和exec(cmd2)互相切换
6.switch_to://线程切换
7.执行子进程
- 首先通过system_call调用 sys_execve函数
- 其次将%eax赋值为EIP+esp,即 %eax = esp+0x1C = 28,即原先返回父进程的地址(int0x80 后面的执行程序)
- 最后 eip[0] = ex.a_entry,即将原先返回父进程的地址变为执行子程序的入口;eip[3] = p,即为栈顶SP
- 此时,ret返回的地址从原来的父进程变为子程序的入口,再次返回到用户栈时,就变为执行子程序
二、内核级线程实现大致梳理
- 首先通过int 0x80进行中断进入内核,建立用户栈和内核栈的联系,并进行相应的中断处理(sys_call)
- 中断处理,引发切换(保护进程现场,判断进程状态从而决定是否切换进程)
- 调用schedule函数选择要切换到的线程并调用switch_to函数
- 通过switch_to函数完成相应TCB切换,即相应线程切换
- 然后通过中断出口iret,完成内核栈到用户栈的二级转换
参考文章: