Chinaunix首页 | 论坛 | 博客
  • 博客访问: 219994
  • 博文数量: 19
  • 博客积分: 757
  • 博客等级: 军士长
  • 技术积分: 320
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-08 07:55
个人简介

醉卧沙场君莫笑

文章分类

全部博文(19)

文章存档

2016年(5)

2015年(2)

2014年(3)

2013年(1)

2012年(5)

2011年(3)

分类: LINUX

2012-05-20 20:59:11


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

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层以保证设备驱动可以专心处理和硬件相关的事。而不必考虑复杂的数据格式化。
阅读(3129) | 评论(1) | 转发(2) |
0

上一篇:git找回删除的提交

下一篇:简单模块模版

给主人留下些什么吧!~~

shirya09282012-10-20 11:02:57