Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1247111
  • 博文数量: 122
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4004
  • 用 户 组: 普通用户
  • 注册时间: 2014-02-20 08:27
文章分类
文章存档

2016年(1)

2015年(21)

2014年(100)

分类: LINUX

2014-09-05 15:00:23

1、基本介绍
    Sysrq魔术键提供了大量的实用功能,可用户快速获取系统运行状态信息,尤其是在系统出现异常时,收集有用信息,对于疑难问题的分析有极大的帮助。
    典型应用场景如:
    1)系统进入了挂死状态(如调度出现异常、或系统负荷过重),但仍能响应中断,此时可以通过Sysrq魔术键(c)手工触发panic,结合kdump,就能收集到vmcore信息,用于问题的后续分析定位,非常有用。
    2)当系统中某进程出现挂死(可能是D状态,或是死锁),此时需要确认该进程具体挂在什么地方,可以使用Sysrq魔术键(t)打印出系统中所有进程的堆栈信息。
    3)当系统出现反应迟钝、交互困难时,难以通过shell或终端交互获取到有用信息,此时可以使用Sysrq魔术键(m,p)打印出系统中内存使用的详细信息和CPU运行上下文信息等。

2、使用
1)编译
    在Kernel hacking中,选中Magic SysRq key (CONFIG_MAGIC_SYSRQ)
2)使用方法
    通常有两种方式:a、通过/proc接口;b、通过键盘输入组合键。
    通过/proc接口的使用方法为:echo ‘command’ > /proc/sysrq-trigger
    其中command是一个字符,可以是’1 – 9’或者’a-z’或者’A-Z’,字母不区分大小写。
    常用的命令有:
    0-9 设置printk的打印级别。和/proc/sys/kernel/printk的第一个参数意思相同
    b 立即重启单板,不进行磁盘同步等操作;调用内核的函数为emergency_restart
    c 进行kexec reboot,需要KEXC支持。调用内核的函数为crash_kexec
    d 显示此有的锁,需要配置CONFIG_LOCKDEP,调用函数为debug_show_all_locks。
    e 向所有进程(init除外)发送SIGTERM信号,发送信号调用force_sig函数
    f 进入out-of-memory流程,杀死一个进程,调用out_of_memory函数
    i 向所有进程(init除外)发送SIGKILL信号
    m 显示当前内存信息,调用内核show_mem函数
    P 显示当前寄存器的值
    q 显示当前的timer,调用timer_list_show
    s 同步当前所有的文件系统,调用emergency_sync函数
    t 显示当前系统上所有的进程信息,调用函数为show_state
    u 重新mount所有文件系统,调用函数为emergency_remount
    w 显示系统中所有处于uninterruptable状态的进程,调用函数为show_state
    通过键盘组合键输入的规则是:
         串口:按住break键,然后5秒内输入command字符
         键盘:alt + sysrq +command
    详细使用规则可以参考linux内核文档:Documentation/sysrq.txt

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

4、码分析

    Sysrq功能使用结构体
sysrq_key_op定义了一个键盘键码所对应的行为,

点击(此处)折叠或打开

  1. struct sysrq_key_op {
  2.     void (*handler)(int);
  3.     char *help_msg;
  4.     char *action_msg;
  5.     int enable_mask;
  6. };
    其中:

    handler表示相应键码所对应的处理函数;
    action_msg是执行处理函数前打印的信息;
    help_msg指相应键码的帮助信息;
    enable_mask指该功能是否打开,仅限于键盘输入方式。

    另外,sysrq还定义了一个静态全局数组sysrq_key_table,共有36个元素,其中09用于命令字0~91036用于命令字a~z。当从/proc/得到输入的命令字后,可以根据这个规则计算出他在sysrq_key_table中的index,然后判断对应handler是否为空,如果不为空的话,则调用handler函数处理。

