printk学习使用

printk级别问题:

printk()
printk()应该是每一个驱动开发者最为亲密的伙伴了,我们常常将它与二分查找法结合在一起寻找代码中发生问题的位置。
通常情况下,对于代码中的两个printk()语句,如果一个正常执行,而另一个没有被执行,就说明问题发生在这两个printk()之间,接下来就可以在这个范围内应用二分查找法定位有问题的代码。
1. printk()与printf()
用户空间有printf(),内核空间有printk(),它们就如代表善与恶的命运双生子,即使长相功能如何的接近,都不能在代码中共存。
对于我们来说,最容易犯的错误是,在需要printk()的地方误用了printf(),而在需要printf()的地方却又误用了printk(),通常这都不会是因为不知道它们的区别,而只是习惯使然。民间流传有这样的说法:当你在编写用户空间应用程序的时候,下意识写出的都是printk(),那么就说明你是个标准的内核开发者了。
2. printk()的消息级别
printk()与printf()的一个重要区别就是前者可以指定消息的打印级别,内核根据这个指定的级别来决定是否将消息打印到终端上。如下表所示,printk()共有8个级别。
级别
 描述
 
KERN_EMERG
 紧急情况,系统可能会崩溃
 
KERN_ALERT
 必须立即响应
 
KERN_CRIT
 临界情况
 
KERN_ERR
 错误信息
 
KERN_WARNING
 警告信息
 
KERN_NOTICE
 普通的但可能需要注意的信息
 
KERN_INFO
 提示性信息
 
KERN_DEBUG
 调试信息
 
