浅析linux printk的实现


printk函数是我们调试linux内核必备的打印接口,

printk定义在/kernel/路径下

声明在/linux/kernel.h中,如果在某个文件中使用printk时编译通不过,通常都是未包含这个头文件(当然还有语法错误,呵呵~)



/**
 * printk - print a kernel message
 * @fmt: format string
 *
 * This is printk().  It can be called from any context.  We want it to work.
 *
 * We try to grab the console_sem.  If we succeed, it's easy - we log the output and
 * call the console drivers.  If we fail to get the semaphore we place the output
 * into the log buffer and return.  The current holder of the console_sem will
 * notice the new output in release_console_sem() and will send it to the
 * consoles before releasing the semaphore.
 *
 * One effect of this deferred printing is that code which calls printk() and
 * then changes console_loglevel may break. This is because console_loglevel
 * is inspected when the actual printing occurs.
 *
 * See also:
 * printf(3)
 *
 * See the vsnprintf() documentation for format string extensions over C99.
 */


asmlinkageint printk(const char *fmt, ...)
{
    va_list args;
    int r;

    va_start(args, fmt);
    r = vprintk(fmt, args);
    va_end(args);

    return r;
}

/*

1.asmlinkage: 是GCC的c语言扩展语法,函数定义前加宏asmlinkage ,表示这些函数通过堆栈而不是通过寄存器传递参数。

2.printk是支持可变参数的,我们都知道一般情况下形参存储在动态数据区的栈区,但是const修饰的会存储在静态数据区,

不管形参在什么地方存储,有一点是不变的就是形参的存储是连续的;

其实不只是printk,只要支持可变参数,它们都有一个共同点:就是第一参数必须给定,根据第一个参数的地址,

就可以找后续的参数,以及参数的个数,以为它们是有逗号分隔的(这个就是实现可变参数的原理)

*/



asmlinkage int vprintk(const char *fmt, va_list args)
{
    int printed_len = 0;
    int current_log_level = default_message_loglevel;
    unsigned long flags;
    int this_cpu;
    char *p;

    boot_delay_msec();
    printk_delay();

    preempt_disable();                                                                          //禁止抢占
    /* This stops the holder of console_sem just where we want him */
    raw_local_irq_save(flags);                                                            //保存本地为处理的中断,这点我不是特别确认,只是根据字面意思理解的
    this_cpu = smp_processor_id();//获得CPU id

    /*
     * Ouch, printk recursed into itself!
     */

    if (unlikely(printk_cpu == this_cpu)) {
        /*
         * If a crash is occurring during printk() on this CPU,
         * then try to get the crash message out but make sure
         * we can't deadlock. Otherwise just return to avoid the
         * recursion and return - but flag the recursion so that
         * it can be printed at the next appropriate moment:
         */

        if (!oops_in_progress) {
            recursion_bug = 1;
            goto out_restore_irqs;
        }
        zap_locks();
    }

    lockdep_off();
    spin_lock(&logbuf_lock);//加锁
    printk_cpu = this_cpu;

    if (recursion_bug) {
        recursion_bug = 0;
        strcpy(printk_buf, recursion_bug_msg);
        printed_len = strlen(recursion_bug_msg);
    }
    /* Emit the output into the temporary buffer */
    printed_len += vscnprintf(printk_buf + printed_len,
                  sizeof(printk_buf) - printed_len, fmt, args);


    p = printk_buf;

    /* Do we have a loglevel in the string? *///获取log level
    if (p[0] == '<') {
        unsigned char c = p[1];
        if (c && p[2] == '>') {
            switch (c) {
            case '0' ... '7': /* loglevel */
                current_log_level = c - '0';
            /* Fallthrough - make sure we're on a new line */
            case 'd': /* KERN_DEFAULT */
                if (!new_text_line) {
                    emit_log_char('\n');
                    new_text_line = 1;
                }
            /* Fallthrough - skip the loglevel */
            case 'c': /* KERN_CONT */
                p += 3;
                break;
            }
        }
    }

    /*//把打印信息copy到log系统
     * Copy the output into log_buf.  If the caller didn't provide
     * appropriate log level tags, we insert them here
     */

    for ( ; *p; p++) {
        if (new_text_line) {
            /* Always output the token */
            emit_log_char('<');
            emit_log_char(current_log_level + '0');
            emit_log_char('>');
            printed_len += 3;
            new_text_line = 0;

            if (printk_time) {
                /* Follow the token with the time */
                char tbuf[50], *tp;
                unsigned tlen;
                unsigned long long t;
                unsigned long nanosec_rem;

                t = cpu_clock(printk_cpu);
                nanosec_rem = do_div(t, 1000000000);
                tlen = sprintf(tbuf, "[%5lu.%06lu] ",
                        (unsigned long) t,
                        nanosec_rem / 1000);

                for (tp = tbuf; tp < tbuf + tlen; tp++)
                    emit_log_char(*tp);
                printed_len += tlen;
            }

            if (!*p)
                break;
        }

        emit_log_char(*p);
        if (*p == '\n')
            new_text_line = 1;
    }

    /*
     * Try to acquire and then immediately release the
     * console semaphore. The release will do all the
     * actual magic (print out buffers, wake up klogd,
     * etc).
     *
     * The acquire_console_semaphore_for_printk() function
     * will release 'logbuf_lock' regardless of whether it
     * actually gets the semaphore or not.
     */

    if (acquire_console_semaphore_for_printk(this_cpu))//调用console 驱动
        release_console_sem();

    lockdep_on();
out_restore_irqs:
    raw_local_irq_restore(flags);

    preempt_enable();
    return printed_len;
}
EXPORT_SYMBOL(printk);
EXPORT_SYMBOL(vprintk);


