gpt4 book ai didi

process - 进程调用syscall wait()后,谁将其唤醒?

转载 作者:行者123 更新时间:2023-12-04 17:32:14 25 4
gpt4 key购买 nike

我有一个大致的想法,一个进程可以在ready_queue中,CPU选择下一个要运行的候选对象。还有其他一些队列,进程在这些队列上等待(广义上来说)事件。我很早以前就从OS课程中知道,有IO和中断的等待队列。我的问题是:


进程可以等待许多事件。是否有与每个此类事件相对应的等待队列?
这些等待队列是否动态创建/销毁?如果是这样,哪个内核模块负责管理这些队列?调度程序?是否有将始终存在的预定义队列?
为了最终使等待进程脱离等待队列,内核是否有一种从每个实际事件(硬件或软件)到等待队列的映射方式,然后删除该队列上的所有进程?如果是这样,内核采用什么机制?


举个例子:

....
pid = fork();
if (pid == 0) { // child process
// Do something for a second;
}
else { // parent process
wait(NULL);
printf("Child completed.");
}
....


wait(NULL)是阻止系统调用。我想知道父流程经历的其余旅程。我对故事情节的看法如下,如果我错过了关键步骤或如果我完全错了,请纠正我:


通过libc运行时进行正常的系统调用设置。现在,父进程处于内核模式,可以执行 wait() syscall中的任何内容。
wait(NULL)创建一个等待队列,内核以后可以在其中找到该队列。
wait(NULL)将父进程放入此队列,在某些映射中创建一个条目,该条目显示“如果我(内核)曾经收到软件中断,信号或任何表明子进程已完成的信息,则调度程序应查看这个等待队列”。
子进程完成,内核以某种方式注意到了这一事实。内核上下文切换到调度程序,该调度程序在映射中查找以找到父进程处于打开状态的等待队列。
Scheduler将父进程移至就绪队列,进行魔术处理,并在某个时候最终选择运行父进程。
父进程仍在 wait(NULL) syscall中处于内核模式。现在,其余系统调用的主要工作是退出内核模式,并最终将父进程返回给用户。
该过程继续执行下一条指令,然后可能在其他等待队列上等待,直到完成为止。


PS:我希望了解OS内核的内部工作原理,进程在内核中经历的哪个阶段以及内核如何交互和操纵这些进程。我确实知道wait()Syscall API的语义和约定,而这不是我想从这个问题中了解的内容。

最佳答案

让我们探索内核源代码。首先,似乎所有
各种等待例程(wait,waitid,waitpid,wait3,wait4)最终出现在
相同的系统调用wait4。这些天,您可以在
通过查找宏SYSCALL_DEFINE1等来查找内核
是参数的数量,对于wait4恰好是4。
我们在自由电子Linux CrossReference中基于google的自由文本搜索最终找到了definition

1674 SYSCALL_DEFINE4(wait4, pid_t, upid, int __user *, stat_addr,
1675 int, options, struct rusage __user *, ru)


在这里,宏似乎将每个参数分为其类型和名称。这个
wait4例程进行一些参数检查,然后将它们复制到 wait_opts
结构,并调用 do_wait(),在同一文件中有几行:

1677         struct wait_opts wo;
1705 ret = do_wait(&wo);

1551 static long do_wait(struct wait_opts *wo)


(正如您所指出的,我在这些摘录中漏掉了几行
非连续行号)。
do_wait()将结构的另一个字段设置为函数的名称,
child_wait_callback()是同一文件中的几行。另一个
字段设置为 current。这是一个主要的“全球”,它指向
关于当前任务的信息:

1558         init_waitqueue_func_entry(&wo->child_wait, child_wait_callback);
1559 wo->child_wait.private = current;


然后将结构添加到专门为流程设计的队列中
等待SIGCHLD信号, current->signal->wait_chldexit

1560         add_wait_queue(&current->signal->wait_chldexit, &wo->child_wait);


让我们看一下 current。很难找到它的定义
根据架构的不同而变化,然后根据它找到最终的结构是
有点兔子沃伦。例如 current.h

  6 #define get_current() (current_thread_info()->task)
7 #define current get_current()


然后 thread_info.h

163 static inline struct thread_info *current_thread_info(void)
165 return (struct thread_info *)(current_top_of_stack() - THREAD_SIZE);

