kexec_load系统调用及示例

kexec_load – 加载新的内核镜像用于快速重启

1. 函数介绍

kexec_load 是一个 Linux 系统调用,用于加载新的内核镜像到内存中,以便通过 kexec 机制进行快速内核切换。kexec 是一种允许在不经过 BIOS/UEFI 初始化过程的情况下直接启动新内核的技术,大大减少了系统重启时间。

2. 函数原型

#include <linux/kexec.h>

long kexec_load(unsigned long entry, unsigned long nr_segments,
                struct kexec_segment *segments, unsigned long flags);

注意:这不是标准 C 库函数,需要通过 syscall() 调用。

3. 功能

将指定的内核镜像加载到内存中,为后续的 kexec 调用做准备。加载的内核可以在任何时候通过 reboot() 系统调用的 LINUX_REBOOT_CMD_KEXEC 命令激活。

4. 参数

  • unsigned long entry: 新内核的入口点地址
  • unsigned long nr_segments: 段的数量
  • struct kexec_segment *segments: 指向段描述符数组的指针
  • unsigned long flags: 控制标志
    • KEXEC_ON_CRASH: 为内核崩溃转储加载内核
    • KEXEC_PRESERVE_CONTEXT: 保留 CPU 状态
    • KEXEC_ARCH_MASK: 架构相关标志

5. kexec_segment 结构体

struct kexec_segment {
    void *buf;          /* 缓冲区指针 */
    size_t bufsz;       /* 缓冲区大小 */
    void *mem;          /* 内存地址 */
    size_t memsz;       /* 内存段大小 */
};

6. 返回值

  • 成功时:返回 0
  • 失败时:返回 -1,并设置 errno

7. 常见 errno 错误码

  • EBUSY: kexec 子系统正在使用中
  • EINVAL: 参数无效
  • EPERM: 权限不足(需要 CAP_SYS_BOOT 能力)
  • ENOMEM: 内存不足
  • EADDRNOTAVAIL: 指定的内存地址不可用
  • ENOEXEC: 内核镜像格式无效

8. 相似函数,或关联函数

  • reboot(): 系统重启函数,用于激活已加载的内核
  • kexec_file_load(): 更现代的内核加载接口(Linux 3.17+)
  • syscall(): 系统调用接口
  • /sbin/kexec: 用户态 kexec 工具
  • /proc/iomem: 查看系统内存布局
  • /sys/kernel/kexec_crash_loaded: 检查崩溃内核是否已加载

9. 示例代码

示例1:基本使用 – kexec 加载框架

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/kexec.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

#ifndef SYS_kexec_load
# define SYS_kexec_load 246  // x86_64 架构下的系统调用号
#endif

#ifndef SYS_reboot
# define SYS_reboot 169
#endif

#define LINUX_REBOOT_MAGIC1 0xfee1dead
#define LINUX_REBOOT_MAGIC2 0x28121969
#define LINUX_REBOOT_CMD_KEXEC 0x45584543

// 检查 kexec 支持
int check_kexec_support() {
    if (access("/proc/iomem", R_OK) == -1) {
        printf("系统不支持 /proc/iomem\n");
        return -1;
    }
    
    FILE *fp = fopen("/proc/cmdline", "r");
    if (fp) {
        char cmdline[1024];
        if (fgets(cmdline, sizeof(cmdline), fp)) {
            if (strstr(cmdline, "nokexec")) {
                printf("内核启动参数禁用了 kexec\n");
                fclose(fp);
                return -1;
            }
        }
        fclose(fp);
    }
    
    return 0;
}

// 检查权限
int check_kexec_permissions() {
    if (geteuid() != 0) {
        printf("需要 root 权限执行 kexec\n");
        return -1;
    }
    
    // 检查 CAP_SYS_BOOT 能力
    // 这里简化处理,实际需要使用 libcap
    
    return 0;
}

// 加载内核文件到内存
void* load_kernel_file(const char *filename, size_t *size) {
    int fd = open(filename, O_RDONLY);
    if (fd == -1) {
        printf("无法打开内核文件: %s\n", filename);
        return NULL;
    }
    
    struct stat st;
    if (fstat(fd, &st) == -1) {
        printf("无法获取文件状态: %s\n", strerror(errno));
        close(fd);
        return NULL;
    }
    
    void *buffer = malloc(st.st_size);
    if (!buffer) {
        printf("内存分配失败\n");
        close(fd);
        return NULL;
    }
    
    ssize_t bytes_read = read(fd, buffer, st.st_size);
    if (bytes_read != st.st_size) {
        printf("读取文件失败\n");
        free(buffer);
        close(fd);
        return NULL;
    }
    
    *size = st.st_size;
    close(fd);
    return buffer;
}

