linux内核抢占的几个细节

简介:

邮件列表每天都能让我学到新东西,感谢他!有朋友问PREEMPT_ACTIVE有什么用,我给出了最简单的回答,就是避免被抢占的进程被无情的赶出运行队列。这个回答显然不能让那位朋友满意...

进程一旦调用了schedule,如果再次被调度运行,那么有下面几种可能:1.状态为TASK_RUNNING,处于运行队列,那么它肯定有机会再运行;2.处于睡眠队列,那么一旦条件满足被唤醒,那么它就会运行。那么如果一个进程被抢占的话,而且它不在运行队列,那么怎么再让它运行呢?答案是它不能运行了。为了避免这种情况,就必须避免处于非TASK_RUNNING的进程被抢占的进程不被赶出运行队列,也就是下面的代码,schedule的代码:

if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {

switch_count = &prev->nvcsw;

if (unlikely((prev->state & TASK_INTERRUPTIBLE) && unlikely(signal_pending(prev))))

prev->state = TASK_RUNNING;

else {

if (prev->state == TASK_UNINTERRUPTIBLE)

rq->nr_uninterruptible++;

deactivate_task(prev, rq);

}

也许有人会问,怎么会有不是TASK_RUNNING的进程而且被抢占的,这个问题实在难以回答,可是记住,进程状态和其所在的队列没有关系,设置进程状态和抢占总是有可能有间隙的。我们看看下面的代码:

for (;;) { /

1: prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); /

2: if (condition) /

3: break; /

4: schedule(); /

}

如果在1中被抢占,恰恰在设置完进程为TASK_UNINTERRUPTIBLE的时候被抢占,本来马上就要测试条件是否满足了,结果又被加入睡眠队列去睡眠了,如果没有PREEMPT_ACTIVE,那么在schedule中就会被移出运行队列,如果只有这一次唤醒机会,那么就永远唤不醒这个进程了,如果本次从schedule回来条件不满足,那么在下面的schedue中就会被移出运行队列,这不是抢占的职责,如果非要怎么做就会出错,在dequeue_task中由array->queue已经为空了,在第二次真正出队的时候就会由于空指针引用而出错(这其实不会发生,因为只要从schedue回来,进程的状态肯定是TASK_RUNNING,仅仅是一个例子)。因此必须保证在将进程从运行队列移除的时候,它必须在运行队列,否则移个鸟啊!实际上PREEMPT_ACTIVE的作用就是防止将处于非TASK_RUNNING状态的进程并且没有在任何睡眠队列的进程移出运行队列,总之必须保证进程在一个队列中或者可以被唤醒,被抢占的进程是不能被唤醒的,如果它还不在运行队列中,那么它将永远不能再运行了。那么PREEMPT_ACTIVE是怎么保证被抢占的进程不会被移除运行队列呢?就是在preempt_schedule实现的:

asmlinkage void __sched preempt_schedule(void)

{

struct thread_info *ti = current_thread_info();

if (likely(ti->preempt_count || irqs_disabled()))

return;

do {

add_preempt_count(PREEMPT_ACTIVE); //设置PREEMPT_ACTIVE位,一直到下面的sub_preempt_count(PREEMPT_ACTIVE),这期间不能再抢占这个进程,不过再抢占也没有意义,如果非要抢占,出了下面的sub_preempt_count(PREEMPT_ACTIVE)也不迟

schedule();

sub_preempt_count(PREEMPT_ACTIVE); //抢占完毕后清除之

barrier();

} while (unlikely(test_thread_flag(TIF_NEED_RESCHED)));

}

除了这里之外,在早一些的内核中从中断返回内核空间时如果要抢占,在entery.S中也会加上这个这个PREEMPT_ACTIVE。现在还有一个问题,就是为何wait_event要用那种实现方式呢?为何需要一个循环呢?我的回答就是:这种情况下进程之所以能被唤醒就是因为它加入了一个睡眠队列,如果如你所说在schedule之后直接判断condition的话是不安全的,因为唤醒不一定是因为条件满足了,万一两个进程同时被唤醒那很可能有一个进程条件不能满足,如果正好此时进程被抢占,那么这个进程就没有机会加入睡眠队列了,也就没有机会被唤醒了,虽然PREEMPT_ACTIVE保证了这个进程不出运行队列,但是却失去了程序的本意,程序的本意是通过唤醒运行队列来使进程运行,而此时却成了完全依据优先级了,即使条件满足因为这个进程不在睡眠队列也不会被唤醒,系统就乱掉了。

