分享
 
 
 

操作系统(UNIX与Linux内核信息输出过程比较)[2]

王朝system·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

Ⅱ . Linux部分(printk)

在Linux中,完成类似工作的,是 linux/kernel/printk.c 中的过程 printk。这个过程,相对来讲,就复杂的多。其中程序代码为(在过程代码之前,有些全局变量的定义省略,需要指出时我们再指出):

/*

* linux/kernel/printk.c

*

* Copyright (C) 1991, 1992 Linus Torvalds

*

* Modified to make sys_syslog() more flexible: added commands to

* return the last 4k of kernel messages, regardless of whether

* they've been read or not. Added option to suppress kernel printk's

* to the console. Added hook for sending the console messages

* elsewhere, in preparation for a serial line console (someday).

* Ted Ts'o, 2/11/93.

* Modified for sysctl support, 1/8/97, Chris Horn.

*/

spinlock_t console_lock;/*结构体变量,控制台锁*/

001 asmlinkage int printk(const char *fmt, ...)

002 {

003 va_list args;

004 int i;

005 char *msg, *p, *buf_end;

006 int line_feed;

007 static signed char msg_level = -1; /* 当前消息的日志等级*/

008 long flags;

009

010 spin_lock_irqsave(&console_lock, flags);

011 va_start(args, fmt);

012 i = vsprintf(buf + 3, fmt, args); /* hopefully i < sizeof(buf)-4 */

013 buf_end = buf + 3 + i;

014 va_end(args);

015 for (p = buf + 3; p < buf_end; p++) {

016 msg = p;

017 if (msg_level < 0) {

018 if (

019 p[0] != '<' ||

020 p[1] < '0' ||

021 p[1] > '7' ||

022 p[2] != '>'

023 ) {

024 p -= 3;

025 p[0] = '<';

026 p[1] = default_message_loglevel + '0';

027 p[2] = '>';

028 } else

029 msg += 3;

030 msg_level = p[1] - '0';

031 }

032 line_feed = 0;

033 for (; p < buf_end; p++) {

034 log_buf[(log_start+log_size) & (LOG_BUF_LEN-1)] = *p;

035 if (log_size < LOG_BUF_LEN)

036 log_size++;

037 else {

038 log_start++;

039 log_start &= LOG_BUF_LEN-1;

040 }

041 logged_chars++;

042 if (*p == '\n') {

043 line_feed = 1;

044

break;

045 }

046 }

047 if (msg_level < console_loglevel && console_drivers) {

048 struct console *c = console_drivers;

049 while(c) {

050 if ((c->flags & CON_ENABLED) && c->write)

051 c->write(c, msg, p - msg + line_feed);

052 c = c->next;

053 }

054 }

055 if (line_feed)

056 msg_level = -1;

057 }

058 spin_unlock_irqrestore(&console_lock, flags);

059 wake_up_interruptible(&log_wait);

060 return i;

061 }

062

这一过程的设计,有些知识需要加以介绍。

首先,printk是内核内部消息日志记录函数。调用printk的一般为紧急事件、调试或普通信息。它与过程printf的参数比较类似,都是一个格式化字符串,并把其后若干个参数(包括0个)加入此字符串中。在printk中,格式化字符串可能是以一组“〈N〉”开始,其中0<=N<=7,而该数字区分了消息的日志等级(log level),只有当日志等级高于当前控制台定义的日志等级(console_loglevel)时,才会打印输出消息。

关于程序,其中要说明的是,spin_lock_irqsave是一个宏,定义如下:

/* 在文件 spinlock.h 中*/

#define spin_lock_irqsave(lock, flags)

do { save_flags(flags); cli(); } while (0)

这个宏定义很有意思,使用do-while结构把目标封装起来。这是一个技巧,可以避免宏带来的歧义。而作用是,获取控制态锁,并初始化flags。va_start和va_end也是宏,与一个特殊类型va_list进行处理,把printk参数中的“…”部分存入args(011至014)。

全局变量给出如下几个定义:

#define LOG_BUF_LEN (16384)/* log_buf 's length */

static char buf[1024];

unsigned long log_size = 0;

struct console *console_drivers = NULL;

