嵌入式Linux驅動筆記(三十一)------SYSRQ組合鍵使用

你好!這裏是風箏的博客,

歡迎和我一起交流。


ALT+SYSRQ組合鍵是Linux調試的一種手段,即使在系統死機、panic、卡住等情況,只要系統還能響應中斷,那麼SYSRQ就派上用場了(比如觸發crash查看系統當前在幹啥),具體的使用情況可以參考內核文檔裏的詳細描述:Documentation/sysrq.txt

note:SYSRQ鍵也就是鍵盤上的Print Screen鍵.

使用SYSRQ組合鍵需要在內核開啓配置:CONFIG_MAGIC_SYSRQ
可以通過在系統中使用

cat /proc/sys/kernel/sysrq

查看sysrq是否開啓,sysrq值對應:

in /proc/sys/kernel/sysrq:
0 - disable sysrq completely
1 - enable all functions of sysrq
1 - bitmask of allowed sysrq functions (see below for detailed function
description):
2 - enable control of console logging level
4 - enable control of keyboard (SAK, unraw)
8 - enable debugging dumps of processes etc.
16 - enable sync command
32 - enable remount read-only
64 - enable signalling of processes (term, kill, oom-kill)
128 - allow reboot/poweroff
256 - allow nicing of all RT tasks

0就是關閉,1就是開啓,當然也可以通過手動echo “number” >/proc/sys/kernel/sysrq設置sysrq,比如

    echo 1 >/proc/sys/kernel/sysrq

開啓了sysrq怎麼用呢?
有兩種方式,如下:

一、/proc/sysrq-trigger節點

在終端輸入:

echo <key> > /proc/sysrq-trigger

key的值在sysrq.txt也有說明,如下:

  • What are the ‘command’ keys?
    ‘b’ - Will immediately reboot the system without syncing or unmounting
    your disks.
    ‘c’ - Will perform a system crash by a NULL pointer dereference.
    A crashdump will be taken if configured.
    ‘d’ - Shows all locks that are held.
    ‘e’ - Send a SIGTERM to all processes, except for init.
    ‘f’ - Will call oom_kill to kill a memory hog process.
    ‘g’ - Used by kgdb (kernel debugger)
    ‘h’ - Will display help (actually any other key than those listed
    here will display help. but ‘h’ is easy to remember 😃
    ‘i’ - Send a SIGKILL to all processes, except for init.
    ‘j’ - Forcibly “Just thaw it” - filesystems frozen by the FIFREEZE ioctl.
    ‘k’ - Secure Access Key (SAK) Kills all programs on the current virtual
    console. NOTE: See important comments below in SAK section.
    ‘l’ - Shows a stack backtrace for all active CPUs.
    ‘m’ - Will dump current memory info to your console.
    ‘n’ - Used to make RT tasks nice-able
    ‘o’ - Will shut your system off (if configured and supported).
    ‘p’ - Will dump the current registers and flags to your console.
    ‘q’ - Will dump per CPU lists of all armed hrtimers (but NOT regular
    timer_list timers) and detailed information about all
    clockevent devices.
    ‘r’ - Turns off keyboard raw mode and sets it to XLATE.
    ‘s’ - Will attempt to sync all mounted filesystems.
    ‘t’ - Will dump a list of current tasks and their information to your
    console.
    ‘u’ - Will attempt to remount all mounted filesystems read-only.
    ‘v’ - Forcefully restores framebuffer console
    ‘v’ - Causes ETM buffer dump [ARM-specific]
    ‘w’ - Dumps tasks that are in uninterruptable (blocked) state.
    ‘x’ - Used by xmon interface on ppc/powerpc platforms.
    ‘y’ - Show global CPU Registers [SPARC-64 specific]
    ‘z’ - Dump the ftrace buffer
    ‘0’-‘9’ - Sets the console log level, controlling which kernel messages
    will be printed to your console. (‘0’, for example would make
    it so that only emergency messages like PANICs or OOPSes would
    make it to your console.)

比如我要重啓機器,向sysrq-trigger節點寫入相應的字母即可:

echo b > /proc/sysrq-trigger

note:無論/proc/sys/kernel/sysrq是什麼值,sysrq-trigger都是有效的。

二、鍵盤SYSRQ組合鍵

在鍵盤上通過組合鍵 ALT + SYSRQ + key 的方式使用(串口serial console不適用這種方法,後面會講)

比如我要查看當前內存信息,可以在鍵盤上按 ALT + SYSRQ + m ,這樣即可。

那麼串口該怎麼用SYSRQ呢,其實sysrq.txt文檔裏面也有說明:

On the serial console (PC style standard serial ports only) -
You send a BREAK, then within 5 seconds a command key. Sending
BREAK twice is interpreted as a normal BREAK.

也就是使用串口時,單機鍵盤上的BREAK 鍵,在5秒內,輸出key值。
比如我要查看當前內存信息,可以在鍵盤上按 BREAK + m ,這樣即可。
note:BREAK 鍵也就是鍵盤上的Pause Break鍵.

那麼爲什麼在串口(嵌入式常用串口作爲調試)上不適用ALT + SYSRQ + key 的方式的方式呢?我們可以簡單的看下sysrq的實現:
sysrq的實現在drivers/tty/sysrq.c

static const struct input_device_id sysrq_ids[] = {
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
				INPUT_DEVICE_ID_MATCH_KEYBIT,
		.evbit = { [BIT_WORD(EV_KEY)] = BIT_MASK(EV_KEY) },
		.keybit = { [BIT_WORD(KEY_LEFTALT)] = BIT_MASK(KEY_LEFTALT) },
	},
	{ },
};

