pivot_root系统调用及示例

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 会执行以下操作:

  1. 将 new_root 变成新的根文件系统 (/)。
  2. 将旧的根文件系统移动到一个指定的位置 (put_old),这个位置必须是在新的根文件系统 new_root 内部。
  3. 更新所有相关进程的根目录和当前工作目录信息。

简单来说,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,你可能需要:

  1. 更新 glibc。
  2. 手动通过 syscall 调用(但这比较复杂,因为需要处理路径)。
  3. 使用 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 能力)。
  • EINVALnew_root 或 put_old 参数无效,例如 new_root 和 put_old 在同一个文件系统上,或者 put_old 不在 new_root 下,或者 new_root 本身就是根目录 (/)。
  • ENOMEM: 内核内存不足。
  • EBUSYnew_root 或 put_old 正在被使用,无法完成操作。
  • ENOENTnew_root 或 put_old 指定的目录不存在。
  • ENOTDIRnew_root 或 put_old 不是目录。
  • EIO: I/O 错误。

7. 重要前提和限制

要成功调用 pivot_root,必须满足以下条件:

  1. new_root 和 put_old 必须是不同的文件系统new_root 和 put_old 不能指向同一个文件系统。通常,new_root 是一个新挂载的文件系统,而 put_old 是新文件系统中的一个目录。
  2. put_old 必须在 new_root 内部put_old 指定的路径必须是 new_root 路径的子目录。
  3. 调用进程必须在 new_root 中运行:调用 pivot_root 时,调用进程的当前工作目录 (cwd) 通常必须位于 new_root 文件系统内。最常见做法是在调用前先 chdir(new_root)
  4. 权限:通常需要 CAP_SYS_ADMIN 能力(root 用户通常拥有此能力)。
  5. 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 的概念和它在系统启动中的作用是非常有价值的,但在日常应用程序开发中很少会直接用到它。

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注