10.2. 键盘 LED 指示灯闪烁

在某些情况下,您可能希望有一种更简单、更直接的方式与外部世界进行通信。闪烁键盘 LED 指示灯可能就是这样一种解决方案:这是一种立即引起注意或显示状态条件的直接方式。键盘 LED 指示灯存在于每种硬件上,它们始终可见,无需任何设置,并且与写入 tty 或文件相比,它们的使用相当简单且非侵入性。

以下源代码演示了一个最小的内核模块,当加载时,它会开始闪烁键盘 LED 指示灯,直到卸载为止。

示例 10-2. kbleds.c

/* 
 *  kbleds.c - Blink keyboard leds until the module is unloaded.
 */

#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/tty.h>		/* For fg_console, MAX_NR_CONSOLES */
#include <linux/kd.h>		/* For KDSETLED */
#include <linux/vt.h>
#include <linux/console_struct.h>	/* For vc_cons */

MODULE_DESCRIPTION("Example module illustrating the use of Keyboard LEDs.");
MODULE_AUTHOR("Daniele Paolo Scarpazza");
MODULE_LICENSE("GPL");

struct timer_list my_timer;
struct tty_driver *my_driver;
char kbledstatus = 0;

#define BLINK_DELAY   HZ/5
#define ALL_LEDS_ON   0x07
#define RESTORE_LEDS  0xFF

/*
 * Function my_timer_func blinks the keyboard LEDs periodically by invoking
 * command KDSETLED of ioctl() on the keyboard driver. To learn more on virtual 
 * terminal ioctl operations, please see file:
 *     /usr/src/linux/drivers/char/vt_ioctl.c, function vt_ioctl().
 *
 * The argument to KDSETLED is alternatively set to 7 (thus causing the led 
 * mode to be set to LED_SHOW_IOCTL, and all the leds are lit) and to 0xFF
 * (any value above 7 switches back the led mode to LED_SHOW_FLAGS, thus
 * the LEDs reflect the actual keyboard status).  To learn more on this, 
 * please see file:
 *     /usr/src/linux/drivers/char/keyboard.c, function setledstate().
 * 
 */

static void my_timer_func(unsigned long ptr)
{
	int *pstatus = (int *)ptr;

	if (*pstatus == ALL_LEDS_ON)
		*pstatus = RESTORE_LEDS;
	else
		*pstatus = ALL_LEDS_ON;

	(my_driver->ioctl) (vc_cons[fg_console].d->vc_tty, NULL, KDSETLED,
			    *pstatus);

	my_timer.expires = jiffies + BLINK_DELAY;
	add_timer(&my_timer);
}

static int __init kbleds_init(void)
{
	int i;

	printk(KERN_INFO "kbleds: loading\n");
	printk(KERN_INFO "kbleds: fgconsole is %x\n", fg_console);
	for (i = 0; i < MAX_NR_CONSOLES; i++) {
		if (!vc_cons[i].d)
			break;
		printk(KERN_INFO "poet_atkm: console[%i/%i] #%i, tty %lx\n", i,
		       MAX_NR_CONSOLES, vc_cons[i].d->vc_num,
		       (unsigned long)vc_cons[i].d->vc_tty);
	}
	printk(KERN_INFO "kbleds: finished scanning consoles\n");

	my_driver = vc_cons[fg_console].d->vc_tty->driver;
	printk(KERN_INFO "kbleds: tty driver magic %x\n", my_driver->magic);

	/*
	 * Set up the LED blink timer the first time
	 */
	init_timer(&my_timer);
	my_timer.function = my_timer_func;
	my_timer.data = (unsigned long)&kbledstatus;
	my_timer.expires = jiffies + BLINK_DELAY;
	add_timer(&my_timer);

	return 0;
}

static void __exit kbleds_cleanup(void)
{
	printk(KERN_INFO "kbleds: unloading...\n");
	del_timer(&my_timer);
	(my_driver->ioctl) (vc_cons[fg_console].d->vc_tty, NULL, KDSETLED,
			    RESTORE_LEDS);
}

module_init(kbleds_init);
module_exit(kbleds_cleanup);

如果本章中的示例都不符合您的调试需求,可能还有其他技巧可以尝试。有没有想过 make menuconfig 中的 CONFIG_LL_DEBUG 有什么用?如果您激活它,您将获得对串行端口的低级访问权限。虽然这本身听起来可能不是很强大,但您可以修补kernel/printk.c或任何其他必要的系统调用来使用 printascii,从而可以跟踪您的代码通过串行线路所做的几乎所有事情。如果您发现自己正在将内核移植到一些新的和以前不受支持的架构,这通常是首先应该实现的事情之一。通过 netconsole 进行日志记录也可能值得一试。

虽然您已经看到了很多可以用来帮助调试的东西,但也有一些事情需要注意。调试几乎总是侵入性的。添加调试代码可能会充分改变情况,使错误看起来消失了。因此,您应该尽量减少调试代码,并确保它不会出现在生产代码中。