static struct input_handler sysrq_handler = {
	.filter		= sysrq_filter,
	.connect	= sysrq_connect,
	.disconnect	= sysrq_disconnect,
	.name		= "sysrq",
	.id_table	= sysrq_ids,
};
static inline void sysrq_register_handler(void)
{
	int error;

	sysrq_of_get_keyreset_config();

	error = input_register_handler(&sysrq_handler);
	if (error)
		pr_err("Failed to register input handler, error %d", error);
	else
		sysrq_handler_registered = true;
}

可以看到,sysrq是通過註冊到input的,關於input可以參考我之前的文章:
嵌入式Linux驅動筆記(二十五)------Input子系統框架

sysrq_ids裏面keybit字段填充的是:KEY_LEFTALT
這點在代碼註釋可以看到解釋:
因爲並不是所有鍵盤都有SysRq鍵,所以用KEY_LEFTALT鍵來匹配,因爲基本每個鍵盤都會有Alt鍵。
接着看input_handler結構體的filter字段填充:sysrq_filter函數
SYSRQ的處理也在這裏面了:

static bool sysrq_filter(struct input_handle *handle,
			 unsigned int type, unsigned int code, int value)
{
	switch (type) {
	case EV_SYN:
		suppress = false;
		break;
	case EV_KEY:
		suppress = sysrq_handle_keypress(sysrq, code, value);
		break;
	default:
		suppress = sysrq->active;
		break;
	}
	return suppress;
}

當輸入設備上報KEY事件時,調用sysrq_handle_keypress函數。

static bool sysrq_handle_keypress(struct sysrq_state *sysrq,
				  unsigned int code, int value)
{
	switch (code) {
	case KEY_LEFTALT:
	case KEY_RIGHTALT:
		if (!value) {
			/* One of ALTs is being released */
			if (sysrq->active && code == sysrq->alt_use)
				sysrq->active = false;
			sysrq->alt = KEY_RESERVED;

		} else if (value != 2) {
			sysrq->alt = code;
			sysrq->need_reinject = false;
		}
		break;

	case KEY_SYSRQ:
		if (value == 1 && sysrq->alt != KEY_RESERVED) {
			sysrq->active = true;
			sysrq->alt_use = sysrq->alt;
			sysrq->need_reinject = true;
		}
		if (sysrq->active)
			clear_bit(KEY_SYSRQ, sysrq->handle.dev->key);

		break;

	default:
		if (sysrq->active && value && value != 2) {
			sysrq->need_reinject = false;
			__handle_sysrq(sysrq_xlate[code], true);
		}
		break;
	}
	//後面省略......
}

這裏就是會對input子系統上報的鍵值進行判斷了,當鍵值是ALT + SYSRQ + key 的方式時,就會調用__handle_sysrq函數進行處理,__handle_sysrq函數就是根據key進行對應的操作。

我們知道,鍵盤就是一個標準的輸入設備,平時按下按鍵,是會通過input子系統進行鍵值上報的,所以我們在鍵盤按下ALT + SYSRQ ,input子系統上報ALT + SYSRQ的鍵值,sysrq驅動是能解析處理的。

但是嵌入式設備在使用串口時,串口輸入走的是tty子系統,並不會對你在串口輸入的值往input裏上報,所以你在串口裏面按ALT + SYSRQ + key 是不會觸發sysrq的!!!

那麼uart走的是tty流程,不走input,那uart怎麼使用sysrq呢?
其實,sysrq裏export有一個接口出來:handle_sysrq

void handle_sysrq(int key)
{
	if (sysrq_on())
		__handle_sysrq(key, true);
}

