You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

clone()系统调用为何失败、暂停并恢复?管道测试异常排查

关于UNIX管道实现中clone()系统调用的疑问

我正在操作系统课程中实现UNIX管道(|),完成后测试用例存在非确定性通过的问题。相关测试用例如下:

def test_no_orphans(self):
    self.assertTrue(self.make, msg='make failed')
    subprocess.call(('strace', '-o', 'trace.log','./pipe','ls','wc','cat','cat'))
    ps = subprocess.Popen(['grep','-o','clone(','trace.log'], stdout=subprocess.PIPE)
    out1 = subprocess.check_output(('wc','-l'), stdin=ps.stdout)
    ps.wait()        
    ps.stdout.close()
    ps = subprocess.Popen(['grep','-o','wait','trace.log'], stdout=subprocess.PIPE)
    out2 = subprocess.check_output(('wc','-l'), stdin=ps.stdout)
    ps.wait()  
    ps.stdout.close()
    out1 = int(out1.decode("utf-8")[0])
    out2 = int(out2.decode("utf-8")[0])
    if out1 == out2 or out1 < out2:
        orphan_check = True
    else:
        orphan_check = False
    self.assertTrue(orphan_check, msg="Found orphan processes")
    subprocess.call(['rm', 'trace.log'])
    self.assertTrue(self._make_clean, msg='make clean failed')

查看strace日志后发现,带有ERESTARTNOINTR (To be restarted)的行导致测试失败,关键日志片段:

clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f04174ad850) = 1330
close(4)                                = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1330, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
pipe([4, 5])                            = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f04174ad850) = 1331
close(5)                                = 0
close(3)                                = 0
pipe([3, 5])                            = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f04174ad850) = ? ERESTARTNOINTR (To be restarted)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1331, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f04174ad850) = 1332
close(5)                                = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1332, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
close(4)                                = 0
pipe([4, 5])                            = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f04174ad850) = 1333

经教授协助调试后,已向TA反馈并推测测试用例可能存在bug。我想了解:

  1. 为何clone()会出现这种情况(fork()的C封装似乎能妥善处理)?
  2. 当前场景具体发生了什么?
  3. 为何系统调用普遍存在未完成和恢复状态?

问题解答

1. 为什么clone()会出现ERESTARTNOINTR,而fork()不会?

fork()是C标准库封装的系统调用,底层调用clone()时,已经帮你处理了信号中断的情况。当clone()被信号中断返回ERESTARTNOINTR时,fork()的封装逻辑会自动重启这个系统调用,直到成功完成。而如果直接调用clone()(没有库层的封装),就需要自己处理这种中断重启的逻辑——你的代码里显然没做这件事,所以strace会捕获到这个未完成的状态。

2. 当前场景具体发生了什么?

从日志里能清晰看到:

  • 程序在调用第三个clone()的时候,刚好之前创建的子进程(PID1331)退出,内核给父进程发送了SIGCHLD信号。
  • 这个信号打断了正在执行中的clone()系统调用,内核返回ERESTARTNOINTR给用户态,意思是“这个调用被信号打断了,你可以选择重启它”。
  • 之后程序应该是重启了clone(),所以后面能看到成功创建了PID1332的子进程。
  • 但测试用例的逻辑是统计clone(的行数和wait的行数:被中断的那次clone()被strace记录了一行,却没有对应的成功返回,而后续重启的clone()又多了一行,导致clone(的计数比wait多,测试就失败了——本质是测试用例没考虑到系统调用被中断重启的情况,把中断的那次也算成了一次成功的进程创建。

3. 为什么系统调用普遍存在未完成和恢复状态?

这是UNIX/Linux系统处理信号和系统调用并发的核心机制:

  • 系统调用执行过程中,内核可能会收到信号(比如子进程退出的SIGCHLD、用户按下Ctrl+C的SIGINT等),此时内核需要暂停当前的系统调用,先处理信号处理函数。
  • 对于一些“可重启”的系统调用(比如clone()、read()、write()等),内核会返回ERESTARTNOINTR这类错误码,告诉用户态程序:“我还没干完,你可以再调用我一次,我接着干”。
  • 这种设计是为了保证系统调用的语义完整性,同时不影响信号的及时处理——如果系统调用一直阻塞不响应信号,程序就会变得“僵死”,无法处理外部事件。

内容的提问来源于stack exchange,提问作者user129393192

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 15:14:59