llinux 内核 sysrq的功能说明

使用方式:

1)编译
在Kernel hacking中,选中Magic SysRq key (CONFIG_MAGIC_SYSRQ) 。
2)使用方法
通常有两种方式:a、通过/proc接口;b、通过键盘输入组合键
通过键盘组合键输入的规则是:
串口:按住break键,然后5秒内输入command字符
键盘:alt + sysrq +command键
下面是通过/proc实现意义的说明。
立即重启计算机 echo “b” > /proc/sysrq-trigger
立即关闭计算机 echo “o” > /proc/sysrq-trigger
导出内存分配的信息 echo “m” > proc/sysrq-trigger (可以用/var/log/message查看)Outputs memory statistics to the console
导出当前CPU寄存器信息和标志位的信息 echo “p” > proc/sysrq-trigger (outputs all flags and registers to the console)
导出线程状态信息 echo “t” > proc/sysrq-trigger (outputs a list of processes to the console)
故意让系统崩溃 echo “c” > proc/sysrq-trigger (crashes the system without first unmounting file systems or syncing disks attached to the system)
立即重新挂载所有的文件系统 echo “s” > proc/sysrq-trigger (attempts to sync disks attached to the system)
立即重新挂载所有的文件系统为只读 echo “u” > proc/sysrq-trigger (attempts to unmount and remount all file systems as read-only)
此外,还有两个类似于强制注销的功能
e ---- kills all processes except init using SIGTERM
i ---- kills all processes except init using SIGKILL

基本原理

Sysrq实现的基本原理为:在键盘或串口驱动中(如果是/proc接口方式,则直接定义/proc的相关写入接口即可),对按键进行判断过滤,然后根据不同的按键进行相应的处理。普通键盘和串口的流程不尽相同,主要差别在键盘和串口驱动的具体实现上,总体流程一致。
对于普通键盘来说 ,其底层的处理(从硬件中断到键盘驱动)过程依赖于内核中的输入(input)子系统。键盘处理的大致流程如下:
1)键盘中断调用中断服务程序
2)键盘中断服务程序调用输入子系统
3)输入子系统调用键盘设备对应的键盘事件处理器
4)键盘事件处理器完成键码的转换分类工作,根据按键类型的不同,执行不同的操作。对于输入类按键,先将按键值存放到临时缓冲区,激活临时缓冲区的工作队列,然后结束。对于控制类按键,激活对应此次控制操作的工作队列,然后结束。
5)系统在适当的时机调度工作队列执行,完成剩下的操作
而Sysrq魔术键的处理比较特殊,在内核主分支的代码中,在上述步骤4中的键盘事件处理器中进行相应的处理,不依赖于工作队列,相当于直接在硬件中断中处理。而在3.10内核版本的分支代码中,处理流程不太一样,其合入了相应的补丁,使sysrq的处理剥离出来,放在input子系统进行处理,而脱离了键盘事件的处理流程,其还是在中断上下文中处理的,不依赖于工作队列等。主要是通过注册input_handler实现,具体见后面的代码分析。
另一方面,对于串口设备来说,其sysrq的处理流程根据各串口驱动的实现而稍有不同,但基本都是直接在硬件中断中直接处理的。
所以,总的来说,sysrq魔术键基本都在中断上下文中处理,优先级很高,能在关键时刻发挥重要作用。

驱动部分

代码位置: drivers/tty/sysrq.c

static struct sysrq_key_op sysrq_unraw_op = {
	.handler	= sysrq_handle_unraw,
	.help_msg	= "unraw(r)",
	.action_msg	= "Keyboard mode set to system default",
	.enable_mask	= SYSRQ_ENABLE_KEYBOARD,
};

其中:
handler表示相应键码所对应的处理函数;
action_msg是执行处理函数前打印的信息;
help_msg指相应键码的帮助信息;
enable_mask指该功能是否打开,仅限于键盘输入方式。
另外,sysrq还定义了一个静态全局数组sysrq_key_table,共有36个元素,其中0~9用于命令字09,10到36用于命令字az。当从/proc/得到输入的命令字后,可以根据这个规则计算出他在sysrq_key_table中的index,然后判断对应handler是否为空,如果不为空的话,则调用handler函数处理。下面列出几个例子:

static struct sysrq_key_op sysrq_reboot_op = {
	.handler	= sysrq_handle_reboot,
	.help_msg	= "reboot(b)",
	.action_msg	= "Resetting",
	.enable_mask	= SYSRQ_ENABLE_BOOT,
};
static struct sysrq_key_op sysrq_sync_op = {
	.handler	= sysrq_handle_sync,
	.help_msg	= "sync(s)",
	.action_msg	= "Emergency Sync",
	.enable_mask	= SYSRQ_ENABLE_SYNC,
};
........
static struct sysrq_key_op *sysrq_key_table[36] = {
	&sysrq_loglevel_op,		/* 0 */
	&sysrq_loglevel_op,		/* 1 */
	&sysrq_loglevel_op,		/* 2 */
	&sysrq_loglevel_op,		/* 3 */
	&sysrq_loglevel_op,		/* 4 */
	&sysrq_loglevel_op,		/* 5 */
	&sysrq_loglevel_op,		/* 6 */
	&sysrq_loglevel_op,		/* 7 */
	&sysrq_loglevel_op,		/* 8 */
	&sysrq_loglevel_op,		/* 9 */

