Linux内核设计与实现 进程调度3: 进程抢占

负载均衡

        我们先前提到过,schedule()和运行队列等等都是针对于单个处理器而言的。那么,是否存在某种机制来解决多处理器系统中负载不均的状况呢?想象某个拥有五个处理器的系统,Acpu上处理5个进程,而Bcpu仅仅处理1个,毋庸置疑地浪费了Bcpu中大量的计算资源,却又使得Acpu苦不堪言,导致整个系统的性能下降。所以内核提供了负载均衡器(load balancer)解决该难题。在<kernel/sched.c>中能看到相关代码。

        负载均衡器的核心函数是load_balance(),该函数可以通过两种渠道被调用。一种是在schedule()中,当schedule()检测到当前CPU运行队列中的进程数为零时,负载均衡函数被调用。还有一种是因为定时器而被调用——比如每过一毫秒且CPU相对空闲的时候,以及类似的时候。在load_balance()运行时,将会给当前运行队列上锁,以及确保中断禁用,防止并发地访问运行队列。

        两种方式的调用结果存在差异。schedule()的调用里,工作十分简单——因为当前队列是空的。所以我们只需要寻找到某个不为空的核,将其进程拉过来即可。在定时器的调用里,我们需要解决任何运行队列中的不平衡。

        接下来是load_balance()的执行步骤。

        1.调用find_busiest_queue()寻找最繁忙的运行队列。如果最繁忙的队列中进程的数目至少比当前运行队列中多25%,则程序继续,否则返回。

        2.寻找其试图迁移的优先队列,一般来说推荐过气队列,因为过气队列中的进程已经有一段时间没有运行了,也更有可能不在当前CPU的CACHE中。如果过气队列为空,那么搜寻活动队列。

        3.搜寻选定队列中的最高优先级进程的列表。这些进程更重要因此更迫切地需要运行。当然之间有一些小插曲,详细看下面的代码:

load_balance() 1

        在这里稍微提一下已经多次出现的list_entry()函数,这实际上是一个双向链表相关的函数。在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head。虽然Linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作list_head 的通用接口很容易实现代码的重用,有点类似于C++的继承机制。详情见Linux 内核list_head 学习(一)

        4.每个给定优先级列表中的任务都被分析,以此来寻找适当的任务进行迁移。“适当”指的(1)是不在当前CPU的CACHE中。(2)不被禁止迁移。(3)不是当前运行的任务。这一步骤通过调用can_migrate_task()来实现:

can_migrate_task()

        5.如果不平衡情况依旧存在,则跳转继续处理。否则退出函数。

进程抢占和上下文切换

        上下文切换是指从当前运行进程切换到其他可执行进程。通过定义在<kernel/sched.c>的context_switch()来实现。当某个新的进程被选择而即将被运行时,该函数被schedule()调用。该函数执行了以下两个基本工作:

        1.调用<asm/mmu_context.h>中定义的switch_mm()函数,切换虚拟内存映射。

        2.调用<asm/system.h>定义的switch_to()函数。保存当前进程的上下文,并进行上下文切换

        进程上下文,就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。

        (1)用户级上下文: 正文、数据、用户堆栈以及共享存储区;

        (2)寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);

        (3)系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。

      当发生进程调度时,进行进程切换就是上下文切换(context switch).操作系统必须对上面提到的全部信息进行切换,新调度的进程才能运行。而系统调用进行的模式切换(mode switch)。模式切换与进程切换比较起来,容易很多,而且节省时间,因为模式切换最主要的任务只是切换进程寄存器上下文的切换。详情查看用户空间与内核空间,进程上下文与中断上下文[总结]一文。

        回归主题,那么对于内核来说。内核必须知道什么时候该执行schedule()。如果只在代码显示地调用时调用,那么用户空间的程序就会无休无止地运行下去。所以,内核提供了nead_resched(该标志似乎存储在thread_info结构的flags里,其对应与flags取值为TIF_NEED_RESCHED)标志来表示是否需要执行调度。如果要设定这个标志,就要调用scheduler_tick(),而调用只会发生在(1)当一个进程耗尽其时间片。(2)在try_to_wake_up()函数里——当一个比当前进程优先级更高的任务被唤醒时。

        以下介绍三个用于nead_resched的函数:

        1.set_tsk_need_resched()

        2.clear_tsk_need_resched()

        3.need_resched()

        除上述,当从用户空间返回,或者是从中断函数中返回的时候,nead_resched就会被检查。该标志存储在每个进程的thread_info结构中的flags成员里。如果flags的值为TIF_NEED_RESCHED时,就等价于nead_resched被置位。

 

