我正在看书Linux内核开发上章流程调度。第 61 页,部分起来,第一段内容如下:
唤醒是通过wake_up()来处理的,唤醒全部在给定等待队列上等待的任务。它(Q1:这是
it
指什么?)调用try_to_wake_up(),它设置任务的(Q2:哪个任务?所有唤醒任务?)状态为TASK_RUNNING,调用enqueue_task()将任务添加到红黑树中,如果唤醒任务的优先级高于当前任务的优先级,则设置need_resched。导致事件发生的代码通常调用wake_up( )本身。例如,当数据从硬盘到达时,VFS 在等待队列上调用wake_up(),该队列保存等待数据的进程。
我对以上的情况感到很困惑。我就用上一段的例子,即读取数据后磁盘中断,但有一个更完整的画面。以下如有错误或不完整之处,敬请指正:
某些用户进程发出阻塞读操作,触发系统调用,并且该进程位于内核领域。
内核设置磁盘控制器请求所需的数据,并将该进程置于睡眠状态(该进程被放入等待队列)。内核安排另一个进程运行。
发生磁盘中断。 CPU暂停当前正在执行的进程并跳转到磁盘中断处理。
磁盘控制器将在中断处理期间启动,将从磁盘读取的数据传输到主内存(在 CPU 的指导下或通过 DMA)
(不确定,请更正)正如段落中所说,VFS在等待队列上调用wake_up(),该队列保存等待数据的进程。
我的具体问题如下:
Q1(参见引用的段落):我假设它第二句中指的是函数wake_up()
。为什么函数会被wake_up
唤醒全部任务而不仅仅是等待该磁盘数据的任务?
Q2(请参阅引用的段落):是否以try_to_wake_up()
某种方式知道需要将其状态设置为 TASK_RUNNING 的特定任务?或者try_to_wake_up()
将所有唤醒任务的状态设置为 TASK_RUNNING?
第三季度:内核需要管理多少个等待队列?如果这样的等待队列超过2个,那么内核如何知道选择哪个队列,使得等待磁盘数据的进程就在那个等待队列上呢?
第四季度:现在假设我们知道等待进程所在的队列。内核如何知道哪个进程正在等待磁盘上的数据。我只能想象一些特定于请求磁盘数据的进程的信息被传递到磁盘控制器,比如进程的 PID、内存地址等等。然后,在完成中断处理后,磁盘控制器(或内核?)使用此信息来查明等待队列上的进程。
请帮我完成这张唤醒过程的图片!谢谢!
答案1
Q1:“它”是wake_up
。它唤醒所有任务正在等待磁盘数据。如果他们不等待该数据,他们就不会在该队列中等待。
Q2:我不确定我是否理解这个问题。每个唤醒队列条目都包含一个指向任务的指针。try_to_wake_up
接收一个指向它应该唤醒的任务的指针。每个函数调用一次。
Q3:排队等候的人很多。每一种可能发生的事件都有一个。磁盘驱动程序为每个磁盘请求设置一个等待队列。例如,当文件系统驱动程序想要某个磁盘块的内容时,它会向磁盘驱动程序询问该块,然后该请求从发出文件系统请求的任务开始。如果对同一块的另一个请求进来,而该块仍然未完成,则其他条目可能会添加到等待队列中。
当中断发生时,磁盘驱动程序根据硬件传递的信息确定哪个磁盘有可用数据,并查找包含该磁盘的内核数据的数据结构,以找到要满足哪个请求。在该数据结构中,除其他外,还有要写入数据的位置以及指示下一步要做什么的相应唤醒队列。
Q4:进程进行系统调用,比如说读取文件。这会触发文件系统驱动程序中的一些代码,确定需要从磁盘加载数据。该代码向磁盘驱动程序发出请求,并将调用进程添加到请求的等待队列中。 (实际上有更多的层,但你明白了。)当磁盘读取完成时,等待队列事件触发,进程因此从磁盘的等待队列中删除。等待队列事件触发的代码是文件系统驱动程序提供的函数,它将数据复制到进程的内存并导致系统read
调用返回。
答案2
以吉尔斯的答案为基础,
Q2:我不确定,但我会将书中的段落解释为中断处理程序调用wake_up()
一次
(将队列标识符作为参数传递),并wake_up()
调用try_to_wake_up()
该队列上的每个进程。 (这重申了吉尔斯的回答Q1:它不会唤醒所有任务,只会唤醒与调用的事件关联的等待队列中的任务wake_up()
。)
第三季度:每个等待队列都由某些内核代码“拥有”——主要是设备驱动程序,还有一些其他代码。拥有队列的例程根据队列所属事件的某些唯一特征为其分配唯一标识符。当它(驱动程序/其他模块)将进程置于睡眠状态时,它会(通过 ID)指定将其放入哪个队列。调用的例程wake_up()
(通常是中断处理程序)必须属于使进程进入睡眠状态的同一模块的一部分,因此它知道与发生的事件相对应的队列的标识符。
上次我查看 Unix 内核源代码(那是很多年前),磁盘驱动程序对于每个 I/O 请求都有不同的事件 ID。正如 Gilles 所说,如果多个进程同时读取同一个文件,则它们可能会等待同一个事件。 (当然,这也与Q1.)
第四季度:当我听到“磁盘控制器”这个词时,我想到的是硬件。但是,除此之外,你是对的;磁盘司机
(内核中的软件模块)可以(至少有可能)访问全部有关调用它的任何进程(即通过执行磁盘 I/O)的信息。因此,当磁盘驱动程序由于启动了需要时间才能完成的物理 I/O 而将进程置于睡眠状态时,它(驱动程序)会将“进程的 PID、内存地址或其他内容”放入等待队列中。无论这是什么,都足以try_to_wake_up()
唤醒该进程。
您引用的段落的最后一句是:“……VFS 在等待队列上调用wake_up()……”。我质疑这是否真的准确。文件系统代码位于磁盘驱动程序之上的一层。我希望中断(从磁盘硬件到CPU的信号)由磁盘中断处理程序(磁盘驱动程序的一部分)处理,这将唤醒正在等待的进程(通过调用wake_up()
)。驱动程序随后将唤醒文件系统代码。 (这个术语也可能不精确。最好说驱动程序做了一些事情来允许文件系统代码恢复处理。)然后文件系统代码可能返回到用户进程,或者它可能再次调用磁盘驱动程序,导致进程再次进入睡眠状态。
我对你的第四步有异议。如果设备正在使用 DMA,则在数据传输完成之前不会中断。
答案3
你提到的问题,醒来全部等待的进程就是所谓的“惊群”问题(当资源可用时,许多进程被唤醒,它们争夺哪个进程获得对该资源的独占访问权,其他进程则返回睡眠状态)。当有很多处理器时,这就会成为一个问题,因此有很多处理器在这里竞争。较新版本的 Linux 通过唤醒一个正在等待的进程来解决这个问题。
大多数情况下,会有一个(或几个)进程等待某一特定事件(进程可以等待的事件数量并不固定,更不用说数量很少了)。
吉尔斯的回答一一阐述了您的观点,这里没有太多补充。