细说进程为什么需要睡眠?

细说进程为什么需要睡眠?

我们还是以在餐厅等位作为例子,如果餐厅提供一个通知客人的设备,当有空位时,服务员可以通过这个设备通知等待中的客人。那么,客人就不需要不断去询问服务员空位的状况,可以利用等待这段时间小瞌一会。

类似的,服务端程序在等待客户端请求到来这段时间,操作系统可以先把服务端进程挂起(睡眠),然后执行其他可运行的进程。当有客户端请求到来时,操作系统唤醒服务端进程来处理客户端的请求。

如下图所示:

当进程M在等待系统中某些资源变为就绪状态时,操作系统会把进程M的从 CPU 中切换出去,然后把进程M防止到睡眠队列中。接着操作系统会从可运行队列中,选择一个最合适的进程(如图中的进程1)调度到 CPU 中运行。

当进程M等待的资源变为就绪状态后,操作系统操作系统便会把进程M放置回可运行队列中。这样,进程M就可以在下一个调度周期中争夺 CPU 的运行时间。如下图所示:

在上图中,当客户端请求到来后,操作系统便会唤醒等待客户端请求的进程M,然后把其放回到可运行队列中。

这个就是操作系统中的睡眠与唤醒机制。

Linux 的睡眠与唤醒机制实现

在 Linux 内核中,很多系统调用和内核函数都可能会导致进程睡眠,如 I/O 相关的系统调用、sleep类内核函数、内存分配函数等。

下面我们以sleep类内核函数来分析 Linux 是如何实现睡眠与唤醒机制的。

1. 睡眠函数的使用

在 Linux 内核中,如果想让一个进程进入睡眠状态,可以调用schedule_timeout_interruptible内核函数。其原型如下:

signedlong__schedschedule_timeout_interruptible(signedlongtimeout);

参数timeout表示希望进程睡眠多长时间,此函数会让进程睡眠timeout个时钟节拍(tick)的时间。例如,如果希望进程睡眠 1 秒,可以使用如下代码实现:

...

schedule_timeout_interruptible(1*HZ);

//1秒后进程被唤醒,继续执行下面代码

...

2. 睡眠函数的实现

接下来,我们来分析一下schedule_timeout_interruptible内核函数是如何让进程进入睡眠状态的。其代码如下所示:

signedlong__schedschedule_timeout_interruptible(signedlongtimeout)

{

__set_current_state(TASK_INTERRUPTIBLE);

returnschedule_timeout(timeout);

}

schedule_timeout_interruptible内核函数主要做了如下两件事情:

调用 __set_current_state函数将进程设置为可 中断睡眠状态。需要注意的是,这个步骤只是将进程的状态设置为可中断睡眠状态,但此时进程还没有被内核调度程序移出 CPU。

调用 schedule_timeout函数使进程真正进入睡眠状态(放弃 CPU 的使用权限)。

调用 __set_current_state函数将进程设置为可 中断睡眠状态。需要注意的是,这个步骤只是将进程的状态设置为可中断睡眠状态,但此时进程还没有被内核调度程序移出 CPU。

调用 schedule_timeout函数使进程真正进入睡眠状态(放弃 CPU 的使用权限)。

从上面代码可以看出,schedule_timeout函数才是使进程进入睡眠的主体。那么,我们继续来分析schedule_timeout函数的实现:

signedlong__schedschedule_timeout(signedlongtimeout)

{

structtimer_listtimer;

unsignedlongexpire;

...

expire=timeout+jiffies;

//1.将当前进程添加到定时器中,定时器的超时时间为expire,回调函数为process_timeout

setup_timer_on_stack(&timer,process_timeout,(unsignedlong)current);

__mod_timer(&timer,expire,false,TIMER_NOT_PINNED);

//2.主动触发内核进行进程调度

schedule;

//3.将进程从定时器中删除

del_singleshot_timer_sync(&timer);

destroy_timer_on_stack(&timer);

timeout=expire-jiffies;

out:

returntimeout<0?0:timeout;

}

schedule_timeout函数的逻辑主要分为以下三个步骤:

将当前进程添加到定时器中,定时器的超时时间设置为 expire,回调函数为 process_timeout。那么当定时器超时时,便会触发调用 process_timeout函数。

调用 schedule函数触发内核进行进程调度。由于当前进程在 schedule_timeout_interruptible函数中被设置为 可中断睡眠状态,所以当调度器发现当前进程是 可中断睡眠状态时,将会把当前进程移出可运行队列,并且让出 CPU 的使用权限。

当进程被唤醒后,将会把进程从定时器中删除。

将当前进程添加到定时器中,定时器的超时时间设置为 expire,回调函数为 process_timeout。那么当定时器超时时,便会触发调用 process_timeout函数。

调用 schedule函数触发内核进行进程调度。由于当前进程在 schedule_timeout_interruptible函数中被设置为 可中断睡眠状态,所以当调度器发现当前进程是 可中断睡眠状态时,将会把当前进程移出可运行队列,并且让出 CPU 的使用权限。

当进程被唤醒后,将会把进程从定时器中删除。

从上面的分析可知,在调用schedule_timeout函数时,内核会为当前进程创建一个定时器,其超时时间被设置为schedule_timeout函数传入的参数加上当前时间。当定时器到期后,便会触发调用process_timeout函数,而process_timeout函数最终会调用try_to_wake_up函数来唤醒进程。

我们接着来分析下try_to_wake_up函数的实现,看看其如何唤醒进程的:

staticint

try_to_wake_up(structtask_struct*p,unsignedintstate,intwake_flags)

{

unsignedlongflags;

intcpu,success=0;

...

//1.为进程挑选一个最合适的CPU运行

cpu=select_task_rq(p,p->wake_cpu,SD_BALANCE_WAKE,wake_flags);

...

//2.把进程添加到CPU的可运行队列中

ttwu_queue(p,cpu);

...

returnsuccess;

}

在上面的代码中,我们只保留了核心的代码。可以看出try_to_wake_up函数主要完成 2 件事情:

调用 select_task_rq函数为进程挑选一个最合适的 CPU 运行。

调用 ttwu_queue函数把进程添加到 CPU 的可运行队列中。

调用 select_task_rq函数为进程挑选一个最合适的 CPU 运行。

调用 ttwu_queue函数把进程添加到 CPU 的可运行队列中。

被唤醒的进程添加到 CPU 的可运行队列后,并不会立即被执行。内核会在下一个调度周期中,选择合适的进程进行调度时,被唤醒的进程才有可能被选中运行。

总结

本文主要介绍了进程为什么需要有睡眠和唤醒功能,并且分析了进程睡眠与唤醒的实现原理。进程睡眠与唤醒功能主要为了解决进程在等待某些资源变为可用时,需要不断探测资源的状态。这种探测既白白浪费了宝贵的 CPU 时间,而且还影响了系统的吞吐量。

而进程睡眠可以在进程等待资源变为可用状态时,主动放弃 CPU 的使用权限,这时 CPU 便可运行其他可运行的进程,从而使 CPU 的利用率达到最优。当进程等待的资源变为可用时,内核主动唤醒等待中的进程,进程便可以继续运行。返回搜狐,查看更多

🌟 相关推荐

《魔兽世界》战友招募专题
365bet开户地址

《魔兽世界》战友招募专题

📅 07-05 👀 1306
10 款基於埃及神話的最佳遊戲排名
365bet开户地址

10 款基於埃及神話的最佳遊戲排名

📅 08-28 👀 6961
佛跳墙的典故和名字来历——你知道它为何叫这个名字吗?