其实很简单,必须在将进程加入到睡眠队列以后再判断条件,因为这样可以不漏掉唤醒通知,如果反过来的话,就是先判断再加入睡眠队列,如果在加入之前其它进程唤醒了这个睡眠队列,那么这个进程就会漏掉这次唤醒,之所以会有一个循环是因为可能不止一个进程被唤醒,那么就会出现竞争,这个循环就是为了竞争而设置的,这个循环保证了每个出了这个循环的进程都能安全带着结果为真的条件。

另外,说到TASK_RUNNING这个状态,又有人问了,为何在缺页中要把进程状态设置为TASK_RUNNING,难道缺页前不是TASK_RUNNING吗?大部分情况下应该是,可是linux内核不敢保证,之所以在handle_mm_fault中将进程状态设置为TASK_RUNNING是为了保证在缺页处理中如果睡眠,那么进程可以被唤醒,举个例子,在select中,当进程被设置为非TASK_RUNNING之后还会copy_from_user,而这却可能引起缺页。如果不把进程状态设置为TASK_RUNNING,那么万一在page fault中schedule了,那么这个进程就会被赶出运行队列,就再也回不来了,为了预防之,措施是:在任何调用schedule的地方分辨状态,然后设置进程状态,比如前面说的用PREEMPT_ACTIVE来预防,另外就是像handle_mm_fault中做到的一样,尽量使进程在TASK_RUNNABE状态下进入schedule。不过我想是不是现在这个应该去掉了,即使在缺页中不把进程设置为运行态,如果非要调度,也在之前设为运行台了。

ACTIVE_PREEMPT的作用:防止已经处于非运行态的进程还没有加入睡眠队列的时候就被抢占然后剔除出运行队列。这样就永远也回不来了,虽然这种情况很少见,一般都是先将进程放到睡眠队列再设置状态。


 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1273505


相关文章
|
9天前
|
Linux C语言
Linux内核队列queue.h
Linux内核队列queue.h
|
28天前
|
存储 Shell Linux
【Shell 命令集合 系统设置 】Linux 生成并更新内核模块的依赖 depmod命令 使用指南
【Shell 命令集合 系统设置 】Linux 生成并更新内核模块的依赖 depmod命令 使用指南
30 0
|
28天前
|
Shell Linux C语言
【Shell 命令集合 系统设置 】⭐Linux 卸载已加载的内核模块rmmod命令 使用指南
【Shell 命令集合 系统设置 】⭐Linux 卸载已加载的内核模块rmmod命令 使用指南
29 1
|
2月前
|
Ubuntu Linux 虚拟化
Linux下的IMX6ULL——构建bootloader、内核、文件系统(四)
Linux下的IMX6ULL——构建bootloader、内核、文件系统(四)
67 0
Linux下的IMX6ULL——构建bootloader、内核、文件系统(四)
|
2天前
|
算法 Linux 调度
深入理解Linux内核的进程调度机制
【4月更文挑战第17天】在多任务操作系统中,进程调度是核心功能之一,它决定了处理机资源的分配。本文旨在剖析Linux操作系统内核的进程调度机制,详细讨论其调度策略、调度算法及实现原理,并探讨了其对系统性能的影响。通过分析CFS(完全公平调度器)和实时调度策略,揭示了Linux如何在保证响应速度与公平性之间取得平衡。文章还将评估最新的调度技术趋势,如容器化和云计算环境下的调度优化。
|
7天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
18 3
|
14天前
|
负载均衡 算法 Linux
深度解析:Linux内核调度器的演变与优化策略
【4月更文挑战第5天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分——内核调度器。文章将首先回顾Linux内核调度器的发展历程,从早期的简单轮转调度(Round Robin)到现代的完全公平调度器(Completely Fair Scheduler, CFS)。接着,分析当前CFS面临的挑战以及社区提出的各种优化方案,最后提出未来可能的发展趋势和研究方向。通过本文,读者将对Linux调度器的原理、实现及其优化有一个全面的认识。
|
14天前
|
Ubuntu Linux
Linux查看内核版本
在Linux系统中查看内核版本有多种方法:1) 使用`uname -r`命令直接显示版本号;2) 通过`cat /proc/version`查看内核详细信息;3) 利用`dmesg | grep Linux`显示内核版本行;4) 如果支持,使用`lsb_release -a`查看发行版及内核版本。
36 6
|
17天前
|
Linux 内存技术
Linux内核读取spi-nor flash sn
Linux内核读取spi-nor flash sn
14 1
|
24天前
|
存储 网络协议 Linux
【Linux 解惑 】谈谈你对linux内核的理解
【Linux 解惑 】谈谈你对linux内核的理解
23 0