如果没有指定消息的级别,printk()会使用默认的DEFAULT_MESSAGE_LOGLEVEL(通常是KERN_WARNING)。
3. 控制台的日志级别(console_loglevel)
当printk指定的消息级别小于指定的控制台日志级别时,消息的内容就会显示在该控制台上。控制台的日志级别定义在include/linux/kernel.h文件中,默认为DEFAULT_CONSOLE_LOGLEVEL(值等于7),也就是说默认情况下,比KERN_DEBUG级别高的printk()消息内容都可以在控制台上显示。
我们可以执行下面的命令使任何级别的printk()消息都被打印在终端上
$ echo 8 > /proc/sys/kernel/printk
4. printk()的变体
内核在include/linux/kernel.h文件中提供了两个printk()的变体pr_debug和pr_info,它们的定义为:
235 #define pr_debug(fmt,arg...) \
236 printk(KERN_DEBUG fmt,##arg)
244 #define pr_info(fmt,arg...) \
245 printk(KERN_INFO fmt,##arg)
5. printk()不是万能的
printk()虽然很好用,但它并不是万能的,在系统启动时,终端还没有初始化之前,它并不能被使用,不过如果不是在调试系统的启动过程的话,这并不能算是个问题。
其实内核提供了一个printk()的变体early_printk(),专门用于在系统启动的初期在终端上打印消息,它与printk()的区别仅仅在于名字的不同以及它能够更早地工作。
 

函数printk的使用方法和printf相似,用于内核打印消息。printk根据日志级别(loglevel)对消息进行分类。

日志级别用宏定义,日志级别宏展开为一个字符串,在编译时由预处理器将它和消息文本拼接成一个字符串,因此printk 函数中日志级别宏和格式字符串间不能有逗号。

下面是两个printk的例子,一个用于打印调试信息,另一个用于打印临界条件信息。

printk(KERN_DEBUG "Here I am: %s:%i\n", _ _FILE_ _, _ _LINE_ _); 
printk(KERN_CRIT "I'm trashed; giving up on %p\n", ptr); 

printk的日志级别定义如下(在linux26/includelinux/kernel.h中):

#defineKERN_EMERG"<0>"/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/
#defineKERN_ALERT"<1>"/*报告消息,表示必须立即采取措施*/
#defineKERN_CRIT"<2>"/*临界条件,通常涉及严重的硬件或软件操作失败*/
#defineKERN_ERR"<3>"/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/
#defineKERN_WARNING"<4>"/*警告条件,对可能出现问题的情况进行警告*/
#defineKERN_NOTICE"<5>"/*正常但又重要的条件,用于提醒。常用于与安全相关的消息*/
#defineKERN_INFO"<6>"/*提示信息,如驱动程序启动时,打印硬件信息*/
#defineKERN_DEBUG"<7>"/*调试级别的消息*/

extern int console_printk[];

#define console_loglevel  (console_printk[0])
#define default_message_loglevel  (console_printk[1])
#define minimum_console_loglevel  (console_printk[2])
#define default_console_loglevel  (console_printk[3])

日志级别的范围是0~7,没有指定日志级别的printk语句默认采用的级别是 DEFAULT_ MESSAGE_LOGLEVEL,其定义列出如下(在linux26/kernel/printk.c中):

/*没有定义日志级别的printk使用下面的默认级别*/
#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING 警告条件*/

内核可把消息打印到当前控制台上,可以指定控制台为字符模式的终端或打印机等。默认情况下,“控制台”就是当前的虚拟终端。

为了更好地控制不同级别的信息显示在控制台上,内核设置了控制台的日志级别console_loglevel。printk日志级别的作用是打印一定级别的消息,与之类似,控制台只显示一定级别的消息。

当日志级别小于console_loglevel时,消息才能显示出来。控制台相应的日志级别定义如下:

/* 显示比这个级别更重发的消息*/
#define MINIMUM_CONSOLE_LOGLEVEL  1   /*可以使用的最小日志级别*/
#define DEFAULT_CONSOLE_LOGLEVEL  7 /*比KERN_DEBUG 更重要的消息都被打印*/

int console_printk[4] = {
DEFAULT_CONSOLE_LOGLEVEL,/*控制台日志级别,优先级高于该值的消息将在控制台显示*/
/*默认消息日志级别,printk没定义优先级时,打印这个优先级以上的消息*/
DEFAULT_MESSAGE_LOGLEVEL,
/*最小控制台日志级别,控制台日志级别可被设置的最小值(最高优先级)*/
MINIMUM_CONSOLE_LOGLEVEL,
DEFAULT_CONSOLE_LOGLEVEL,/* 默认的控制台日志级别*/
};

如果系统运行了klogd和syslogd,则无论console_loglevel为何值,内核消息都将追加到/var/log/messages中。如果klogd没有运行,消息不会传递到用户空间,只能查看/proc/kmsg。

变量console_loglevel的初始值是DEFAULT_CONSOLE_LOGLEVEL,可以通过sys_syslog系统调用进行修改。调用klogd时可以指定-c开关选项来修改这个变量。如果要修改它的当前值,必须先杀掉klogd,再加-c选项重新启动它。
通过读写/proc/sys/kernel/printk文件可读取和修改控制台的日志级别。查看这个文件的方法如下:

#cat /proc/sys/kernel/printk
6   4  1   7

上面显示的4个数据分别对应控制台日志级别、默认的消息日志级别、最低的控制台日志级别和默认的控制台日志级别。

可用下面的命令设置当前日志级别:

# echo 8 > /proc/sys/kernel/printk


<think>好的,用户想了解Linux内核中的printk函数的定义和作用。根据提供的引用内容,我需要整合这些信息,给出一个清晰的解释。首先,用户之前的问题是关于Python的,现在突然转到内核的printk,可能需要考虑他正在学习内核开发或驱动编程。 先看用户提供的三个引用。引用1提到printk直接使用tty_write,而用户态用printf通过系统调用,所以内核态只能用printk。引用2说明printk是内核日志的基础API,用于调试和跟踪,还提到log buffer和级别控制。引用3展示了驱动模块中使用printk的例子,包括头文件和模块初始化的用法。 用户的问题集中在printk的定义和作用,需要详细说明其功能、与printf的区别、使用场景以及相关机制,比如日志级别和循环缓冲区。可能需要解释为什么在内核中不能使用printf,而必须用printk,以及printk如何在内核调试中发挥作用。 另外,用户之前的回答结构比较清晰,分点列出,所以这次也应该保持类似结构。需要确保使用正确的LaTeX格式,比如$...$和$$...$$,但用户的问题中没有数学公式,可能不需要太多数学表达式,但要注意技术术语的正确格式。 还要注意引用标记,每个引用来源在回答末尾需要正确标注。比如,关于printk在内核态和用户态的区别来自引用1,日志机制来自引用2,驱动模块例子来自引用3。确保每个引用都自然插入到回答中,并在段落末尾加上标识。 可能还需要回答用户潜在的问题,比如printk的日志级别如何设置,或者如何查看内核日志,但用户当前的问题集中在定义和作用,所以可能需要生成相关问题,比如如何调整printk的输出级别,或者printk和syslog的关系。 最后检查是否覆盖了用户的所有需求:printk的定义、作用、与printf的区别、使用场景、实现机制(如log buffer),以及示例代码。确保回答结构清晰,分点明确,引用正确,并且语言符合中文习惯。</think>### Printk函数的定义与作用详解 #### 一、Printk的定义 `printk` 是Linux内核中用于输出调试和日志信息的核心函数,其名称来源于 **Print Kernel**。与用户空间的`printf`不同,`printk` 直接操作内核资源,无需依赖系统调用。其定义位于内核头文件`<linux/printk.h>`,基本语法为: ```c printk(const char *fmt, ...); ``` 例如: ```c printk(KERN_INFO "Device initialized: %s\n", dev_name); // 带日志级别的输出[^3] ``` #### 二、核心作用 1. **内核日志输出** 用于内核模块、驱动程序或核心代码的调试信息输出,例如追踪函数执行路径或硬件状态: ```c printk("Interrupt triggered at address: 0x%x\n", addr); // 调试中断处理[^3] ``` 2. **日志级别控制** `printk` 支持8种日志级别(如`KERN_EMERG`、`KERN_INFO`),通过优先级过滤决定是否输出到控制台。级别定义如下: ```c #define KERN_EMERG "<0>" // 系统不可用 #define KERN_ALERT "<1>" // 需立即处理 #define KERN_INFO "<6>" // 普通信息[^2] ``` 若未显式指定级别,默认使用`DEFAULT_MESSAGE_LOGLEVEL`(通常为`KERN_WARNING`)。 3. **日志存储与访问** - 所有`printk`输出会被写入内核的**循环缓冲区**(`log_buf`),默认大小由`CONFIG_LOG_BUF_SHIFT`配置[^2]。 - 用户可通过`dmesg`命令或`/proc/kmsg`文件读取缓冲区内容。 #### 三、与用户态printf的差异 | 特性 | printk | printf | |---------------------|---------------------------|---------------------------| | **调用环境** | 内核态(特权级) | 用户态(需系统调用)[^1] | | **输出目标** | 内核日志缓冲区 + 控制台 | 标准输出(stdout) | | **实现方式** | 直接调用`tty_write()` | 通过`write()`系统调用[^1] | | **线程安全性** | 需处理中断上下文与并发 | 依赖用户态线程管理 | #### 四、典型应用场景 1. **驱动开发调试** 在设备初始化或异常处理中输出关键信息: ```c static int __init my_driver_init(void) { printk(KERN_DEBUG "Driver loaded: version 1.0\n"); return 0; } module_init(my_driver_init); // 注册模块初始化函数[^3] ``` 2. **内核行为追踪** 监控系统调用、中断或内存管理事件: ```c void handle_page_fault(unsigned long address) { printk(KERN_ERR "Page fault at 0x%lx (PID: %d)\n", address, current->pid); } ``` 3. **性能分析** 记录函数执行时间戳(需结合`jiffies`或`ktime_get()`): ```c u64 start = ktime_get_ns(); // ...执行操作... printk("Function execution time: %llu ns\n", ktime_get_ns() - start); ``` #### 五、注意事项 1. **避免频繁调用** 在高速路径(如中断处理)中过度使用`printk`可能导致日志缓冲区溢出或系统延迟增加。 2. **日志级别配置** 可通过`/proc/sys/kernel/printk`动态调整控制台输出阈值: ```bash echo "6 4 1 7" > /proc/sys/kernel/printk # 设置当前控制台日志级别为6(INFO)[^2] ``` 3. **格式化限制** 内核环境下`printk`不支持浮点数格式化(如`%f`),且部分格式符(如`%p`)行为与用户态不同。 --- ### 相关问题 1. 如何通过`dmesg`过滤特定级别的内核日志? 2. 为什么在内核中断上下文中使用`printk`需要特别谨慎? 3. Linux内核的循环日志缓冲区大小如何修改? [^1]: 内核态与用户态函数调用权限差异 [^2]: `printk`的日志缓冲与级别控制机制 : 驱动模块中`printk`的典型用法
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值