ivot_root() 将调用进程的根文件系统移动到目录 put_old 并使 new_root 成为调用进程的新根文件系统。
ivot_root() 的典型用途是在系统启动期间,当系统挂载临时根文件系统(例如 initrd)时,然后挂载真实根文件系统,并最终将后者变成所有相关进程或线程的当前根。
ivot_root() 可能会也可能不会更改使用旧根目录的任何进程或线程的当前根目录和当前工作目录。 ivot_root() 的调用者必须确保根目录或当前工作目录位于旧根目录的进程在任何一种情况下都能正确运行。确保这一点的一个简单方法是在调用pivot_root()之前将其根目录和当前工作目录更改为new_root。
上面的段落故意含糊不清,因为pivot_root()的实现将来可能会改变。在撰写本文时,pivot_root() 将每个进程或线程的根目录和当前工作目录更改为 new_root(如果它们指向旧根目录)。这是必要的,以防止内核线程使旧根目录忙于其根目录和当前工作目录,即使它们从未以任何方式访问文件系统。将来,可能会有一种机制让内核线程显式放弃对文件系统的任何访问,这样这种相当侵入性的机制就可以从pivot_root()中删除。
...
错误
ivot_root() 不必更改系统中所有其他进程的根目录和当前工作目录。
ivot_root() 的一些比较晦涩的用法可能很快就会导致疯狂。
-- man pivot_root
, Linux 手册页 4.15
我正在研究一种情况,当调用pivot_root() 时有多个进程正在运行。
联机帮助页似乎不太清楚pivot_root() 的两种可能实现如何处理多进程的情况。假设我们有两个进程,S(ystemd) 和 P(lymouth)。目前,P和S都将根目录和工作目录更改为new_root,然后S调用pivot_root()。根据当前的实现,这工作得很好。
假设 S 和 P 在pivot_root() 之前使用 chroot()“更改其根目录”。但是,正如man chroot
告诉我们的,如果您是 root ( ),则可以离开 chroot() 监狱mkdir foo; chroot foo; cd ..; chroot .
。显然,进程有两个相关的根源:
- 他们当前的 chroot
- 他们的挂载命名空间的根
在执行pivot_root()之后,S必须观察到其挂载命名空间的根等于其当前的chroot。因为如果有一个更深的根文件系统可以在将来逃逸到,那么该根文件系统将很忙并且无法卸载。我认为允许卸载旧的根文件系统是pivot_root()的主要目的。
目前,P 观察到同样的事情 - 因为它与 S 位于相同的挂载命名空间中。
听起来,pivot_root() 的替代实现会将调用进程放入一个新的、更改的安装命名空间中。这是有效的阅读吗?
(我注意到这种替代实现/sbin/pivot_root
几乎毫无意义)。
我相信最初的pivot_root()实际上早于挂载命名空间。我们是否知道这个针对pivot_root()的替代实现的计划是否预见到了对挂载命名空间的某些功能的需求,或者是否忽略了这一要求?
(我注意到挂载命名空间听起来也非常像“内核线程显式放弃对文件系统的任何访问的机制”,例如内核线程可以对空的 tmpfs 执行相当于pivot_root() 的操作)。
答案1
听起来,pivot_root() 的替代实现会将调用进程放入一个新的、更改的安装命名空间中。这是有效的阅读吗?
不,我认为这不是很清楚,但有一个更加一致和正确的读数。
ivot_root() 的基本部分(在任一实现中都必须相同)是:
ivot_root() 将调用进程的根文件系统移动到目录 put_old 并使 new_root 成为调用进程的新根文件系统。
ivot_root() 的核心部分不仅限于调用进程。此引用中描述的操作适用于调用进程的挂载命名空间。它将影响同一挂载命名空间中所有进程的视图。
考虑一下基本更改对第二个进程(或内核线程)的影响,其工作目录是旧的根文件系统。它的当前目录仍然是旧的根文件系统。这将使/put_old
挂载点保持繁忙,因此无法卸载旧的根文件系统。
如果您控制第二个进程,则可以根据联机帮助页,通过在调用pivot_root()之前将其工作目录设置为new_root来解决此问题。调用pivot_root()后,其当前目录仍将是新的根文件系统。
因此,进程 S(ystemd) 已配置为向进程 P(lymouth) 发送信号,以便在 S 调用ivot_root() 之前更改工作目录。没问题。但是,我们也有内核线程,它们以/
.当前的ivot_root()实现为我们处理内核线程;它相当于将内核线程和任何其他进程的工作目录设置到new_root
pivot_root()的基本部分之前。
除此之外,pivot_root() 的当前实现仅在旧工作目录为 时才更改进程的工作目录/
。所以实际上很容易看出这造成的差异:
$ unshare -rm
# cd /tmp # work in a subdir instead of '/', and pivot_root() will not change it
# /bin/pwd
/tmp
# mount --bind /new-root /new-root
# pivot_root /new-root /new-root/mnt
# /bin/pwd
/mnt/tmp # see below: if pivot_root had not updated our current chroot, this would still show /tmp
与
$ unshare -rm
# cd /
# /bin/pwd
/
# ls -lid .
2 dr-xr-xr-x. 19 nfsnobody nfsnobody 4096 Jun 13 01:17 .
# ls -lid /newroot
6424395 dr-xr-xr-x. 20 nfsnobody nfsnobody 4096 May 10 12:53 /new-root
# mount --bind /new-root /new-root
# pivot_root /new-root /new-root/mnt
# /bin/pwd
/
# ls -lid .
6424395 dr-xr-xr-x. 20 nobody nobody 4096 May 10 12:53 .
# ls -lid /
6424395 dr-xr-xr-x. 20 nobody nobody 4096 May 10 12:53 /
# ls -lid /mnt
2 dr-xr-xr-x. 19 nobody nobody 4096 Jun 13 01:17 /mnt
现在我明白了工作目录发生了什么,我发现更容易理解 chroot() 发生了什么。调用pivot_root()的进程的当前chroot可能是对原始根文件系统的引用,就像其当前工作目录一样。
注意,如果你执行了 chdir()+pivot_root() 但忘记了 chroot(),那么你的当前目录将是外部你当前的 chroot。当当前目录位于当前 chroot 之外时,事情会变得非常混乱。您可能不想在这种状态下运行您的程序。
# cd /
# python
>>> import os
>>> os.chroot("/newroot")
>>> os.system("/bin/pwd")
(unreachable)/
0
>>> os.getcwd()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 2] No such file or directory
>>> os.system("ls -l ./proc/self/cwd")
lrwxrwxrwx. 1 root root 0 Jun 17 13:46 ./proc/self/cwd -> /
0
>>> os.system("ls -lid ./proc/self/cwd/")
2 dr-xr-xr-x. 19 root root 4096 Jun 13 01:17 ./proc/self/cwd/
0
>>> os.system("ls -lid /")
6424395 dr-xr-xr-x. 20 root root 4096 May 10 12:53 /
0
在这种情况下,POSIX 不指定pwd
getcwd() 或 getcwd() 的结果:)。 POSIX 不会提示您可能会从 getcwd() 收到“没有此类文件或目录”(ENOENT) 错误。 Linux 联机帮助页指出,如果工作目录未链接(例如使用rm
),则可能会出现此错误。我认为这是一个很好的类比。