Linux sysrq紧急魔术键handle_sysrq调度

Linux sysrq紧急魔术键handle_sysrq调度

SysRq(System Request)是Linux内核提供的一套紧急管理功能组合键,即使在系统挂起或无响应时仍能通过键盘输入或/proc接口执行关键操作。其核心调度函数是handle_sysrq,接收一个字符参数并查找对应的操作处理函数。

SysRq操作通过sysrq_key_op描述,每个操作包含处理函数、帮助文本和启用掩码:

struct sysrq_key_op {
void (*handler)(int keys);
const char *help_msg;
const char *action_msg;
int enable_mask;
};

所有操作被组织在sysrq_key_table中:

static struct sysrq_key_op *sysrq_key_table[SYSRQ_KEY_TABLE_SIZE] = {
&sysrq_loglevel_ops, /* '0'-'9' 日志级别 */
...
&sysrq_reboot_ops, /* 'b' 立即重启 */
&sysrq_crashdump_ops, /* 'c' 触发crash dump */
&sysrq_term_ops, /* 'e' 向所有进程发送SIGTERM */
&sysrq_moom_ops, /* 'f' 调用oom killer */
&sysrq_help_ops, /* 'h' 显示帮助 */
&sysrq_kill_ops, /* 'i' 向所有进程发送SIGKILL */
&sysrq_thaw_ops, /* 'j' 解冻所有文件系统 */
&sysrq_ftrace_dump_ops, /* 'l' ftrace dump */
&sysrq_sync_ops, /* 's' 同步所有挂载的文件系统 */
&sysrq_showtask_ops, /* 't' 显示任务信息 */
&sysrq_unmount_ops, /* 'u' 重新挂载所有文件系统为只读 */
&sysrq_showstate_ops, /* 'p' 显示寄存器 */
};

handle_sysrq函数根据传入的key从表中查找对应的ops并调用handler:

void handle_sysrq(int key)
{
struct sysrq_key_op *op_p;
int orig_log_level;
unsigned long flags;

if (sysrq_on())
__handle_sysrq(key, false);
}

static void __handle_sysrq(int key, bool check_mask)
{
struct sysrq_key_op *op_p;
int orig_log_level;
int i;

if ((key >= 36) && (key <= 126))
i = key - 36; /* 将ASCII码映射为表索引 */
else
i = key;

op_p = sysrq_key_table[i];
if (!op_p || !op_p->handler)
return;

/* 检查当前sysrq掩码是否允许该操作 */
if (!sysrq_on_mask(op_p->enable_mask))
return;

pr_info("SysRq : %s\n", op_p->action_msg);

/* 提高日志级别,确保消息可见 */
orig_log_level = console_loglevel;
console_loglevel = CONSOLE_EXTENDED;

op_p->handler(key);

console_loglevel = orig_log_level;
}

以sysrq_reboot_ops为例,reboot操作通过emergency_restart绕过正常关机流程直接重置硬件:

static struct sysrq_key_op sysrq_reboot_ops = {
.handler = sysrq_handle_reboot,
.help_msg = "reboot(b)",
.action_msg = "Emergency Reboot",
.enable_mask = SYSRQ_ENABLE_BOOT,
};

static void sysrq_handle_reboot(int key)
{
emergency_restart();
}

sysrq_handle_crash直接触发panic,用于测试kdump配置:

static void sysrq_handle_crash(int key)
{
char *killer = NULL;

pr_info("sysrq: Trigger a crash\n");
*killer = 1; /* 制造NULL指针解引用 */
}

SysRq触发路径有三个入口:

1. 键盘中断:当用户按下Alt+SysRq+命令键时,键盘驱动调用handle_sysrq。在AT键盘驱动中,中断处理函数kbd_keycode识别到SysRq按下事件后设置sysrq_alt状态:

static void kbd_keycode(struct vc_data *vc, unsigned char keycode, ...)
{
if (keycode == KEY_SYSRQ) {
sysrq_alt = 1;
return;
}

if (sysrq_alt && keycode != KEY_LEFTALT && keycode != KEY_RIGHTALT) {
handle_sysrq(keycode);
sysrq_alt = 0;
return;
}
}

2. /proc/sysrq-trigger接口:写入字符直接触发,无需键盘硬件支持:

static ssize_t write_sysrq_trigger(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
char c;

if (get_user(c, buf))
return -EFAULT;
handle_sysrq(c);
return count;
}

3. 串口SysRq:在串口终端中输入break信号序列后跟随命令字符。

可调节的sysrq掩码通过/proc/sys/kernel/sysrq控制,决定哪些功能可用。掩码值按位分配:1为所有功能,0为禁用,大于1时按bitmask精细控制:

#define SYSRQ_ENABLE_LOG (1 << 0)
#define SYSRQ_ENABLE_SYNC (1 << 1)
#define SYSRQ_ENABLE_REMOUNT (1 << 2)
#define SYSRQ_ENABLE_SIGNAL (1 << 3)
#define SYSRQ_ENABLE_BOOT (1 << 4)
#define SYSRQ_ENABLE_RT (1 << 5)

在SMP系统中,handle_sysrq被设计为只有接收到SysRq的CPU执行操作。对于像't'(显示任务信息)这样的操作,每个CPU可能只看到部分信息,但整体设计优先考虑操作的原子性和低延迟。