printk分析

本人一直好奇怎么通过printk输出信息到串口,于是分析源码。

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);
r = vprintk(fmt, args);
va_end(args);


return r;
}

注意va_start(args, fmt);//处理变长参数
r = vprintk(fmt, args);
va_end(args);

跟踪进入vkdb_printf(fmt, args);

该函数里面有/* Emit the output into the temporary buffer */
printed_len += vscnprintf(printk_buf + printed_len,
 sizeof(printk_buf) - printed_len, fmt, args);

这一句很重要,将输入内容格式化并写道printk_buf缓冲区里面。

并且之后调用了很多emit_log_char函数,

static void emit_log_char(char c)
{
LOG_BUF(log_end) = c;//复制到log_buf
log_end++;
if (log_end - log_start > log_buf_len)
log_start = log_end - log_buf_len;
if (log_end - con_start > log_buf_len)
con_start = log_end - log_buf_len;
if (logged_chars < log_buf_len)
logged_chars++;
}

#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)  logbuf是128k。

static char __log_buf[__LOG_BUF_LEN];
static char *log_buf = __log_buf;
static int log_buf_len = __LOG_BUF_LEN;

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

该函数将printk_buf里面的内容复制到log_buf里面,并改变log_start等一系列标志。

回到vprintk函数中,经过一系列复杂的处理,后来调用

/*
* Try to acquire and then immediately release the
* console semaphore. The release will do all the
* actual magic (print out buffers, wake up klogd,
* etc). 
*
* The console_trylock_for_printk() function
* will release 'logbuf_lock' regardless of whether it
* actually gets the semaphore or not.
*/
if (console_trylock_for_printk(this_cpu))
console_unlock();//重点,真正地调用串口输出

/**
 * console_unlock - unlock the console system
 *
 * Releases the console_lock which the caller holds on the console system
 * and the console driver list.
 *
 * While the console_lock was held, console output may have been buffered
 * by printk().  If this is the case, console_unlock(); emits
 * the output prior to releasing the lock.
 *
 * If there is output waiting for klogd, we wake it up.
 *
 * console_unlock(); may be called from any context.
 */
void console_unlock(void)
{
unsigned long flags;
unsigned _con_start, _log_end;
unsigned wake_klogd = 0;


if (console_suspended) {
up(&console_sem);
return;
}


console_may_schedule = 0;


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;


/* Release the exclusive_console once it is used */
if (unlikely(exclusive_console))
exclusive_console = NULL;


up(&console_sem);
spin_unlock_irqrestore(&logbuf_lock, flags);
if (wake_klogd)
wake_up_klogd();
}
EXPORT_SYMBOL(console_unlock);


/*
 * Call the console drivers, asking them to write out
 * log_buf[start] to log_buf[end - 1].
 * The console_lock must be held.
 */
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)) {
/* strip log prefix */
cur_index += log_prefix(&LOG_BUF(cur_index), &msg_level, NULL);
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);//前面一层一层的,快晕了吧,都是各种处理,继续快完了
}

/*
 * Write out chars from start to end - 1 inclusive
 */
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);//最后一次了,该函数直接调用write
__call_console_drivers(0, end & LOG_BUF_MASK);
} else {
__call_console_drivers(start, end);
}
}
}

static void __call_console_drivers(unsigned start, unsigned end)
{
struct console *con;


for_each_console(con) {
if (exclusive_console && con != exclusive_console)
continue;
if ((con->flags & CON_ENABLED) && con->write &&
(cpu_online(smp_processor_id()) ||
(con->flags & CON_ANYTIME)))
con->write(con, &LOG_BUF(start), end - start);
}
}

现在可能有疑问了,这个con是哪来的?实际上con就是exclusive_console的赋值,原形为static struct console *exclusive_console;

那么这个控制台是哪里冒出来的?

