Linux内核printk实现

 基于MTK 6595分析,内核版本3.10.5

1 Printk函数分析

内核为Printk维护一个环形缓冲区,其大小为:

#define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT)

大小可以通过CONFIG_LOG_BUF_SHIFT去控制

1.1函数原型如下:

asmlinkage int printk(const char *fmt, ...)

{

         va_listargs;

         intr;

         va_start(args,fmt);

         r= vprintk_emit(0, -1, NULL, 0, fmt, args);

         va_end(args);

         returnr;

}

 

 

asmlinkage int vprintk_emit(int facility,int level,

                                const char *dict, size_t dictlen,

                                const char *fmt, va_list args)

{

         staticint recursion_bug;

         staticchar textbuf[LOG_LINE_MAX];

         char*text = textbuf;

         size_ttext_len;

         enumlog_flags lflags = 0;

         unsignedlong flags;

         intthis_cpu;

         intprinted_len = 0;

   int in_irq_disable, in_non_preempt;

   in_irq_disable = irqs_disabled();//是否为中断上下文

   in_non_preempt = in_atomic();//是否为原子上下文

         vscnprintf(text,sizeof(textbuf), fmt, args);

         memset(text,0x0, sizeof(textbuf));

         boot_delay_msec(level);

        

/* This stops the holder of console_sem just where we want him */

         local_irq_save(flags);

//禁止本地中断,因为printk可以在任何环境中使用,而下面又要获取logbug_lock去保护环形缓冲区,所以需要禁止本地中断,防止死锁.

         this_cpu= smp_processor_id();

         /*

          * Ouch, printk recursed into itself!

          */

        

         lockdep_off();

         raw_spin_lock(&logbuf_lock);//禁止本地中断

         logbuf_cpu= this_cpu ;

    //log保存到text

         text_len= vscnprintf(text, sizeof(textbuf), fmt, args);

         /*mark and strip a trailing newline */

         if(text_len && text[text_len-1] == ‘\n‘) {

                   text_len--;

                   lflags|= LOG_NEWLINE;

         }

         /*strip kernel syslog prefix and extract log level or control flags */

         if(facility == 0) {

                   intkern_level = printk_get_level(text);//获取信息level

         if(level == -1)

                   level= default_message_loglevel;

    

#ifdef CONFIG_PRINTK_PROCESS_INFO

   if (in_irq_disable)

       __raw_get_cpu_var(printk_state) = ‘-‘;//如果为中断上下文,log中加”-“标志

#ifdef CONFIG_MT_PRINTK_UART_CONSOLE

   else if (printk_disable_uart == 0)

       __raw_get_cpu_var(printk_state) = ‘.‘; //如果为非中断上下文,在log中加”.”标志

#endif

   else

       __raw_get_cpu_var(printk_state) = ‘ ‘;

#endif

        

         if(!(lflags & LOG_NEWLINE)) {

                   ………………………………………….

         }else {

                   boolstored = false;

                   if(!stored)

                            log_store(facility,level, lflags, 0,//log保存在环形缓冲区中,这个重点分析

                                       dict, dictlen, text, text_len);

         }

         printed_len+= text_len;

 

         /*

          * Try to acquire and then immediately releasethe console semaphore.

          * The release will print out buffers and wakeup /dev/kmsg and syslog()

          * users.

          *

          * The console_trylock_for_printk() functionwill release ‘logbuf_lock‘

          * regardless of whether it actually gets theconsole semaphore or not.

          */

         if(console_trylock_for_printk(this_cpu))//尝试获取控制台信号量,

                   console_unlock();//输出信息到控制台

         lockdep_on();

out_restore_irqs:

         local_irq_restore(flags);//恢复本地cpu 中断

 

         returnprinted_len;

}

1.2 log_store 分析

 Log_store函数主要想环形缓冲区中保存log信息,环形缓冲区组织如下:

 (struct log +log_data) ---(struct log +log_data)—(struct log + log_data)……..

也就是说每行Log都是以struct log开头,然后接着存放log 数据

structlog {

u64 ts_nsec;             /*timestamp in nanoseconds *//时间标志

u16 len;             /*length of entire record *///总长度

u16 text_len;            /*length of text buffer */ //log 数据长度

u16 dict_len;             /*length of dictionary buffer */

u8 facility;                  /*syslog facility */

u8 flags:5;                  /*internal record flags */

u8 level:3;                  /*syslog level */ //调试级

};

另外内核还定义了四个全局变量来管理缓冲区

/* index and sequence number of the first record stored in thebuffer */

/*static*/ u64 log_first_seq; //指向当前可读的struct log 索引号

/*static*/ u32 log_first_idx; //指向环形缓冲区可以读的位置

/* index and sequence number of the next record to store in thebuffer */

/*static*/ u64 log_next_seq;//指向当前可写的struct log 索引号

/*static*/ u32 log_next_idx; //指向环形缓冲区可写的位置

 

static void log_store(int facility, int level,

                enum log_flags flags, u64 ts_nsec,

                const char *dict, u16 dict_len,

                const char *text, u16 text_len)

{

struct log *msg;

u32 size, pad_len;

    int this_cpu = smp_processor_id();

    char state =__raw_get_cpu_var(printk_state);

    /*printk prefix {*/

    char tbuf[50];

    unsigned tlen;

    if (console_suspended == 0) {

        //这里给Log添加前缀:

        // [  121.939790].(0)[124:bat_thread_kthr]

      121.939790: 当前时间

      .         : 处于非中断上下文

(0)          : cpu 0上执行

124bat_thread_kthr :124号进程进程调用了printkbat_thread_kthr为线程名

 

       tlen = snprintf(tbuf, sizeof(tbuf),"%c(%x)[%d:%s]",

               state, this_cpu,current->pid, current->comm);

    } else {

        tlen = snprintf(tbuf, sizeof(tbuf),"%c%x)", state, this_cpu);

    }

    /*printk prefix }*/

/* number of ‘\0‘ padding bytes to next message*/

size = sizeof(struct log) + text_len +tlen +dict_len;

pad_len = (-size) & (LOG_ALIGN - 1);

size += pad_len;

 

while (log_first_seq < log_next_seq) {

           u32 free;

           if (log_next_idx > log_first_idx)

                    free = max(log_buf_len -log_next_idx, log_first_idx);//选择更大的free空间

           else

                    free = log_first_idx -log_next_idx; //log_next_idxlog_first_idx直接的内存空间

 

           if (free > size + sizeof(structlog)) //log_next_idx后的内存不足

                    break;

           /* drop old messages until we haveenough contiuous space */

           log_first_idx =log_next(log_first_idx);//log_first_idx移到下一个,此时旧log会被覆盖

           log_first_seq++;

}

 

//这种情况下需要重置log_next_idx

if (log_next_idx + size + sizeof(struct log)>= log_buf_len) {

           /*

            * This message + an additional empty headerdoes not fit

            * at the end of the buffer. Add an emptyheader with len == 0

            * to signify a wrap around.

            */

           memset(log_buf + log_next_idx, 0,sizeof(struct log));

           log_next_idx = 0;

}

 

/* fill message */ //填充信息

msg = (struct log *)(log_buf + log_next_idx);

//memcpy(log_text(msg), text, text_len);

    memcpy(log_text(msg), tbuf, tlen);

memcpy(log_text(msg) + tlen, text, text_len);

    text_len += tlen;

msg->text_len = text_len;

memcpy(log_dict(msg), dict, dict_len);

msg->dict_len = dict_len;

msg->facility = facility;

msg->level = level & 7;

msg->flags = flags & 0x1f;

if (ts_nsec > 0)

           msg->ts_nsec = ts_nsec;

else

           msg->ts_nsec = local_clock();//获取时间信息

memset(log_dict(msg) + dict_len, 0, pad_len);

msg->len = sizeof(struct log) + text_len +dict_len + pad_len;

 

/* insert message */

log_next_idx += msg->len;//指向下一个可以写位置

log_next_seq++;//sep相应加1

}

1.3 console_unlock分析

Console_unlock函数主要把环形缓存区的buf输出到串口,并唤醒需要读取Log的线程

 

2 用户空间获取kernel  log

在printk中实现了/dev/kmsg和do_syslog系统调用,从而用户空间可以通过syslog 或klogctl .

获取内核log

Kmsg.c中通过调用do_syslog函数读取log,从而实现/proc/kmsg函数

3. printk.c提供的其他函数

int __printk_ratelimit(const char *func) //每个5s之内,只能打印10个msg

bool kmsg_dump_get_line(struct kmsg_dumper*dumper, bool syslog,

                            char*line, size_t size, size_t *len)//buf中获取一个record,也称为一个行log

bool kmsg_dump_get_buffer(structkmsg_dumper *dumper, bool syslog,

                              char *buf, size_t size, size_t *len)//从buf中获取size字节的log

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。