走读printk代码
在我们书写内核代码的时候通常会使用printk,这里我们看下printk是如何和uart关联起来的。关于uart的相关的硬件知识不介绍了,so easy。我们这里只是走读下printk的代码,看看和uart的驱动的关联。
printk---函数实现在kernel/printk.c文件中。接下来的很多的函数都是在这个文件中。
这是一个神奇的函数哦,参数中的“...”可以让你参数输入的比较随意了。在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。
这里就是对数据的规整,最终走到了vprintk这个函数中。
那么下面我们看看release_console_sem是如何实现输出的。
release_console_sem的作用是解锁console系统
so,从上面的函数我们去看看call_console_drivers函数中到底有何门道。
这里还没用看到,但是看到更加深入的接口_call_console_drivers。这个函数里面一探究竟吧。
紧接着的__call_console_drivers是不是呢?
你以为躲起来就找不到你了吗?没有用的,你是那样拉风的函数,不管在什么地方,就好象漆黑中的萤火虫一样,那样的鲜明,那样的出众,你那忧郁的眼神,唏嘘的胡碴子,神乎奇迹的刀法,还有那杯DRY MARTINE,都深深的迷住了我。不过虽然你是这样的出色,但是行有行规,我也要剖析你的内心了con->write。
到了这里,我们先暂时忍耐下,先看看这个拉轰的结构体
哈哈,你一定也注意到了,这里有write,是的,这就是在上面con->write中调用的。
在你的UART的驱动中你一定看到了register_console(&arch-xxx_uart_console);这个函数了,里面的write就是注册在此让printk使用的。write中的实现就是UART的那些小case了,这里不多说了。
当然这里还有这个console_initcall(arch—xxx_uart_console_init);将你的uart加入起来的了。
你可能在看arch-xxx_uart_console这个结构体定义的时候注意到没用read函数?这是什么原因呢?
在LDD中有这个答案,“当tty驱动程序接收到数据后,它将负责把从硬件获得的任何数据传递给tty核心,而不使用传统的read函数。tty核心将缓冲数据直接接到来自用户的请求。由于tty核心已经提供了缓冲逻辑,因此没用必要为每个tty驱动程序实现他们自己的缓冲区逻辑。”。当然这段话是教材这么说的,后续在看的时候会把这部分源码在展开来读读看。
当然printk不一定非得是串口输出,这里只是一个例子,今天就先到这里了,下面会把如何指定printk的输出给看看,这里先说下在__call_console_drivers()函数中的for_each_console()是个关键。下一篇见。
Have fun!
在我们书写内核代码的时候通常会使用printk,这里我们看下printk是如何和uart关联起来的。关于uart的相关的硬件知识不介绍了,so easy。我们这里只是走读下printk的代码,看看和uart的驱动的关联。
printk---函数实现在kernel/printk.c文件中。接下来的很多的函数都是在这个文件中。
这是一个神奇的函数哦,参数中的“...”可以让你参数输入的比较随意了。在函数体中声明一个va_list,然后用va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束。
asmlinkage int printk(const char *fmt, ...)
{
va_list args;
int r;
#ifdef CONFIG_KGDB_KDB
if (unlikely(kdb_trap_printk)) {
va_start(args, fmt);
r = vkdb_printf(fmt, args);
va_end(args);
return r;
}
#endif
va_start(args, fmt);
_trace_kernel_printk(_RET_IP_);
r = vprintk(fmt, args);
va_end(args);
return r;
}
这里就是对数据的规整,最终走到了vprintk这个函数中。
asmlinkage int vprintk(const char *fmt, va_list args)
{
........................
spin_lock(&logbuf_lock); //申请到对log buffer的锁,锁的使用对防止并发有很好的作用
........................
/* 这里会看到我们的输入的log level的处理 */
if (p[0] == '<') {
unsigned char c = p[1];
if (c && p[2] == '>') {
switch (c) {
case '0' ... '7': /* loglevel */
current_log_level = c - '0';
/* Fallthrough - make sure we're on a new line */
case 'd': /* KERN_DEFAULT */
if (!new_text_line) {
emit_log_char('\n');
new_text_line = 1;
}
/* Fallthrough - skip the loglevel */
case 'c': /* KERN_CONT */
p += 3;
break;
}
}
}
/*
* 在这里就把所要输出的内容copy to log_buf。然后判断是否需要打印log tag,和时间戳。
*/
for ( ; *p; p++) {
if (new_text_line) {
/* Always output the token */
emit_log_char('<');
emit_log_char(current_log_level + '0');
emit_log_char('>');
printed_len += 3;
new_text_line = 0;
if (printk_time) {
/* Follow the token with the time */
char tbuf[50], *tp;
unsigned tlen;
unsigned long long t;
unsigned long nanosec_rem;
t = cpu_clock(printk_cpu);
nanosec_rem = do_div(t, 1000000000);
tlen = sprintf(tbuf, "[%5lu.%06lu] ",
(unsigned long) t,
nanosec_rem / 1000);
for (tp = tbuf; tp < tbuf + tlen; tp++)
emit_log_char(*tp);
printed_len += tlen;
}
if (!*p)
break;
}
emit_log_char(*p);
if (*p == '\n')
new_text_line = 1;
}
/*
*取得console的使用标志并且要立即释放掉,释放函数会做输出的动作
*
* 在前面看到的spin_lock(&logbuf_lock),在函数acquire_console_semaphore_for_printk函数中也会得到释放
*重申:对于锁一定要记得去释放
*/
if (acquire_console_semaphore_for_printk(this_cpu))
release_console_sem();
............................
}
那么下面我们看看release_console_sem是如何实现输出的。
release_console_sem的作用是解锁console系统
void release_console_sem(void)
{
//先判断了下是否系统需要休眠,需要休眠则离开
for ( ; ; ) {
spin_lock_irqsave(&logbuf_lock, flags);
wake_klogd |= log_start - log_end;
if (con_start == log_end)
break; /* Nothing to print */
_con_start = con_start;
_log_end = log_end;
con_start = log_end; /* Flush */
spin_unlock(&logbuf_lock);
stop_critical_timings(); /* don't trace print latency */
//在把数据拿出来之后,这里终于看到了调用串口驱动的接口
call_console_drivers(_con_start, _log_end);
start_critical_timings();
local_irq_restore(flags);
}
console_locked = 0;
up(&console_sem);
spin_unlock_irqrestore(&logbuf_lock, flags);
if (wake_klogd)
wake_up_klogd();
}
EXPORT_SYMBOL(release_console_sem);
so,从上面的函数我们去看看call_console_drivers函数中到底有何门道。
/*
* 调用console的驱动,首先要先将log_buf[start]到log_buf[end-1]的数据拿出来
*在这个过程中,要把console的标志一直保持住
*/
static void call_console_drivers(unsigned start, unsigned end)
{
unsigned cur_index, start_print;
static int msg_level = -1;
BUG_ON(((int)(start - end)) > 0);
cur_index = start;
start_print = start;
while (cur_index != end) {
if (msg_level < 0 && ((end - cur_index) > 2) &&
LOG_BUF(cur_index + 0) == '<' &&
LOG_BUF(cur_index + 1) >= '0' &&
LOG_BUF(cur_index + 1) <= '7' &&
LOG_BUF(cur_index + 2) == '>') {
msg_level = LOG_BUF(cur_index + 1) - '0';
cur_index += 3;
start_print = cur_index;
}
while (cur_index != end) {
char c = LOG_BUF(cur_index);
cur_index++;
if (c == '\n') {
if (msg_level < 0) {
/*
* printk() has already given us loglevel tags in
* the buffer. This code is here in case the
* log buffer has wrapped right round and scribbled
* on those tags
*/
msg_level = default_message_loglevel;
}
_call_console_drivers(start_print, cur_index, msg_level);
msg_level = -1;
start_print = cur_index;
break;
}
}
}
_call_console_drivers(start_print, end, msg_level);
}
这里还没用看到,但是看到更加深入的接口_call_console_drivers。这个函数里面一探究竟吧。
static void _call_console_drivers(unsigned start,
unsigned 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)) {
/* wrapped write */
__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 start, unsigned end)
{
struct console *con;
for_each_console(con) {
if ((con->flags & CON_ENABLED) && con->write &&
(cpu_online(smp_processor_id()) ||
(con->flags & CON_ANYTIME)))
con->write(con, &LOG_BUF(start), end - start);
}
}
你以为躲起来就找不到你了吗?没有用的,你是那样拉风的函数,不管在什么地方,就好象漆黑中的萤火虫一样,那样的鲜明,那样的出众,你那忧郁的眼神,唏嘘的胡碴子,神乎奇迹的刀法,还有那杯DRY MARTINE,都深深的迷住了我。不过虽然你是这样的出色,但是行有行规,我也要剖析你的内心了con->write。
到了这里,我们先暂时忍耐下,先看看这个拉轰的结构体
struct console {
char name[16];
void (*write)(struct console *, const char *, unsigned);
int (*read)(struct console *, char *, unsigned);
struct tty_driver *(*device)(struct console *, int *);
void (*unblank)(void);
int (*setup)(struct console *, char *);
int (*early_setup)(void);
short flags;
short index;
int cflag;
void *data;
struct console *next;
};
哈哈,你一定也注意到了,这里有write,是的,这就是在上面con->write中调用的。
在你的UART的驱动中你一定看到了register_console(&arch-xxx_uart_console);这个函数了,里面的write就是注册在此让printk使用的。write中的实现就是UART的那些小case了,这里不多说了。
当然这里还有这个console_initcall(arch—xxx_uart_console_init);将你的uart加入起来的了。
你可能在看arch-xxx_uart_console这个结构体定义的时候注意到没用read函数?这是什么原因呢?
在LDD中有这个答案,“当tty驱动程序接收到数据后,它将负责把从硬件获得的任何数据传递给tty核心,而不使用传统的read函数。tty核心将缓冲数据直接接到来自用户的请求。由于tty核心已经提供了缓冲逻辑,因此没用必要为每个tty驱动程序实现他们自己的缓冲区逻辑。”。当然这段话是教材这么说的,后续在看的时候会把这部分源码在展开来读读看。
当然printk不一定非得是串口输出,这里只是一个例子,今天就先到这里了,下面会把如何指定printk的输出给看看,这里先说下在__call_console_drivers()函数中的for_each_console()是个关键。下一篇见。
Have fun!