static char log_buf[LOG_BUF_LEN];

static unsigned long log_start = 0;

static unsigned long logged_chars = 0;

而其中console是一个结构体,定义如下:

/* 在文件 console.h 中*/

struct console

{

void (*write)(struct console *, const char *, unsigned);

}

如此,您便可以暂时回去继续看一看这段程序,以己之力,来辨伊人。

对于Linux内核的这个过程,下面我们来进行详细的分析。

为方便讨论,我们给出下图,作为叙述的参考:

014结束时,我们已经获得控制台锁,并且,printk的参数“…”部分(如果有)与fmt部分都装入buf[]内。其中012行的vsprintf为Linux内核自己所实现的过程,向buf中写入格式化字符串并返回所写入字符串的长度(不包括最后终止字符0字节),由调用情况来看,忽略了buf的前三个字符,分析后面的代码就会知道原因。

015 至 057 外层for循环,具体的处理消息字符串。

016 msg记录 buf+3的位置。

017 当前消息日志的等级msg_level是静态变量,被初始化为-1。所以首次调用printk时,017至031肯定会被执行的。

如果快速的扫描程序,您会发现改变msg_level值的机会只有两个,一是在030,最后一次在056,如果line_feed的值为1,则msg_level的值又赋值为-1。

018 至 029 这个if判断非常有意思。这里重新审视buf[]的前三个字符(一个子串)。只要不是“〈N〉”,其中0<=N<=7,就需要对这个子串重新设置,消息日志的等级被置为default_message_loglevel(024至027);否则,msg后移三位。

030 msg_level记录当前消息日志的等级(为1到7的数字之一)!

032 line_feed初始化为0。line_feed其实记录的是当前已处理消息的行数。

033 至 046 是内层for循环。这里更有意思,也凸现出c语言的能力,及Linux内核代码编写者运用语言的能力。注意此时开始p和msg的指向,前者指向日志等级序列,后者指向紧跟其后的消息文本。

034 缓冲区log_buf[]是不是看起来容易让人糊涂?这里的按位与(&)运算是什么意思?是快速求模运算(%)!当然,需要有个保障,就是LOG_BUF_LEN的值要为2的幂。这样,log_buf[]其实是个循环缓冲器。它将记录最新的内部消息的一行。

035 至 040 就是对循环缓冲器访问机制的重新设置,使log_start+log_size在循环(取模LOG_BUF_LEN-1)的意义上后移一位。

041 logged_chars是全局静态变量,记录的是机器启动后由printk写入的所有字符的个数,这里自增1。

042 至 045 是内循环退出的另一出口,如果当前p所指内容为‘\n’,表明消息一行即将结束。则line_feed记录为1,退出内层for循环。

从这里可以回头看看这个内层for循环所做的工作。可以知道,每次内层for循环开始,都开始一个新的打印行。只不过通常printk只用于打印一行,所以内层for循环通常也只执行一次。

047 至 054 打印一行消息的工作。047的判断若为真,则表明当前消息的日志等级高于当前控制台定义的日志等级,且控制台驱动链表console_drivers可以打开。其后就是遍历console_drivers告知每一个控制台驱动去打印当前行。我们不讨论这个过程的详细情况,只提醒您051传入的第二个参数是msg而不是p,这样就在没有日志等级序列的情况下写入了消息。以后若需要日志等级,可以访问log_buf[]。

055 和 056 如果line_feed为1,则一行已经结束。无论是否存在,则都可视为新的一行开始,msg_level又置为-1。

现在,可以看看msg_level的控制逻辑。如下图(是简略图,只涉及msg_level):

对照printk,脉络就会更清楚些。其中包括017到031部分的执行,现在可以说是在每一新行的消息处理时。

058 释放控制台锁。

059 唤醒等待被写入控制台日志的所有进程。

060 返回I。

061 结束。

这个过程虽较UNIX的printf复杂,但是两者相同的是,代码写的都很漂亮。逻辑控制无论简繁而总不失条理,充分的考虑了效率的要求,又没有失去代码的可读性。笔者称其为“清丽之意,乃介乎天生丽质,韵味内涵皆清晰可观“,即指此也。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有