printk打印消息机制

2.1.4  printk打印消息机制

在内核中,函数printk将消息打印到环形缓冲区__log_buf中,并将消息传给控制台进行显示。控制台驱动程序根据控制台的日志级别显示日志消息。

应用程序通过系统调用sys_syslog管理环形缓冲区__log_buf,它可以读取数据、清除缓冲区、设置日志级别、开/关控制台等。

当系统调用sys_syslog从环形缓冲区__log_buf读取数据时,如果缓冲区没有数据,系统调用sys_syslog所在进程将被加入到等待队列log_wait中进行等待。当printk将数据打印到缓冲区后,将唤醒系统调用sys_syslog所在进程从缓冲区中读取数据。等待队列log_wait定义如下:

DECLARE_WAIT_QUEUE_HEAD(log_wait);//等待队列log_wait

环形缓冲区__log_buf在使用之前就是已定义好的全局变量,缓冲区的长度为1 << CONFIG_LOG_ BUF_SHIFT。变量CONFIG_LOG_BUF_SHIFT在内核编译时由配置文件定义,对于i386平台,其值定义如下(在linux26/arch/i386/defconfig中):

CONFIG_LOG_BUF_SHIFT=18

在内核编译时,编译器根据配置文件的设置,产生如下的宏定义:

#define CONFIG_LOG_BUF_SHIFT  18

环形缓冲区__log_buf定义如下(在linux26/kernel/printk.c中):

#define __LOG_BUF_LEN(1 << CONFIG_LOG_BUF_SHIFT) //定义环形缓冲区的长度,i386平台为 

static char __log_buf[__LOG_BUF_LEN]; //printk的环形缓冲区

static char *log_buf = __log_buf;

static int log_buf_len = __LOG_BUF_LEN;

/*互斥锁logbuf_lock保护log_buf、log_start、log_end、con_start和logged_chars */

static DEFINE_SPINLOCK(logbuf_lock);

通过宏定义LOG_BUF,缓冲区__log_buf具备了环形缓冲区的操作行为。宏定义LOG_BUF得到缓冲区指定位置序号的字符,位置序号超过缓冲区长度时,通过与长度掩码LOG_BUF_MASK进行逻辑与操作,位置序号循环回到环形缓冲区中的位置。

宏定义LOG_BUF及位置序号掩码LOG_BUF_MASK的定义列出如下:

#define LOG_BUF_MASK (log_buf_len-1)

#define LOG_BUF(idx)  (log_buf[(idx) & LOG_BUF_MASK])

为了指明环形缓冲区__log_buf字符读取位置,定义了下面的位置变量:

static unsigned long log_start;/*系统调用syslog读取的下一个字符*/

static unsigned long con_start;/*送到控制台的下一个字符*/

static unsigned long log_end;/*最近已写字符序号加1 */

static unsigned long logged_chars; /*自从上一次read+clear 操作以来产生的字符数*/

任何地方的内核调用都可以调用函数printk打印调试、安全、提示和错误消息。函数printk尝试得到控制台信号量(console_sem),如果得到,就将信息输出到环形缓冲区__log_buf中,然后函数release_console_sem()在释放信号量之前把环形缓冲区中的消息送到控制台,调用控制台驱动程序显示打印的信息。如果没得到信号量,就只将信息输出到环形缓冲区后返回。函数printk的调用层次如图2-2所示。
错误!

图2-2 函数printk的调用层次图

函数printk列出如下(在linux26/kernel/printk.c中):

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

{

va_list args;

int r;

     

va_start(args, fmt);

r = vprintk(fmt, args);

va_end(args);



return r;

}



asmlinkage int vprintk(const char *fmt, va_list args)

