pivot_root系统调用及示例
data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">相关文章: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. 函数原型
1 | #define _GNU_SOURCE // 启用 GNU 扩展 |
注意: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 的典型用法步骤。请注意:在真实系统上运行此代码可能会导致系统不稳定或无法启动,请在虚拟机或容器中谨慎测试。
1 | #define _GNU_SOURCE |
10. 实际场景:initramfs 中的 pivot_root
在 Linux 启动过程中,pivot_root 的使用最为典型。一个简化版的 initramfs 脚本流程如下:
1 | #!/bin/sh |
11. 编译和运行 (概念性)
1 | # 假设代码保存在 pivot_root_demo.c 中 |
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 的概念和它在系统启动中的作用是非常有价值的,但在日常应用程序开发中很少会直接用到它。