Linux0.11内核--进程的调度(运行态(就绪态)和睡

系统 3940 0

<!-- p { margin-bottom: 0.08in; } -->

当进程等待资源或者事件时,就进入睡眠状态。有两种睡眠态,不可中断睡眠态( TASK_UNINTERRUPTIBLE )和可中断睡眠态( TASK_INTERRUPTIBLE )。

处于可中断睡眠态的进程不光可以由 wake_up 直接唤醒,还可以由信号唤醒。在 schedule() 函数中,会把处于可中断睡眠态并且收到信号的进程变成运行态,使他参与调度选择。 Linux0.11 中进入可中断睡眠状态的方法有 3

  1. 调用 interruptible_sleep_on() 函数

  2. 调用 sys_pause() 函数

  3. 调用 sys_waitpid() 函数。

第一种情况用于等待外设资源时(如等待 I/O 设备),这时当前进程会挂在对应的等待队列上。第二第三种情况用于事件,即等待信号。

进程要进入不可中断睡眠态,只能通过 sleep_on() 函数。要使处于不可中断睡眠态的进程进入运行态,只能由其他进程调用 wake_up() 将它唤醒。当进程等待系统资源(比如高速缓冲块,文件 i 节点或者文件系统的超级块)时,会调用 sleep_on() 函数,使当前进程挂起在相关资源的等待队列上。

这部分代码很短,一共三个函数 sleep_on() wake_up() interruptible_sleep_on() 。在 sched.c 中。但是代码比较难理解,因为构造的等待队列是一个隐式队列,利用进程地址空间的独立性隐式地连接成一个队列。这个想法很奇妙。

<!-- p { margin-bottom: 0.08in; } -->

sleep_on()

<!-- p { margin-bottom: 0.08in; } -->

这个函数牵涉到 3 个指针, p tmp current

p 是指向指针的指针,实际上 *p 指向的是等待队列头。系统资源(高速缓冲块,文件 i 节点或者文件系统的超级块)的数据结构中都一个 struct task_struct * 类型的指针,指向的就是等待该资源的进程队列头。比如 i 节点中的 i_wait ,高速缓冲块中的 b_wait ,超级块中的 s_wait *p 对于等待队列上的所有进程都是一样的。

current 指向的是当前进程指针,是全局变量。

tmp 位于当前进程的地址空间内,是局部变量。不同的进程有不同 tmp 变量。等待队列就是利用这个变量把所有等待同一个资源的进程连接起来。具体的说,所有等待在队列上的进程,都是在 sleep_on() schedule() 中被切换出去的,这些进程还停留在 sleep_on() 函数中,在函数的堆栈空间里面,存放了局部变量 tmp

假如当前进程要进入某个高速缓冲块的等待队列,而且该等待队列上已经有另外两个进程 task1 task2 先后进入。形成的队列如图。等待队列是堆栈式的,先进入队列的进程排在最后。

Linux0.11内核--进程的调度(运行态(就绪态)和睡眠态之间的转换)

<!-- p { margin-bottom: 0.08in; } -->

在调用了 sleep_on() 的地方,我们可以发现 sleep_on() 往往是放在一个循环中的(比如 wait_on_buffer() wait_on_inode() lock_inode() lock_super() wait_on_super() 等函数)。当进程从 sleep_on() 返回时,并不能保证当前进程取得了资源使用权,因为调用 wake_up() 进程切换到从 sleep_on() 中苏醒的过程中,发生了进程调度,中间很可能有别的进程取得了资源。

wake_up()

<!-- p { margin-bottom: 0.08in; } --> <!-- p { margin-bottom: 0.08in; } -->

下面分析 sleep_on() wait_up() 配合使用的情况

情况一 游离队列的产生

先分析一下 sleep_on() wake_up() 在通常情况下的工作原理。考虑一个非常简单的情况,假设目前系统只有 3 个进程,且都等在队列上,队列的头指针设为 wait

<!-- p { margin-bottom: 0.08in; } -->

然后系统资源得到释放,当前进程调用 wake_up(wait) 。这时 Task C 变成了运行态。

<!-- p { margin-bottom: 0.08in; } -->

之后进程调度发生, Task C 被选中,开始运行。 Task C 是从 sheep_on() 中的 schedule() 的后一条语句开始运行,它把 Task B 的状态变成运行态。随后 Task C 退出 sheep_on() 函数,堆栈中的局部变量 tmp 消失,这样再没有指向 Task B 的指针, Task B 开头的队列游离了。

Linux0.11内核--进程的调度(运行态(就绪态)和睡眠态之间的转换)

<!-- p { margin-bottom: 0.08in; } -->

情况 1-1

这时对同一个资源有两个进程是可运行状态,但是当前进程是 Task C ,只要它不调用 schedule ,它是不会被抢断的。因此 Task C 继续运行,取得了它想要的资源,这时 Task C 可以完成它的任务了。当进程调度再次发生时, Task B 会被选中,同样, Task B 会把 Task A 变成可运行态,而它自己得到了资源。最终 Task A 也会得到执行。这样,等待在一个资源上的三个任务最终都得到运行。

情况 1-2

假设 Task C 在得到资源后,又主动调用了 schedule() ,进程调度程序这时选中了 Task B Task B 从上次中断的地方开始运行,即从 sleep_on() schedule() 后面的语句开始运行。它会把 Task A 也变成可运行状态。然后退出 sleep_on() tmp 变量消失。但是不幸的是它发现资源仍然被占用,所以再次进入睡眠,又连接到 wait 队列上了。

Linux0.11内核--进程的调度(运行态(就绪态)和睡眠态之间的转换)

<!-- p { margin-bottom: 0.08in; } -->

从这个情况可以看到,虽然系统运行过程中,可能会把等待队列切分成很多游离队列,但是这些队列头上的进程都是运行态,这保证 schedule() 函数最终还是会找到它。

情况二 游离队列的合并

假设目前进程等待资源的情况如下,某个进程占用资源不放,导致有 7 个进程等待该资源。产生 3 个队列,其中两个游离。

Linux0.11内核--进程的调度(运行态(就绪态)和睡眠态之间的转换)

<!-- p { margin-bottom: 0.08in; } -->

这时调度函数选中 Task E 执行, Task E 先唤醒 Task D 但发现资源不能用,再次睡眠,把自己移到 wait 队列,脱离了游离队列。调度再次发生。

Linux0.11内核--进程的调度(运行态(就绪态)和睡眠态之间的转换)

<!-- p { margin-bottom: 0.08in; } -->

假如这时 Task B 得到运行,同样 Task B 也只能唤醒 Task A ,而把自己移动到等待队列

Linux0.11内核--进程的调度(运行态(就绪态)和睡眠态之间的转换)

p { margin-bottom: 0.08in; }

这样,只要游离队列头上的进程是运行态,游离队列可以再次合并到原先的等待队列上。

p { margin-bottom: 0.08in; }

interruptible_sleep_on()

Linux0.11内核--进程的调度(运行态(就绪态)和睡眠态之间的转换)


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论