{

unsigned long flags;

int printed_len;

char *p;

static char printk_buf[1024];

static int log_level_unknown = 1;



preempt_disable(); //关闭内核抢占

if (unlikely(oops_in_progress) && printk_cpu == smp_processor_id())

/*如果在printk运行时,这个CPU发生崩溃,

确信不能死锁,10秒1次初始化锁logbuf_lock和console_sem,留时间

给控制台打印完全的oops信息*/

zap_locks();



local_irq_save(flags);  //存储本地中断标识

lockdep_off();

spin_lock(&logbuf_lock);

printk_cpu = smp_processor_id(); 



/*将输出信息发送到临时缓冲区printk_buf */

printed_len = vscnprintf(printk_buf, sizeof(printk_buf), fmt, args);



/*拷贝printk_buf数据到循环缓冲区,如果调用者没提供合适的日志级别,插入默认值*/

for (p = printk_buf; *p; p++) {

if (log_level_unknown) {

 /* log_level_unknown signals the start of a new line */

if (printk_time) {

int loglev_char;

char tbuf[50], *tp;

unsigned tlen;

unsigned long long t;

unsigned long nanosec_rem;



/*在时间输出之前强制输出日志级别*/

if (p[0] == '<' && p[1] >='0' &&

p[1] <= '7' && p[2] == '>') {

loglev_char = p[1]; //获取日志级别字符

p += 3;

printed_len -= 3;

} else {

loglev_char = default_message_loglevel

+ '0';

}

t = printk_clock();//返回当前时钟,以ns为单位

nanosec_rem = do_div(t, 1000000000);

tlen = sprintf(tbuf,

"<%c>[%5lu.%06lu] ",

loglev_char,

(unsigned long)t,

nanosec_rem/1000);//写入格式化后的日志级别和时间



for (tp = tbuf; tp < tbuf + tlen; tp++) 

emit_log_char(*tp);  //将日志级别和时间字符输出到循环缓冲区

printed_len += tlen;

} else {

if (p[0] != '<' || p[1] < '0' ||

   p[1] > '7' || p[2] != '>') {

emit_log_char('<');

emit_log_char(default_message_loglevel

+ '0');  //输出字符到循环缓冲区

emit_log_char('>');

printed_len += 3;

}

}

log_level_unknown = 0;

if (!*p)

break;

}

emit_log_char(*p);//将其他printk_buf数据输出到循环缓冲区

if (*p == '/n')

log_level_unknown = 1;

}



if (!down_trylock(&console_sem)) {

/*拥有控制台驱动程序,降低spinlock并让release_console_sem()打印字符 */

console_locked = 1;

printk_cpu = UINT_MAX;

spin_unlock(&logbuf_lock);



/*如果CPU准备好,控制台就输出字符。函数cpu_online检测CPU是否在线,

函数have_callable_console()检测是否

有注册的控制台启动时就可以使用*/

if (cpu_online(smp_processor_id()) || have_callable_console()) {

console_may_schedule = 0;

release_console_sem();

} else {

/*释放锁避免刷新缓冲区*/

console_locked = 0;

up(&console_sem);

}

lockdep_on();

local_irq_restore(flags); //恢复本地中断标识

} else {

/*如果其他进程拥有这个驱动程序,本线程降低spinlock,

允许信号量持有者运行并调用控制台驱动程序输出字符*/ 

printk_cpu = UINT_MAX;

spin_unlock(&logbuf_lock);

lockdep_on();

local_irq_restore(flags); //恢复本地中断标识

}



preempt_enable();  //开启抢占机制

return printed_len;

}

函数release_console_sem()给控制台系统开锁,释放控制台系统及驱动程序调用者持有的信号量。持有信号量时,表示printk已在缓冲区存有数据。函数release_console_sem()在释放信号量之前将这些数据送给控制台显示。如果后台进程klogd在等待环形缓冲区装上数据,它唤醒klogd进程。

函数release_console_sem列出如下(在linux26/kernel/printk.c中):

void release_console_sem(void)

{

unsigned long flags;

unsigned long _con_start, _log_end;

unsigned long wake_klogd = 0;



for ( ; ; ) {

spin_lock_irqsave(&logbuf_lock, flags);

wake_klogd |= log_start - log_end;

if (con_start == log_end)

break;/* 没有需要打印的数据*/

_con_start = con_start;

_log_end = log_end;

con_start = log_end;/* Flush */

spin_unlock_irqrestore(&logbuf_lock, flags);

         //调用控制台driver的write函数写入到控制台

call_console_drivers(_con_start, _log_end);

}

console_locked = 0;

console_may_schedule = 0;

up(&console_sem);

spin_unlock_irqrestore(&logbuf_lock, flags);

if (wake_klogd && !oops_in_progress && waitqueue_active(&log_wait))

wake_up_interruptible(&log_wait);//唤醒在等待队列上的进程

}

函数_call_console_drivers将缓冲区中从start到end - 1的数据输出到控制台进行显示。在输出数据到控制台之前,它检查消息的日志级别。只有日志级别小于控制台日志级别console_loglevel的消息,才能交给控制台驱动程序进行显示。

函数_call_console_drivers列出如下:

static void _call_console_drivers(unsigned long start,

unsigned long end, int msg_log_level)

{

//日志级别小于控制台日志级别的消息才能输出到控制台

if ((msg_log_level < console_loglevel || ignore_loglevel) &&

console_drivers && start != end) {

if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {

/* 调用控制台驱动程序的写操作函数 */

__call_console_drivers(start & LOG_BUF_MASK,  log_buf_len);

__call_console_drivers(0, end & LOG_BUF_MASK);

} else {

__call_console_drivers(start, end);

}

}

}

函数__call_console_drivers调用控制台驱动程序的写操作函数显示消息。其列出如下:

static void __call_console_drivers(unsigned long start, unsigned long end)

{

struct console *con;



for (con = console_drivers; con; con = con->next) {

if ((con->flags & CON_ENABLED) && con->write &&

(cpu_online(smp_processor_id()) ||

(con->flags & CON_ANYTIME)))

con->write(con, &LOG_BUF(start), end - start); //调用驱动程序的写操作函数

}

}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值