进程切换的流程
- 一个进程出于某种原因想要进入休眠状态,比如说出让CPU或者等待数据,它会先获取自己的锁;
- 之后进程将自己的状态从RUNNING设置为RUNNABLE;
- 之后进程调用switch函数,其实是调用sched函数在sched函数中再调用的switch函数;
- switch函数将当前的线程切换到调度器线程;
- 调度器线程之前也调用了switch函数,现在恢复执行会从自己的switch函数返回;
- 返回之后,调度器线程会释放刚刚出让了CPU的进程的锁
第1步中获取进程的锁:
阻止其他CPU核的调度器线程在当前进程完成切换前,发现进程是RUNNABLE的状态并尝试运行它
在进程切换的最开始,进程先获取自己的锁,并且直到调用switch函数时也不释放锁。而另一个线程,也就是调度器线程会在进程的线程完全停止使用自己的栈之后,再释放进程的锁。释放锁之后,就可以由其他的CPU核再来运行进程的线程,因为这些线程现在已经不在运行了。
xv6系统相关注意点:
- XV6中,不允许进程在执行switch函数的过程中,持有任何其他的锁
Sleep&Wakeup 接口
UART的驱动
当shell需要输出时会调用write系统调用最终走到uartwrite函数中
uartwrite函数
对于buffer中的每个字符,我们都会等待UART可以接收下一个字符,之后写入一个字符,将tx_done设置为0,回到循环的最开始并再次调用sleep函数进行睡眠状态,直到tx_done为1
uartintr
UART硬件会在完成传输一个字符后,触发一个uartintr的中断处理程序,当UART传输完了这个字符,uartintr函数会将tx_done设置为1,并唤醒uartwrite所在的线程。所以对于每个字符都有调用一次sleep和wakeup,并占用一次循环。
exit系统调用
子进程exit的最后,它都没有释放所有的资源,因为它还在运行的过程中,所以不能释放这些资源。相应的其他的进程,也就是父进程,释放了运行子进程代码所需要的资源。这样的设计可以让我们极大的精简exit的实现。