kexec_load – 加载新的内核镜像用于快速重启
1. 函数介绍
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 时需要注意:
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. 配合用户态工具使用效果更佳