您的位置:首页 > 运维架构 > Linux

Linux内核修炼之道》精华分享与讨论(21)——二分法与printk()

2010-08-08 16:06 573 查看
人生就是一个茶几,上面摆满了杯具。内核也是一个大茶几,不过它上面的杯具是一个个的bug
。确定bug
什么时候被引入是一个很关键的步骤,在这个定位bug
的过程中,不论有意或无意,都会很自然地用到二分查找的方法。

二分查找法的基本原理

对于二分查找法,我们不会也不应该会感到陌生。作为
一种高效的查找算法,它曾出现在我们的数据结构课堂里,出现在一次又一次的面试里,更是会频繁地应用在我们的代码里。在我们所接触到的各种算法里,它可以
说是最为大众化、最充满生活智慧的一个,很多人并不知道二分查找法的概念,却能够在生活中熟练的去应用。
比如,一个工人要维修一条10km
长的电话线,首先他需要定位出故障所在,如果沿着线路一小段一小段地查找,显然非常得困难,每查一个点都要爬一次电线杆,10km
长的距离会有大约200
多根电线杆。假设电线两端分别为A
、B
,这时他会很自然地首先从中间的C
开始查起,用话机向两端测试时,发现AC
段正常,故而断定故障在BC
段,再到BC
段的中点D
,如果发现BD
段正常,则故障在CD
段,然后再到CD
的中间点E
查找,这样每查一次,就可以把待查线路的长度缩减一半,因而经过7
次查找,就可以将故障发生的范围缩小到50
~100m
左右,即在一两根电线杆附近。如此一来要节省很多的精力与时间。

这是二分查找法在生活中的一个典型应用,实际上,查找内核的bug
与查找电话线的故障相比,本质上都是相同的,并没有高深到哪里去,都是首先要定位出故障的位置,然后去解决它。
比如你在使用某个版本的内核时,发现了一个内核bug
,这时你需要知道它究竟是在应用哪个补丁时被引入的,如果一个一个的去还原那些补丁,每还原一个补丁就要测试一次内核,那么必然会浪费过多的时间,而应用二分查找法,首先确定一个肯定没有出现该bug
的内核版本,然后去测试位于这两个版本中间的那个版本,这样重复筛选,就能够很容易的定位出是从哪个版本开始出现了这个bug

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()
的区别仅仅在于名字的不同以及它能够更早地工作。

附:

5月11号和13号会做两个在线的presentation,有兴趣的朋友可以去http://www.farsight.com.cn/zt-webcasts2/renqiaowei.aspx
报名
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