走读printk代码

走读printk代码


在我们书写内核代码的时候通常会使用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!
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值