没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
Linux printk 实现原理
console 初始化流程
Console 初始化是在 start_kernel 中完成的,从 start_kernel 中 console_init()函数位置看还是比
较靠后的,在 console 初始化之前代码里面已经开始用到 printk 函数了,而此时 printk 只是
打要打印的消息添加到了一个全局的__log_buf[__LOG_BUF_LEN]里面,在真正的 console 口
驱动初始化完成之后才会把__log_buf 中的数据输出到终端显示器上面。图一为 console 初始
化流程图:
start_kernel(void)
printk(KERN_NOTICE "%s", linux_banner)
console_init()
………
rest_init()
………
do_initcalls()
图一
从上图可以看出只要在 console_init 后面,printk 打印就会生效,但是在调试代码时候发现
其实 printk 并没有立刻打印消息,而是在 do_initcalls 调用串口驱动后才开始打印的所以可以
得出如下猜测:
console_init()函数没有做到真正的初始化成功。
do_initcalls()中可能完成了 console_init 没完成的工作。
注:以上结论不针对所有 console 初始化,因为有些 console_init 确实可以初始化成功,不过
这需要静态初始化硬件部分参数,对于动态初始化硬件参数来说上述结论是成立的。
对于串口的静态初始化以 8250.c 为例其初始化函数为 serial8250_isa_init_ports(),在该函数中
主要是把 old_serial_port 事先初始化好的端口赋值给我们新的 serial8250_ports 端口变量,但
是 old_serial_port 有些驱动没有对其初始化,这时候在调用 console_register 时候就不会注册
成功 return。
上述的 serial8250_ports 没有初始化成功。只有带到 do_initcalls()函数里面来初始化,其初始
化 的 主 要 方 法 还 是 借 助 platform 总线驱 动 模 型 来 完 成 的 。 具 体 函 数 参 见
serial8250_probe(struct platform_device *dev),该函数详细的完成了硬件资源与软件资源挂
接的一个过程。
console 的第二次初始化
console_init 在初始化的中途直接返回了但是不会对内核启动造成损害,而 start_kernel 函数
继续向下执行,当代码在执行内核驱动调用的时候,掉用到了 serial8250_init 串口驱动的注
册,大家可以看下这个初始化函数的具体实现,当函数执行 platform_driver_register 函数的
时候图二的 console_register 的第二次初始化的流程开始被调用。
serial8250_probe
serial8250_register_port
uart_add_one_port
uart_configure_port
rest_init()
register_console(port->cons)
………
图二
在上述流程图执行完成之后我们的 printk 函数正式开始工作,将之前存在__logbuf 的数据格
式化输出到我们的终端显示器上面。
console 对 uboot 参数处理
start_kernel()函数启动到 parse_early_param()时开始对 boot 参数进行处理,最终通过
strcmp(param, "console")对 boot 参数的对比来调用我们的 printk 里面的 set_up 函数。
Console_cmdline[8]
Console[0]
Console[1]
Console[2]
…………
Console[7]
Console_setup()
console=ttyS0,115200,console=ttyS1,9600……
register_console()
Add List
console_driver
get console
compare
YES
For(;;){console_driver}
printk
uboot 参数
uboot 传递的 console 信息,内核对它做了哪些事情?
首先 printk.c 中的__setup("console=", console_setup)会根据 console=的标志来解析对
boot 传递的参数进行处理后存放到 console_cmdline[MAX_CMDLINECONSOLES]全局数组里面,
其中 MAX_CMDLINECONSOLES 宏最大数值为 8,也就是说我们最多可以提供八个 console 设
备。
static int __init console_setup(char *str)
{
char buf[sizeof(console_cmdline[0].name) + 4]; /* 定义存放 name 的 buff 包括 index*/
char *s, *options, *brl_options = NULL;
int idx;
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
if (!memcmp(str, "brl,", 4)) {
brl_options = "";
str += 4;
} else if (!memcmp(str, "brl=", 4)) {
brl_options = str + 4;
str = strchr(brl_options, ',');
if (!str) {
printk(KERN_ERR "need port name after brl=\n");
return 1;
}
*(str++) = 0;
}
#endif
/*
* Decode str into name, index, options.
*/
if (str[0] >= '0' && str[0] <= '9') {/*首个参数属于字符 0-9,名字定义为 ttyS*/
strcpy(buf, "ttyS");
strncpy(buf + 4, str, sizeof(buf) - 5);/设备号放到 ttyS 字符串后面/
} else {
strncpy(buf, str, sizeof(buf) - 1);/*直接把名称与设备号放入 buff*/
}
buf[sizeof(buf) - 1] = 0;
if ((options = strchr(str, ',')) != NULL)
*(options++) = 0;
#ifdef __sparc__
if (!strcmp(str, "ttya"))
strcpy(buf, "ttyS0");
if (!strcmp(str, "ttyb"))
strcpy(buf, "ttyS1");
#endif
for (s = buf; *s; s++)
if ((*s >= '0' && *s <= '9') || *s == ',')
break;
idx = simple_strtoul(s, NULL, 10);
*s = 0;
/*此时 buf=ttyS0,idx=0,*/
__add_preferred_console(buf, idx, options, brl_options);
console_set_on_cmdline = 1;
return 1;
}
将通过解析好的数据存入 console_cmdline 结构中,该结构存入后会在 register_console()函数
中被遍历来作为把串口注册为 console 的主要依据
剩余17页未读,继续阅读
资源评论
- 魏水华2023-07-29文章中还加入了对printk代码实现的实际应用案例,使得理论与实践相结合,对读者有很大帮助。
- 傅融2023-07-29文章清晰地解释了printk函数在Linux内核中的作用和编写原理。
- 高工-老罗2023-07-29这篇文章对于理解Linux控制台中的printk代码实现非常有帮助。
- 奔跑的楠子2023-07-29作者运用简洁明了的语言,将复杂的代码逻辑解构得十分清晰。
- 練心2023-07-29通过本文,读者能够更好地了解和应用printk函数,提高调试的效率。
其实不想懂
- 粉丝: 1
- 资源: 13
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功