0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux进程的睡眠和唤醒

马哥Linux运维 来源:未知 作者:李倩 2018-03-20 14:34 次阅读

1 Linux 进程的睡眠和唤醒

在 Linux 中,仅等待 CPU 时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状 态标志位为 TASK_RUNNING。一旦一个运行中的进程时间片用完, Linux 内核的调度器会剥夺这个进程对 CPU 的控制权,并且从运行队列中选择一个合适的进程投入运行。

当然,一个进程也可以主动释放 CPU 的控制权。函数 schedule() 是一个调度函数,它可以被一个进程主动调用,从而调度其它进程占用 CPU。一旦这个主动放弃 CPU 的进程被重新调度占用 CPU,那么它将从上次停止执行的位置开始执行,也就是说它将从调用 schedule() 的下一行代码处开始执行。

有时候,进程需要等待直到某个特定的事件发生,例如设备初始化完成、I/O 操作完成或定时器到时等。在这种情况下,进程则必须从运行队列移出,加入到一个等待队列中,这个时候进程就进入了睡眠状态。

Linux 中的进程睡眠状态有两种:一种是可中断的睡眠状态,其状态标志位

TASK_INTERRUPTIBLE;

另一种是不可中断 的睡眠状态,其状态标志位为 TASK_UNINTERRUPTIBLE。可中断的睡眠状态的进程会睡眠直到某个条件变为真,比如说产生一个硬件中断、释放 进程正在等待的系统资源或是传递一个信号都可以是唤醒进程的条件。不可中断睡眠状态与可中断睡眠状态类似,但是它有一个例外,那就是把信号传递到这种睡眠 状态的进程不能改变它的状态,也就是说它不响应信号的唤醒。不可中断睡眠状态一般较少用到,但在一些特定情况下这种状态还是很有用的,比如说:进程必须等 待,不能被中断,直到某个特定的事件发生。

在现代的 Linux 操作系统中,进程一般都是用调用 schedule() 的方法进入睡眠状态的,下面的代码演示了如何让正在运行的进程进入睡眠状态。

sleeping_task = current;

set_current_state(TASK_INTERRUPTIBLE);

schedule();

func1();

/* Rest of the code ... */

在第一个语句中,程序存储了一份进程结构指针 sleeping_task,current 是一个宏,它指向正在执行的进程结构。set_current_state() 将该进程的状态从执行状态 TASK_RUNNING 变成睡眠状态TASK_INTERRUPTIBLE。 如果 schedule() 是被一个状态为TASK_RUNNING 的进程调度,那么 schedule() 将调度另外一个进程占用 CPU;如果 schedule() 是被一个状态为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 的进程调度,那么还有一个附加的步骤将被执行:当前执行的进程在另外一个进程被调度之前会被从运行队列中移出,这将导致正在运行的那个进程进入睡眠,因为 它已经不在运行队列中了。

我们可以使用下面的这个函数将刚才那个进入睡眠的进程唤醒。

wake_up_process(sleeping_task);

在调用了 wake_up_process() 以后,这个睡眠进程的状态会被设置为 TASK_RUNNING,而且调度器会把它加入到运行队列中去。当然,这个进程只有在下次被调度器调度到的时候才能真正地投入运行。

2 无效唤醒

几乎在所有的情况下,进程都会在检查了某些条件之后,发现条件不满足才进入睡眠。可是有的时候进程却会在 判定条件为真后开始睡眠,如果这样的话进程就会无限期地休眠下去,这就是所谓的无效唤醒问题。在操作系统中,当多个进程都企图对共享数据进行某种处理,而 最后的结果又取决于进程运行的顺序时,就会发生竞争条件,这是操作系统中一个典型的问题,无效唤醒恰恰就是由于竞争条件导致的。

设想有两个进程 A 和 B,A 进程正在处理一个链表,它需要检查这个链表是否为空,如果不空就对链表里面的数据进行一些操作,同时 B 进程也在往这个链表添加节点。当这个链表是空的时候,由于无数据可操作,这时 A 进程就进入睡眠,当 B 进程向链表里面添加了节点之后它就唤醒 A 进程,其代码如下:

A 进程:

1 spin_lock(&list_lock);

2if(list_empty(&list_head)) {

3 spin_unlock(&list_lock);

4 set_current_state(TASK_INTERRUPTIBLE);

5 schedule();

6 spin_lock(&list_lock);

7 }

8

9/* Rest of the code ... */

10 spin_unlock(&list_lock);

B 进程:

100 spin_lock(&list_lock);

101 list_add_tail(&list_head, new_node);

102 spin_unlock(&list_lock);

103 wake_up_process(processa_task);

这里会出现一个问题,假如当 A 进程执行到第 3 行后第 4 行前的时候,B 进程被另外一个处理器调度投入运行。在这个时间片内,B 进程执行完了它所有的指令,因此它试图唤醒 A 进程,而此时的 A 进程还没有进入睡眠,所以唤醒操作无效。在这之后,A 进程继续执行,它会错误地认为这个时候链表仍然是空的,于是将自己的状态设置为 TASK_INTERRUPTIBLE 然后调用 schedule() 进入睡 眠。由于错过了 B 进程唤醒,它将会无限期的睡眠下去,这就是无效唤醒问题,因为即使链表中有数据需要处理,A 进程也还是睡眠了。

