首页 文章

如何根据Linux系统调用实现后台处理?

提问于
浏览
4

如何在Linux系统调用方面实现后台处理(例如,在Bash中)?


我的问题的目的是我不明白为什么bash手册说

在子shell环境中调用异步命令,

(如果我是正确的,"aynchronous commands"表示在后台运行命令),而通过使用 strace ,我发现父shell进程首先调用 clone() 来创建一个子shell,它是自身的副本,然后子shell调用 execve() 到使用命令替换子shell本身以在后台运行 .

这就像运行前台进程一样 . 我没有看到在子shell中调用该命令 . 如果我是正确的,在子shell中调用命令意味着子shell调用 clone() 来创建子子shell,然后subsubshell调用 execve() 将subsubshell本身替换为在后台运行的命令 . 但实际上,子shell不会调用 clone() .

例如,

在Ubuntu中,我在一个交互式bash shell中运行 date ,它的pid是6913,同时,通过 strace 跟踪来自另一个交互式bash shell的bash shell .

运行 date 时,跟踪第二个shell中第一个shell 6913的输出是:

$ sudo strace -f -e trace=process -p 6913
[sudo] password for t: 
Process 6913 attached
clone(Process 12918 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f457c05ca10) = 12918
[pid  6913] wait4(-1,  <unfinished ...>
[pid 12918] execve("/bin/date", ["date"], [/* 66 vars */]) = 0
[pid 12918] arch_prctl(ARCH_SET_FS, 0x7ff00c632740) = 0
[pid 12918] exit_group(0)               = ?
[pid 12918] +++ exited with 0 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WSTOPPED|WCONTINUED, NULL) = 12918
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12918, si_status=0, si_utime=0, si_stime=0} ---
wait4(-1, 0x7ffea6781518, WNOHANG|WSTOPPED|WCONTINUED, NULL) = -1 ECHILD (No child processes)

运行 date & 时,跟踪第二个shell中第一个shell 6913的输出是:

$ sudo strace -f -e trace=process -p 6913
Process 6913 attached
clone(Process 12931 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f457c05ca10) = 12931
[pid 12931] execve("/bin/date", ["date"], [/* 66 vars */]) = 0
[pid 12931] arch_prctl(ARCH_SET_FS, 0x7f530c5ee740) = 0
[pid 12931] exit_group(0)               = ?
[pid 12931] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12931, si_status=0, si_utime=0, si_stime=0} ---
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED|WCONTINUED, NULL) = 12931
wait4(-1, 0x7ffea6780718, WNOHANG|WSTOPPED|WCONTINUED, NULL) = -1 ECHILD (No child processes)

2 回答

  • 1

    文本"asynchronous commands are invoked in a subshell environment"没有具体提到背景,因为背景是来自"POSIX job control"的交互式概念 . 而异步命令以交互方式或非交互方式发生 .

    "Invoked in a subshell environment"只是shell术语,这意味着发生 fork ,并且这些命令在子进程中运行,该进程无法修改父级和其他状态中的变量:

    $ VAR=value &
    [1] 15479
    [1]+  Done                    VAR=value
    $ echo $VAR
    $
    

    因为变量赋值在子shell中运行,所以它在父shell中没有任何影响 .

    后台在系统调用方面的工作原理是POSIX作业控制围绕一组进程进行,这些进程被组织成进程组,进程组属于一个连接到控制终端的会话 . 会话中一次只有一个组是前台进程组 .

    设计围绕组而不是单个过程的原因是因为当使用管道时,作业由多个过程组成 . 例如, sort -u file | grep foo 创建一个包含两个进程的进程组 .

    shell本身也在一个进程组中 . 当shell提示您输入时,该进程组位于前台 . 当shell在前台执行作业时,它会通过一些特殊的作业控制系统调用将自己置于后台 .

    当您将Ctrl-Z发送到TTY时,它会为前台进程组中的每个进程生成 SIGTSTP 信号 . shell检测到子状态的这种变化(通过 waitpid 或其他一些)然后将该组混洗到后台,再次将自己置于前台,以接收TTY输入 .

    当您与shell通信时,所有作业都在后台,无论它们是否正在运行:shell是前台进程组,因此所有其他作业都不是 . 使用 bg 命令,您只需更改挂起的后台作业的运行状态 . Ctrl-Z发送了一个暂停它的 SIGTSTP ,并将shell移动到后台 . bg 将恢复它,允许它执行 . 但是,如果后台作业已恢复并尝试从TTY获取输入,则会收到 SIGTTIN 信号并再次暂停:

    $ cat &
    [1] 12620
    $ bg
    [1]+ cat &
    $     # hit Enter 
    [1]+  Stopped                 cat
    $ bg
    [1]+ cat &
    $     # hit Enter
    [1]+  Stopped                 cat
    

    cat 想要读取tty,所以当我们把它放在后台时,它会从内核的TTY子系统中获取一个 SIGTTIN 来阻止它 . 与Ctrl-Z / SIGTSTP 类似,shell检测到这种信号驱动的状态变化,并打印一条消息,告诉我们该作业已停止 . 每次我们(恢复它)都会发生同样的事情 . shell调度 cat (可能带有 SIGCONT ), cat 立即恢复尝试从TTY获取输入,响应“你不是前台进程组的成员,坏的小猫: SIGTTIN 为你” . 所有这一切, cat 永远不会离开背景 .

  • 1

    子shell不调用clone()

    这是一个明确的优化 . bash意识到如果子shell的唯一工作是同步执行单个外部命令,那么做另一个昂贵的fork是没有意义的 . 来自execute_cmd.c

    /* If this is a simple command, tell execute_disk_command that it
         might be able to get away without forking and simply exec.
         This means things like ( sleep 10 ) will only cause one fork.
         If we're timing the command or inverting its return value, however,
         we cannot do this optimization. */
    

    因此, datedate & 之间唯一真正的系统调用差异是后者后面没有 wait .

    bash手册提到的更有趣的情况是运行非外部命令 . let i++let i++ & 之间的区别在于前者是由bash本身评估的,而后者是由子shell评估然后退出,因此没有效果 .

相关问题