zoukankan      html  css  js  c++  java
  • printk解析

    以下源码基于Linux-2.6.32.2

    printk是Linux内核空间的信息输出函数,printk入口位于kernel/Printk.c

    以下是printk的流程解析:

    printk
        vprintk
            /* Emit the output into the temporary buffer */
             * vsnprintf把printk的参数进行解析分类,然后用一个暂时缓冲区接受解析后的数据
             */
            vscnprintf(printk_buf + printed_len,sizeof(printk_buf) - printed_len, fmt, args);
            p = printk_buf;
    
            /* Do we have a loglevel in the string? 
             *提取printk信息级别:<c>     c就是级别
             */
    
            /*
             * Copy the output into log_buf.  If the caller didn't provide
             * appropriate log level tags, we insert them here
             * 把printk的输出信息放到全局缓冲区log_buf里面,
             *     static char *log_buf = __log_buf;
             */
            emit_log_char('<');
            emit_log_char(current_log_level + '0');
            emit_log_char('>');
            emit_log_char(*p);
    
            release_console_sem();
                call_console_drivers(_con_start, _log_end);
                    _call_console_drivers(start_print, cur_index, msg_level);
                        /*如果printk的输出信息级别 < 控制台的信息级别,则输出*/
                        if ((msg_log_level < console_loglevel || ignore_loglevel) &&console_drivers && start != end)
                        __call_console_drivers(start, end);
                            /*对每个控制台调用写函数,输出printk信息*/
                            for_each_console(con)
                            con->write(con, &LOG_BUF(start), end - start);

    以上流程可以知道,printk最终会调用到控制台的写函数,用于输出数据
    另外,在设置bootargs的时候,设置了console=ttySAC0,也就是设置控制台为ttySAC0,通过下面的解析,得出name="ttySAC"等,并把数据放到console_cmdline数组内

    static int __init console_setup(char *str)
        strncpy(buf, str, sizeof(buf) - 1);
        for (s = buf; *s; s++)
            if ((*s >= '0' && *s <= '9') || *s == ',')
                break;
        __add_preferred_console(buf, idx, options, brl_options);
            c = &console_cmdline[i];
            strlcpy(c->name, name, sizeof(c->name));
            c->options = options;
            c->index = idx;
    
    __setup("console=", console_setup);    //当解析到console= 这个字符串的时候,调用console_setup函数进行处理

    可以想象的是,这时候我们设置的是控制台的名字,当我们需要设置控制台的操作函数时,通过比对该名字,如果匹配名字则把操作函数设置为底层的操作函数,
    在内核中搜索ttySAC,得到一个匹配项,位于drivers/serial/Samsung.c

    #define S3C24XX_SERIAL_NAME    "ttySAC"
    
    static struct console s3c24xx_serial_console = {
        .name        = S3C24XX_SERIAL_NAME,        //"ttySAC"
        .device        = uart_console_device,        //init进行,用户程序打开/dev/console时用到
        .flags        = CON_PRINTBUFFER,        //注册完成该结构体后,打印先前在log_buf中保存的信息,其实前面的数据都是还没能输出的,只是放在log_buf内
        .index        = -1,                //表示使用哪个串口命令由命令行参数决定
        .write        = s3c24xx_serial_console_write,    //串口控制台输出函数
        .setup        = s3c24xx_serial_console_setup    //串口控制台设置函数
    };

    以上就是控制台的各个属性跟操作函数,注册函数为
    s3c24xx_serial_initconsole(struct platform_driver *drv,struct s3c24xx_uart_info *info)
      register_console(&s3c24xx_serial_console);

    那么上面的函数是如何被调用的呢?
    我们搜索s3c24xx_serial_initconsole,发现下面的宏定义:

    #define s3c24xx_console_init(__drv, __inf)            \
    static int __init s3c_serial_console_init(void)            \
    {                                \
        return s3c24xx_serial_initconsole(__drv, __inf);    \
    }                                \
                                    \
    console_initcall(s3c_serial_console_init)

    在drivers/serial/S3c2440.c内有这么一行:

    s3c24xx_console_init(&s3c2440_serial_driver, &s3c2440_uart_inf);

    在include/linux/Init.h里面有这么一个宏定义(其中里面的initcall_t 为一个返回值为int的函数指针,__initcall_##fn中的##是一个连接符号)

    #define console_initcall(fn) \
        static initcall_t __initcall_##fn \
        __used __section(.con_initcall.init) = fn

    也就是上面的宏会变成:

    static int __init s3c_serial_console_init(void)
    {
        return s3c24xx_serial_initconsole(&s3c2440_serial_driver, &s3c2440_uart_inf);
    }
    
    static int __initcall_s3c_serial_console_init() __used __section(.con_initcall.init) = s3c_serial_console_init;

    也就是在.con_initcall.init段内定义了一个函数,该函数指针指向s3c_serial_console_init,最终调用到s3c24xx_serial_initconsole,从而注册控制台

    在linux源码搜索.con_initcall.init,得到如下信息

             __con_initcall_start = .;
                 *(.con_initcall.init)
             __con_initcall_end = .;

    在arch/arm/kernel/vmlinux.lsd.S里面有定义.con_initcall.init 段,段首为__con_initcall_start,段尾为__con_initcall_end

    这里我们可以猜想,linux在初始化的时候,调用该段内的所有函数,达到初始化控制台的目的

    在linux源码搜索__con_initcall_start,
    在driver/char/Tty_io.c内得到了如下结果

        call = __con_initcall_start;
            while (call < __con_initcall_end) {
            (*call)();
            call++;
            }

    印证了的上述猜想。

    然后向上搜索,发现该函数是在console_init内的,而console是在start_kernel内调用的。因此可以得到控制台初始化的整个轮廓:

    start_kernel(void)        //main.c
        console_init(void)    //driver/char/Tty_io.c
            call = __con_initcall_start;
            while (call < __con_initcall_end) {
            (*call)();
            call++;
            }
                /*然后就调用到了里面的函数
                 *实际上是调用s3c_serial_console_init
                 */
                __initcall_s3c_serial_console_init()    
                    s3c24xx_serial_initconsole(&s3c2440_serial_driver, &s3c2440_uart_inf)
                        register_console(&s3c24xx_serial_console);    
                        /*该函数位于kernel/Printk.c内
                         *在该函数内比对我们输入的console(console_cmdline数组)跟所初始化的console
                         */
  • 相关阅读:
    2020/2/14
    2020/2/13
    《人类简史》
    2020/2/12
    bzoj3157国王奇遇记(秦九韶算法+矩乘)&&bzoj233AC达成
    [noip科普]关于LIS和一类可以用树状数组优化的DP
    [uva11722&&cogs1488]和朋友会面Joining with Friend
    Bzoj2154 Crash的数字表格 乘法逆元+莫比乌斯反演(TLE)
    NOIP2016滚粗记
    bzoj2228[ZJOI2011]礼物(gift)
  • 原文地址:https://www.cnblogs.com/TaigaCon/p/2839334.html
Copyright © 2011-2022 走看看