3 避免无效唤醒

如何避免无效唤醒问题呢?我们发现无效唤醒主要发生在检查条件之后和进程状态被设置为睡眠状态之前, 本来 B 进程的 wake_up_process() 提供了一次将 A 进程状态置为 TASK_RUNNING 的机会,可惜这个时候 A 进程的状态仍然是 TASK_RUNNING,所以 wake_up_process() 将 A 进程状态从睡眠状态转变为运行状态的努力 没有起到预期的作用。要解决这个问题,必须使用一种保障机制使得判断链表为空和设置进程状态为睡眠状态成为一个不可分割的步骤才行,也就是必须消除竞争条 件产生的根源,这样在这之后出现的 wake_up_process () 就可以起到唤醒状态是睡眠状态的进程的作用了。找到了原因后,重新设计一下 A 进程的代码结构,就可以避免上面例子中的无效唤醒问题了。

A 进程:

1 set_current_state(TASK_INTERRUPTIBLE);

2 spin_lock(&list_lock);

3if(list_empty(&list_head)) {

4 spin_unlock(&list_lock);

5 schedule();

6 spin_lock(&list_lock);

7 }

8 set_current_state(TASK_RUNNING);

9

10/* Rest of the code ... */

11 spin_unlock(&list_lock);

可以看到,这段代码在测试条件之前就将当前执行进程状态转设置成 TASK_INTERRUPTIBLE 了,并且在链表不为空的情况下又将自己置为 TASK_RUNNING 状态。这样一来如果 B 进程在 A 进程进程检查了链表为空以后调用 wake_up_process(),那么 A 进程的状态就会自动由原来 TASK_INTERRUPTIBLE变成 TASK_RUNNING,此后即使进程又调用了 schedule(),由于它现在的状态是 TASK_RUNNING,所以仍然不会被从运行队列中移出,因而不会错误的进入睡眠,当然也就避免了无效唤醒问题。

4 Linux 内核的例子

在 Linux 操作系统中,内核的稳定性至关重要,为了避免在 Linux 操作系统内核中出现无效唤醒问题,Linux 内核在需要进程睡眠的时候应该使用类似如下的操作:

/* ‘q’是我们希望睡眠的等待队列 */

DECLARE_WAITQUEUE(wait,current);

add_wait_queue(q, &wait);

set_current_state(TASK_INTERRUPTIBLE);

/* 或 TASK_INTERRUPTIBLE */

while(!condition) /* ‘condition’ 是等待的条件 */

schedule();

set_current_state(TASK_RUNNING);

remove_wait_queue(q, &wait);

上面的操作,使得进程通过下面的一系列步骤安全地将自己加入到一个等待队列中进行睡眠:首先调用 DECLARE_WAITQUEUE () 创建一个等待队列的项,然后调用 add_wait_queue() 把自己加入到等待队列中,并且将进程的状态设置为TASK_INTERRUPTIBLE 或者 TASK_INTERRUPTIBLE。然后循环检查条件是否为真:如果是的话就没有必要睡眠,如果条件不为真,就调用 schedule()。当进程 检查的条件满足后,进程又将自己设置为 TASK_RUNNING 并调用 remove_wait_queue() 将自己移出等待队列。

从上面可以看到,Linux 的内核代码维护者也是在进程检查条件之前就设置进程的状态为睡眠状态,然后才循环检查条件。如果在进程开始睡眠之前条件就已经达成了,那么循环会退出并用 set_current_state() 将自己的状态设置为就绪,这样同样保证了进程不会存在错误的进入睡眠的倾向,当然也就不会导致出现无效唤醒问题。

下面让我们用 linux 内核中的实例来看看 Linux 内核是如何避免无效睡眠的,这段代码出自 Linux2.6 的内核 (linux-2.6.11/kernel/sched.c: 4254):

4253/* Wait for kthread_stop */

4254 set_current_state(TASK_INTERRUPTIBLE);

4255while (!kthread_should_stop()) {

4256 schedule();

4257 set_current_state(TASK_INTERRUPTIBLE);

4258 }

4259 __set_current_state(TASK_RUNNING);

4260return0;

上面的这些代码属于迁移服务线程 migration_thread,这个线程不断地检查 kthread_should_stop(),

直 到 kthread_should_stop() 返回 1 它才可以退出循环,也就是说只要 kthread_should_stop() 返回 0 该进程就会一直睡 眠。从代码中我们可以看出,检查 kthread_should_stop() 确实是在进程的状态被置为 TASK_INTERRUPTIBLE 后才开始执行 的。因此,如果在条件检查之后但是在 schedule() 之前有其他进程试图唤醒它,那么该进程的唤醒操作不会失效。

小结

通过上面的讨论,可以发现在 Linux 中避免进程的无效唤醒的关键是在进程检查条件之前就将进程的状态置为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE,并且如果检查的条件满足的话就应该将其状态重新设置为 TASK_RUNNING。这样无论进程等待的条件是否满足, 进程都不会因为被移出就绪队列而错误地进入睡眠状态,从而避免了无效唤醒问题。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11296

    浏览量

    209358

