【转】printk和printf 实现完全不一样

这两个函数是经常用到的函数,闲暇之余,剖析下这两个函数的原理。这两个函数都是把字符串打印到终端上。其最终所要做的就是把存放在缓存区里的内容输出到串口。

printk
  printk函数在kernel/printk.c中,其把主要工作交给了vprintk。vprintk经过vscnprintf把要打印的数据格式化 后存放到printk_buf缓存区中,然后通过emit_log_char把要打印的数据放到__log_buf里。emit_log_char保证了 __log_buf不会下标越界——因为每次到了缓存区末又从头开始存放数据。代码中使用new_text_line变量来判断当前字符是不是行首,因为 内核在配置下可能会在行首打印时间或者当前打印的级别。
  真正调用打印的函数在console_unlock里面,在该函数里会执行call_console_drivers(_con_start, _log_end).接下来的调用流程是:
        _call_console_drivers(start_print, end, msg_level);
         -> __call_console_drivers(start, end);
          -> for_each_console(con) { ... con->write(con, &LOG_BUF(start), end - start); ... }

  for_each_console展开就是for (con = console_drivers; con != NULL; con = con->next),设置这个console_drivers的write方法流程如下所示:
       start_kernel //init/main.c
         -> console_init //drivers/tty/tty_io.c   所有和console相关的初始化函数在链接脚本里指定好了放在.con_initcall.init区
          -> serial8250_console_init //drivers/tty/serial/8250.c
           -> register_console(&serial8250_console); // 关键代码在于console_drivers = newcon;

  这时就可以看到con->write其实就是serial8250_console.write,即serial8250_console_write。这个函数所做的就是对硬件进行操作。就不继续往下说了。
  但是在调用console_init之前调用printk也能打印出信息,这是為什麼呢?在start_kernel函数中很早就调用了 parse_early_param函数,该函数会调用到链接脚本中.init.setup段的函数。其中就有 setup_early_serial8250_console函数。该函数通过 register_console(&early_serial8250_console);注册了一个比较简单的串口设备。可以用来打印内核启 动早期的信息。

printf
  printf其本质就是通过write系统调用完成的。如果感兴趣可以用strace观察下。那么就从sys_write这个系统调用开始分析吧。该系统调用的定义位于fs/read_write.c 中:SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count)。打印出信息所要经过的流程如下:
        vfs_write
        -> redirected_tty_write  // tty_io.c:tty_init 中设置file->f_op->write指向该函数
         -> tty_write // 关键在于调用 ret = do_tty_write(ld->ops->write, tty, file, buf, count);
          -> n_tty_write
           -> process_output_block
            -> uart_write
             -> uart_start
              -> __uart_start
               -> serial8250_start_tx
                -> transmit_chars

        光从这个调用流程来看,就足够复杂了。可以用户态要打印一个字符可真不容易。
        redirected_tty_write函数判断终端重定向(通过ioctl的TIOCCONS控制字)。
        tty_write做一些检测,把任务交给了do_tty_write。
        do_tty_write通过copy_from_user(tty->write_buf, buf, size)把要打印的字符拷贝到内核空间,再调用ld->ops->write函数。(注册ldisc还是在上文中的 console_init函数中。tty_ldisc_begin函数完成ldisc的设置。)
  接下来的函数都可以顾名思义的。

  為什麼用户程序的打印如此复杂呢?内核在用户和硬件中间加了一个tty层以保证设备驱动可以专心处理和硬件相关的事。而不必考虑复杂的数据格式化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值