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上执行
124:bat_thread_kthr :在124号进程进程调用了printk,bat_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_idx和log_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