转自:http://blog.chinaunix.net/u3/112940/showart_2450064.html
{
va_list args;
int r;
/*将fmt后的参数信息保存到args中*/
va_start(args, fmt);
r = vprintk(fmt, args);
va_end(args);
}
boot_delay_msec(); /*禁止内核抢占*/ /* lockdep_off(); if (recursion_bug) { /*
/* Always output the token */ /*如果设置了此选项,则在每一条printk信息前都要加上时间参数*/ t = cpu_clock(printk_cpu); for (tp = tbuf; tp < tbuf + tlen; tp++) if (!*p) emit_log_char(*p); /*
lockdep_on(); preempt_enable(); |
(自己直接贴代码总是不能对齐,还在抱怨uc对于代码支持不好呢。。第一次用这个粘贴代码。。。)
对于printk来说,一共有两个缓冲区printk_buf以及log_buf,前者有种临时缓冲的意思,后者用来存储最终要输出的字符串。后面将详细说一下其中最主要的log_buf。
对于vscnprintf函数来说,其就是最终通过vsnprintf()函数将printk的参数根据fmt格式进行转换,并将转换的结果暂存到printk_buf中,最终又将printk_buf中的数据保存到log_buf中。
下面在讨论往log_buf缓冲区写数据的函数emit_log_char之前,先简要说一下printk中的log_buf缓冲区。
|
其中的log_end标志着下一个写入的位置,其是上一次写的末尾+1;而log_start和con_start则是syslog和consoles读取数据的起始位置。在缓冲区写入的时候正是通过这三个变量以及C语言的特性完成环形的实现。
下面看一下写缓冲区的具体函数实现。
|
这个写入满足每次只写入一个字符,通过LOG_BUF将字符c赋值给缓冲区,通过后面的长度变化来实现环形的概念。其中的LOG_BUF是这样定义的:
#define LOG_BUF(idx) (log_buf[(idx) & LOG_BUF_MASK])
在写入时根据log_end的大小mod缓冲区长度,获取最终的写入位置。
环形缓冲区在字面看来就是一个数组 static char __log_buf[__LOG_BUF_LEN];其长度一般为4096大小(内核可修改)。而log_end长度为unsigned long范围,远远大于数组的大小,对于每一个字符的赋值log_end则只管++,在加一之后进行判断,如果log_end的值大于log_start,则表示缓冲区的长度已经达到最大,下一次的写入就将覆盖之前最旧的位置,因此log_start = log_end - log_buf_len,将log_start的位置向后移一位(因为每次只写入一个字符,不可能超过一位)。log_end和log_start通过unsigned long的自然溢出来实现环形的判断,而对其中每一次赋值则不再考虑环形的实现形式。(罗里啰嗦了这么多,也不知道能不能看明白,不过我是明白了。。。感觉方法挺巧的。。)
函数的最后,则是release_console_sem函数,在此函数中完成console相关的操作。主要过程就是将con_start与log_end间的数据通过call_console_drivers函数来完成数据往控制台的传递,并且在最后环形klogd进程。
而call_console_drivers函数则是遍历内核中的console链表console_driver,对于其中的每一个console结构,调用其注册的write函数。
这两个函数的代码都比较简单,就不再多说了。
if (console_suspended) { console_may_schedule = 0; for ( ; ; ) { |
call_console_drivers函数在最终是通过__call_console_drivers函数来实现的。
for (con = console_drivers; con; con = con->next) { |
至此,整个printk的实现流程就已经结束了,并不复杂,流程比较清晰,嘿嘿。
圣诞节一个人在公司加班写博客,oye!