7.8 代码:wait, exit和kill

Sleepwakeup可用于多种等待。第一章介绍的一个有趣的例子是子进程exit和父进程wait之间的交互。在子进程死亡时,父进程可能已经在wait中休眠,或者正在做其他事情;在后一种情况下,随后的wait调用必须观察到子进程的死亡,可能是在子进程调用exit后很久。xv6记录子进程终止直到wait观察到它的方式是让exit将调用方置于ZOMBIE状态,在那里它一直保持到父进程的wait注意到它,将子进程的状态更改为UNUSED,复制子进程的exit状态码,并将子进程ID返回给父进程。如果父进程在子进程之前退出,则父进程将子进程交给init进程,init进程将永久调用wait;因此,每个子进程退出后都有一个父进程进行清理。主要的实现挑战是父级和子级waitexit,以及exitexit之间可能存在竞争和死锁。

Wait使用调用进程的p->lock作为条件锁,以避免丢失唤醒,并在开始时获取该锁(kernel/proc.c:398)。然后它扫描进程表。如果它发现一个子进程处于ZOMBIE状态,它将释放该子进程的资源及其proc结构体,将该子进程的退出状态码复制到提供给wait的地址(如果不是0),并返回该子进程的进程ID。如果wait找到子进程但没有子进程退出,它将调用sleep以等待其中一个退出(kernel/proc.c:445),然后再次扫描。这里,sleep中释放的条件锁是等待进程的p->lock,这是上面提到的特例。注意,wait通常持有两个锁:它在试图获得任何子进程的锁之前先获得自己的锁;因此,整个xv6都必须遵守相同的锁定顺序(父级,然后是子级),以避免死锁。

Wait查看每个进程的np->parent以查找其子进程。它使用np->parent而不持有np->lock,这违反了通常的规则,即共享变量必须受到锁的保护。np可能是当前进程的祖先,在这种情况下,获取np->lock可能会导致死锁,因为这将违反上述顺序。这种情况下无锁检查np->parent似乎是安全的:进程的parent字段仅由其父进程更改,因此如果np->parent==ptrue,除非当前流程更改它,否则该值无法被更改,

Exitkernel/proc.c:333)记录退出状态码,释放一些资源,将所有子进程提供给init进程,在父进程处于等待状态时唤醒父进程,将调用方标记为僵尸进程(zombie),并永久地让出CPU。最后的顺序有点棘手。退出进程必须在将其状态设置为ZOMBIE并唤醒父进程时持有其父进程的锁,因为父进程的锁是防止在wait中丢失唤醒的条件锁。子级还必须持有自己的p->lock,否则父级可能会看到它处于ZOMBIE状态,并在它仍运行时释放它。锁获取顺序对于避免死锁很重要:因为wait先获取父锁再获取子锁,所以exit必须使用相同的顺序。

Exit调用一个专门的唤醒函数wakeup1,该函数仅唤醒父进程,且父进程必须正在wait中休眠(kernel/proc.c:598)。在将自身状态设置为ZOMBIE之前,子进程唤醒父进程可能看起来不正确,但这是安全的:虽然wakeup1可能会导致父进程运行,但wait中的循环在scheduler释放子进程的p->lock之前无法检查子进程,所以waitexit将其状态设置为ZOMBIE(kernel/proc.c:386)之前不能查看退出进程。

exit允许进程自行终止,而killkernel/proc.c:611)允许一个进程请求另一个进程终止。对于kill来说,直接销毁受害者进程(即要杀死的进程)太复杂了,因为受害者可能在另一个CPU上执行,也许是在更新内核数据结构的敏感序列中间。因此,kill的工作量很小:它只是设置受害者的p->killed,如果它正在睡眠,则唤醒它。受害者进程终将进入或离开内核,此时,如果设置了p->killedusertrap中的代码将调用exit。如果受害者在用户空间中运行,它将很快通过进行系统调用或由于计时器(或其他设备)中断而进入内核。

如果受害者进程在sleep中,killwakeup的调用将导致受害者从sleep中返回。这存在潜在的危险,因为等待的条件可能不为真。但是,xv6对sleep的调用总是封装在while循环中,该循环在sleep返回后重新测试条件。一些对sleep的调用还在循环中测试p->killed,如果它被设置,则放弃当前活动。只有在这种放弃是正确的情况下才能这样做。例如,如果设置了killed标志,则管道读写代码返回;最终代码将返回到陷阱,陷阱将再次检查标志并退出。

一些XV6的sleep循环不检查p->killed,因为代码在应该是原子操作的多步系统调用的中间。virtio驱动程序(kernel/virtio_disk.c:242)就是一个例子:它不检查p->killed,因为一个磁盘操作可能是文件系统保持正确状态所需的一组写入操作之一。等待磁盘I/O时被杀死的进程将不会退出,直到它完成当前系统调用并且usertrap看到killed标志

copyright by duguosheng all right reserved,powered by Gitbook该文件修订时间: 2021-08-19 14:05:52

results matching ""

    No results matching ""