进程替换中fd分配的顺序

进程替换中fd分配的顺序

受到这个答案的启发https://security.stackexchange.com/a/166645 我想知道当我运行这些命令时奇怪的排序背后的原因:

root@6cb8704148bf:/usr/app# echo <(printf "111")
/dev/fd/63
root@6cb8704148bf:/usr/app# echo <(printf "111")
/dev/fd/63
root@6cb8704148bf:/usr/app# echo <(printf "111") <(printf "222")
/dev/fd/63 /dev/fd/62

到目前为止,这似乎有些正常。然后我想知道如果你继续降到 0 会发生什么。

root@6cb8704148bf:/usr/app# echo <(printf "111") <(printf "222") <(printf "222")  <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222")  <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222")  <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222")  <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222") <(printf "222")
/dev/fd/63 /dev/fd/62 /dev/fd/61 /dev/fd/60 /dev/fd/59 /dev/fd/58 /dev/fd/57 /dev/fd/56 /dev/fd/55 /dev/fd/54 /dev/fd/53 /dev/fd/52 /dev/fd/51 /dev/fd/50 /dev/fd/49 /dev/fd/48 /dev/fd/47 /dev/fd/46 /dev/fd/45 /dev/fd/44 /dev/fd/43 /dev/fd/42 /dev/fd/41 /dev/fd/40 /dev/fd/39 /dev/fd/38 /dev/fd/37 /dev/fd/36 /dev/fd/35 /dev/fd/34 /dev/fd/33 /dev/fd/32 /dev/fd/31 /dev/fd/30 /dev/fd/29 /dev/fd/28 /dev/fd/27 /dev/fd/26 /dev/fd/25 /dev/fd/24 /dev/fd/23 /dev/fd/22 /dev/fd/21 /dev/fd/20 /dev/fd/19 /dev/fd/18 /dev/fd/17 /dev/fd/16 /dev/fd/15 /dev/fd/14 /dev/fd/13 /dev/fd/12 /dev/fd/11 /dev/fd/10 /dev/fd/9 /dev/fd/8 /dev/fd/7 /dev/fd/6 /dev/fd/5 /dev/fd/3 /dev/fd/4 /dev/fd/64 /dev/fd/65 /dev/fd/66 /dev/fd/67 /dev/fd/68 /dev/fd/69 /dev/fd/70 /dev/fd/71 /dev/fd/72 /dev/fd/73 /dev/fd/74 /dev/fd/75 /dev/fd/76 /dev/fd/77 /dev/fd/78 /dev/fd/79 /dev/fd/80 /dev/fd/81 /dev/fd/82 /dev/fd/83 /dev/fd/84 /dev/fd/85 /dev/fd/86 /dev/fd/87 /dev/fd/88 /dev/fd/89 /dev/fd/90 /dev/fd/91 /dev/fd/92 /dev/fd/93 /dev/fd/94 /dev/fd/95 /dev/fd/96 /dev/fd/97 /dev/fd/98 /dev/fd/99 /dev/fd/100 /dev/fd/101

为什么会这样:

63
..
5
3
4
64
...

谁能解释一下这个顺序吗?

答案1

如果你看代码, 你会看到的:

  /* Move the parent end of the pipe to some high file descriptor, to
     avoid clashes with FDs used by the script. */
  parent_pipe_fd = move_to_high_fd (parent_pipe_fd, 1, 64);

这个想法是 sh 中的 fds 0 到 9 保留给可以执行操作的用户cmd < x > y 2> z ... 9> ...。因此,shell 尝试将超出该范围的 fd 用于其所有内部 FD。这不仅限于进程替换,您可以看到它也用于协同进程,例如:

$ bash -c 'coproc :; echo "${COPROC[@]}"'
63 60

而在其他一些情况下,例如在重定向内置函数或来自 zsh 时保存文件描述符{fd}>...,它只是尝试获取 fd >= 10 (使用fcntl (fd, F_DUPFD, SHELL_FD_BASE)):

$ bash -c 'exec {fd}</dev/null; echo "$fd"'
10

move_to_high_fd()该函数的代码,查找maxfd传递的参数(此处为 64)下方的第一个空闲 fd,并且该 fd 大于3,并将 fd 移至该位置。如果失败,如果fd 4到63都在使用,那么fd不会被移动,这解释了为什么你得到那些3、4、64。

为什么它从“高”值向后执行,而不是像 zsh 那样获得第一个高于 9 的 fd,我认为这与 bash 实际上允许用户使用高于 9 的 fd 作为标准的扩展这一事实有关。

该代码或类似代码已在 1996 年的 2.0 中出现,但进程替换仅在 2.0.1 中才开始使用它。在此之前,进程替换不会移动 fds,因此您可能会遇到与 ksh93 中遇到的相同类型的问题,它不会移动这些 fds:

$ ksh -c 'echo <(:)'
/dev/fd/3
$ ksh -c 'exec 3< <(echo test); cat <&3'
ksh: 3: cannot open [Bad file descriptor]

你通过什么来解决问题保留执行进程替换之前的 fd:

$ ksh -c 'exec 3<&0 3< <(echo test); cat <&3'
test

当前bash版本存在同样的问题:

$ bash -c 'exec 63< <(echo test); cat <&63'
bash: line 1: 63: Bad file descriptor
$ bash -c 'exec 63<&0 63< <(echo test); cat <&63'
test

move_to_high_fd()不能防止冲突,它只是在假设人们更喜欢使用低 fd 数的情况下起作用。

为什么是64我不知道。CWRU/changelogbash-2.05b 中提到:

subst.c
        - in process_substitute, call move_to_high_fd with `maxfd' parameter
          of -1 instead of 64, so move_to_high_fd will use its maximum

bash-2.05b-beta1在和的发布之间bash-2.05b-beta2,但看起来它在 2.05b 发布之前就被恢复了。

move_to_high_fd()根据变更日志,该变更日志条目是在 bash-2.0-alpha4 和 bash-2.0-beta1 之间引入的:

通用.c

  • 新函数:move_to_high_fd(fd),它尝试将 FD 移动到接近允许最大值的文件描述符,返回新的 fd 并关闭旧的(或者如果出现问题则返回旧的)

最初不接受参数maxfd,但转向确定的最高允许值(上限为 256)。maxfd在 bash-2.01-alpha1 和 bash-2.01-beta1 之间添加了以下内容:

通用.c、通用.h

  • move_to_high_fd 现在采用第三个参数:开始查找的最高 fd。如果小于 20,则使用 getdtablesize() 返回的最大打开文件数(这是它之前所做的)

jobs.c、shell.c、subst.c

  • 适当更改了对 move_to_high_fd 的调用

但没有说明为什么有时使用 64 作为maxfd.据猜测,也许某些系统没有/dev/fd/nn 大于 63 的情况。


¹ 进程替换是由 ksh 在 80 年代中期引入的,但当时您不能使用进程替换作为重定向的目标。最近在 ksh93 中对此进行了更改。

相关内容