這裏面和通過鍵盤ALT + SYSRQ最終處理的函數是相同的,都是__handle_sysrq函數!!!所以串口可以通過使用handle_sysrq函數實現。

文章開頭描述有,想在串口使用sysrq,得按BREAK鍵,這部分在uart驅動處理,不過不同廠家的uart驅動各有不同,不過大體都是一樣的,如果你在串口按BREAK + key 沒反應,可能是你設備的uart驅動沒有支持sysrq。

那uart驅動裏是怎麼解析捕獲break的呢,可以參考
drivers/tty/serial/8250/8250_port.c
想要使用SYSRQ,得先定義 SUPPORT_SYSRQ 宏

#if defined(CONFIG_SERIAL_8250_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif
static void serial8250_read_char(struct uart_8250_port *up, unsigned char lsr)
{
	struct uart_port *port = &up->port;
	unsigned char ch;
	char flag = TTY_NORMAL;

	if (likely(lsr & UART_LSR_DR))
		ch = serial_in(up, UART_RX);
	else
		ch = 0;

	port->icount.rx++;

	lsr |= up->lsr_saved_flags;
	up->lsr_saved_flags = 0;

	if (unlikely(lsr & UART_LSR_BRK_ERROR_BITS)) {
		if (lsr & UART_LSR_BI) {
			lsr &= ~(UART_LSR_FE | UART_LSR_PE);
			port->icount.brk++;
			/*
			 * We do the SysRQ and SAK checking
			 * here because otherwise the break
			 * may get masked by ignore_status_mask
			 * or read_status_mask.
			 */
			if (uart_handle_break(port))
				return;
		} else if (lsr & UART_LSR_PE)
			port->icount.parity++;
		else if (lsr & UART_LSR_FE)
			port->icount.frame++;
		if (lsr & UART_LSR_OE)
			port->icount.overrun++;

		/*
		 * Mask off conditions which should be ignored.
		 */
		lsr &= port->read_status_mask;

		if (lsr & UART_LSR_BI) {
			pr_debug("%s: handling break\n", __func__);
			flag = TTY_BREAK;
		} else if (lsr & UART_LSR_PE)
			flag = TTY_PARITY;
		else if (lsr & UART_LSR_FE)
			flag = TTY_FRAME;
	}
	if (uart_handle_sysrq_char(port, ch))
		return;

	uart_insert_char(port, lsr, UART_LSR_OE, ch, flag);
}

這是uart中斷裏面的獲取輸入的函數,當我們在串口按下Break鍵時,函數裏if (lsr & UART_LSR_BI) 這句話是成立的,UART_LSR_BI也就是Break interrupt indicator

進而調用到uart_handle_break函數,uart_handle_break函數裏面操作:port->sysrq = jiffies + HZ*5;
這裏也就是5秒。

然後serial8250_read_char會return掉,因爲捕獲到了Break鍵,下一次就是解析隨之而來的key。在uart_handle_sysrq_char函數裏面解析。

static inline int
uart_handle_sysrq_char(struct uart_port *port, unsigned int ch)
{
	if (port->sysrq) {
		if (ch && time_before(jiffies, port->sysrq)) {
			handle_sysrq(ch);
			port->sysrq = 0;
			return 1;
		}
		port->sysrq = 0;
	}
	return 0;
}

這裏要求隨後輸出的key和Break間隔時間不能超過port->sysrq,也就是5秒。最後就會調用到handle_sysrq函數來解析key了。

網上也有使用案例:利用 SysRq 鍵排除和診斷系統故障


後續:
我看到網上有些文章描述如下:
/****************************************************************************************************
那麼如何產生一個SysRq鍵呢?

  • 在Ubuntu下,圖形界面環境不能使用SysRq,需進入文本虛擬終端環境(Ctrl+Alt+F1從圖形桌面切換到虛擬終端,Alt+F7可切回來),然後同時按下Alt和Print Screen鍵以及相應的字母鍵。
  • 在嵌入式設備上,通過串口工具也可以觸發SysRq,如果使用SecureCRT,則同時按下Alt和Print Screen鍵,會出現上述HELP,然後緊接着按下某個字母。如果使用teraTerm,則點擊菜單中的Control->Send Break,會出現上述HELP,然後緊接着按下某個字母。
    ***********************************************************************************************/

對於嵌入式設備這塊有點扯,內核文檔也有描述,串口是通過Break鍵觸發SYSRQ的,難道同時按下Alt和Print Screen鍵,會在uart觸發UART_LSR_BI中斷嗎?反正我是沒發現Break中斷被觸發。又觸發的網友可以給我留言一下。。。。

參考:llinux 內核 sysrq的功能說明

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章