55 struct thread_info {
56 struct task_struct *task; /* main task structure */


所以 current指向 task_struct,我们在 sched.h中找到

1460 struct task_struct {
1461 volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
1659 /* signal handlers */
1660 struct signal_struct *signal;


因此,我们在 current->signal中找到了 current->signal->wait_chldexit
并且结构 signal_struct在同一个文件中:

670 struct signal_struct {
677 wait_queue_head_t wait_chldexit; /* for wait4() */


因此,我们上面提到的 add_wait_queue()调用是指
wait_chldexit类型的 wait_queue_head_t结构。

等待队列只是一个最初为空的,双向链接的结构列表,其中包含一个
struct list_head types.h

184 struct list_head {
185 struct list_head *next, *prev;
186 };


通话 add_wait_queue()
wait.c
通过内联函数临时锁定结构
wait.h
呼叫您可以在其中找到的 list_add()
list.h
这将适当地设置next和prev指针,以在上添加新项目
名单。
空列表的两个指针指向list_head结构。

将新条目添加到列表后, wait4()系统调用将设置一个
标志,将在下一个从可运行队列中删除进程
重新安排并调用 do_wait_thread()

1573         set_current_state(TASK_INTERRUPTIBLE);
1577 retval = do_wait_thread(wo, tsk);


该例程为进程的每个子进程调用 wait_consider_task()

1501 static int do_wait_thread(struct wait_opts *wo, struct task_struct *tsk)
1505 list_for_each_entry(p, &tsk->children, sibling) {
1506 int ret = wait_consider_task(wo, 0, p);


这很深,但实际上只是想看看是否有孩子
满足syscall,我们可以立即返回数据。的
对您来说有趣的情况是什么也没发现,但仍在运行
孩子们。我们最终调用 schedule(),此时该进程给出
cpu,我们的系统调用“挂起”以备将来使用。

1594                 if (!signal_pending(current)) {
1595 schedule();
1596 goto repeat;
1597 }


唤醒过程后,它将继续执行以下代码
schedule()再次遍历所有孩子,看看是否等待
条件得到满足,并且可能返回给调用者。

是什么唤醒了这样做的过程?一个孩子死亡并产生SIGCHLD
信号。
signal.c
do_notify_parent()在进程终止时被调用:

1566  * Let a parent know about the death of a child.
1572 bool do_notify_parent(struct task_struct *tsk, int sig)
1656 __wake_up_parent(tsk, tsk->parent);


__wake_up_parent()调用 __wake_up_sync_key()并完全使用
我们先前设置的 wait_chldexit等待队列。
exit.c

1545 void __wake_up_parent(struct task_struct *p, struct task_struct *parent)
1547 __wake_up_sync_key(&parent->signal->wait_chldexit,
1548 TASK_INTERRUPTIBLE, 1, p);


我认为我们应该到此为止,因为 wait()显然是其中之一
系统调用和使用等待队列的复杂示例。你可以找到
从2005年开始的这3页 Linux Journalarticle中,对该机制进行了更简单的介绍。
发生了变化,但对原理进行了解释。您可能还会买书
“ Linux设备驱动程序”和“ Linux内核开发”,或查看
这些的较早版本可以在网上找到。

对于从用户空间到内核的“系统调用剖析”
您可能会阅读这些 lwn articles



每当执行任务时,整个内核都会大量使用等待队列,
需要等待一些条件。通过内核源代码的grep查找
超过1200个 init_waitqueue_head()调用,这是您如何初始化
您只需通过 kmalloc()-空间动态创建的等待队列
保持结构。

DECLARE_WAIT_QUEUE_HEAD()宏的grep查找超过150种用途
静态等待队列结构的此声明。没有内在的
这些之间的区别。例如,驾驶员可以选择任何一种方法
创建一个等待队列,通常取决于它是否可以管理
许多类似的设备,每个设备都有自己的队列,或者只需要一个设备。

尽管有常见的问题,但没有中央代码负责这些队列
代码以简化其使用。例如,驱动程序可能会创建一个空的
安装和初始化时等待队列。当您使用它来读取某些数据时
硬件,它可能会通过直接写入
硬件寄存器,然后在其等待队列中将一个条目(用于“此”任务,即 current)排队以放弃
直到硬件准备好数据为止。

然后,硬件将中断CPU,内核将调用
驾驶员的中断处理程序(在初始化时注册)。处理程序代码
只需在等待队列上调用 wake_up(),内核即可
将所有任务放到运行队列中的等待队列中。

当任务再次获得cpu时,它将从中断处继续(在
schedule())并检查硬件是否已完成操作,以及
然后可以将数据返回给用户。

因此内核不负责驱动程序的等待队列,因为它只是
当驾驶员调用它时会看着它。没有来自
例如,等待队列的硬件中断。

如果同一等待队列上有多个任务,则有以下几种变体
wake_up()调用可用于仅唤醒1个任务或全部
它们,或仅是处于可中断等待状态的那些(即旨在
能够取消操作并在出现以下情况时返回给用户
信号),等等。

关于process - 进程调用syscall wait()后,谁将其唤醒?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40054413/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com