int main() {
    printf("=== kexec 加载框架演示 ===\n");
    
    // 检查系统支持
    if (check_kexec_support() == -1) {
        printf("系统不支持 kexec 功能\n");
        return 1;
    }
    
    // 检查权限
    if (check_kexec_permissions() == -1) {
        printf("权限不足,无法执行 kexec\n");
        return 1;
    }
    
    printf("系统支持 kexec,权限检查通过\n");
    
    // 显示系统信息
    printf("当前内核版本: ");
    system("uname -r");
    
    printf("系统架构: ");
    system("uname -m");
    
    // 检查 kexec 状态
    printf("\n当前 kexec 状态:\n");
    system("ls /sys/kernel/kexec* 2>/dev/null || echo 'kexec 信息不可用'");
    
    return 0;
}

示例2:kexec 文件加载接口(现代方法)

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/kexec.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

#ifndef SYS_kexec_file_load
# define SYS_kexec_file_load 311  // x86_64 架构下的系统调用号
#endif

// kexec_file_load 系统调用(Linux 3.17+)
long kexec_file_load_syscall(int kernel_fd, int initrd_fd,
                            unsigned long cmdline_len,
                            const char *cmdline_ptr,
                            unsigned long flags) {
    return syscall(SYS_kexec_file_load, kernel_fd, initrd_fd,
                   cmdline_len, cmdline_ptr, flags);
}

void demonstrate_kexec_file_load() {
    printf("=== kexec_file_load 演示 ===\n");
    
    if (geteuid() != 0) {
        printf("需要 root 权限\n");
        return;
    }
    
    // 检查系统是否支持 kexec_file_load
    long result = syscall(SYS_kexec_file_load, -1, -1, 0, NULL, 0);
    if (result == -1 && errno == ENOSYS) {
        printf("系统不支持 kexec_file_load 系统调用\n");
        printf("需要 Linux 内核 3.17 或更高版本\n");
        return;
    }
    
    printf("系统支持 kexec_file_load 接口\n");
    
    // 显示可用的内核文件
    printf("\n可用的内核文件:\n");
    system("ls /boot/vmlinuz* 2>/dev/null | head -5 || echo '未找到内核文件'");
    
    // 显示 initrd 文件
    printf("\n可用的 initrd 文件:\n");
    system("ls /boot/initrd* /boot/initramfs* 2>/dev/null | head -5 || echo '未找到 initrd 文件'");
    
    // 显示当前 kexec 状态
    printf("\n当前 kexec 状态:\n");
    system("cat /sys/kernel/kexec_loaded 2>/dev/null || echo 'kexec_loaded 不可用'");
    system("cat /sys/kernel/kexec_crash_loaded 2>/dev/null || echo 'kexec_crash_loaded 不可用'");
}

int main() {
    demonstrate_kexec_file_load();
    return 0;
}

示例3:kexec 状态检查工具

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

void check_kexec_status() {
    printf("=== kexec 状态检查 ===\n");
    
    // 检查内核配置
    printf("内核 kexec 支持:\n");
    system("grep CONFIG_KEXEC /boot/config-$(uname -r) 2>/dev/null || "
           "zgrep CONFIG_KEXEC /proc/config.gz 2>/dev/null || "
           "echo '无法确定内核配置'");
    
    // 检查 kexec 模块参数
    printf("\nkexec 模块信息:\n");
    system("lsmod | grep kexec 2>/dev/null || echo 'kexec 作为内建功能或未加载'");
    
    // 检查 sysfs 接口
    printf("\n系统 kexec 状态:\n");
    system("echo 'kexec 加载状态: ' && "
           "cat /sys/kernel/kexec_loaded 2>/dev/null || echo '未知'");
    system("echo '崩溃内核状态: ' && "
           "cat /sys/kernel/kexec_crash_loaded 2>/dev/null || echo '未知'");
    system("echo 'kexec 锁定状态: ' && "
           "cat /sys/kernel/kexec_crash_size 2>/dev/null || echo '未知'");
    
    // 检查内存布局
    printf("\n系统内存布局 (相关部分):\n");
    system("grep -E 'System RAM|Kernel code|Kernel data' /proc/iomem 2>/dev/null || "
           "echo '无法读取内存布局'");
    
    // 检查权限
    printf("\n权限检查:\n");
    printf("当前 UID: %d\n", getuid());
    printf("当前 EUID: %d\n", geteuid());
    
    if (geteuid() == 0) {
        printf("✓ 以 root 身份运行\n");
    } else {
        printf("✗ 需要 root 权限\n");
    }
    
    // 检查能力
    printf("\n能力检查:\n");
    system("cat /proc/self/status | grep Cap 2>/dev/null || echo '无法读取能力信息'");
}