/*
 * The console driver calls this routine during kernel initialization
 * to register the console printing procedure with printk() and to
 * print any messages that were printed by the kernel before the
 * console driver was initialized.
 *
 * This can happen pretty early during the boot process (because of
 * early_printk) - sometimes before setup_arch() completes - be careful
 * of what kernel features are used - they may not be initialised yet.
 *
 * There are two types of consoles - bootconsoles (early_printk) and
 * "real" consoles (everything which is not a bootconsole) which are
 * handled differently.
 *  - Any number of bootconsoles can be registered at any time.
 *  - As soon as a "real" console is registered, all bootconsoles
 *    will be unregistered automatically.
 *  - Once a "real" console is registered, any attempt to register a
 *    bootconsoles will be rejected
 */
void register_console(struct console *newcon);

注释已经说得很清楚了,在系统初始化的时候,会将exclusive_console初始化为early_console或real console,real consoles应该就是真实的uart,好吧,看看exclusive_console是在什么时候调用的

21285.c (drivers\tty\serial): register_console(&serial21285_console);
68328serial.c (drivers\tty\serial): register_console(&m68328_driver);
68360serial.c (drivers\tty\serial): register_console(&sercons);
68360serial.c (drivers\tty\serial): /*register_console (console_print_68360); - 2.0.38 only required a write
8250.c (drivers\tty\serial): register_console(&serial8250_console);
8250_early.c (drivers\tty\serial): register_console(&early_serial8250_console);
Altera_jtaguart.c (drivers\tty\serial): register_console(&altera_jtaguart_console);
Altera_uart.c (drivers\tty\serial): register_console(&altera_uart_console);
Amiserial.c (drivers\tty): register_console(&sercons);
Apbuart.c (drivers\tty\serial): register_console(&grlib_apbuart_console);
Arc_con.c (arch\mips\fw\arc): register_console(&arc_cons);
Atmel_serial.c (drivers\tty\serial): register_console(&atmel_console);
Atmel_serial.c (drivers\tty\serial): register_console(&atmel_console);
Bcm63xx_uart.c (drivers\tty\serial): register_console(&bcm63xx_console);
Bfin_5xx.c (drivers\tty\serial): * & earlysetup in ./kernel/printk.c:register_console()
Bfin_5xx.c (drivers\tty\serial): register_console(&bfin_serial_console);

随便找个分析下,看看

int s3c24xx_serial_initconsole(struct platform_driver *drv,
      struct s3c24xx_uart_info **info)


{
struct platform_device *dev = s3c24xx_uart_devs[0];


dbg("s3c24xx_serial_initconsole\n");


/* select driver based on the cpu */


if (dev == NULL) {
printk(KERN_ERR "s3c24xx: no devices for console init\n");
return 0;
}


if (strcmp(dev->name, drv->driver.name) != 0)
return 0;


s3c24xx_serial_console.data = &s3c24xx_uart_drv;
s3c24xx_serial_init_ports(info);


register_console(&s3c24xx_serial_console);//
return 0;
}

static struct console s3c24xx_serial_console = {
.name = S3C24XX_SERIAL_NAME,
.device = uart_console_device,
.flags = CON_PRINTBUFFER,
.index = -1,
.write = s3c24xx_serial_console_write,
.setup = s3c24xx_serial_console_setup
};

s3c24xx_serial_console是对真正串口设备uart_console_device的封装,现在一目了然了吧。。

诸如类似s3c24xx_serial_initconsole的初始化串口设备的函数会放在__con_initcall节中,通过console_init调用其中所有的初始化函数。

void __init console_init(void)
{
initcall_t *call;


/* Setup the default TTY line discipline. */
tty_ldisc_begin();


/*
* set up the console device so that later boot sequences can
* inform about problems etc..
*/
call = __con_initcall_start;
while (call < __con_initcall_end) {
(*call)();
call++;
}
}


最后补充一个suspend_console();//挂起终端控制台(此后串口就不再有内容输入

为什么能挂起了?注意输出到串口的关键一步是调用console_unlock。

里面有

if (console_suspended) {
up(&console_sem);
return;
}

如果console_suspended生效,就直接返回,没有后续的操作了。


内核启动在console_init之前,串口的信息是不能打印出来的。

另外内核启动后,如果reset,内存里面的信息居然没变。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值