用户抢占

        用户级的进程抢占发生在内核将要返回用户空间时,并且nead_resched被设置时。   

        总而言之,用户级进程抢占可能发生在:

            1.从系统调用返回用户空间时。

            2.从中断处理函数返回用户空间时。

内核抢占

        Linux和其他大多数的Unix系统以及其他派别的系统不同,Linux是一个完全可抢占的(先发制人的)操作系统。在不是完全可抢占的系统中,内核代码会持续执行直到全部完成。也就是说,调度进程没有能力去打断一个正在内核中运行的任务。内核进程是合作地进行(平均分配时间?),而不是引入抢占机制的。内核代码会运行直到它完成并返回到用户空间,或者是显式地阻塞。但是,我们的Linux系统是完全可抢占的:一个进程可能在任何时间点被抢占,只要任务处在安全的可切换的状态。

        什么时候适合重新安排呢?当内核任务不占用“锁”时,内核就具备抢占其的能力。也就是说,如果一个内核进程不拥有锁,那么该进程就是可重入的,并且是可被抢占的。

        与用户级抢占不同,内核级别的抢占引入了新的标志,就是thread_info结构中的preempt_count成员。其初始值为0,当进程每每获得锁时就增加1,每每释放锁时就减1。所以当该值为0时,内核是可抢占的。下面讨论从中断里返回的情况:如果返回的是内核空间,内核会检查当前的进程的preempt_countnead_resched的值。如果都满足需求——这意味着某任务继续调度,并且当前进程处在安全(可以重新调度)的状态,就会调用schedule()。如果不满足上述条件。中断程序结束后,将会常规地返回被中断处继续执行。当前内核进程释放完所有锁时,也会检查nead_resched是否被设置。

        总而言之,内核级抢占可能发生在:

        1.当中断进程返回到内核空间时。

        2.当内核程序再次变得可抢占时。

        3.内核态运行的任务显示调用schedule()时。

        4.内核态进程阻塞时。(是任务代码的自我实现吗?还是某种机制检测到该任务阻塞后启动调度进程?)

实时

        Linux提供了两种实时调度算法SCHED_FIFO和SCHED_RR。前者就是简单地先到先执行原则,在该调度算法中不存在时间片的说法,当这样的任务变为可运行状态,会一直运行直到它阻塞或者是显式地放弃cpu。并且用SCHED_FIFO的进程总比SCHED_NORMAL的优先调度。采用SCHED_FIFO算法的进程只能被同为SCHED_FIFO的或者是SCHED_RR的进程抢占。两个以上的具有相同优先级的FIFO类进程才用轮流执行的方式。如果一个SCHED_FIFO的进程执行,那么所有优先级低于它的进程无法翻身,直到该进程结束。

      SCHED_RR与SCHED_FIFO的区别在于,使用该算法的进程拥有时间片。进程们只能在耗尽自己预定义的时间片前运行。同一优先级的进程轮流执行。因此在SCHED_RR中时间片仅对相同优先级的进程有意义。一个较低优先级的进程永远不可能抢占一个SCHED_RR的进程,尽管这个进程耗尽了自己的时间片(但未结束)。

        SCHED_NORMAL就是我们先前讨论的一般进程调度方法。

        我们用实时优先级表示实时进程的优先级。正如先前提到,其值为[0,99(MAX_RT_PRIO-1)]。而我们知道常规的进程优先级是[-20,19]。后来为了体系里表示的简单,我们将Nice的值映射到了[MAX_RT_PRIO,MAX_RT_PRIO+40]。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容