/**
 * release_console_sem - unlock the console system
 *
 * Releases the semaphore which the caller holds on the console system
 * and the console driver list.
 *
 * While the semaphore was held, console output may have been buffered
 * by printk().  If this is the case, release_console_sem() emits
 * the output prior to releasing the semaphore.
 *
 * If there is output waiting for klogd, we wake it up.
 *
 * release_console_sem() may be called from any context.
 */

void release_console_sem(void)
{
    unsigned long flags;
    unsigned _con_start, _log_end;
    unsigned wake_klogd = 0;

    if (console_suspended) {
        up(&console_sem);
        return;
    }

    console_may_schedule = 0;

    for ( ; ; ) {
        spin_lock_irqsave(&logbuf_lock, flags);
        wake_klogd |= log_start - log_end;
        if (con_start == log_end)
            break;            /* Nothing to print */
        _con_start = con_start;
        _log_end = log_end;
        con_start = log_end;       /* Flush */
        spin_unlock(&logbuf_lock);
        stop_critical_timings();   /* don't trace print latency */
        call_console_drivers(_con_start, _log_end);
        start_critical_timings();
        local_irq_restore(flags);
    }
    console_locked = 0;
    up(&console_sem);
    spin_unlock_irqrestore(&logbuf_lock, flags);
    if (wake_klogd)
        wake_up_klogd();
}


/*
 * Call the console drivers, asking them to write out
 * log_buf[start] to log_buf[end - 1].
 * The console_sem must be held.
 */

static void call_console_drivers(unsigned start, unsigned end)
{
    unsigned cur_index, start_print;
    static int msg_level = -1;

    BUG_ON(((int)(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') {
                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);
}

/*
 * Write out chars from start to end - 1 inclusive
 */

static void _call_console_drivers(unsigned start,
                unsigned 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);
        }
    }
}

/*
 * Call the console drivers on a range of log_buf
 */

static void __call_console_drivers(unsigned start, unsigned end)
{
    struct console *con;

    for_each_console(con) {
        if ((con->flags & CON_ENABLED) && con->write &&
                (cpu_online(smp_processor_id()) ||
                (con->flags & CON_ANYTIME)))
            con->write(con, &LOG_BUF(start), end - start);
    }
}

最终会调用con->write,把信息写到console,

con->write的初始化在文件Console.c (drivers\usb\serial)中, 被初始化成usb_console_write。

这个介绍比较浅显,很多内容我也不是特别理解,尤其是驱动那部分。





你可能感兴趣的:(*Linux)