在新分叉的进程中使用
unshare(2)
创建两个嵌套的挂载命名空间时;使用在前一个命名空间中打开的 FD 从最后一个命名空间调用 readlink(2)
会返回缺少挂载点组件的路径。
即
/dev/zero
变为 /zero
,/mnt/disk/file
变为 /file
。
但是,如果两个取消共享调用之间存在分叉,则似乎不会发生这种情况。 fork 做了什么来改变这种行为?
下面的C++代码可以演示这个问题。我还附上了它在 Linux 6.1.23 上运行时的输出。
可以选择提供一个参数“fork”,它在创建第一个命名空间后立即进行分叉,以便在从父进程继承第一个命名空间的不同进程中创建第二个命名空间。
#include <unistd.h>
#include <sched.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <iostream>
#include <string>
#include <vector>
std::string readlink_path(const std::string &path) {
std::vector<char> buf(4096);
ssize_t size = readlink(path.c_str(), buf.data(), buf.size());
return size == -1 ? "" : std::string(buf.data(), static_cast<size_t>(size));
}
std::string readlink_fd(int fd) { return readlink_path("/proc/self/fd/" + std::to_string(fd)); }
int main(int argc, const char *argv[]) {
int fdFromFirstNS = -1, fdFromInitialNS = -1;
/* Initial namespace */
std::cout << readlink_path("/proc/self/ns/mnt") << '\n';
fdFromInitialNS = open("/dev/zero", O_RDONLY);
std::cout << "open() = " << fdFromInitialNS << '\n';
std::cout << "readlink(" << fdFromInitialNS << ") = " << readlink_fd(fdFromInitialNS) << "\n\n";
/* First namespace */
if (unshare(CLONE_NEWNS) != 0) goto END;
std::cout << readlink_path("/proc/self/ns/mnt") << '\n';
fdFromFirstNS = open("/dev/zero", O_RDONLY);
std::cout << "open() = " << fdFromFirstNS << '\n';
std::cout << "readlink(" << fdFromInitialNS << ") = " << readlink_fd(fdFromInitialNS) << '\n';
std::cout << "readlink(" << fdFromFirstNS << ") = " << readlink_fd(fdFromFirstNS) << '\n' << std::endl;
/* Fork if the first argument is "fork" */
if (argc == 2 && std::string("fork") == argv[1]) {
pid_t child_pid = fork();
if (child_pid)
{
waitpid(child_pid, NULL, 0);
goto END;
}
std::cout << "Forked. " << readlink_path("/proc/self/ns/mnt") << "\n\n";
}
/* Second namespace */
if (unshare(CLONE_NEWNS) != 0) goto END;
std::cout << readlink_path("/proc/self/ns/mnt") << '\n';
std::cout << "readlink(" << fdFromInitialNS << ") = " << readlink_fd(fdFromInitialNS) << '\n';
std::cout << "readlink(" << fdFromFirstNS << ") = " << readlink_fd(fdFromFirstNS) << '\n';
END:
close(fdFromInitialNS);
close(fdFromFirstNS);
}
g++ test.cpp && sudo ./a.out
:
mnt:[4026532253]
open() = 3
readlink(3) = /dev/zero
mnt:[4026532259]
open() = 4
readlink(3) = /dev/zero
readlink(4) = /dev/zero
mnt:[4026532260]
readlink(3) = /dev/zero
readlink(4) = /zero
g++ test.cpp && sudo ./a.out fork
:
mnt:[4026532253]
open() = 3
readlink(3) = /dev/zero
mnt:[4026532259]
open() = 4
readlink(3) = /dev/zero
readlink(4) = /dev/zero
Forked. mnt:[4026532259]
mnt:[4026532260]
readlink(3) = /dev/zero
readlink(4) = /dev/zero
fork
创建一个新的父进程。
鉴于
unshare
在创建新命名空间时复制父进程命名空间,这有其重要性:
无分叉:在命名空间 4026532259 处于活动状态时创建 fd=4,但后者调用
unshare
从 4026532253(父进程命名空间)复制
fork:fd=4 在命名空间 4026532259 中创建,后面对
unshare
的调用从 that 命名空间复制,因为它现在是父进程的命名空间。