扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
2.2 代码样例
了解Linux代码风格最好的方法就是实际研究一下它的部分代码。即使你不完全理解本节所讨论代码的细节也无关紧要,毕竟本节的主要目的不是理解代码,一些读者可以只对本节进行浏览。本节的主要目的是让读者对Linux代码进行初步了解,为今后的工作提供必要基础。该讨论将涉及部分广泛使用的内核代码。
2.2.1 printk
printk(25836行)是内核内部消息日志记录函数。在出现诸如内核检测到其数据结构出现不一致的事件时,内核会使用printk把相关信息打印到系统控制台上。对于printk的调用一般分为如下几类:
* 紧急事件(emergency)—例如,panic函数(25563行)多次使用了printk。当内核检测到发生不可恢复的内部错误时就会调用panic函数,然后尽其所能地安全关闭计算机。这个函数中调用printk以提示用户系统将要关闭。
* 调试—从3816行开始的#ifdef块使用printk来打印SMP逻辑单元(box)中每一个处理器的相关配置信息,但是此过程只有在使用SMP_DEBUG标志编译代码的情况下才能够被执行。
* 普通信息—例如,当机器启动时,内核必须估计系统速度以确保设备驱动程序能够忙等待(busy-wait)一个精确的极短周期。计算这种估计值的函数名为calibrate_delay(19654行),它既在19661行使用printk声明马上开始计算,又在19693行报告计算结果。另外,在第4章将详细的介绍calibrate_delay函数。
如果你已经浏览过这些参照行,你可能已经注意到printk和printf的参数十分类似:一个格式化字符串,后跟零个或者多个参数加入字符串中。格式化字符串可能是以一组“ 从14946行开始的#define块说明了这些特殊序列,这些定义可以帮助调用者正确区分对printk的调用。简单地说,我称日志等级0到4为“紧急事件”,等级5到等级6为“普通信息”,等级7自然就是我所说的“调试”(这种分类方法并不意味着其他更好的分类方法没有用处,而只是目前我们还不关心它而已)。
在上面讨论的基础上,我们研究一下代码本身。
printk
25836:参数fmt是printf类型的格式化字符串。如果你对“...”部分的内容不熟悉,那就 需要参阅一本好的C语言参考书(在其索引中查找“变参函数,variadic function”)。另外,在安装的GNU/Linux中的stdarg帮助里也包含了一个有关变参函数的简明描述,在这儿只需要敲入“man stdarg”就可以看到。
简单地说,“...”部分提示编译器fmt后面可能紧跟着数量不定的任何类型的参数。由于这些参数在编译的时候还没有类型和名字,内核使用由三个宏va_start、va_arg和va_end组成的特殊组及一个特殊类型—va_list对它们进行处理。
25842:msg_level记录了当前消息的日志等级。它是静态的,这看起来可能会有些奇怪—为什么下一次对printk的调用需要记录日志等级呢?问题的答案是只有打印出新行(\n)或者赋给一个新的日志等级序列以后,当前消息才会结束。这样,通过在包含消息结束的新行里调用printk,就保证了在多个短期冲突的情况下,调用者只打印唯一一个长消息。
25845:在SMP逻辑单元中,内核可能试图从不同的CPU向控制台同时打印信息(有时在单处理机(UP)逻辑单元中也会发生同样问题,但由于中断还未被覆盖掉,所以问题也并不十分明显)。如果不进行任何协同的话,结果就将处于完全无法让人了解的杂乱无章的状态,每个消息的各个部分都和其他消息的各个部分混杂交织在一起。
相反,内核使用旋转锁(spin-lock)来控制对控制台的访问。旋转锁将在第10章进行深入介绍。
如果你对flags 在传送给spin_lock_irqsave之前为什么不对它初始化感到疑惑,请不要担心:spin_lock_irqsave(对于不同的版本请分别参看12614行,12637行,12716行和12837行)是一个宏,而不是一个函数。该宏实际上是将值写入flags中,而不是从flags中读出值(在25895行中,存储在flags中的信息被spin_unlock_irqrestore回读,请参看12616行,12639行,12728行和12841行)。
25846:初始化变量args,该变量代表printk参数中的“...”部分。
25848:调用内核自身的vsprintf(为节省空间而省略)实现。该函数的功能与标准vsprintf函数非常相似,向buf中写入格式化文本(25634行)并返回写入字符串的长度(长度不包括最后一位终止字符0字节)。很快,你将可以看到为什么这种机制会忽略buf的前三个字符。
(正如25847行的注释中所述)我们应该注意到在这里并没有采取严格的措施来保证缓冲器不会过载。这里系统假定1024个字符长度的buf已经足够使用(参阅25634行)。如果内核在这里能够使用vsnprintf函数的话,情况就会好许多。然而,vsnprintf还有另外一个参数限制了它能够写入缓冲器的字符长度。
25849:计算buf中最近使用的元素,调用va_end终止对“...”参数的处理。
25851:开始格式化消息的循环。其中存在一个内部循环能够处理更多内容(这一点随后就能看到),因此,每次内循环开始,都开始一个新的打印行。由于通常情况下printk只用于打印单行,所以在每次调用中,这种循环通常只执行一次。
25853:如果预先不知道消息的日志等级,printk会检查当前行是否以日志等级序列开头。
25860:如果不是,buf中开始未使用的三个字符就能够起作用了(第一次以后的每次循环,都会覆盖部分消息文本,但是这样并不会引起问题,因为这里的文本只是前面行中的一部分,它们已经被打印过,而且以后也不再需要了)。这样,就可以将日志等级插入buf中。
25866:此处有如下属性:p指向日志等级序列(消息文本紧随其后),msg指向消息文本—请注意25852行和25865行中对msg的赋值。
由于已知p用来指示日志等级序列的开头—该日志等级序列可能是由函数自身所创建的,日志等级可以从p中抽出并存到msg_level中。
25868:没有检测到新行,清空line_feed标志。
25869:这是前面谈到过的内循环,循环将运行到本行结束(也就是检测到新行标志)或者缓冲器的末尾为止。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者