共计 2533 个字符,预计需要花费 7 分钟才能阅读完成。
“小明同学最近贼郁闷,去年玩比特币亏得一塌糊涂,想在股市里翻盘,听信大 V 举荐的股票,买了康美和长生生物,被 A 股狠狠的收割了一把。本想往年好好工作,谁晓得遇上了 O 记大裁员。尽管拿了 N +6,然而还是要找工作养家糊口啊!只能硬着头皮去面试了,还好,面试题目都不难,因为小明认真看过《奔跑吧》”
01 面试题目
在一个双核处理器的零碎中,在 shell 界面下运行 test 程序。CPU0 的就绪队列上有 4 个过程,而 CPU1 的就绪队列有 1 个过程。假如 test 程序和这个 5 个过程的 nice 值都是为 0。
test 程序
执行 test 过程
问题:
- 请画出 test 程序在内核空间的执行流程图。
- 若干工夫之后,CPU0 和 CPU1 的就绪队列变动如何?
小明听到这题目,嘿嘿一笑,so easy,难不到窝,这题目比炒 A 股简略多了!于是在黑板上开始边写边画,缄口结舌。
02 题目解析
站在用户空间的角度看,在 shell 界面下执行 test 程序,shell 程序会调用 fork 零碎调用来创立一个新过程,而后调用 exec 零碎调用来装载 test 过程,因而新过程便开始执行 test 程序。
站在用户空间的角度看问题,咱们只能看到 test 程序被执行了,然而咱们是看不到新过程是如何被创立的,它会增加到哪个 CPU 里,它是如何执行的,以及 CPU0 和 CPU1 之间如何做负载平衡等等问题。
小明画的 test 过程执行流程图
从上图可知,咱们把 test 过程在内核空间的执行过程分成了 6 个步骤。
(1)调用零碎调用 fork 零碎调用来创立一个新过程
(2)do_fork()创立新过程。do_fork 要做好多事件,比方:
a)创立新过程过程管制块 PCB,task_struct 数据结构。
b)拷贝父过程的 task_struct 数据结构内容到新过程。
c)拷贝父过程的页表项内容到新过程。
d)设置新过程的内核栈
(3)父过程调用 wake_up_new_task()尝试去唤醒新过程。
a)调用调度类的 select_task_rq()办法,为新过程寻找一个负载最轻的 CPU,这里抉择 CPU1。
b)调用调度类的 enqueue_task()办法把新过程增加到 CPU1 的就绪队列里。
(4)CPU1 从新抉择一个适合的过程来运行。
a)每次时钟滴答到来时,scheduler_tick()会调用调度类的 task_tick()去查看是否须要从新调度。check_preempt_tick()查看是否须要从新调度。若须要调度,则设置以后过程的 thread_info 中的 TIF_NEED_RESCHED 标记位。假如在咱们这个场景里,CPU1 筹备调度咱们的新过程 P,那么就会设置以后过程 curr 的 thread_info 中的 TIF_NEED_RESCHED 标记位。
b)在中断返回前会查看以后过程 curr 的 TIF_NEED_RESCHED 标记位。如果须要调度的话,调用 preempt_schedule_irq()来切换过程运行。
c)调度器的 schedule()函数会调用调度类的 pick_next_task()办法来抉择下一个最合适运行的过程。在咱们的场景中,抉择新过程 P。
d)switch_mm()切换 prev 过程和新过程的页表。
e)在 CPU1 上,switch_to()切换新过程 P 来执行。
(5)新过程执行。
a)新过程的第一次执行会调用 ret_from_fork()函数。
b)返回用户空间执行 shell 程序。
c)shell 程序调用 exec()零碎调用来执行 test 程序,最终新过程变成了 test 过程。
(6)负载平衡。
a)在每次时钟滴答到来时去查看是否须要触发软中断来做 SMP 负载平衡。scheduler_tick()->trigger_load_balance()。下一次做负载平衡的工夫点寄存在就绪队列的 next_balance 成员里。
b)触发 SCHED_SOFTIRQ 软中断。
c)在软中断处理函数 run_rebalance_domains()里,从以后 CPU 开始遍历 CPU 拓扑关系图,从调度域的低层往高层遍历调度域,并寻找有负载不平均的调度组。本例子中的 CPU 拓扑关系图很简略,只有一层 MC 级别的调度域。
d)CPU0 对应的调度域是 domain_mc_0,对应的调度组是 group_mc_0;CPU1 对应的调度域是 domain_mc_1,对应的调度组是 group_mc_1。CPU0 的调度域 domain_mc_0 管辖 CPU0 和 CPU1,其中 group_mc_0 和 group_mc_1 这两个调度组会连贯到 domain_mc_0 的一个链表里,同理 CPU1 的调度域 domain_mc_1 也是同样治理着 group_mc_1 和 group_mc_0 这两个调度组。
e)假如以后运行的 CPU 是 CPU1,也就是说运行到 run_rebalance_domains()函数的 CPU 为 CPU1,那么在以后 MC 的调度域 (domain_mc_1) 里去找哪个调度组是最忙碌的。在咱们的场景里,很容易找到 CPU0 的那个调度组 (group_mc_0) 是最忙碌的。计算须要迁徙多少的负载量到 CPU1 上能力放弃两个调度组负载平衡。
f)迁徙过程从 CPU0 到 CPU1。
g)达到新的均衡。
笨叔点评:
这是一道很棒的面试题目,把过程方方面面的问题都考到了,比如说过程的创立,过程的调度,过程的执行,SMP 负载平衡。外面任何的一点,都足以难倒很多人,比如说:
过程的实质是什么?
过程的调度实质是什么?
调度器怎么抉择下一个过程?
处理器是怎么就切换和执行到下一个过程?
CFS 调度器和过程优先级到底有什么关系?
CFS 调度器里的虚构时钟是个什么鬼?
runnable 和 running 是啥关系?
什么是过程的权重?
什么是过程的负载?
调度域和调度组是个什么鬼?
如何能疾速画出一个零碎的 CPU 拓扑关系图?
SMP 负载平衡里是怎么掂量一个就绪队列的负载的?
SMP 负载平衡算法是怎么玩的?
负载和权重到底有什么关系?
Linux 的负载平衡算法里说的负载是怎么计算的?
怎么了解衰减(decay)?
…
笨叔正在批改蓝色版本的《奔跑吧》,基于 Linux 5.0+ARM64,欢送大家提意见和 idea!好的 idea,笨叔好酒相赠!
更多精彩内容,关注笨叔的微信公众号以及笨叔免费旗舰篇视频。