Printk函数是在开发驱动过程中经常用到的一个函数,作用就是在串口上输出内核的信息。这个常用的函数的执行过程未必大家都很清楚,下面的文章分析一下 printk 的执行过程,并说明 printk 函数的一些特点。
下面是printk 的执行过程,重要的步骤有如下几个
1. printk调用 vprintk 函数,首先是禁止抢占,并且关闭中断;
2. 调用vscnprintf 函数,将输出字符串按格式组织在 printk_buf 缓冲区域中;
3. 调用emit_log_char 函数,将 printk_buf 中的内容拷贝到 log_buf 中去;
4. 调用release_console_sem 函数将 log_buf 的内容发送到硬件上;
根据上面列出来的printk 的执行过程,我们需要了解到几个问题。
Printk执行过程是禁止抢占和关闭中断的,而从串口输出字符的速度比较缓慢,只导致 printk 会严重影响程序执行的速率。
然后printk 函数中存在两个缓冲 buf ,一个 buf 叫做 printk_buf ,一个 buf 是 log_buf 。 Printk_buf 只有 1K ,它的作用是临时按格式组织字符串的缓冲。而 log_buf 是根据内核编译时来配置的,可以大道 16K 左右,是一个环形的缓冲。
在linux 系统中,用户态还可以通过 /proc/kmsg 将 log_buf 中的内容读出来。
/* * Call the console drivers on a range of log_buf */ static void __call_console_drivers( unsigned start, unsigned 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); } }
将log_buf 中的内容向硬件上输出的时候,在 console_drivers 上面找到能用的 console 终端,然后调用 write 函数,将数据从硬件上输出去。
关于console 终端的注册也是比较有意思的。我们应该见过 u-boot 给内核传递内核参数的时候有这样的内容,指明了 console 的名称是 ttySAC0 ,而内核的串口驱动在初始化的时候会注册 console ,如果 u-boot 传递的这个 console 名称和串口驱动注册的名称一样,那么 printk 就会利用串口注册的 console 函数来操作硬件输出内容。
下面的register_console 函数注册 console
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 }; 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 ; }
在register_console 函数中会根据 u-boot 传入的 console 名称选择合适的 console 设备注册的
/* * See if this console matches one we selected on * the command line. */ for (i = 0 ; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[ 0 ]; i++) { if (strcmp(console_cmdline[i].name, console->name) != 0 ) continue ;
如果上面找到了名称相同的console 结构体,调用 setup 函数进行初始化
if (console->setup && console->setup(console, console_cmdline[i].options) != 0 ) break ; console->flags |= CON_ENABLED; console->index = console_cmdline[i].index;