printk 源码
之前我们讲了整个调用过程,本章从源码进行分析。

1 2 3 4 5 6 7 8 9 10 11 12
| asmlinkage __visible int _printk(const char *fmt, ...) { va_list args; int r;
va_start(args, fmt); r = vprintk(fmt, args); va_end(args);
return r; } EXPORT_SYMBOL(_printk);
|
上面 const char *fmt 是格式化字符串,... 是变长参数,借用 C 语言的变长参数机制。
把打包好的 fmt 和 args 一起传入到 vprintk 中。
继续向下,vprintk 会调用 vprintk_default 函数,随后会调用 vprintk_emit。
1 2 3 4
| int vprintk_default(const char *fmt, va_list args) { return vprintk_emit(0, LOGLEVEL_DEFAULT, NULL, fmt, args); }
|
会调用 vprintk_store 写入 ring buffer,其作用就是把格式化后的内容写入缓冲区,我们看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| int vprintk_store(int facility, int level, const struct dev_printk_info *dev_info, const char *fmt, va_list args) { struct prb_reserved_entry e; struct printk_record r;
u16 reserve_size; u16 text_len;
...
reserve_size = vsnprintf(prefix_buf, sizeof(prefix_buf), fmt, args2) + 1;
...
prb_rec_init_wr(&r, reserve_size);
if (!prb_reserve(&e, prb, &r)) { ... }
text_len = printk_sprint(r.text_buf, reserve_size, facility, &flags, fmt, args);
r.info->text_len = text_len; r.info->facility = facility; r.info->level = level; r.info->flags = flags; r.info->ts_nsec = ts_nsec; r.info->caller_id = caller_id;
prb_final_commit(&e);
...
return text_len; }
|
上面有三个核心数据结构,第一个是 printk_record:
1 2 3 4 5
| struct printk_record { struct printk_info *info; char *text_buf; unsigned int text_buf_size; };
|
表示构造一条日志记录。
prb_reserved_entry 指向本次申请的 Ring Buffer 记录位置;
1
| struct printk_ringbuffer *prb = &printk_rb_static;
|
这是 printk 使用的全局环形缓冲区 printk_ringbuffer。
上面大致做的就是首先计算日志所需空间,初始化日志记录结构体 printk_record,向 ring buffer 申请空间,写入日志正文,填写日志的信息,提交日志。
未完待续……