kexec_load - 加载新的内核镜像用于快速重启
1. 函数介绍 kexec_load 是一个 Linux 系统调用,用于加载新的内核镜像到内存中,以便通过 kexec 机制进行快速内核切换。kexec 是一种允许在不经过 BIOS/UEFI 初始化过程的情况下直接启动新内核的技术,大大减少了系统重启时间。
2. 函数原型 1 2 3 4 5 #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: 控制标志
5. kexec_segment 结构体 1 2 3 4 5 6 7 struct kexec_segment { void *buf; /* 缓冲区指针 */ size_t bufsz; /* 缓冲区大小 */ void *mem; /* 内存地址 */ size_t memsz; /* 内存段大小 */ };
6. 返回值
成功时:返回 0
失败时:返回 -1,并设置 errno
7. 常见 errno 错误码
8. 相似函数,或关联函数
reboot(): 系统重启函数,用于激活已加载的内核
kexec_file_load(): 更现代的内核加载接口(Linux 3.17+)
syscall(): 系统调用接口
/sbin/kexec: 用户态 kexec 工具
/proc/iomem: 查看系统内存布局
/sys/kernel/kexec_crash_loaded: 检查崩溃内核是否已加载
9. 示例代码 示例1:基本使用 - kexec 加载框架 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 #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 文件加载接口(现代方法) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #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 状态检查工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 #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 安全和监控工具 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 #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 相关结构体和常量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 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:高可用性集群 1 2 3 4 5 void ha_cluster_kexec() { // 在 HA 集群中使用 kexec 进行快速故障转移 // 减少服务中断时间 }
场景2:内核开发调试 1 2 3 4 5 void kernel_development_kexec() { // 快速测试新内核版本 // 避免漫长的 BIOS/UEFI 初始化过程 }
场景3:系统维护 1 2 3 4 5 void system_maintenance_kexec() { // 在维护窗口期间快速重启 // 最大化可用性 }
12. 注意事项 使用 kexec 时需要注意:
权限要求: 需要 CAP_SYS_BOOT 能力或 root 权限2. 内存需求: 需要足够的内存来加载新内核3. 兼容性: 新内核必须与硬件兼容4. 安全性: 内核镜像需要验证完整性5. 调试困难: kexec 后的调试信息可能有限6. 硬件初始化: 跳过 BIOS/UEFI 可能影响某些硬件
13. 现代替代方案 1 2 3 4 5 6 // 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 系统中实现快速内核切换的核心系统调用:
data-ad-format="fluid"
data-ad-layout-key="-7k+ex-4a-9w+4a">
关键特性:1. 快速重启: 跳过 BIOS/UEFI 初始化2. 内存加载: 将新内核加载到内存中3. 灵活控制: 支持多种加载选项4. 崩溃支持: 支持崩溃转储功能
主要应用:1. 高可用性系统快速故障恢复2. 内核开发和测试3. 系统维护和升级4. 崩溃分析和调试
使用要点:1. 需要特殊权限和内核支持2. 注意内存和兼容性要求3. 建议使用现代的 kexec_file_load 接口4. 配合用户态工具使用效果更佳
kexec 技术显著提高了 Linux 系统的可用性和维护效率,是企业级系统管理的重要工具。