`/proc/self/environ` 在某些 shell 中的奇怪行为;到底是怎么回事?

Kam*_*ski 9 shell io-redirection environment-variables proc

我使用的是 Debian GNU/Linux 9。我知道它/proc很特别,我知道什么/proc/self.

这个命令

sh -c '/bin/cat /proc/self/comm - </proc/self/comm'
Run Code Online (Sandbox Code Playgroud)

产量

cat
sh
Run Code Online (Sandbox Code Playgroud)

如果我使用dash而不是sh. 但是用bash,ksh或者zsh结果是

cat
cat
Run Code Online (Sandbox Code Playgroud)

/proc/self/stat代替/proc/self/comm我可以证实这两个cat-s其实都是同一个过程。显然,引擎盖下的外壳不同,没关系。现在让我们采取

sh -c '/bin/cat /proc/self/environ - </proc/self/environ'
Run Code Online (Sandbox Code Playgroud)

看了上面的,sh还是dash期待看到cat第一个的环境,后面的shell的环境。它似乎有效(无论如何,两种环境很可能是相同的,因此很难判断一切是否按预期工作,但我的观点是:两者都不environ是空的)。

bash,ksh或者zsh我希望看到cat两次的环境,但它只打印一次。分为两种不同的情况:

  • bash -c '/bin/cat - </proc/self/environ'什么都不打印,好像environ是空的;
  • bash -c '/bin/cat /proc/self/environ' 打印出预期的东西。

到底是怎么回事?comm或不是这种情况stat。为什么environ不同?

$ uname -a
Linux barbaz 4.9.0-6-amd64 #1 SMP Debian 4.9.88-1 (2018-04-29) x86_64 GNU/Linux
Run Code Online (Sandbox Code Playgroud)

Ste*_*itt 5

外壳之间的差异是由于流程设置的差异造成的。dash在 fork 之前设置重定向,所以/proc/self指向 shell;bashzsh在fork后设置它们,所以/proc/self指向新的过程。你可以看到这种情况发生strace -f

  • strace -f dash -c '/bin/cat /proc/self/comm - </proc/self/comm' 显示(以及许多其他内容)

      open("/proc/self/comm", O_RDONLY)       = 3
      fcntl(0, F_DUPFD, 10)                   = 10
      close(0)                                = 0
      fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
      dup2(3, 0)                              = 0
      close(3)                                = 0
      clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f12581299d0) = 7743
      strace: Process 7743 attached
      [pid  7742] wait4(-1,  <unfinished ...>
      [pid  7743] execve("/bin/cat", ["/bin/cat", "/proc/self/comm", "-"], [/* 43 vars */]) = 0
    
    Run Code Online (Sandbox Code Playgroud)

    (/proc/self/commclone系统调用之前打开,这是进程分叉的地方);

  • strace -f bash -c '/bin/cat /proc/self/comm - </proc/self/comm' 显示

      clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fb506bdee10) = 8106
      strace: Process 8106 attached
      [... snip a ton of signal-handling setup ...]
      [pid  8106] open("/proc/self/comm", O_RDONLY) = 3
      [pid  8106] dup2(3, 0)                  = 0
      [pid  8106] close(3)                    = 0
      [pid  8106] execve("/bin/cat", ["/bin/cat", "/proc/self/comm", "-"], [/* 43 vars */]) = 0
    
    Run Code Online (Sandbox Code Playgroud)

    (调用/proc/self/comm后打开clone,在子进程中,8106)。

理解为什么environ显示为空需要更多的解释。/proc/<pid>/environ被打开时,内核会保存一份指向任务的 指针的副本mm_struct,其中包含指向环境的指针。但是execve,用于启动cat进程的 ,为进程创建了一个新mm_struct。因此,重定向最终指向过时的信息,并且在cat读取其输入时,它看不到其真实环境。它确实看到的环境应该是其父级的副本,但所涉及的 shell 在分叉和设置新环境(由 设置execve)之前对其进行清理。