📄 00000004.htm
字号:
<HTML><HEAD> <TITLE>BBS水木清华站∶精华区</TITLE></HEAD><BODY><CENTER><H1>BBS水木清华站∶精华区</H1></CENTER>发信人: axp33a (无聊中...), 信区: Linux <BR>标 题: Linux内核源代码分析2-2-1 <BR>发信站: BBS 水木清华站 (Thu Aug 3 11:20:29 2000) WWW-POST <BR> <BR>2.2 代码样例
<BR>了解Linux代码风格最好的方法就是实际研究一下它的部分代码。即使你不完全理解本节 <BR>所讨论代码的细节也无关紧要,毕竟本节的主要目的不是理解代码,一些读者可以只对本 <BR>节进行浏览。本节的主要目的是让读者对Linux代码进行初步了解,为今后的工作提供必 <BR>要基础。该讨论将涉及部分广泛使用的内核代码。
<BR>2.2.1 printk
<BR>printk(25836行)是内核内部消息日志记录函数。在出现诸如内核检测到其数据结构出 <BR>现不一致的事件时,内核会使用printk把相关信息打印到系统控制台上。对于printk的调 <BR>用一般分为如下几类:
<BR>* 紧急事件(emergency)—例如,panic函数(25563行)多次使用了printk。当内核检 <BR>测到发生不可恢复的内部错误时就会调用panic函数,然后尽其所能地安全关闭计算机。 <BR>这个函数中调用printk以提示用户系统将要关闭。
<BR>* 调试—从3816行开始的#ifdef块使用printk来打印SMP逻辑单元(box)中每一个处理器 <BR>的相关配置信息,但是此过程只有在使用SMP_DEBUG标志编译代码的情况下才能够被执行 <BR>。
<BR>* 普通信息—例如,当机器启动时,内核必须估计系统速度以确保设备驱动程序能够忙等 <BR>待(busy-wait)一个精确的极短周期。计算这种估计值的函数名为calibrate_delay( <BR>19654行),它既在19661行使用printk声明马上开始计算,又在19693行报告计算结果。 <BR>另外,在第4章将详细的介绍calibrate_delay函数。
<BR>如果你已经浏览过这些参照行,你可能已经注意到printk和printf的参数十分类似:一个 <BR>格式化字符串,后跟零个或者多个参数加入字符串中。格式化字符串可能是以一组“<N> <BR>”开始,这里的N是从0到7的数字,包括0和7在内。数字区分了消息的日志等级(log <BR>level),只有当日志等级高于当前控制台定义的日志等级(console_loglevel,25650行 <BR>)时,才会打印消息。root可以通过适当减小控制台的日志等级来过滤不是很紧急的消息 <BR>。如果内核在格式化字符串中检测不到日志等级序列,那么就会一直打印消息(实际上, <BR>日志等级序列并不一定要在格式化字符串中出现,可以在格式化文本中查找到它的代码) <BR>。
<BR>从14946行开始的#define块说明了这些特殊序列,这些定义可以帮助调用者正确区分对 <BR>printk的调用。简单地说,我称日志等级0到4为“紧急事件”,等级5到等级6为“普通信 <BR>息”,等级7自然就是我所说的“调试”(这种分类方法并不意味着其他更好的分类方法 <BR>没有用处,而只是目前我们还不关心它而已)。
<BR>在上面讨论的基础上,我们研究一下代码本身。
<BR>printk
<BR>25836:参数fmt是printf类型的格式化字符串。如果你对“...”部分的内容不熟悉,那 <BR>就 需要参阅一本好的C语言参考书(在其索引中查找“变参函数, <BR>variadic function”)。另外,在安装的GNU/Linux中的stdarg帮助里也包含了一个有关 <BR>变参函数的简明描述,在这儿只需要敲入“man stdarg”就可以看到。
<BR>简单地说,“...”部分提示编译器fmt后面可能紧跟着数量不定的任何类型的参数。由于 <BR>这些参数在编译的时候还没有类型和名字,内核使用由三个宏va_start、va_arg和va_end <BR>组成的特殊组及一个特殊类型—va_list对它们进行处理。
<BR>25842:msg_level记录了当前消息的日志等级。它是静态的,这看起来可能会有些奇怪— <BR>为什么下一次对printk的调用需要记录日志等级呢?问题的答案是只有打印出新行(\n) <BR>或者赋给一个新的日志等级序列以后,当前消息才会结束。这样,通过在包含消息结束的 <BR>新行里调用printk,就保证了在多个短期冲突的情况下,调用者只打印唯一一个长消息。 <BR>
<BR>25845:在SMP逻辑单元中,内核可能试图从不同的CPU向控制台同时打印信息(有时在单 <BR>处理机(UP)逻辑单元中也会发生同样问题,但由于中断还未被覆盖掉,所以问题也并不 <BR>十分明显)。如果不进行任何协同的话,结果就将处于完全无法让人了解的杂乱无章的状 <BR>态,每个消息的各个部分都和其他消息的各个部分混杂交织在一起。
<BR>相反,内核使用旋转锁(spin-lock)来控制对控制台的访问。旋转锁将在第10章进行深 <BR>入介绍。
<BR>如果你对flags 在传送给spin_lock_irqsave之前为什么不对它初始化感到疑惑,请不要 <BR>担心:spin_lock_irqsave(对于不同的版本请分别参看12614行,12637行,12716行和 <BR>12837行)是一个宏,而不是一个函数。该宏实际上是将值写入flags中,而不是从flags <BR>中读出值(在25895行中,存储在flags中的信息被spin_unlock_irqrestore回读,请参看 <BR>12616行,12639行,12728行和12841行)。
<BR>25846:初始化变量args,该变量代表printk参数中的“...”部分。
<BR>25848:调用内核自身的vsprintf(为节省空间而省略)实现。该函数的功能与标准 <BR>vsprintf函数非常相似,向buf中写入格式化文本(25634行)并返回写入字符串的长度( <BR>长度不包括最后一位终止字符0字节)。很快,你将可以看到为什么这种机制会忽略buf的 <BR>前三个字符。
<BR>(正如25847行的注释中所述)我们应该注意到在这里并没有采取严格的措施来保证缓冲 <BR>器不会过载。这里系统假定1024个字符长度的buf已经足够使用(参阅25634行)。如果内 <BR>核在这里能够使用vsnprintf函数的话,情况就会好许多。然而,vsnprintf还有另外一个 <BR>参数限制了它能够写入缓冲器的字符长度。
<BR>25849:计算buf中最近使用的元素,调用va_end终止对“...”参数的处理。
<BR>25851:开始格式化消息的循环。其中存在一个内部循环能够处理更多内容(这一点随后 <BR>就能看到),因此,每次内循环开始,都开始一个新的打印行。由于通常情况下printk只 <BR>用于打印单行,所以在每次调用中,这种循环通常只执行一次。
<BR>25853:如果预先不知道消息的日志等级,printk会检查当前行是否以日志等级序列开头 <BR>。
<BR>25860:如果不是,buf中开始未使用的三个字符就能够起作用了(第一次以后的每次循环 <BR>,都会覆盖部分消息文本,但是这样并不会引起问题,因为这里的文本只是前面行中的一 <BR>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -