pivot_root系统调用及示例
相关文章:pivot_root系统调用及示例 pivot_root系统调用及示例-CSDN博客
我们来深入学习 pivot_root
系统调用
1. 函数介绍
在 Linux 系统中,每个运行的进程都看到一个文件系统层次结构(就是我们熟悉的 /
, /home
, /usr
等目录树)。这个层次结构的根目录 (/
) 是所有路径的起点。
chroot
系统调用可以改变当前进程及其子进程看到的根目录。例如,执行 chroot /newroot
后,进程再访问 /
实际上是访问 /newroot
,访问 /bin
实际上是访问 /newroot/bin
。但是,chroot
有一个重要的限制:它只影响调用进程及其后续创建的子进程。如果系统上还有其他进程在运行,它们看到的根目录仍然是原来的那个。
pivot_root
是一个更强大、更彻底的系统调用。它的作用是交换整个系统当前的根文件系统和另一个文件系统。
想象一下,你有两个完整的、独立的 Linux 文件系统环境:
- 当前的根文件系统 (
/
):这是系统当前运行所在的环境。 - 新的根文件系统 (
new_root
):这是你准备好的另一个完整的、可以独立运行的 Linux 环境(通常是一个临时的、精简的或者用于恢复的系统)。
pivot_root
会执行以下操作:
- 将
new_root
变成新的根文件系统 (/
)。 - 将旧的根文件系统移动到一个指定的位置 (
put_old
),这个位置必须是在新的根文件系统new_root
内部。 - 更新所有相关进程的根目录和当前工作目录信息。
简单来说,pivot_root
就像是整个系统(所有进程)瞬间从一个“世界”(旧根文件系统)“传送”到了另一个“世界”(新根文件系统),而旧的“世界”被折叠打包放在了新“世界”里的一个盒子里。
典型应用场景:
- Linux 启动过程:这是
pivot_root
最重要的用途之一。Linux 启动时,内核通常先挂载一个临时的、基于内存的初始根文件系统(initramfs 或 initrd)。这个临时系统包含了加载真实根文件系统所需的驱动程序。一旦真实根文件系统被挂载(例如挂载到/mnt
),启动脚本就会执行pivot_root /mnt /mnt/old_root
,将/mnt
变成新的根目录 (/
),并将旧的 initramfs 环境移动到新的根目录下的/old_root
目录中。然后,系统会执行新的根文件系统中的/sbin/init
程序,完成启动。 - 系统救援/恢复:在系统无法正常启动时,可以从外部介质(如 Live CD/USB)启动一个救援环境,然后使用
pivot_root
切换到硬盘上损坏的系统分区进行修复。 - 容器技术:虽然现代容器(如 Docker)主要使用
chroot
和mount
命名空间,但在某些高级场景下,pivot_root
也可能被用于设置容器的根文件系统。
2. 函数原型
#define _GNU_SOURCE // 启用 GNU 扩展
#include <unistd.h> // 包含 pivot_root 函数声明 (在某些系统上可能在 <linux/unistd.h>)
int pivot_root(const char *new_root, const char *put_old);
注意:pivot_root
不是 POSIX 标准函数,它是 Linux 特有的系统调用。在标准 C 库 (glibc) 中可能没有直接的包装函数,或者需要特定版本才支持。如果 #include <unistd.h>
后编译报错找不到 pivot_root
,你可能需要:
- 更新 glibc。
- 手动通过
syscall
调用(但这比较复杂,因为需要处理路径)。 - 使用
syscall(SYS_pivot_root, new_root, put_old)
,但这通常不推荐,因为路径参数需要特殊处理。
在大多数现代 Linux 系统上,直接包含 <unistd.h>
并调用 pivot_root
是可行的。
3. 功能
将当前进程的根文件系统切换为 new_root
指向的文件系统,并将原来的根文件系统移动到 put_old
指向的目录中(该目录必须位于 new_root
内)。
4. 参数
new_root
:const char *
类型。- 指向一个目录的路径名,该目录是新的根文件系统的挂载点。这个文件系统必须已经挂载好。
put_old
:const char *
类型。- 指向一个目录的路径名,该目录必须位于
new_root
文件系统内。调用成功后,原来的根文件系统会被挂载到这个目录下。
5. 返回值
- 成功: 返回 0。
- 失败: 返回 -1,并设置全局变量
errno
来指示具体的错误原因。
6. 错误码 (errno
)
EPERM
: 调用进程没有足够的权限执行此操作(通常需要CAP_SYS_ADMIN
能力)。EINVAL
:new_root
或put_old
参数无效,例如new_root
和put_old
在同一个文件系统上,或者put_old
不在new_root
下,或者new_root
本身就是根目录 (/
)。ENOMEM
: 内核内存不足。EBUSY
:new_root
或put_old
正在被使用,无法完成操作。ENOENT
:new_root
或put_old
指定的目录不存在。ENOTDIR
:new_root
或put_old
不是目录。EIO
: I/O 错误。
7. 重要前提和限制
要成功调用 pivot_root
,必须满足以下条件:
new_root
和 put_old 必须是不同的文件系统:new_root
和put_old
不能指向同一个文件系统。通常,new_root
是一个新挂载的文件系统,而put_old
是新文件系统中的一个目录。put_old
必须在new_root
内部:put_old
指定的路径必须是new_root
路径的子目录。- 调用进程必须在
new_root
中运行:调用pivot_root
时,调用进程的当前工作目录 (cwd
) 通常必须位于new_root
文件系统内。最常见做法是在调用前先chdir(new_root)
。 - 权限:通常需要
CAP_SYS_ADMIN
能力(root 用户通常拥有此能力)。 new_root
不能是根目录:new_root
本身不能是/
。
8. 相似函数或关联函数
chroot
: 更简单、限制更多的改变根目录的方法,只影响调用进程及其子进程。mount
: 用于挂载文件系统,pivot_root
通常需要先用mount
挂载好new_root
。umount
: 用于卸载文件系统。在pivot_root
后,通常需要卸载旧根文件系统(现在位于put_old
)。init
: 系统的第一个进程(PID 1),在pivot_root
后通常会执行新的init
。switch_root
: 一个用户空间命令(通常在 initramfs 中),它不仅执行pivot_root
,还会尝试卸载旧的根文件系统并执行新的/sbin/init
。它比直接使用pivot_root
更高级。
9. 示例代码
由于 pivot_root
是一个系统级操作,通常在用户空间程序中很少直接调用。它主要在系统启动脚本(initramfs)或容器运行时中使用。
下面是一个概念性的示例,展示了 pivot_root
的典型用法步骤。请注意:在真实系统上运行此代码可能会导致系统不稳定或无法启动,请在虚拟机或容器中谨慎测试。
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mount.h> // 包含 MS_MOVE 等
#include <sys/stat.h> // 包含 mkdir
#include <fcntl.h> // 包含 open, O_* flags
#include <errno.h>
#include <string.h>
int main(int argc, char *argv[]) {
// 注意:这是一个高度简化的、用于演示的示例。
// 真实的 initramfs 或切换脚本会更复杂,并处理更多细节和错误。
// 假设:
// 1. 系统当前运行在一个临时的 initramfs 根文件系统上。
// 2. 真实的目标根文件系统已经被挂载到了 /mnt/new_root。
// 3. 本程序运行在 /mnt/new_root 目录下。
const char *new_root = "/mnt/new_root";
const char *put_old = "/mnt/new_root/old_root";
printf("--- Demonstrating pivot_root (Conceptual Example) ---\n");
printf("IMPORTANT: This is a simplified example. Running this on a real system can be dangerous!\n");
printf("Make sure you understand the implications and run it in a safe environment (VM/Container).\n\n");
// 1. 检查参数
if (argc != 1) {
fprintf(stderr, "Usage: %s (no arguments needed for this conceptual demo)\n", argv[0]);
fprintf(stderr, "This example assumes the setup described in the comments.\n");
exit(EXIT_FAILURE);
}
// 2. (在真实场景中) 挂载目标根文件系统到 new_root
// 例如: mount("/dev/sda1", "/mnt/new_root", "ext4", 0, NULL);
// 这里我们假设它已经挂载好了。
// 3. 创建 put_old 目录 (如果不存在)
if (mkdir(put_old, 0755) == -1 && errno != EEXIST) {
perror("mkdir put_old");
fprintf(stderr, "Failed to create %s\n", put_old);
exit(EXIT_FAILURE);
}
printf("1. Ensured directory '%s' exists.\n", put_old);
// 4. 关键步骤:切换当前工作目录到 new_root
// 这是 pivot_root 成功的关键前提之一。
if (chdir(new_root) == -1) {
perror("chdir new_root");
fprintf(stderr, "Failed to chdir to %s\n", new_root);
exit(EXIT_FAILURE);
}
printf("2. Changed current working directory to '%s'.\n", new_root);
// 5. 调用 pivot_root
// 这是核心操作
printf("3. Calling pivot_root('%s', '%s')...\n", new_root, put_old);
if (pivot_root(".", "./old_root") == -1) { // 使用相对路径
perror("pivot_root");
fprintf(stderr, "pivot_root failed. Check kernel logs (dmesg) for more details.\n");
fprintf(stderr, "Common issues: Not running as root, new_root/put_old setup incorrect.\n");
exit(EXIT_FAILURE);
}
printf(" pivot_root succeeded!\n");
// 6. (在真实场景中) 清理:卸载旧的根文件系统
// 现在旧的根文件系统挂载在 /old_root
printf("4. Attempting to unmount the old root filesystem (now at /old_root)...\n");
// 在卸载之前,通常需要将一些关键的文件系统(如 /proc, /sys, /dev)
// 从旧根移动到新根,或者重新挂载它们。
// 这里我们简化处理。
if (umount2("/old_root", MNT_DETACH) == -1) {
perror("umount2 /old_root");
fprintf(stderr, "Failed to unmount old root. It might still be accessible at /old_root.\n");
// 不退出,继续演示
} else {
printf(" Old root filesystem unmounted (or detached).\n");
}
// 7. (在真实场景中) 执行新的 init 系统
printf("5. Would now typically exec(/sbin/init) to start the new OS environment.\n");
printf(" For this demo, we will just print a message and exit.\n");
printf("\n--- New Root Environment is Active ---\n");
printf("If this were a real initramfs, the next step would be:\n");
printf("execl(\"/sbin/init\", \"init\", (char *)NULL);\n");
printf("And the system would boot from the new root filesystem.\n");
// 注意:在真实情况下,这里不会返回。
// execl("/sbin/init", "init", (char *)NULL);
// 如果 execl 返回,说明执行失败
// perror("execl /sbin/init");
return 0;
}
10. 实际场景:initramfs 中的 pivot_root
在 Linux 启动过程中,pivot_root
的使用最为典型。一个简化版的 initramfs 脚本流程如下:
#!/bin/sh
# 这是一个在 initramfs 中运行的简化版启动脚本 (概念性)
# 1. 加载必要的内核模块 (例如文件系统驱动)
# modprobe ext4
# 2. 扫描硬件,找到根设备 (例如 /dev/sda1)
# 3. 创建一个目录用于挂载真实根文件系统
mkdir /mnt
# 4. 挂载真实根文件系统到 /mnt
mount -t ext4 /dev/sda1 /mnt
# 5. 确保新根中有存放旧根的目录
mkdir /mnt/old_root
# 6. 切换根目录 (使用 switch_root 命令,它内部调用 pivot_root 并清理)
# 注意:直接调用 pivot_root 后还需要很多清理工作,switch_root 更安全
# 但如果要手动模拟:
# cd /mnt
# pivot_root . ./old_root
# exec chroot . /sbin/init <dev/console >dev/console 2>&1
# 更推荐使用 switch_root
exec switch_root /mnt /sbin/init
11. 编译和运行 (概念性)
# 假设代码保存在 pivot_root_demo.c 中
# 注意:此代码仅为演示概念,直接运行有风险!
gcc -o pivot_root_demo pivot_root_demo.c
# 这个程序不应该在常规系统上直接运行。
# 它需要在一个特定的、已准备好 new_root 和 put_old 的环境中运行。
# 通常,它会是 initramfs 的一部分。
12. 总结
pivot_root
是一个功能强大但使用场景非常特定的 Linux 系统调用。
- 核心作用:全局性地切换系统的根文件系统,影响所有进程。
- 与
chroot
的区别:chroot
只改变调用进程及其子进程的根视图。pivot_root
改变整个系统的根视图。
- 典型用途:Linux 系统启动(initramfs 到真实根文件系统)、系统救援。
- 关键前提:
new_root
和put_old
必须是不同且已挂载的文件系统。put_old
必须在new_root
内部。- 调用进程的当前目录通常需要在
new_root
中。 - 通常需要 root 权限。
- 后续操作:
pivot_root
后,通常需要卸载旧根 (umount
) 并启动新的init
进程。 - 高级工具:
switch_root
命令封装了pivot_root
和后续的清理/执行init
的步骤,是更安全的选择。
对于 Linux 编程新手,理解 pivot_root
的概念和它在系统启动中的作用是非常有价值的,但在日常应用程序开发中很少会直接用到它。