原文标题:关于 Linux 进程的睡眠和唤醒 ,来看这篇就够了~

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux进程睡眠唤醒

    Linux中,仅等待CPU时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状 态标志位为 TASK_RUNNING。一旦一个运行中的
    发表于 06-07 12:26 476次阅读

    一文搞懂Linux进程睡眠唤醒

    ): 进程在等待某个条件满足(如I/O操作),可以被信号唤醒Linux通过内核提供的系统调用来控制进程睡眠。常用的系统调用有: sl
    发表于 11-04 15:15

    Linux学习杂谈】之进程状态

    等待态的进程就是进程在等待某些条件,当条件成熟之后可以进入就绪态等待CPU的调度执行。进程位于等待态的情况下如果给了它调度的权限,CPU也是无法执行的。 浅度睡眠等待的时候是可以被信
    发表于 09-27 00:36

    Linux下的进程结构

    进程不但包括程序的指令和数据,而且包括程序计数器和处理器的所有寄存器及存储临时数据的进程堆栈,因此正在执行的进程包括处理器当前的一切活动。 因为Linux是一个多
    发表于 05-27 09:24

    如何设置以及唤醒stm32 ucos下睡眠

    请教一下ucos进入睡眠模式,是不是应该先挂起所有任务然后进入睡眠,还是直接进入睡眠等待唤醒(等待唤醒任务是否还在执行调度)?
    发表于 07-29 00:42

    睡眠时的BOR/LPBOR无法唤醒

    。但我的意思是,如果棕色的持续时间很长,那么PIC通常会醒来吗?棕色是否会导致PIC无法正常唤醒或根本无法唤醒的情况?如果可能的话,那么我会在睡眠时也启用BOR。我可以在没有睡眠时启用
    发表于 10-12 14:46

    Linux进程管理

    Linux进程管理 本章主要介绍进程的概念、状态、构成以及Linux进程的相关知识。 掌握进程
    发表于 04-28 14:57 0次下载

    渐响式睡眠唤醒器电路图

    渐响式睡眠唤醒器电路图
    发表于 05-25 13:43 1496次阅读
    渐响式<b class='flag-5'>睡眠</b><b class='flag-5'>唤醒</b>器电路图

    Linux守护进程详解

    分享到:标签:进程控制 Linux 守护进程进程 7.3 Linux守护进程 7.3.1 守
    发表于 10-18 14:24 0次下载
    <b class='flag-5'>Linux</b>守护<b class='flag-5'>进程</b>详解

    如何设置Linux进程睡眠唤醒

    Linux中,仅等待CPU时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状 态标志位为TASK_RUNNING。
    发表于 04-23 14:29 944次阅读

    你知道Linux进程睡眠唤醒操作?

    Linux 中的进程睡眠状态有两种:一种是可中断的睡眠状态,其状态标志位TASK_INTERRUPTIBLE;
    发表于 04-23 14:56 960次阅读
    你知道<b class='flag-5'>Linux</b><b class='flag-5'>进程</b>的<b class='flag-5'>睡眠</b>和<b class='flag-5'>唤醒</b>操作?

    GD32低功耗:深度睡眠唤醒系统时钟变慢问题

    一、问题1、进入深度睡眠后,通过外部中断唤醒,发现系统时钟变慢。2、进入休眠模式,通过任何中断唤醒,系统时钟正常。二、原因1、从电源管理章节可知,睡眠模式下使用没有什么需要需要注意的,
    发表于 12-02 15:06 21次下载
    GD32低功耗:深度<b class='flag-5'>睡眠</b><b class='flag-5'>唤醒</b>系统时钟变慢问题

    STM32 低功耗睡眠模式(SLEEP)事件(EVENT)唤醒实现及优化

    STM32 低功耗睡眠模式(SLEEP)事件(EVENT)唤醒实现及优化1. 介绍STM32具有多种低功耗模式,当前以STM32L4系列的低功耗模式最为丰富,此处基于STM32L476
    发表于 12-31 19:08 34次下载
    STM32 低功耗<b class='flag-5'>睡眠</b>模式(SLEEP)事件(EVENT)<b class='flag-5'>唤醒</b>实现及优化

    AN010 从深度睡眠模式2唤醒并恢复

    AN010 从深度睡眠模式2唤醒并恢复
    发表于 02-27 18:18 1次下载
    AN010 从深度<b class='flag-5'>睡眠</b>模式2<b class='flag-5'>唤醒</b>并恢复

    Linux电源管理的组成与睡眠唤醒

    (Clock)、频率(Frequency)、电压(Voltage)、睡眠/唤醒(Suspend/Resume)等方方面面。 Generic PM 软件架构 Generic PM 主要处理关机、重启、冬眠
    的头像 发表于 09-11 15:54 540次阅读
    <b class='flag-5'>Linux</b>电源管理的组成与<b class='flag-5'>睡眠</b><b class='flag-5'>唤醒</b>