schedulelinux
Ⅰ 一文解析linux进程的睡眠和唤醒
在Linux中,进程的睡眠与唤醒是一个关键概念。当进程执行完时间片后,Linux内核会通过调度器选择下一个进程,使之获取CPU控制权。此外,进程也可以主动通过调用`schele()`函数释放CPU控制权,使其他进程运行。当进程需要等待特定事件发生,如设备初始化完成或I/O操作结束时,它会进入睡眠状态,从运行队列移出,加入等待队列。
Linux中的进程睡眠有两种状态:可中断的睡眠与不可中断的睡眠。可中断的睡眠进程在满足特定条件时被唤醒,如硬件中断、系统资源释放或信号传递。而不可中断的睡眠状态较为少见,主要用于进程必须等待特定事件,直到事件发生而不会被中断。
Linux操作系统中,进程通常通过`schele()`函数进入睡眠状态。程序首先存储进程结构指针,然后使用`set_current_state()`将进程状态从执行状态改为睡眠状态。如果`schele()`被处于睡眠状态的进程调用,进程将从运行队列中移出。要唤醒睡眠中的进程,可以使用`wake_up_process()`函数,这将改变进程状态并将其加入运行队列,等待下一次调度。
然而,进程有时可能因竞争条件而进入无效唤醒状态,导致无限期睡眠。这个问题发生在进程检查条件后,状态未被改为睡眠状态之前。解决方法是将判断条件与状态更改合并为一个步骤,消除竞争条件,确保唤醒过程有效。
为了在Linux内核中避免无效唤醒,应使用特定的内核函数进行进程睡眠。例如,`DECLARE_WAITQUEUE()`用于创建等待队列的项,`add_wait_queue()`将进程加入等待队列并更改状态为可中断状态。然后,进程循环检查条件,如果条件满足,设置状态为运行状态并移出等待队列。若条件未满足,调用`schele()`使进程重新调度。这种设计确保进程在条件满足时能够及时退出睡眠状态,避免无效唤醒。
在Linux内核中,代码示例展示了如何通过合理设计避免无效唤醒。通过在检查条件之前设置进程状态为睡眠状态,确保即使在条件满足时进程也不会错误地进入睡眠,从而有效避免了无效唤醒问题。
原文作者:Linux内核那些事
参考资源:内核技术中文网 - 构建全国最权威的内核技术交流分享论坛
Ⅱ linux 什么时候调用schele
Linux在众多进程中是怎么进行调度的,这个牵涉到Linux进程调度时机的概念,由Linux内核中Schele()的函数来决定是否要进行进程的切换,如果要切换的话,切换到哪个进程等等。
Linux进程调度时机主要有:
1、进程状态转换的时刻:进程终止、进程睡眠;
2、当前进程的时间片用完时(current->counter=0);
3、设备驱动程序
4、进程从中断、异常及系统调用返回到用户态时;
时机1,进程要调用sleep()或exit()等函数进行状态转换,这些函数会主动调用调度程序进行进程调度;
时机2,由于进程的时间片是由时钟中断来更新的,因此,这种情况和时机4是一样的。
时机3,当设备驱动程序执行长而重复的任务时,直接调用调度程序。在每次反复循环中,驱动程序都检查need_resched的值,如果必要,则调用调度程序schele()主动放弃CPU。
时机4,如前所述,不管是从中断、异常还是系统调用返回,最终都调用ret_from_sys_call(),由这个函数进行调度标志的检测,如果必要,则调用调用调度程序。那么,为什么从系统调用返回时要调用调度程序呢?这当然是从效率考虑。从系统调用返回意味着要离开内核态而返回到用户态,而状态的转换要花费一定的时间,因此,在返回到用户态前,系统把在内核态该处理的事全部做完。
对于直接执行调度程序的时机,我们不讨论,因为后面我们将会描述调度程序的工作过程。前面我们讨论了时钟中断,知道了时钟中断的重要作用,下面我们就简单看一下每个时钟中断发生时内核要做的工作,首先对这个最频繁的调度时机有一个大体了解,然后再详细讨论调度程序的具体工作过程。
每个时钟中断(timer interrupt)发生时,由三个函数协同工作,共同完成进程的选择和切换,它们是:schele()、do_timer()及ret_form_sys_call()。我们先来解释一下这三个函数:
schele():进程调度函数,由它来完成进程的选择(调度);
do_timer():暂且称之为时钟函数,该函数在时钟中断服务程序中被调用,是时钟中断服务程序的主要组成部分,该函数被调用的频率就是时钟中断的频率即每秒钟100次(简称100赫兹或100Hz);
ret_from_sys_call():系统调用返回函数。当一个系统调用或中断完成时,该函数被调用,用于处理一些收尾工作,例如信号处理、核心任务等等。
这三个函数是如何协调工作的呢?
前面我们看到,时钟中断是一个中断服务程序,它的主要组成部分就是时钟函数do_timer(),由这个函数完成系统时间的更新、进程时间片的更新等工作,更新后的进程时间片counter作为调度的主要依据。
在时钟中断返回时,要调用函数ret_from_sys_call(),前面我们已经讨论过这个函数,在这个函数中有如下几行:
cmpl $0, _need_resched
jne reschele
……
restore_all:
RESTORE_ALL
reschele:
call SYMBOL_NAME(schele)
jmp ret_from_sys_call
这几行的意思很明显:检测 need_resched 标志,如果此标志为非0,那么就转到reschele处调用调度程序schele()进行进程的选择。调度程序schele()会根据具体的标准在运行队列中选择下一个应该运行的进程。当从调度程序返回时,如果发现又有调度标志被设置,则又调用调度程序,直到调度标志为0,这时,从调度程序返回时由RESTORE_ALL恢复被选定进程的环境,返回到被选定进程的用户空间,使之得到运行。
以上就是时钟中断这个最频繁的调度时机。讨论这个的主要目的使读者对时机4有个大致的了解。
另外,TIF_NEED_RESCHED的设置时机 :
设置这个标志的函数主要有两个: resched_task(),set_tsk_need_resched().主要是resched_task,而resched_task的调用者 check_preempt_curr更是通过:try_to_wake_up/wake_up_new_task/pull_task /__migrate_task 这些被广泛使用的函数, 从而分布在内核中大量的检查点有机会抢占进程.
最后要说明的是,系统调用返回函数ret_from_sys_call()是从系统调用、异常及中断返回函数通常要调用的函数,但并不是非得调用,对于那些要经常被响应的和要被尽快处理的中断请求信号,为了减少系统开销,处理完成后并不调用 ret_from_sys_call()(因为很显然的,从这些中断处理程序返回到的用户空间肯定是那个被中断的进程,无需重新选择),并且,它们作的工作要尽可能少,因为响应的频率太高了。
Linux进程调度和其他的UNIX进程调度不同,尤其是在“nice level”优先级的处理上,与优先权调度(priority高的进程最先运行)不同,Linux用的是时间片轮转调度(Round Robing),但同时又保证了高优先级的进程运行的既快、时间又长(both sooner and longer)。而标准的UNIX调度程序都用到了多级进程队列。大多数的实现都用到了二级优先队列:一个标准队列和一个实时(“real time”)队列。一般情况下,如果实时队列中的进程未被阻塞,它们都要在标准队列中的进程之前被执行,并且,每个队列中,“nice level”高的进程先被执行。
总体上,Linux 调度序程在交互性方面表现很出色,当然了,这是以牺牲一部分“吞吐量”为代价的。