0%

L12-内核级线程的实现

  • 内核级线程实现即对 L11 章节的内容进行代码实现,由 L11 章节所述,内核级线程的切换涉及到一个线程中用户栈与内核栈之间的切换以及线程与线程之间的切换,接下来将详细讲述这两部分的代码实现过程。

L12 内核级线程实现

  • 内核级线程实现即对 L11 章节的内容进行代码实现,由 L11 章节所述,内核级线程的切换涉及到一个线程中用户栈与内核栈之间的切换以及线程与线程之间的切换,接下来将详细讲述这两部分的代码实现过程。

用户栈和内核栈的切换

用户态->内核态
  • 用户栈和内核栈的切换就要涉及到操作系统由用户态进入内核态,由 ”实验3-系统调用“ 可知,操作系统由用户态进入内核态是通过 int 0x80 中断指令来实现的,由下图可知,内核栈在存储了用户栈和返回地址的信息之后,接下来就是要调用 linux-0.11/kernel/system_call.s 的 system_call 函数进行中断处理。

    在进行中断处理之前,需要保护现场,此时各个寄存器中存储的数据还都是用户态的数据,如下图所示将这些数据保存至内核栈中,以便在后面能够恢复现场。

    接着就是 system_call 函数通过这行代码 call sys_call_table(,%eax,4) ,通过系统调用表和传进来的系统调用号,调用真正要实现功能的程序(在这里是 sys_fork ),

内核态->用户态
  • 在 sys_fork 执行过程中,可能会因为某些原因导致线程阻塞(实际上 sys_fork 不太可能,反而是 sys_write 和 sys_read 这种,由于要和 I/O 设备进行交互而可能引起阻塞),此时 system_call 函数就继续向下运行,进行线程切换,通过 schedule 函数来实现切换,线程切换的内容(即五段论的其中三段)先按下不表,在线程切换后,通过 ret_from_sys_call 又从内核态返回到用户态(ret_from_sys_call 是一段包含 iret 指令的代码),并将之前存储在栈中的各种寄存器数据弹出,恢复现场

    注意:此时从栈中弹出的各种寄存器的数据是切换后的那个线程的内核栈中的,而非切换前的线程的内核栈中的。

线程切换

  • 线程切换就是通过内核态中两个线程之间内核栈的切换来实现的,而内核栈的切换就涉及到调度函数 schedule(如下图中的 schedule 函数),通过调度算法找到下一个将要切换的线程后,通过 switch_to 函数实现两个线程内核栈的切换,即实现了两个线程的切换。

    linux-0.11/include/linux/sched.hswitch_to函数 中,使用的是 tss 切换:先将 CPU 寄存器中的数据保存到当前线程的 tss 中,然后找到下一个要切换的 tss 的 TR(_TSS(n)),再通过 ljmp 指令实现 TR 切换,在将 TR 切换后,就可以通过 TR中的 TSS 描述符找到对应的 tss,将该 tss 中的数据都输入到 CPU 的寄存器中,就实现了线程的切换。

    另:tss 切换相较于内核栈切换来说比较简单,核心指令是 ljmp 指令,但执行效率不高,所以在实验5中的内容就是要将 linux-0.11 中的 tss 切换转换为内核栈切换。

线程的创建

  • 在讲述了用户栈和内核栈之间的切换以及线程之间的切换后,就来到了线程的创建,只有在线程创建时把该创建的东西都创建了,才能为前面所述的两种切换提供保障,我们通过 sys_fork 来讲述线程的创建过程。

    linux-0.11/kernel/system_call.s 中,有 sys_fork 系统调用,该段代码中的 call copy_process 调用 copy_process 函数,而 copy_process 函数就创建了一个子线程,copy_process 函数的括号中的参数都是父进程的内核栈中的数据,是在call copy_process 之前 push 进内核栈的。

  • 接下来就深入到 copy_process 函数,来看看一个线程是如何创建的。

    • 首先是要创建TCB、内核栈和用户栈,在这里,给子进程创建了和父进程有区分的PCB、内核栈,但子进程的用户栈是和父进程共用的,并将TCB和内核栈、内核栈和用户栈关联了起来。即在创建好了 tss 后,就可以可进行线程的切换。

    • 然后是 tss 的细节补充。(eip 即父进程用户态中 int 0x80 指令的下一句代码

---------------The End---------------