漫谈操作系统9 -- 线程运行状态

来源:互联网 发布:米思米3d数据库 编辑:程序博客网 时间:2024/06/05 07:57

这篇博文简单介绍一下操作系统中的线程运行状态,为后文继续介绍操作系统的调度器做一些准备的工作。

为了分时的复用硬件的处理器资源,操作系统需要通过上下文切换将不同的调度实例(一般是线程)分时的在处理器上运行,从而对外表现为每个线程都在正常运行的错觉。而这就要求线程在合适的时机被切换出处理器,并且由操作系统内核的调度器选择合适的新线程来运行。为了记录调度实例(线程)的工作状态,操作系统一般会将调度实例放置在以下几个运行状态:


根据上图我们可以很简单的将一个调度实例划分为简单的3个状态,它们分别为Ready, Running, Blocked。 

Ready 代表当前的调度实例在可执行队列中,随时可以被切换到占用处理器的运行状态。

Running代表当前的调度实例正在占用处理器运行中。

Blocked代表当前的调度实例在等待相应的资源。


下面我们分别深入的理解这三个状态。  Ready/Running两个状态是由调度器根据调度算法来进行切换的,我们通常所指的调度算法实际上主要控制的是调度实例的Ready/Running之间切换的方法,比如是否可以抢占,是否按照时间片轮流调度,是否按照优先级来调度等等,所以调度算法主要控制的是这两个状态之间的切换。

而Ready/Running这两个状态代表当前此调度实例没有依赖于任何系统资源,它可以随时得到执行,相对应的Blocked状态指的就是当前调度实例依赖于某个系统资源并且没有得到满足,所以当前的调度实例被阻塞,并且在资源满足前永远不会得到运行。

在什么情况下调度实例会进入Blocked状态呢, 这个有很多种情况,比如当前的用户态线程调用了阻塞版本的recv函数,并且当前的TCP链接中没有收到数据,那么此用户态线程就会被标记为Blocked状态,并且休眠在socket的recvbuff之上,而当内核协议栈收到数据包并且将数据成功放入socket的recvbuff之后会唤醒Blocked在此休眠队列的调度实例从而将此用户态线程放入到Ready队列,然后等待调度算法调度其运行。此外比如线程的mutex/sleep/读写文件/管道操作/...等都会有线程被Blocked的可能。

但是不论Blocked在何种资源之上,内核中都不应该出现复杂的调度实例状态设计,比如曾经遇到过一个系统的内核其中的Blocked状态分为,Blocked On Signal/ Blocked on Msg/ Blocked On Sem/...等5个不同的Blocked的状态,这种是典型的将调度实例的状态和等待的资源混淆在了一起,这是一种很失败的设计,如果系统中多了一种新的IO资源其就会需要增加一种新的线程状态。

正确的做法是不论当前调度实例在等待何种资源,其都只有一种Blocked状态,而通过其调度实例控制块中的等待队列来具体区分其在等待的实际系统资源是什么。 在这种设计中,如果有新的阻塞系统资源加入,只需要将等待队列的指针赋值到新的阻塞资源之上就可以了,从而避免了上面复杂的调度实例状态设计。


扫盲操作系统属于, P/V 操作, 记得自己在考系统分析师的时候,一直在纠结这个老土的概念到底指什么,虽然通过了考试但是实际上一直到最后自己真的长时间工作在操作系统领域才真的理解了其重要性。 P操作简单的讲就是将当前的调度实例阻塞在资源之上,而V操作则是P操作的相反操作,其将阻塞在资源之上的调度实例唤醒,重新加入到系统运行中。 P/V 操作是操作系统中最重要的元语,还需要学习操作系统的人深入的体会其意义。

原创粉丝点击