点击(此处)折叠或打开

  1. static struct sysrq_key_op *sysrq_key_table[36] = {
  2.     &sysrq_loglevel_op,        /* 0 */
  3.     &sysrq_loglevel_op,        /* 1 */
  4.     &sysrq_loglevel_op,        /* 2 */
  5.     &sysrq_loglevel_op,        /* 3 */
  6.     &sysrq_loglevel_op,        /* 4 */
  7.     &sysrq_loglevel_op,        /* 5 */
  8.     &sysrq_loglevel_op,        /* 6 */
  9.     &sysrq_loglevel_op,        /* 7 */
  10.     &sysrq_loglevel_op,        /* 8 */
  11.     &sysrq_loglevel_op,        /* 9 */

  12.     /*
  13.      * a: Don't use for system provided sysrqs, it is handled specially on
  14.      * sparc and will never arrive.
  15.      */
  16.     NULL,                /* a */
  17.     &sysrq_reboot_op,        /* b */
  18.     &sysrq_crash_op,        /* c & ibm_emac driver debug */
  19.     &sysrq_showlocks_op,        /* d */
  20.     &sysrq_term_op,            /* e */
  21.     &sysrq_moom_op,            /* f */
  22.     /* g: May be registered for the kernel debugger */
  23.     NULL,                /* g */
  24.     NULL,                /* h - reserved for help */
  25.     &sysrq_kill_op,            /* i */
  26. #ifdef CONFIG_BLOCK
  27.     &sysrq_thaw_op,            /* j */
  28. #else
  29.     NULL,                /* j */
  30. #endif
  31.     &sysrq_SAK_op,            /* k */
  32. #ifdef CONFIG_SMP
  33.     &sysrq_showallcpus_op,        /* l */
  34. #else
  35.     NULL,                /* l */
  36. #endif
  37.     &sysrq_showmem_op,        /* m */
  38.     &sysrq_unrt_op,            /* n */
  39.     /* o: This will often be registered as 'Off' at init time */
  40.     NULL,                /* o */
  41.     &sysrq_showregs_op,        /* p */
  42.     &sysrq_show_timers_op,        /* q */
  43.     &sysrq_unraw_op,        /* r */
  44.     &sysrq_sync_op,            /* s */
  45.     &sysrq_showstate_op,        /* t */
  46.     &sysrq_mountro_op,        /* u */
  47.     /* v: May be registered for frame buffer console restore */
  48.     NULL,                /* v */
  49.     &sysrq_showstate_blocked_op,    /* w */
  50.     /* x: May be registered on ppc/powerpc for xmon */
  51.     /* x: May be registered on sparc64 for global PMU dump */
  52.     NULL,                /* x */
  53.     /* y: May be registered on sparc64 for global register dump */
  54.     NULL,                /* y */
  55.     &sysrq_ftrace_dump_op,        /* z */
  56. };


    如之前所说,3.10版本内核代码中使用了input_handler来实现Sysrq魔术键的单独处理。
    相应的input_handler定义:

点击(此处)折叠或打开

  1. static struct input_handler sysrq_handler = {
  2.     .filter        = sysrq_filter,/*相应的filter处理函数,在input子系统中调用,其中调用了Sysrq的处理函数*/
  3.     .connect    = sysrq_connect,
  4.     .disconnect    = sysrq_disconnect,
  5.     .name        = "sysrq",
  6.     .id_table    = sysrq_ids,
  7. };


    注册input_handler,函数调用流程:sysrq_init()-->sysrq_register_handler()-->input_register_handler()


点击(此处)折叠或打开

  1. static int __init sysrq_init(void)
  2. {
  3.  sysrq_init_procfs();

  4.  if (sysrq_on())
  5.   sysrq_register_handler();

  6.  return 0;
  7. }

  8. static inline void sysrq_register_handler(void)
  9. {
  10.  unsigned short key;
  11.  int error;
  12.  int i;

  13.  for (i = 0; i < ARRAY_SIZE(sysrq_reset_seq); i++) {
  14.   key = platform_sysrq_reset_seq[i];
  15.   if (key == KEY_RESERVED || key > KEY_MAX)
  16.    break;

  17.   sysrq_reset_seq[sysrq_reset_seq_len++] = key;
  18.  }

  19.  error = input_register_handler(&sysrq_handler);
  20.  if (error)
  21.   pr_err("Failed to register input handler, error %d", error);
  22.  else
  23.   sysrq_handler_registered = true;
  24. }

