驱动调试之原理介绍

调试内核,驱动的最简单的方法,是使用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命令即可
小知识点:
在这里插入图片描述

发布了83 篇原创文章 · 获赞 3 · 访问量 1245

猜你喜欢

转载自blog.csdn.net/qq_41936794/article/details/105322427
今日推荐