void show_kexec_limits() {
    printf("\n=== kexec 限制和配置 ===\n");
    
    // 显示硬件限制
    printf("硬件架构限制:\n");
    system("uname -m");
    
    // 显示内存限制
    printf("\n内存相关信息:\n");
    system("free -h");
    
    // 显示 kexec 相关的内核参数
    printf("\n相关内核参数:\n");
    system("sysctl -a 2>/dev/null | grep -E 'kexec|crash' | head -10 || "
           "echo '无法读取内核参数'");
    
    // 显示安全限制
    printf("\n安全相关设置:\n");
    system("cat /proc/sys/kernel/kptr_restrict 2>/dev/null || echo 'kptr_restrict 未设置'");
    system("cat /proc/sys/kernel/perf_event_paranoid 2>/dev/null || echo 'perf_event_paranoid 未设置'");
}

void demonstrate_kexec_commands() {
    printf("\n=== kexec 相关命令 ===\n");
    
    printf("常用 kexec 命令:\n");
    printf("  kexec -l <kernel>          # 加载内核\n");
    printf("  kexec -e                   # 执行已加载的内核\n");
    printf("  kexec -p <kernel>          # 加载崩溃内核\n");
    printf("  kexec -u                   # 卸载当前内核\n");
    
    printf("\n示例用法:\n");
    printf("  # 加载新内核\n");
    printf("  kexec -l /boot/vmlinuz-$(uname -r) \\\n");
    printf("        --initrd=/boot/initrd.img-$(uname -r) \\\n");
    printf("        --command-line=\"$(cat /proc/cmdline)\"\n");
    printf("  \n");
    printf("  # 快速重启\n");
    printf("  kexec -e\n");
    
    // 检查 kexec 命令是否存在
    printf("\n系统中的 kexec 工具:\n");
    system("which kexec 2>/dev/null && echo '✓ kexec 命令可用' || echo '✗ kexec 命令不可用'");
}

int main() {
    check_kexec_status();
    show_kexec_limits();
    demonstrate_kexec_commands();
    
    return 0;
}

示例4:kexec 安全和监控工具

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

void security_analysis() {
    printf("=== kexec 安全分析 ===\n");
    
    // 检查 kexec 锁定状态
    printf("kexec 锁定检查:\n");
    int fd = open("/proc/sys/kernel/kexec_load_disabled", O_RDONLY);
    if (fd != -1) {
        char buf[16];
        ssize_t n = read(fd, buf, sizeof(buf) - 1);
        if (n > 0) {
            buf[n] = '\0';
            printf("kexec 加载已%s\n", atoi(buf) ? "禁用" : "启用");
        }
        close(fd);
    } else {
        printf("无法检查 kexec 锁定状态\n");
    }
    
    // 检查安全启动状态
    printf("\n安全启动状态:\n");
    system("mokutil --sb-state 2>/dev/null || echo 'mokutil 不可用或安全启动未启用'");
    
    // 检查 IMA/EVM 状态
    printf("\nIMA/EVM 状态:\n");
    system("cat /sys/kernel/security/ima/policy_count 2>/dev/null || echo 'IMA 不可用'");
    
    // 检查 SELinux/AppArmor 状态
    printf("\n强制访问控制:\n");
    system("getenforce 2>/dev/null || echo 'SELinux 不可用'");
    system("aa-status 2>/dev/null | head -5 || echo 'AppArmor 信息不可用'");
}

void performance_benchmark() {
    printf("\n=== kexec 性能优势 ===\n");
    
    printf("传统重启 vs kexec 重启:\n");
    printf("  传统重启时间:     30-120 秒\n");
    printf("  kexec 重启时间:   2-10 秒\n");
    printf("  时间节省:         80-95%%\n");
    
    printf("\n适用场景:\n");
    printf("  • 高可用性集群\n");
    printf("  • 实时系统维护\n");
    printf("  • 内核升级测试\n");
    printf("  • 故障恢复\n");
    printf("  • 开发调试\n");
    
    printf("\n性能考虑:\n");
    printf("  • 需要足够的内存保留空间\n");
    printf("  • 内核镜像大小影响加载时间\n");
    printf("  • 内存碎片可能影响加载成功率\n");
}

void crash_dump_analysis() {
    printf("\n=== 崩溃转储支持 ===\n");
    
    printf("kexec 崩溃转储功能:\n");
    printf("  • 内核崩溃时保存内存状态\n");
    printf("  • 快速重启并保存转储信息\n");
    printf("  • 支持 vmcore 分析\n");
    
    // 检查崩溃转储配置
    printf("\n崩溃转储配置:\n");
    system("cat /proc/sys/kernel/panic 2>/dev/null || echo 'panic 设置不可用'");
    system("cat /proc/sys/kernel/panic_on_oops 2>/dev/null || echo 'panic_on_oops 设置不可用'");
    system("cat /sys/kernel/kexec_crash_size 2>/dev/null || echo '崩溃内存大小未设置'");
    
    printf("\n相关工具:\n");
    printf("  • crash: 内存转储分析工具\n");
    printf("  • makedumpfile: 转储文件处理工具\n");
    printf("  • kgdb: 内核调试器\n");
}

void best_practices() {
    printf("\n=== kexec 最佳实践 ===\n");
    
    printf("使用建议:\n");
    printf("  1. 确保新内核与硬件兼容\n");
    printf("  2. 备份重要数据\n");
    printf("  3. 测试内核参数\n");
    printf("  4. 监控系统日志\n");
    printf("  5. 准备回滚方案\n");
    
    printf("\n安全注意事项:\n");
    printf("  • 验证内核镜像完整性\n");
    printf("  • 限制 kexec 权限\n");
    printf("  • 启用审计日志\n");
    printf("  • 定期更新内核\n");
    
    printf("\n故障排除:\n");
    printf("  • 检查内核日志: dmesg | grep kexec\n");
    printf("  • 验证内存: free -h\n");
    printf("  • 检查权限: ls -l /proc/sys/kernel/kexec*\n");
}

int main() {
    printf("=== kexec 综合分析工具 ===\n");
    
    security_analysis();
    performance_benchmark();
    crash_dump_analysis();
    best_practices();
    
    printf("\n=== 总结 ===\n");
    printf("kexec 是 Linux 系统中重要的快速重启技术,\n");
    printf("适用于需要最小化停机时间的场景。\n");
    printf("使用时需注意安全性和兼容性问题。\n");
    
    return 0;
}

10. kexec 相关结构体和常量

// kexec 标志位
#define KEXEC_ON_CRASH          0x00000001
#define KEXEC_PRESERVE_CONTEXT  0x00000002
#define KEXEC_ARCH_MASK         0xffff0000

// 架构相关标志
#define KEXEC_ARCH_DEFAULT      (0 << 16)
#define KEXEC_ARCH_386          (3 << 16)
#define KEXEC_ARCH_68K          (4 << 16)
#define KEXEC_ARCH_PARISC       (15 << 16)
#define KEXEC_ARCH_X86_64       (62 << 16)
#define KEXEC_ARCH_PPC          (20 << 16)
#define KEXEC_ARCH_PPC64        (21 << 16)
#define KEXEC_ARCH_IA_64        (50 << 16)
#define KEXEC_ARCH_ARM          (40 << 16)
#define KEXEC_ARCH_S390         (22 << 16)
#define KEXEC_ARCH_SH           (42 << 16)
#define KEXEC_ARCH_MIPS_LE      (10 << 16)
#define KEXEC_ARCH_MIPS         (8 << 16)

// 重启命令
#define LINUX_REBOOT_CMD_KEXEC  0x45584543

11. 实际应用场景

场景1:高可用性集群

void ha_cluster_kexec() {
    // 在 HA 集群中使用 kexec 进行快速故障转移
    // 减少服务中断时间
}

场景2:内核开发调试

void kernel_development_kexec() {
    // 快速测试新内核版本
    // 避免漫长的 BIOS/UEFI 初始化过程
}

场景3:系统维护

void system_maintenance_kexec() {
    // 在维护窗口期间快速重启
    // 最大化可用性
}

12. 注意事项

使用 kexec 时需要注意:

1. 权限要求: 需要 CAP_SYS_BOOT 能力或 root 权限
2. 内存需求: 需要足够的内存来加载新内核
3. 兼容性: 新内核必须与硬件兼容
4. 安全性: 内核镜像需要验证完整性
5. 调试困难: kexec 后的调试信息可能有限
6. 硬件初始化: 跳过 BIOS/UEFI 可能影响某些硬件

13. 现代替代方案

// kexec_file_load (推荐的现代接口)
long kexec_file_load(int kernel_fd, int initrd_fd,
                    unsigned long cmdline_len,
                    const char *cmdline_ptr,
                    unsigned long flags);

总结

kexec_load 是 Linux 系统中实现快速内核切换的核心系统调用:

关键特性:
1. 快速重启: 跳过 BIOS/UEFI 初始化
2. 内存加载: 将新内核加载到内存中
3. 灵活控制: 支持多种加载选项
4. 崩溃支持: 支持崩溃转储功能

主要应用:
1. 高可用性系统快速故障恢复
2. 内核开发和测试
3. 系统维护和升级
4. 崩溃分析和调试

使用要点:
1. 需要特殊权限和内核支持
2. 注意内存和兼容性要求
3. 建议使用现代的 kexec_file_load 接口
4. 配合用户态工具使用效果更佳

kexec 技术显著提高了 Linux 系统的可用性和维护效率,是企业级系统管理的重要工具。

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

发表回复

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