调试内核,驱动的最简单的方法,是使用printk函数打印信息.printk函数与用户空间的printf函数格式完全相同,它所打印的字符串头部可以加入""样式的字符,其中n为0~7,表示这条信息的记录级别,那我们的printk串口输出时,是调用串口1还是串口2还是输出到LCD上,是由uboot里面的bootargs的console=xxx(下面的讲解以ttySAC0为例子)决定,来找到对应的硬件处理函数输出打印信息,那么内核是怎样根据命令行参数确定printk的输出设备呢,在kernel/printk.c中有如下代码
/*先注册控制台信息*/
_setup("console=",console_setup);
*/
static int __init console_setup(char *str)
{
char name[sizeof(console_cmdline[0].name)];
char *s, *options;
int idx;
/*
* Decode str into name, index, options.
*/
if (str[0] >= '0' && str[0] <= '9') {
strcpy(name, "ttyS");
strncpy(name + 4, str, sizeof(name) - 5);
} else {
strncpy(name, str, sizeof(name) - 1);
}
name[sizeof(name) - 1] = 0;
if ((options = strchr(str, ',')) != NULL)
*(options++) = 0;
#ifdef __sparc__
if (!strcmp(str, "ttya"))
strcpy(name, "ttyS0");
if (!strcmp(str, "ttyb"))
strcpy(name, "ttyS1");
#endif
for (s = name; *s; s++)
if ((*s >= '0' && *s <= '9') || *s == ',')
break;
idx = simple_strtoul(s, NULL, 10);
*s = 0;
add_preferred_console(name, idx, options);//我想用名为"ttySAC0"的控制台,先记录下来
return 1;
}
static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
.......
int __init add_preferred_console(char *name, int idx, char *options)
{
struct console_cmdline *c;
int i;
/*
* See if this tty is not yet registered, and
* if we have a slot free.
*/
/*看看控制台是否已经注册了,我们是否还有空的位置可以插入控制台信息,有的话找到该位置的下标*/
for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++)
if (strcmp(console_cmdline[i].name, name) == 0 &&
console_cmdline[i].index == idx) {
selected_console = i;
return 0;
}
if (i == MAX_CMDLINECONSOLES)
return -E2BIG;
selected_console = i;
c = &console_cmdline[i]; //将可以插入控制台的位置地址赋给c
/*将控制器台信息插入改位置*/
memcpy(c->name, name, sizeof(c->name));
c->name[sizeof(c->name) - 1] = 0; //保存控制台的名字,用以比较配对,如串口则为ttySAC0
c->options = options;
c->index = idx;
return 0;
}
内核开始执行时,发现形如"console=…"的命令参数时,就会调用console_setup函数进行解析,在console_setup函数里面会调用add_preferref_console,顾名思义就是添加我想用的控制台,就是添加名字为"ttySAC0"的控制台,我们来分析下这个add_preferref_console函数,它会解析出:设备名(name),索引(index),这些信息被保存在类型为console_cmdline,名称为console_cmdlined的全局数组中,
static int s3c24xx_serial_initconsole(void)
{
struct s3c24xx_uart_info *info;
struct platform_device *dev = s3c24xx_uart_devs[0];
dbg("s3c24xx_serial_initconsole\n");
/* select driver based on the cpu */
if (dev == NULL) {
printk(KERN_ERR "s3c24xx: no devices for console init\n");
return 0;
}
if (strcmp(dev->name, "s3c2400-uart") == 0) {
info = s3c2400_uart_inf_at;
} else if (strcmp(dev->name, "s3c2410-uart") == 0) {
info = s3c2410_uart_inf_at;
} else if (strcmp(dev->name, "s3c2440-uart") == 0) {
info = s3c2440_uart_inf_at;
} else if (strcmp(dev->name, "s3c2412-uart") == 0) {
info = s3c2412_uart_inf_at;
} else {
printk(KERN_ERR "s3c24xx: no driver for %s\n", dev->name);
return 0;
}
if (info == NULL) {
printk(KERN_ERR "s3c24xx: no driver for console\n");
return 0;
}
s3c24xx_serial_console.data = &s3c24xx_uart_drv;
s3c24xx_serial_init_ports(info);
register_console(&s3c24xx_serial_console);
return 0;
static struct console s3c24xx_serial_console =
{
.name = S3C24XX_SERIAL_NAME, //S3C24XX_SERIAL_NAME为"ttySAC"
.device = uart_console_device, //init进行,用户程序打开/dev/console时使用到
.flags = CON_PRINTBUFFER, //打印先前在log_buf中保存的信息
.index = -1, //表示使用哪个串口由命令行参数决定
.write = s3c24xx_serial_console_write, //串口控制台的输出函数
.setup = s3c24xx_serial_console_setup //串口控制台的设置函数
};
上面这段代码来自于drivers/serial/s3c24xx.c中,调用"register_console(&s3c24xx_serial_console)"注册控制台时,会将s3c24xx_serial_console结构与console_cmdline数组中的设备进行比较,发现名字,索引相同,s3c24xx_serial_console结构中索引为-1,表示使用命令行参数解析出来的索引0,表示串口0,综上所述,命令行参数“console=ttySAC0”决定printk信息通过s3c24xx_serial_console结构中的相关函数,从串口0输出,所以当我们调用printk函数的时候会把输出的信息稍作处理(对打印级别的处理)后放入一个log_buf里面,然后再调用硬件的函数将log_buf里面的数据打印出去,比如上述的 s3c24xx_serial_console_write函数,系统启动后,想查看printk信息时,直接运行dmesg命令即可
小知识点: