Multiple Processes


操作系统只要记录好并按照合理的次序推进(分配资源进行调试)。
notion image
进程 = 资源 + 指令执行序列
将资源和指令分开,便得到了线程。线程保留了并发的优点、避免了进程切换的代价。

用户级线程(User Threads)

notion image
操作系统无法感知用户级线程的存在,一旦其中一个线程阻塞就会导致进程Schedule。

线程切换

假设两个执行序列在一个栈中切换:
notion image
void Yield() { jmp xxx; }
为了可以在两个执行序列之间进行切换,在执行第一个yield时应jmp到300,第二个yield中jmp到204。接下来执行到B函数的结尾},即ret指令,404出栈,跳到404执行,执行顺序出错。
因此,两个执行序列应该在两个栈之间进行切换。
 
notion image
 
void Yield() { TCB2.esp = esp; esp = TCB1.esp; jmp 204; //应删除这一行 }
以第二个yield为例,执行时先切换esp,之后执行到B函数的}时已经是TCB1对应的esp,出栈的是204,到204执行,重复执行了204。
因为yield中包含了jmp到204的指令,所以yield的}不会被执行,因此去掉jmp即可。

线程创建

 
notion image
notion image
 
void ThreadCreate(A) { TCB *tcb = malloc(); *stack = malloc(); *stack = A;//100 程序入口,线程切换时通过弹出A找到要执行的地址 tcb.esp = stack; }

内核线程(Kernel Threads)

notion image
核心级线程的创建等操作是系统调用,会进行内核中执行,其中一个线程阻塞不会导致进程切换,并发性更好。
多核处理器中只包含一个MMU(内存管理单元),即一个映射表。只有在内核中才能把多个线程分配在不同的核上。

线程切换

notion image
用户级线程切换时是TCB切换,然后根据TCB切换用户栈。内核级线程包含用户栈和内核栈,因此线程切换时应该在两套栈之间进行切换。
当执行到中断时开始使用内核栈,自动压入多个寄存器,使内核栈通过指针与用户栈相连。
notion image
然后开始内核中的切换:switch_to
sys_read() { ... // 启动磁盘读并将自己阻塞 ... // 找到nextTCB switch_to(cur, next); }
switch_to通过TCB找到内核栈指针,然后通过ret(switch_to的})从内核栈中弹出一段包含iret的指令,切换到某个内核程序,内核程序执行完后再用iret指令通过内核栈中的CS:IP切换回用户程序、通过SS:IP还原用户栈。
 
notion image
其中??是线程T的用户态代码;????是一段包含iret的完成第二级返回的代码;
总结:
  1. 中断入口(进入切换,用户栈→内核栈):初始化内核栈,关联用户栈,调用中断处理;
  1. 中断处理(引发切换):启动了磁盘读或时钟中断,不应该继续执行,对其进行schedule;
  1. schedule:找到下一个线程TCB,调用switch_to;
  1. switch_to(内核栈切换):调取TCB中的esp切换内核栈,执行ret指令;
  1. 中断出口(第二级切换,内核栈→用户栈):通过iret指令切换回用户栈和执行地址
notion image

线程创建

void ThreadCreate(...) { TCB tcb = get_free_page(); *krlstack = ...; *userstack = ...; ... // 填写两个stack tcb.esp = krlstack; ... // tcb置为就绪态 ... // tcb入队 }

内核级线程实现

线程切换

从某个中断开始,以fork为例:
main() { A(); B(); } A() { fork(); }
执行到A函数,用户栈中压入A的返回地址(也是B的初始地址),然后进入A函数执行。
执行到INT 0x80时,CPU找到内核栈,并在内核栈中压入当前SS:SPCS:IP
notion image
接下来,执行INT 0x80的中断处理函数,即system_call
在内核栈中保存用户态的信息:
notion image
然后执行中断对应的系统调用,如sys_fork,此时有可能产生磁盘读写引起切换。
sys_fork中对当前进程的状态进行判断,非0时进行阻塞或时间片用尽,重新调度:
notion image
reschedule将完成切换的中间三步,然后由ret_from_sys_call进行中断返回,完成内核栈到用户栈的切换。
notion image
notion image
reschedule中schedule是C函数,执行完后弹栈执行ret_from_sys_call
notion image
在linux-0.11中,switch_to切换算法采用TSS(Task state segment)实现:
notion image
将cpu中所有寄存器放在当前TR指向的段中,然后将新的TSS中的内容全部复制回CPU。

线程创建

sys_fork开始CreateThread:(这里是进程创建,使用TSS创建)
notion image
notion image
 
notion image
其中,esp0是内核栈,esp是用户栈;0x10是内核数据段;内核栈与PCB共用一页内存,用户栈与父进程共用。
notion image
if(!fork()) {exec(cmd);} 的实现:
notion image
 
notion image
在执行exec系统调用时,在内核栈中修改EIP的值,中断结束iret后会自动跳转到修改后的位置执行。这里压栈的eax_do_execve的参数。
notion image