Sysrq魔术键处理代码流程
atkbd_interrupt()  //键盘中断ISR
    input_event() //输入子系统相关处理
        input_handle_event()
            input_pass_values()
                input_to_handler()
                    handler->filter() //sysrq预先注册好的handler(sysrq_handler)的filter接口(sysrq_filter)
                        sysrq_filter()
                            sysrq_handle_keypress()
                                __handle_sysrq()  //sysrq魔术键具体处理
最终在__handle_sysrq()函数中完成Sysrq魔术键的具体处理。

键盘其它按键的处理
键盘其它按键的处理也是通过注册相应的input_handler(kbd_handler)来实现的,相应的event接口kbd_event()也在input子系统中调用:

点击(此处)折叠或打开

  1. /*键盘按键相应的input_handler*/
  2. static struct input_handler kbd_handler = {
  3.     .event        = kbd_event,
  4.     .match        = kbd_match,
  5.     .connect    = kbd_connect,
  6.     .disconnect    = kbd_disconnect,
  7.     .start        = kbd_start,
  8.     .name        = "kbd",
  9.     .id_table    = kbd_ids,
  10. };
kbd_handler注册:kbd_init()-->input_register_handler()

点击(此处)折叠或打开

  1. int __init kbd_init(void)
  2. {
  3.     int i;
  4.     int error;

  5.     for (i = 0; i < MAX_NR_CONSOLES; i++) {
  6.         kbd_table[i].ledflagstate = kbd_defleds();
  7.         kbd_table[i].default_ledflagstate = kbd_defleds();
  8.         kbd_table[i].ledmode = LED_SHOW_FLAGS;
  9.         kbd_table[i].lockstate = KBD_DEFLOCK;
  10.         kbd_table[i].slockstate = 0;
  11.         kbd_table[i].modeflags = KBD_DEFMODE;
  12.         kbd_table[i].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE;
  13.     }

  14.     error = input_register_handler(&kbd_handler);
  15.     if (error)
  16.         return error;

  17.     tasklet_enable(&keyboard_tasklet);
  18.     tasklet_schedule(&keyboard_tasklet);

  19.     return 0;
  20. }
键盘其它按键的处理代码流程:
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()

串口驱动(以8250串口为例)中对Sysrq魔术键的支持
按照sysrq的设计,通过标准串口,按下break键后5秒内,再按住command字符,会command应的sysrq流程。
相关的处理流程如下(从串口驱动的接收函数serial8250_rx_chars()开始,此函数在中断上下文中执行):
serial8250_rx_chars()
     /*
      * 判断是否按下了break键,用于判断Sysrq。当按住break键时,在uart_handle_break中判断port->sysrq是否为0,如果为0,
      * 则将port->sysrq置为5秒后的jiffies数。
      */
    uart_handle_break()
        /*
         * 对于每一个接收的字符,都会调用uart_handle_sysrq_char。如果当前jiffies数值比port->sysrq小,
         * 则说明当前字符是在按住了break后5秒内输入的,因此调用handle_sysrq处理该命令。
         */
        uart_handle_sysrq_char()
阅读(8242) | 评论(2) | 转发(1) |
给主人留下些什么吧!~~

humjb_19832014-11-25 17:07:29

呵呵,感谢专家点评

CU博客助理2014-11-25 16:47:09

专家点评:作者清晰的讲解了sysrq键值的处理逻辑,贯通内核的tty设备、INPUT子系统,读者可以从调用逻辑上快速理解相关内容。