	/*
	 * a: Don't use for system provided sysrqs, it is handled specially on
	 * sparc and will never arrive.
	 */
	NULL,				/* a */
	&sysrq_reboot_op,		/* b */
	&sysrq_crash_op,		/* c */
	&sysrq_showlocks_op,		/* d */
	&sysrq_term_op,			/* e */
	&sysrq_moom_op,			/* f */
	........
	}

键盘其它按键的处理代码流程:
atkbd_interrupt() //键盘中断ISR
input_event() //输入子系统相关处理
input_handle_event()
input_pass_values()
input_to_handler()
handler->events() //键盘初始化是预先注册好的handler(kbd_handler)的event接口(kbd_event)
kbd_event()
kbd_keycode()
put_queue()
tty_insert_flip_char() //将键盘键值对应的编码数据写入缓冲区
tty_schedule_flip() //激活工作队列处理,处理函数为flush_to_ldisc
最终在kbd_keycode()函数进行相应的键码处理,主要完成键码的转换分类工作,根据按键类型的不同,执行不同的操作。对于输入类按键,先将按键值存放到临时缓冲区,激活临时缓冲区的工作队列,然后结束。对于控制类按键,激活对应此次控制操作 的工作队列,然后结束。

通过/proc接口触发Sysrq魔术键的主要函数流程(write_sysrq_trigger()为/proc/sysrq-trigger接口的write接口):
write_sysrq_trigger()
__handle_sysrq()

代码流程:串口函数

438 #ifdef SUPPORT_SYSRQ
439 static inline int
440 uart_handle_sysrq_char(struct uart_port *port, unsigned int ch)
441 {
442         if (port->sysrq) {
443                 if (ch && time_before(jiffies, port->sysrq)) {
444                         handle_sysrq(ch);
445                         port->sysrq = 0;
446                         return 1;
447                 }
448                 port->sysrq = 0;
449         }
450         return 0;
451 }
452 #else
453 #define uart_handle_sysrq_char(port,ch) ({ (void)port; 0; })
454 #endif

这个函数一般在uart rx的中断函数中调用uart_handle_sysrq_char 最好的是第二个参数ch表示用户当前按下key的ascll码
uart_handle_sysrq_char 继续调用handle_sysrq出来按下的key

source/drivers/tty/sysrq.c
577 void handle_sysrq(int key)
578 {
579         if (sysrq_on())
580                 __handle_sysrq(key, true);
581 }

继续调用__handle_sysrq

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

	 * Raise the apparent loglevel to maximum so that the sysrq header
	 * is shown to provide the user with positive feedback.  We do not
	 * simply emit this at KERN_EMERG as that would change message
	 * routing in the consumers of /proc/kmsg.
	 */
	 ***************
	orig_log_level = console_loglevel;
	console_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
	pr_info("SysRq : ");
        op_p = __sysrq_get_key_op(key);
}

__sysrq_get_key_op(key) 拿到对应key的函数,例如按下0~9 来改变log lever.对应的hanler为sysrq_handle_loglever 然后调用这个handle。
我们看看最重要的__sysrq_get_key_op实现.

504 struct sysrq_key_op *__sysrq_get_key_op(int key)
505 {
506         struct sysrq_key_op *op_p = NULL;
507         int i;
508 
509         i = sysrq_key_table_key2index(key);
510         if (i != -1)
511                 op_p = sysrq_key_table[i];
512 
513         return op_p;
514 }
其中sysrq_key_table_key2index将key转成index,很简单
488 static int sysrq_key_table_key2index(int key)
489 {
490         int retval;
491 
492         if ((key >= '') && (key <= '9'))
493                 retval = key - '';
494         else if ((key >= 'a') && (key <= 'z'))
495                 retval = key + 10 - 'a';
496         else
497                 retval = -1;
498         return retval;
499 }
得到这个index后就查询sysrq_key_table 数组得到对应key的handle。

 static struct sysrq_key_op *sysrq_key_table[36] = {
429         &sysrq_loglevel_op,             /* 0 */
430         &sysrq_loglevel_op,             /* 1 */
431         &sysrq_loglevel_op,             /* 2 */

例如1对应的op是sysrq_loglevel_op

 93 static struct sysrq_key_op sysrq_loglevel_op = {
 94         .handler        = sysrq_handle_loglevel,
 95         .help_msg       = "loglevel(0-9)",
 96         .action_msg     = "Changing Loglevel",
 97         .enable_mask    = SYSRQ_ENABLE_LOG,
 98 };

可以看到handler是sysrq_handle_loglevel,
我们再来看看sysrq_handle_loglevel具体是怎么改变log level的

 84 static void sysrq_handle_loglevel(int key)
 85 {
 86         int i;
 87 
 88         i = key - '';
 89         console_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
 90         pr_info("Loglevel set to %d\n", i);
 91         console_loglevel = i;
 92 }

可以看到是通过console_loglevel 来改变kernel log level的.
如果想要定制key对应的handler,就需要来修改sysrq_key_table。改法很简单的。只要替换对应key的handle就行了

猜你喜欢

转载自blog.csdn.net/weixin_43836778/article/details/90694179