分享

浅析2.6.24内核printk函数(转)

 WUCANADA 2012-06-27
浅析2.6.24内核printk函数 (2008-04-28 09:15)


浅析2.6.24内核printk函数

文章来源:http://gliethttp.cublog.cn

【浅析printk的emit_log_char()算法具体实现】

  有段时间没有看内核了从2月1号进入新的工作以后,虽然工作中使用的都是ubuntu,但是一方面以前从来没有在纯linux下工作过,另一方面对于 linux下各种应用软件和vim编辑器都很生疏,加上工作性质并不是对内核,所以这2个多月都在掌握linux的基本操作和vim的使用,应该说经过这 2个多月锻炼,已经能够在linux下轻松的工作了,尤其发现vim是一个超级好用功能强大的编辑软件,wmii是一个超级好用的窗口管理器,现在连 windows我也装上了gvim,因为linux环境已经熟悉了,所以以后可以多花些时间在我感兴趣的内核上了,下面进入主题:

  printk是内核向外界打log的函数,对于2.6.24内核,一次性输入给printk函数的数据不能超过1k,在printk()-> vprintk()函数中使用了一个static char printk_buf[1024];临时缓冲区,用来处理一次传给printk的数据的格式化操作,最后将格式化了的临时存储到printk_buf数组中的数据通过emit_log_char函数一个一个的输送到log_buf[]--真正的所有待打印数据的缓冲区,这个缓冲区的大小在编译内核执行 make menuconfig时指定,我的CONFIG_LOG_BUF_SHIFT=14即16k,通过for循环将printk_buf中的所有待发数据输送到log_buf[]之后,紧接着执行
if (!down_trylock(&console_sem))
{
    ...
    release_console_sem();
    ...
}
如果console驱动已经安装,那么将在执行release_console_sem()的时候调用console驱动,
static void call_console_drivers(unsigned long start, unsigned long end)
{
    unsigned long cur_index, start_print;
    static int msg_level = -1;

    BUG_ON(((long)(start - end)) > 0);

    cur_index = start;
    start_print = start;
    while (cur_index != end) {
        if (msg_level < 0 && ((end - cur_index) > 2) &&
                LOG_BUF(cur_index + 0) == '<' &&
                LOG_BUF(cur_index + 1) >= '0' &&
                LOG_BUF(cur_index + 1) <= '7' &&
                LOG_BUF(cur_index + 2) == '>') {
            msg_level = LOG_BUF(cur_index + 1) - '0';
            cur_index += 3;
            start_print = cur_index;
        }
        while (cur_index != end) {
            char c = LOG_BUF(cur_index);

            cur_index++;
            if (c == '\n') {//gliethttp_20080428以\n换行符为组,调用一次console驱动,来发送数据
                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);
}

static void _call_console_drivers(unsigned long start,
                unsigned long 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);
            __call_console_drivers(0, end & LOG_BUF_MASK);
        } else {
            __call_console_drivers(start, end);
        }
    }
}

对于console的注册登记,由void register_console(struct console *console)函数完成,

比如对于driver驱动的注册由module_init()完成,而对于通过module_init方式编译进内核的驱动来说,
会在start_kernel()->rest_init()->kernel_thread建立内核线程kernel_init()->do_basic_setup()->do_initcalls()->中通过
for (call = __initcall_start; call < __initcall_end; call++)循环方式依次调用编译进内核的驱动初始化模块函数!
arch\arm\kernel\vmlinux.lds.s 中定义了
// __initcall_start = .;
// INITCALLS
// __initcall_end = .;
include\asm-generic\vmlinux.lds.h 中定义了这些东西
//#define INITCALLS \
// *(.initcall0.init) \
// *(.initcall0s.init) \
// *(.initcall1.init) \
// *(.initcall1s.init) \
// *(.initcall2.init) \
// *(.initcall2s.init) \
// *(.initcall3.init) \
// *(.initcall3s.init) \
// *(.initcall4.init) \
// *(.initcall4s.init) \
// *(.initcall5.init) \
// *(.initcall5s.init) \
// *(.initcallrootfs.init \
// *(.initcall6.init) \
// *(.initcall6s.init) \
// *(.initcall7.init) \
// *(.initcall7s.init
不过为了能够提早的将内核的log数据打印出来,2.6.24在start_kernel()中及早的调用console_init();函数来注册登记了打印输出log的专用console;
void __init console_init(void)
{
    initcall_t *call;

    /* Setup the default TTY line discipline. */
    (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);

    /*
     * 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++;
    }
}
__con_initcall_start在arch\arm\kernel\vmlinux.lds.s 中定义
...
        __con_initcall_start = .;
            *(.con_initcall.init)
        __con_initcall_end = .;
...
在include\linux\init.h中有如下定义
#define console_initcall(fn) \
    static initcall_t __initcall_##fn \
    __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn
而console驱动们就是通过console_initcall来及早注册登记自己的,如:drivers\serial\atmel_serial.c
static int __init atmel_console_init(void)
{
    if (atmel_default_console_device) {
        add_preferred_console(ATMEL_DEVICENAME, atmel_default_console_device->id, NULL);
        atmel_init_port(&(atmel_ports[atmel_default_console_device->id]), atmel_default_console_device);
        register_console(&atmel_console);
    }

    return 0;
}
console_initcall(atmel_console_init);

又如:drivers/serial/pxa.c
static int __init
serial_pxa_console_init(void)
{
    register_console(&serial_pxa_console);
    return 0;
}

console_initcall(serial_pxa_console_init);

printk函数是原子的操作,一进入printk函数,就调用preempt_disable()来禁止内核调度器对当前执行printk函数调用的内核线程对自己进行调度,不让自己让出cpu,切换到其他内核线程,也同时禁止了irq中断函数可能引发的更高优先级的内核线程被调度,因为 preempt_disable()已经锁住了调度器,直到printk函数执行完毕之后,调用preempt_enable();内 核才重新获得可抢占性!从这里我们可以看出,如果printk数据量很大,那么内核调度器虽然能够在irq之类的地方根据优先级调度之类的算法登记需要立 即执行的内核线程,但是因为preempt_disable的原因,不能立即执行内核调度进行线程切换,所以本来需要立即执行的一个高优先级内核线程就只 能等待,等到 printk调用console->write驱动函数将所有数据发送完毕执行preempt_enable()之 后,内核调度器才会使本该早早获得cpu的执行权的更高优先级的内核线程推迟到现在才获得cpu,如果printk数据量很大,再加上串口输出速率比较 低,所以等待这些log数据发送完毕是需要几十个ms的,实际应用中需要注意,当然现在很多arm处理器都使用了dma传输,比如at91rm9200使 用uart的pdc模式,它的 dma缓冲区为4k,这样对于小于4k的数据传输,也就可以交给pdc来完成,cpu不需要block在printk中,这样的话,禁止内核抢占的时间也 就可以大为缩短了,但是我看到的console内核驱动程序并没有这样来实现,因为内核在console这方面想的更多的是通用,而console驱动设 计者自己也并没有把这个作为必须要实现的咚咚,除非到了必须,否则肯定是能省事就省事!(gliethttp_20080428)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多