Printk实现流程

一、初步结论
1.如何把字符串放到缓存,如何从缓存写到串口。
首先是在关中断,关调度,保持logbuf_lock自旋锁的情况下,将数据格式化后,放到printk_buf缓冲区,其大小为1K,也就是说,每次printk只能打印1K的内容。格式化完毕后,将数据再复制到log_buf缓冲区。由于在向串口输出的过程中,会暂时打开自旋锁,所以在SMP下,其他CPU可能继续向log_buf中存放数据,并由驱动输出。简单的说:调用一次printk,需要打印的并不仅仅是本次printk需要输出的内容,还可能有其他CPU上输出的内容。
从缓存中输出到真实的设备是由注册的控制台个数决定的。注册多少个设备,就向多少个设备输出。也就是说,如果注册了两个串口控制台,那么关中断的时间就会增加一倍。
2.采用中断还是轮询。
采用的是轮询方式。

二、代码流程详细分析
Printk的主要流程由vprintk辅助函数实现。
Vprintk的流程如下:

1.关抢占。这是因为需要调用smp_processor_id,如果不关调度,并且是在异常或者内核线程中调用printk的话,可能会导致任务飘移到其他CPU,从而导致后续逻辑判断错误。
2.如果当前上下文是在异常中,那么异常可能是printk引起的,由于后续会使用logbuf_lock自旋锁,为了防止自旋锁嵌套,导致系统死锁,调用zap_locks,复位自旋锁。
3.关中断,并获得logbuf_lock自旋锁,以保护printk_buf缓冲区,这样即使是在SMP上,printk打印出来的内容都不会交错输出。
4.将格式化的信息输出到printk_buf缓冲区,并复制到log_buf。
5.使用down_trylock非阻塞的申请console_sem信号量。视情况进入以下处理流程:
A:如果申请成功,则将log_buf中的内容输出到物理设备上。
B:如果申请失败,则开中断并释放自旋锁,并退出。本次输出到printk_buf缓冲区的内容,由其他printk执行路径输出到物理设备上。所以,某次printk输出到物理设备上的内容,并不一定仅仅是本次传递给printk的内容,还有可能是其他CPU上的printk输出到缓冲区中的内容。

当printk向缓冲区输出申请console_sem信号量成功后,printk会向物理设备输出,在这个过程中,会临时释放自旋锁,以允许其他CPU向缓冲区中输出内容。具体流程如下:
1.暂时释放logbuf_lock自旋锁,这样,其他CPU可以向log_buf缓冲区中输出数据。
2.调用release_console_sem向物理设备输出内容。
3.release_console_sem首先在循环执行如下流程:
A:得到当前log_buf缓冲区的起始地址和物理地址。如果没有新的数据,则退出。新的数据可能是其他cpu调用printk输出到缓冲区中的。
B:打开自旋锁,允许其他CPU向缓冲区中输出数据。
C:调用call_console_drivers将当前缓冲区中的内容输出到物理设备。本次输出的内容,不包含释放自旋锁后,其他CPU向缓冲区中输出的内容。
4. 循环结束后,release_console_sem判断本次printk是否确实输出了数据(当printk参数为空串时,可能没有数据),如果有数据输出,就唤醒klogd进程。

call_console_drivers函数首先将输出内容按行分拆,并将每行数据传递给当前注册的所有控制台设备,调用控制台驱动将数据打印出来。由于整个过程都处于关中断状态,所以,只要某个注册的控制台驱动是慢速设备,都会影响到关中断的时间。更为严重的是:如果某个CPU在调用printk输出期间,其他CPU也准备向控制台输出,那么其他CPU也会因为申请不到logbuf_lock自旋锁而处于忙等状态。

串口驱动函数:
耗时的过程是向串口等慢速设备输出数据的过程。由于printk可以在中断中使用,所以也决定了驱动不能采用中断方式,而必须是轮询串口的方式。

8250串口驱动回调函数serial8250_console_write实现了控制台write回调。这个函数的流程如下:
1.首先调用一下NMI软件狗回调函数。看来串口驱动已经意识到慢速设备对系统的影响了。但是这个回调没有办法解决硬件狗的问题。
2.关中断。
3.对需要输出的每个字符,回调serial8250_console_putchar函数。
4.最费时的地方就在serial8250_console_putchar中。它调用wait_for_xmitr(up, UART_LSR_THRE);一句,轮询串口状态。直到串口可写,才向串口输出一个字符。
5.恢复中断状态。实际上,在printk上下文中,中断此时仍然是关闭的,直到log_buf缓冲区中的所有数据都输出到串口。

<think>好的,我需要帮助用户找到Linux内核中printk函数的实现路径和相关文件。首先,我得回忆一下printk在内核中的位置。printk是内核的日志记录函数,属于内核的核心功能,通常在kernel/printk目录下。主要的实现文件可能包括printk.c和相关的头文件。 用户可能需要具体的文件路径,所以我要确认内核源代码中的结构。根据经验,printk实现主要位于kernel/printk/printk.c,而头文件可能在include/linux/printk.h。此外,控制台输出的部分可能在kernel/printk/printk.c中处理,比如console_unlock函数。 另外,需要考虑用户可能想了解printk的调用流程,比如消息存储到环形缓冲区,然后通过控制台输出。这里涉及到的一些关键数据结构,如log_buf,可能在printk.c中定义。同时,日志级别的控制,比如设置/proc/sys/kernel/printk,这些参数的处理函数可能也在同一目录下。 用户可能还想知道如何查找这些信息,比如使用git仓库搜索或者grep命令。可以建议他们通过git grep或普通grep在内核源码中查找printk的定义,这样可以找到相关的文件和调用链。此外,文档方面,Documentation/printk-formats.txt可能有格式化输出的说明,而内核配置选项如CONFIG_PRINTK也相关。 需要确保引用的文件路径正确,避免过时的信息。比如,旧版本内核可能将printk.c放在不同的目录,但现在主流版本应该是在kernel/printk/。另外,头文件的包含路径是否正确,比如include/linux/printk.h是否存在。 最后,生成相关问题时要考虑用户可能的后续疑问,比如printk的日志缓冲机制、如何调整日志级别、printk的性能影响,以及如何调试内核日志问题。这些问题可以帮助用户深入理解printk的使用和实现。</think>Linux内核中$printk$函数的实现路径及相关文件主要位于内核源代码树的以下位置: 1. **核心实现文件** - $kernel/printk/printk.c$(实现日志缓冲、格式化输出、控制台分发等核心逻辑)[^2] - $include/linux/printk.h$(函数声明和日志级别定义) 2. **关键功能模块** - **环形缓冲区管理**:在$kernel/printk/printk.c$中通过$log_buf$数组实现 - **控制台输出**:通过$console_unlock()$函数实现(同一文件内) - **日志级别控制**:通过$/proc/sys/kernel/printk$参数文件处理 3. **相关调用路径** ```c // 典型调用示例 printk("%s: initialization complete\n", __func__); ↓ vprintk_func() // kernel/printk/printk.c ↓ vsnprintf() // lib/vsprintf.c(格式化处理) ↓ log_output() // 写入环形缓冲区 ``` 4. **调试辅助工具** - 通过$dmesg$命令可查看$printk$输出的内核日志 - 调试参数$ignore_loglevel$可强制显示所有级别日志(需内核启动参数配置)[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值