本文主要介绍线程的调度激活机制(Scheduler Activations),主要内容:
- 调度激活机制简介
- 上行调用(upcall)
- 中断处理(Interrupt)
一、 调度激活机制简介
上一篇文章详细阐述了用户空间和内核空间的线程实现,各有优劣,内核线程在各方面都比较灵活,但是太慢,性能不高,经常会出现请求在用户空间和内核空间的传递。那么如何在拥有内核空间线程的灵活性的同时又提高性能呢。这就是Scheduler Activations机制要做的事情。
该机制的主要目标是在用户空间模拟内核空间的线程(从灵活性方面),这样在当一个进程内的一个用户线程被block(比如发生缺页异常)时,不会导致整个进程被block,这个进程的内的其他用户线程还可以继续运行(类似于内核线程)。
当Scheduler Activations机制启用时,内核会给每一个进程分配一定数量的CPU(有可能是虚拟的,如果是多核系统,有可能是真实的CPU)。这样每一个进程的运行时系统(run-time system)就可以给自己的线程(user-space threads)分配这些cpu。
- 初始默认的cpu数量是1
- 进程可以箱内核申请更多的cpu
- 进程可以将不再使用的CPU归还给内核
- 内核可以自己将不用的CPU自己回收
由此可见,这种机制下,进程对于CPU来说依然还是单线程的,内核依然不知道进程拥有多少线程。示意图如下。
二、上行调用(upcall)
上面阐释了调度激活机制,那么是如何实现的,运行时是如何给自己的线程分配cpu的。如何调度的,这个是通过上行调用(upcall)来完成的。在cpu中用户空间为上层,内核为下层层,常规调用应该是上层调用下层,下层不应该调用上层,upcall就是指内核调用用户空间。
上图展示了upcall。并且用传统应用webui和bll的调用做了类比,upcall是不规范的,但是调度激活就是通过upcall实现的。
upcall具体做了什么?
- 内核发现用户进程的一个线程被block了(比如调用了一个被block的system call,或者发生了缺页异常。
- 内核通过进程的运行时线程被block(还会告知被block的线程的详细信息),这个就是upcall
- 进程的运行时系统接收到内核发来的消息,得知自己的线程被block
- 运行时系统先将当前线程标识为block(会保存在线程表-thread table)
- 运行时系统从当前线程表(thread table)选择一个ready的线程进行运行
至此已经完成了当一个进程内的一个用户线程被block(比如发生缺页异常)时,不会导致整个进程被block,这个进程的内的其他用户线程还可以继续运行(类似于内核线程)。
当内核发现之前被block的线程可以run了,同样会通过upcall通知运行时系统,运行时系统要么马上运行该线程,要么把该线程标志位ready放入线程表。
三、中断处理
当一个用户线程在运行的时候硬件产生了一个中断(Interrupt),这个时候应该怎么办呢?
- 被中断的进程不关注这个中断,这个时候被中断的线程会继续返回到中断产生前的状态继续执行
- 被中断的进程关注这个中断,被中断的线程会被挂起,至于运行那个线程:被中断的线程,新的ready线程还是其他线程,这个有运行时系统决定