热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

printk

1.printk.c中的重要变量首先介绍一些kernelprintkprintk.c中定义的一些十分重要的变量。*console_sem用来保护对console_drivers列表

1. printk.c 中的重要变量

首先介绍一些 kernel/printk/printk.c 中定义的一些十分重要的变量。

/* console_sem用来保护对console_drivers列表的访问 */
static DEFINE_SEMAPHORE(console_sem);
/* console_drivers保存所有的控制台驱动,所有的控制台
驱动都调用register_console函数注册到console_drivers
列表中 */
struct console *console_drivers;
EXPORT_SYMBOL_GPL(console_drivers);
/* 注释说这两个变量用来调试VT代码 */
static int console_locked, console_suspended;
/* 如果exclusive_console不是NULL,信息只会输出到这个控制台 */
static struct console *exclusive_console;
/* 包含所有的控制台命令行信息,只有__add_preferred_consoles函数
会向其中增加元素,数组长度为8 */
static struct cmdline_console console_cmdline[MAX_CMDLINECONSOLES];
/* 控制台代码可能会调用schedule()标志 */
static int console_may_schedule;

1.1. struct printk_log

上述变量在 register_console 函数中都有涉及,printk日志系统中最重要的结构体是struct printk_log

struct printk_log {
u64 ts_nsec; /* 时间戳,ns计 */
u16 len; /* 整条记录的长度 */
u16 text_len; /* 文本缓冲区的大小 */
u16 dict_len; /* 字典缓冲区的大小 */
u8 facility; /* syslog设备 */
u8 flags:5; /* 内部记录标志 */
u8 level:3; /* syslog等级 */
};

代码中对于 printk_log 的说明如下:


The printk log buffer consists of a chain of concatenated variable length records. Every record starts with a record header, containing the overall length of the record.


printk日志缓冲区由一系列变长的记录组成,每条记录以一个记录首部信息(struct printk_log)开始,包含记录的所有长度。


The heads to the first and last entry in the buffer, as well as the sequence numbers of these both entries are maintained when messages are stored..


缓冲区的第一项和最后一项的首部信息,以及这两项的序列号都会在存储信息时更新。


If the heads indicate available messages, the length in the header tells the start next message. A length == 0 for the next message indicates a wrap-around to the beginning of the buffer.


如果首部信息显示有可用的信息,首部的长度信息显示了下一条信息的起始位置,length=0表示下一条信息的起始位置在缓冲区的开头。


Every record carries the monotonic timestamp in microseconds, as well as the standard userspace syslog level and syslog facility. The usual kernel messages use LOG_KERN; userspace-injected messages always carry a matching syslog facility, by default LOG_USER. The origin of every message can be reliably determined that way.


每条记录包含线性增加的时间戳信息,以及标准的用户空间syslog等级和syslog设备,普通的内核信息使用LOG_KERN,用户空间嵌入的信息通常携带一个匹配的syslog设备,通常为LOG_USER。每一条信息的源头可以通过这个信息判断。


The human readable log message directly follows the message header. The length of the message text is stored in the header, the stored message is not terminated.


可读的日志信息紧跟在信息的首部之后(因此,log_text 函数可以得到文本的地址),文本的长度保存在首部内,保存的信息没有终止符。


Optionally, a message can carry a dictionary of properties (key/value pairs), to provide userspace with a machine-readable message context.


一条信息可以按照key/value的信息携带一个属性值,向用户空间提供一条机器可读的信息。通常具有以下形式:


Examples for well-defined, commonly used property names are:


DEVICE=b12:8 device identifier
b12:8 block dev_t
c127:3 char dev_t
n8 netdev ifindex
+sound:card0 subsystem:devname
SUBSYSTEM=pci driver-core subsystem name

The 'struct printk_log' buffer header must never be directly exported to userspace, it is a kernel-private implementation detail that might need to be changed in the future, when the requirements change.


不能将缓冲区首部struct printk_log直接导出到用户空间,这个结构体包含的内核私有的实现细节可能会在未来发生改变。


/dev/kmsg exports the structured data in the following line format: "level,sequnum,timestamp;\n"


struct printk_log 相关的变量有:

/* logbuf_lock保护kmsg缓冲区,索引,计数器,可以在调度器内部的rq lock
持有,必须在调用console_unlock前或者任何可能唤起一个进程时释放 */
static DEFINE_RAW_SPINLOCK(logbuf_lock);
DECLARE_WAIT_QUEUE_HEAD(log_wait);
/* 通过syslog(READ)和/proc/kmsg读取的下一个printk记录 */
static u64 syslog_seq;
static u32 syslog_idx;
static enum log_flags syslog_prev;
static size_t syslog_partial;
/* 缓冲区中第一条记录的序列号和索引 */
static u64 log_first_seq;
static u32 log_first_idx;
/* 缓冲区用于保存下一条记录的序列号和索引 */
static u64 log_next_seq;
static u32 log_next_idx;
/* 要写到控制台的下一条printk记录 */
static u64 console_seq;
static u32 cosnole_idx;
static enum log_flags console_prev;
/* 执行过clear后的下一条要读取的printk记录 */
static u64 clear_seq;
static u32 clear_idx;
enum log_flags {
LOG_NOCOnS= 1, /* already flushed, do not print to console */
LOG_NEWLINE = 2, /* text ended with a newline */
LOG_PREFIX = 4, /* text started with a prefix */
LOG_COnT= 8, /* text is a fragment of a continuation line */
};

1.2. 控制台日志缓冲区

kernel/printk/printk.c 中还定义了和保存日志的缓冲区相关的变量:

/* 前缀的最大长度 */
#define PREFIX_MAX 32
/* 单行的最大长度 */
#define LOG_LINE_MAX 1024 - PREFIX_MAX
/* 缓冲区的长度,shift值默认为18,即缓冲区默认为256kb */
#define __LOG_BUF_LEN (1 </* 保存日志的缓冲区,__log_buf,log_buf */
static char __log_buf[__LOG_BUF_LEN];
static char *log_buf = __log_buf;
static u32 log_buf_len = __LOG_BUF_LEN;

1.2.1. 控制台日志的相关操作

log_buf相关的函数有:



  • log_buf_addr_get

    获取log_buf的地址

  • log_buf_len_get

    获取log_buf的长度,即log_buf_len

  • log_text(const struct printk_log *msg)

    获取msgtext的索引

  • log_dict(const struct printk_log *msg)

    获取msgdict的索引

  • log_from_index(u32 idx)

    返回索引idx指向的printk_log对象

  • log_next(u32 idx)

    返回索引idx指向的下一条记录

  • logbuf_has_space(u32 msg_size, bool empty)

    判断log_buf中是否有空间保存给定大小的msg_size记录

  • log_make_free_space(u32 msg_size)

    移动log_first_idx,直到log_buf中有大于msg_size的可用空间

  • msg_used_size(u16 text_len, u16 dict_len, u32 *pad_len)

    根据对齐值,文本的大小,字典的大小计算记录占用的空间大小

  • truncate_msg(u16 *text_len, u16 *trunc_msg_len, u16 *dict_len, u32 *padlen)

    计算切分后的文本的大小


2. console_cont_flush

console_unlock 函数一开始,会先调用 console_cont_flush 函数,将保存在static struct cont cont中的内容输出到控制台。


2.1. struct cont

struct cont cont用来保存连续的多行数据,

static struct cont {
char buf[LOG_LINE_MAX];
size_t len; /* length == 0 means unused buffer */
size_t cons; /* bytes written to console */
struct task_struct *owner; /* task of first print*/
u64 ts_nsec; /* time of first print */
u8 level; /* log level of first message */
u8 facility; /* log level of first message */
enum log_flags flags; /* prefix, newline flags */
bool flushed:1; /* buffer sealed and committed */
} cont;

其中可能有部分数据已经输出到控制台,比如cont.leng=1024cont.cOns=2,表示cont中共有1024byte数据,都保存在cont.buf中,但真正需要输出到控制台的只有1024-2=1022byte数据。

console_cont_flush 函数执行时,根据cont的信息,将其中的数据复制到text参数中,然后调用控制台驱动将这些信息输出。

传入的参数如下:

/* text = ""
size = 1024 */
static void console_cont_flush(char *text, size_t size)
{
unsigned long flags;
size_t len;
raw_spin_lock_irqsave(&logbuf_lock, flags);
if (!cont.len)
goto out;
/*
* We still queue earlier records, likely because the console was
* busy. The earlier ones need to be printed before this one, we
* did not flush any fragment so far, so just let it queue up.
*/
if (console_seq goto out;
/* cont_print_text函数根据cont.len判断cont是否有数据,根据cont.cons判断
cont中的数据是否输出到控制台。如果没有,需要首先输出timestamp,这就
可能导致timestamp的长度加上需要输出的text的长度之和大于1024,即每
一行控制台信息的最大值,这时cont_print_text函数会将多余的数据留在cont
中,修改cont.cons,在下一次调用cont_print_text函数时再输出剩余数据 */
len = cont_print_text(text, size);
raw_spin_unlock(&logbuf_lock);
stop_critical_timings();
/* 遍历console_drivers列表,使用开启的控制台驱动的write函数输出 */
call_console_drivers(cont.level, text, len);
start_critical_timings();
local_irq_restore(flags);
return;
out:
raw_spin_unlock_irqrestore(&logbuf_lock, flags);
}

3. console_unlock

kernel/printk/printk.c 中的函数 register_console 执行到最后,会调用 console_unlock ,根据代码的注释,这个函数会输出持有console_lock时通过 printk 输出的信息。


console_unlock - unlock the console system

Releases the console_lock which the caller holds on the console system and the console driver list.

While the console_lock was held, console output may have been buffered by printk(). If this is the case, console_unlock(); emits the output prior to releasing the lock.

If there is output waiting, we wake /dev/kmsg and syslog() users.

console_unlock(); may be called from any context.


void console_unlock(void)
{
static char text[LOG_LINE_MAX + PREFIX_MAX];
static u64 seen_seq;
unsigned long flags;
bool wake_klogd = false;
bool retry;
//如果控制台已经挂起,释放console_sem信号量,直接返回
if (console_suspended) {
up_console_sem();
return;
}
console_may_schedule = 0;
/* flush buffered message fragment immediately to console */
console_cont_flush(text, sizeof(text));
again:
for (;;) {
struct printk_log *msg;
size_t len;
int level;
raw_spin_lock_irqsave(&logbuf_lock, flags);
/* 第一次执行时为真,之后为假 */
if (seen_seq != log_next_seq) {
wake_klogd = true;
seen_seq = log_next_seq;
}
/* 要输出到控制台的日志的序列号小于logbuf中第一条日志的序列号,
一般情况下不会满足,*/
if (console_seq len = sprintf(text, "** %u printk messages dropped ** ",
(unsigned)(log_first_seq - console_seq));
/* messages are gone, move to first one */
console_seq = log_first_seq;
console_idx = log_first_idx;
console_prev = 0;
} else {
len = 0;
}
skip:
/* 已经输出到控制台的序列号已经为最新,退出循环的唯一途径 */
if (console_seq == log_next_seq)
break;
msg = log_from_idx(console_idx);
if (msg->flags & LOG_NOCONS) {
/*
* Skip record we have buffered and already printed
* directly to the console when we received it.
*/
console_idx = log_next(console_idx);
console_seq++;
/*
* We will get here again when we register a new
* CON_PRINTBUFFER console. Clear the flag so we
* will properly dump everything later.
*/
msg->flags &= ~LOG_NOCONS;
console_prev = msg->flags;
goto skip;
}
level = msg->level;
/* msg_print_text会将msg.text中的'\n'转化,添加prefix,
保存在text中 */
len += msg_print_text(msg, console_prev, false,
text + len, sizeof(text) - len);
console_idx = log_next(console_idx);
console_seq++;
console_prev = msg->flags;
raw_spin_unlock(&logbuf_lock);
stop_critical_timings(); /* don't trace print latency */
/* 调用控制台驱动输出信息 */
call_console_drivers(level, text, len);
start_critical_timings();
local_irq_restore(flags);
}
console_locked = 0;
/* Release the exclusive_console once it is used */
if (unlikely(exclusive_console))
exclusive_cOnsole= NULL;
raw_spin_unlock(&logbuf_lock);
up_console_sem();
/*
* Someone could have filled up the buffer again, so re-check if there's
* something to flush. In case we cannot trylock the console_sem again,
* there's a new owner and the console_unlock() from them will do the
* flush, no worries.
*/
raw_spin_lock(&logbuf_lock);
retry = console_seq != log_next_seq;
raw_spin_unlock_irqrestore(&logbuf_lock, flags);
if (retry && console_trylock())
goto again;
if (wake_klogd)
wake_up_klogd();
}

4. CON_PRINTBUFFER

执行register_console 函数时,要注册的newcon是否具有CON_PRINTBUFFER标志会影响程序的执行流程:

...
/* 如果grub没有提供cOnsole=uart类型的参数,这里的bcon为NULL,
if语句条件为假,不会执行清除CON_PRINTBUFFER的操作。
而serial8250_console设置了CON_PRINTBUFFER标志 */
if (bcon && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV))
newcon->flags &= ~CON_PRINTBUFFER;
...
if (newcon->flags & CON_PRINTBUFFER) {
/*
* console_unlock(); will print out the buffered messages
* for us.
*/
raw_spin_lock_irqsave(&logbuf_lock, flags);
/* 这里会对console_seq,console_idx,console_prev进行修改,
在执行console_unlock函数时,这三个变量会影响输出的信息 */
console_seq = syslog_seq;
console_idx = syslog_idx;
console_prev = syslog_prev;
raw_spin_unlock_irqrestore(&logbuf_lock, flags);
/*
* We're about to replay the log buffer. Only do this to the
* just-registered console to avoid excessive message spam to
* the already-registered consoles.
*/
exclusive_cOnsole= newcon;
}
console_unlock();
...

syslog_seqsyslog_idxsyslog_prev是定义在 kernel/printk/printk.c 中的static变量,只需要查看文件内部对三个变量的使用情况即可。


4.1. syslog_print

第一个修改syslog_seq的函数是 syslog_print,函数接收两个参数,一个是来自用户空间的字符串,另一个是要拷贝的字符串的长度。函数执行期间,还会更新syslog_seqsyslog_idxsyslog_prev的值。

static int syslog_print(char __user *buf, int size)
{
char *text;
struct printk_log *msg;
int len = 0;
text = kmalloc(LOG_LINE_MAX + PREFIX_MAX, GFP_KERNEL);
if (!text)
return -ENOMEM;
while (size > 0) {
size_t n;
size_t skip;
raw_spin_lock_irq(&logbuf_lock);
if (syslog_seq /* messages are gone, move to first one */
syslog_seq = log_first_seq;
syslog_idx = log_first_idx;
syslog_prev = 0;
syslog_partial = 0;
}
/* syslog_seq是最新的,没有日志需要输出,直接返回 */
if (syslog_seq == log_next_seq) {
raw_spin_unlock_irq(&logbuf_lock);
break;
}
/* syslog_partial在@size小于msg中的txtlen时更新 */
skip = syslog_partial;
msg = log_from_idx(syslog_idx);
n = msg_print_text(msg, syslog_prev, true, text,
LOG_LINE_MAX + PREFIX_MAX);
if (n - syslog_partial <= size) {
/* message fits into buffer, move forward */
syslog_idx = log_next(syslog_idx);
syslog_seq++;
syslog_prev = msg->flags;
n -= syslog_partial;
syslog_partial = 0;
} else if (!len){
/* partial read(), remember position */
n = size;
syslog_partial += n;
} else
/* 这个分支只有在输出过若干msg之后,当前要输出的msg的txtlen
大于剩余的buf空闲空间,将n设置为0,可以通过下面的if代码退出
循环 */
n = 0;
raw_spin_unlock_irq(&logbuf_lock);
if (!n)
break;
/* 将信息拷贝到用户空间buf */
if (copy_to_user(buf, text + skip, n)) {
if (!len)
len = -EFAULT;
break;
}
len += n;
size -= n;
buf += n;
}
kfree(text);
return len;
}

4.2. do_syslog

另一个会修改这三个变量的函数是 do_syslog ,而且只有这个函数会调用 syslog_print

do_syslog 是系统调用syslog的主要实现,系统调用的实现代码执行 do_syslog 时,传入的from_filefalse

int do_syslog(int type, char __user *buf, int len, bool from_file)
{
bool clear = false;
static int saved_console_loglevel = -1;
int error;
error = check_syslog_permissions(type, from_file);
if (error)
goto out;
error = security_syslog(type);
if (error)
return error;
switch (type) {
case SYSLOG_ACTION_CLOSE: /* Close log */
break;
case SYSLOG_ACTION_OPEN: /* Open log */
break;
case SYSLOG_ACTION_READ: /* Read from log */
error = -EINVAL;
if (!buf || len <0)
goto out;
error = 0;
if (!len)
goto out;
if (!access_ok(VERIFY_WRITE, buf, len)) {
error = -EFAULT;
goto out;
}
/* sleep直到syslog_seq!=log_next_seq,即有日志需要输出 */
error = wait_event_interruptible(log_wait,
syslog_seq != log_next_seq);
if (error)
goto out;
error = syslog_print(buf, len);
break;
/* Read/clear last kernel messages */
case SYSLOG_ACTION_READ_CLEAR:
clear = true;
/* FALL THRU */
/* Read last kernel messages */
case SYSLOG_ACTION_READ_ALL:
error = -EINVAL;
if (!buf || len <0)
goto out;
error = 0;
if (!len)
goto out;
if (!access_ok(VERIFY_WRITE, buf, len)) {
error = -EFAULT;
goto out;
}
error = syslog_print_all(buf, len, clear);
break;
/* Clear ring buffer */
case SYSLOG_ACTION_CLEAR:
syslog_print_all(NULL, 0, true);
break;
/* Disable logging to console */
case SYSLOG_ACTION_CONSOLE_OFF:
if (saved_console_loglevel == -1)
saved_console_loglevel = console_loglevel;
console_loglevel = minimum_console_loglevel;
break;
/* Enable logging to console */
case SYSLOG_ACTION_CONSOLE_ON:
if (saved_console_loglevel != -1) {
console_loglevel = saved_console_loglevel;
saved_console_loglevel = -1;
}
break;
/* Set level of messages printed to console */
case SYSLOG_ACTION_CONSOLE_LEVEL:
error = -EINVAL;
if (len <1 || len > 8)
goto out;
if (len len = minimum_console_loglevel;
console_loglevel = len;
/* Implicitly re-enable logging to console */
saved_console_loglevel = -1;
error = 0;
break;
/* Number of chars in the log buffer */
case SYSLOG_ACTION_SIZE_UNREAD:
raw_spin_lock_irq(&logbuf_lock);
if (syslog_seq /* messages are gone, move to first one */
syslog_seq = log_first_seq;
syslog_idx = log_first_idx;
syslog_prev = 0;
syslog_partial = 0;
}
if (from_file) {
/*
* Short-cut for poll(/"proc/kmsg") which simply checks
* for pending data, not the size; return the count of
* records, not the length.
*/
error = log_next_idx - syslog_idx;
} else {
u64 seq = syslog_seq;
u32 idx = syslog_idx;
enum log_flags prev = syslog_prev;
error = 0;
while (seq struct printk_log *msg = log_from_idx(idx);
error += msg_print_text(msg, prev, true, NULL, 0);
idx = log_next(idx);
seq++;
prev = msg->flags;
}
error -= syslog_partial;
}
raw_spin_unlock_irq(&logbuf_lock);
break;
/* Size of the log buffer */
case SYSLOG_ACTION_SIZE_BUFFER:
error = log_buf_len;
break;
default:
error = -EINVAL;
break;
}
out:
return error;
}

4.3. syslog_print_all

do_syslog 函数的主要功能通过 syslog_printsyslog_print_all 实现,前者已经介绍过,下面介绍syslog_print_all

sysllog_print_allclear_seq开始,到log_next_seq为止,将适合长度的msg拷贝到 @buf中。如果设置了clear标志,需要更新clear_seqclear_idxlog_next_seq[idx] 。函数实现时,先统计从clear_seqlog_next_seq之间所有的msg的text的长度和len,然后再从clear_seq开始,用这个长度和减去每一个msg的textlen,直到len小于或者等于 @buf 的长度。然后再从这个位置开始,把每一个msg的text拷贝到 @buf 中。

static int syslog_print_all(char __user *buf, int size, bool clear)
{
char *text;
int len = 0;
/* text 大小为1024 */
text = kmalloc(LOG_LINE_MAX + PREFIX_MAX, GFP_KERNEL);
if (!text)
return -ENOMEM;
raw_spin_lock_irq(&logbuf_lock);
if (buf) {
u64 next_seq;
u64 seq;
u32 idx;
enum log_flags prev;
if (clear_seq /* messages are gone, move to first available one */
clear_seq = log_first_seq;
clear_idx = log_first_idx;
}
/*
* Find first record that fits, including all following records,
* into the user-provided buffer for this dump.
*/
seq = clear_seq;
idx = clear_idx;
prev = 0;
/* 计算clear_seq到log_next_seq之间所有msg的txtlen和 */
while (seq struct printk_log *msg = log_from_idx(idx);
/* 只需要计算txtlen,因此@buf=NULL */
len += msg_print_text(msg, prev, true, NULL, 0);
prev = msg->flags;
idx = log_next(idx);
seq++;
}
/* move first record forward until length fits into the buffer */
seq = clear_seq;
idx = clear_idx;
prev = 0;
while (len > size && seq struct printk_log *msg = log_from_idx(idx);
len -= msg_print_text(msg, prev, true, NULL, 0);
prev = msg->flags;
idx = log_next(idx);
seq++;
}
/* 现在,seq到log_next_seq之间的所有msg的txtlen之和小于或
等于size,可以执行拷贝操作 */
/* last message fitting into this dump */
next_seq = log_next_seq;
len = 0;
/* 将seq到log_next_seq之间的所有msg的txt信息
拷贝到@buf中 */
while (len >= 0 && seq struct printk_log *msg = log_from_idx(idx);
int textlen;
textlen = msg_print_text(msg, prev, true, text,
LOG_LINE_MAX + PREFIX_MAX);
if (textlen <0) {
len = textlen;
break;
}
idx = log_next(idx);
seq++;
prev = msg->flags;
raw_spin_unlock_irq(&logbuf_lock);
if (copy_to_user(buf + len, text, textlen))
len = -EFAULT;
else
len += textlen;
raw_spin_lock_irq(&logbuf_lock);
if (seq /* messages are gone, move to next one */
seq = log_first_seq;
idx = log_first_idx;
prev = 0;
}
}
}
if (clear) {
clear_seq = log_next_seq;
clear_idx = log_next_idx;
}
raw_spin_unlock_irq(&logbuf_lock);
kfree(text);
return len;
}

5. printk

内核打印函数 printk 定义在kernel/printk/printk.c 中,函数的主要功能通过 vprintk_emit 实现:

asmlinkage __visible int printk(const char *fmt, ...)
{
/* char *args */
va_list args;
int r;
#ifdef CONFIG_KGDB_KDB
if (unlikely(kdb_trap_printk)) {
va_start(args, fmt);
r = vkdb_printf(fmt, args);
va_end(args);
return r;
}
#endif
va_start(args, fmt);
r = vprintk_emit(0, -1, NULL, 0, fmt, args);
va_end(args);
return r;
}

5.1. 可变参数宏

这个函数使用了可变参数宏va_listva_startva_end,都定义在include/acpi/platform/acenv.h 中,

typedef char *va_list;
/* 64位系统为7 */
#define _AUPBND (sizeof (acpi_native_int) - 1)
#define _ADNBND (sizeof (acpi_native_int) - 1)
/* Variable argument list macro definitions */
/* _bnd(fmt, _AUPBND) => ((sizeof(fmt) + 7) & (0xf8)) = 8*/
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap) (ap = (va_list) NULL)
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))

要理解这些宏,首先要知道函数调用时,需要通过栈来实现。比如代码 printk("line%d\n", line); ,执行时,编译器先将 printk 的参数 "line%d\n", line都压入一个栈中,然后在跳转到 pirntk 函数的地址,执行 pirntk 函数。

va_start(args, fmt);(void)((args) = (((char *)&(fmt)) + 8))) , 指向fmt之上的地址,即 printk 的第一个参数。 va_end(args) 只是将args赋值为NULL

至于va_args 宏,执行的操作是将ap向后移动T类型占用的字节数的宽度,并返回一个T类型的值,类型T需要根据格式化字符串fmt的值进行判断。之后在 vsnprintf 函数中会用到。


5.2. vprintk_emit

vprintk_emit 函数为:

/* @facility = 0 ,STDOUT
@level = -1
@dict = NULL
@dictlen = 0
@fmt = fmt
@args = args */
asmlinkage int vprintk_emit(int facility, int level,
const char *dict, size_t dictlen,
const char *fmt, va_list args)
{
static int recursion_bug;
static char textbuf[LOG_LINE_MAX];
char *text = textbuf;
size_t text_len = 0;
enum log_flags lflags = 0;
unsigned long flags;
int this_cpu;
int printed_len = 0;
bool in_sched = false;
/* cpu currently holding logbuf_lock in this function */
static volatile unsigned int logbuf_cpu = UINT_MAX;
if (level == SCHED_MESSAGE_LOGLEVEL) {
level = -1;
in_sched = true;
}
boot_delay_msec(level);
printk_delay();
/* This stops the holder of console_sem just where we want him */
local_irq_save(flags);
this_cpu = smp_processor_id();
/*
* Ouch, printk recursed into itself!
*/
/* logbuf_cpu会在之后的代码赋值为this_cpu */
if (unlikely(logbuf_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:
*/
/* lockdep_recursing(current) => current->lockdep_recursion */
if (!oops_in_progress && !lockdep_recursing(current)) {
recursion_bug = 1;
goto out_restore_irqs;
}
zap_locks();
}
/* current->lockdep_recursion ++,即增加lock的迭代深度。
推测:lock的迭代深度允许lock的嵌套使用,即每一层深度
都可以申请和释放lock */
lockdep_off();
raw_spin_lock(&logbuf_lock);
logbuf_cpu = this_cpu;
if (unlikely(recursion_bug)) {
static const char recursion_msg[] =
"BUG: recent printk recursion!";
recursion_bug = 0;
/* emit KERN_CRIT message */
/* 保存一条printk recursion信息到log_buf中 */
printed_len += log_store(0, 2, LOG_PREFIX|LOG_NEWLINE, 0,
NULL, 0, recursion_msg,
strlen(recursion_msg));
}
/*
* The printf needs to come first; we need the syslog
* prefix which might be passed-in as a parameter.
*/
if (in_sched)
text_len = scnprintf(text, sizeof(textbuf),
KERN_WARNING "[sched_delayed] ");
/* vscnprintf函数是真正进行格式化输出的函数,将格式化
后的字符串保存在text即textbuf中 */
text_len += vscnprintf(text + text_len,
sizeof(textbuf) - text_len, 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) {
/* 从text[1] 获取log level 信息 */
int kern_level = printk_get_level(text);
if (kern_level) {
/* 如果text包含log level,跳过text中的log level信息 */
const char *end_of_header = printk_skip_level(text);
switch (kern_level) {
case '0' ... '7':
/* 从printk函数执行到这里,level = -1 */
if (level == -1)
/* level = 0 ... 7 */
level = kern_level - '0';
case 'd': /* KERN_DEFAULT */
lflags |= LOG_PREFIX;
}
/*
* No need to check length here because vscnprintf
* put '\0' at the end of the string. Only valid and
* newly printed level is detected.
*/
text_len -= end_of_header - text;
text = (char *)end_of_header;
}
}
if (level == -1)
level = default_message_loglevel;
if (dict)
lflags |= LOG_PREFIX|LOG_NEWLINE;
if (!(lflags & LOG_NEWLINE)) {
/*
* Flush the conflicting buffer. An earlier newline was missing,
* or another task also prints continuation lines.
*/
if (cont.len && (lflags & LOG_PREFIX || cont.owner != current))
cont_flush(LOG_NEWLINE);
/* buffer line if possible, otherwise store it right away */
if (cont_add(facility, level, text, text_len))
printed_len += text_len;
else
printed_len += log_store(facility, level,
lflags | LOG_CONT, 0,
dict, dictlen, text, text_len);
} else {
bool stored = false;
/*
* If an earlier newline was missing and it was the same task,
* either merge it with the current buffer and flush, or if
* there was a race with interrupts (prefix == true) then just
* flush it out and store this line separately.
* If the preceding printk was from a different task and missed
* a newline, flush and append the newline.
*/
if (cont.len) {
if (cont.owner == current && !(lflags & LOG_PREFIX))
stored = cont_add(facility, level, text,
text_len);
cont_flush(LOG_NEWLINE);
}
if (stored)
printed_len += text_len;
else
printed_len += log_store(facility, level, lflags, 0,
dict, dictlen, text, text_len);
}
logbuf_cpu = UINT_MAX;
raw_spin_unlock(&logbuf_lock);
/* If called from the scheduler, we can not call up(). */
if (!in_sched) {
/*
* Try to acquire and then immediately release the console
* semaphore. The release will print out buffers and wake up
* /dev/kmsg and syslog() users.
*/
if (console_trylock_for_printk(this_cpu))
console_unlock();
}
lockdep_on();
out_restore_irqs:
local_irq_restore(flags);
return printed_len;
}

5.2.1. vscnprintf

函数 vprintk_emit 的格式化输出功能通过 vscnprintf 实现,后者的主要功能通过 vsnprintf 函数实现。内核中 vsnprintf 的注释如下:


vsnprintf - Format a string and place it in a buffer

@buf: The buffer to place the result into

@size: The size of the buffer, including the trailing null space

@fmt: The format string to use

@args: Arguments for the format string


@buf保存转化后的字符串

@size保存buf的长度

@fmt保存格式化字符串

@arg保存格式化字符串的参数


The return value is the number of characters which would be generated for the given input, excluding the trailing '\0', as per ISO C99. If you want to have the exact number of characters written into @buf as return value (not including the trailing '\0'), use vscnprintf(). If the return is greater than or equal to @size, the resulting string is truncated.

If you're not already dealing with a va_list consider using snprintf().


函数的返回值是根据输入生成的字符的长度,不包括结尾的'\0'。如果要返回值等于写入buf中的字符的数量,使用vscvprintf()函数。如果返回值大于或者等于size,说明生成的字符串被截断。

vprintk_emit 函数中对 vscnprintf 的调用为例,text_len += vscnprintf(text + text_len, sizeof(textbuf) - text_len, fmt, args); ,传递给 vsnprintf 的参数为:

/* @buf = text
@size = 1024 - 32
@fmt = fmt
@args = args */
int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
unsigned long long num;
char *str, *end;
struct printf_spec spec = {0};
/* Reject out-of-range values early. Large positive sizes are
used for unknown buffer sizes. */
if (WARN_ON_ONCE((int) size <0))
return 0;
str = buf;
end = buf + size;
/* Make sure end is always >= buf */
/* 这种情况只会发生在buf指向一个较大的地址,buf+size产生了溢出,
导致end 后续的操作都会判断buf是否已经用尽,如果用尽,不会再将数据保存在
buf中,而只是增加str指针 */
if (end end = ((void *)-1);
size = end - buf;
}
while (*fmt) {
const char *old_fmt = fmt;
/* 根据fmt填充spec信息,返回处理的格式化字符串的长度 */
int read = format_decode(fmt, &spec);
fmt += read;
switch (spec.type) {
/* 普通的字符串,包括printk的日志等级,KERN_INFO等 */
case FORMAT_TYPE_NONE: {
int copy = read;
if (str /* buf的剩余空间不足以保存长度为read的字符串 */
if (copy > end - str)
copy = end - str;
memcpy(str, old_fmt, copy);
}
str += read;
break;
}
case FORMAT_TYPE_WIDTH:
/* 宽度值通过一个整型指明 */
spec.field_width = va_arg(args, int);
break;
case FORMAT_TYPE_PRECISION:
/* 精度值通过一个整型指明 */
spec.precision = va_arg(args, int);
break;
case FORMAT_TYPE_CHAR: {
char c;
/* 右对齐,补齐空格 */
if (!(spec.flags & LEFT)) {
while (--spec.field_width > 0) {
if (str *str = ' ';
++str;
}
}
/* 这里也用的是整型,结合之后的代码,推测编译器将参数压
栈时,最小的对齐宽度是4字节 */
c = (unsigned char) va_arg(args, int);
if (str *str = c;
++str;
/* 左对齐,补齐空格;如果是右对齐,条件不会满足 */
while (--spec.field_width > 0) {
if (str *str = ' ';
++str;
}
break;
}
/* 字符串,char *类型 ,格式化后的字符串保存在str中 */
case FORMAT_TYPE_STR:
str = string(str, end, va_arg(args, char *), spec);
break;
case FORMAT_TYPE_PTR:
str = pointer(fmt+1, str, end, va_arg(args, void *),
spec);
while (isalnum(*fmt))
fmt++;
break;
case FORMAT_TYPE_PERCENT_CHAR:
if (str *str = '%';
++str;
break;
default:
switch (spec.type) {
case FORMAT_TYPE_LONG_LONG:
num = va_arg(args, long long);
break;
case FORMAT_TYPE_ULONG:
num = va_arg(args, unsigned long);
break;
case FORMAT_TYPE_LONG:
num = va_arg(args, long);
break;
case FORMAT_TYPE_SIZE_T:
if (spec.flags & SIGN)
num = va_arg(args, ssize_t);
else
num = va_arg(args, size_t);
break;
case FORMAT_TYPE_PTRDIFF:
num = va_arg(args, ptrdiff_t);
break;
case FORMAT_TYPE_UBYTE:
num = (unsigned char) va_arg(args, int);
break;
case FORMAT_TYPE_BYTE:
num = (signed char) va_arg(args, int);
break;
case FORMAT_TYPE_USHORT:
num = (unsigned short) va_arg(args, int);
break;
case FORMAT_TYPE_SHORT:
num = (short) va_arg(args, int);
break;
case FORMAT_TYPE_INT:
num = (int) va_arg(args, int);
break;
default:
num = va_arg(args, unsigned int);
}
str = number(str, end, num, spec);
}
}
if (size > 0) {
if (str *str = '\0';
else
end[-1] = '\0';
}
/* the trailing null byte doesn't count towards the total */
return str-buf;
}


推荐阅读
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了如何使用vue-awesome-swiper组件,包括在main.js中引入和使用swiper和swiperSlide组件,以及设置options和ref属性。同时还介绍了如何在模板中使用swiper和swiperSlide组件,并展示了如何通过循环渲染swipes数组中的数据,并使用picUrl属性显示图片。最后还介绍了如何添加分页器。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 使用C++编写程序实现增加或删除桌面的右键列表项
    本文介绍了使用C++编写程序实现增加或删除桌面的右键列表项的方法。首先通过操作注册表来实现增加或删除右键列表项的目的,然后使用管理注册表的函数来编写程序。文章详细介绍了使用的五种函数:RegCreateKey、RegSetValueEx、RegOpenKeyEx、RegDeleteKey和RegCloseKey,并给出了增加一项的函数写法。通过本文的方法,可以方便地自定义桌面的右键列表项。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 本文主要复习了数据库的一些知识点,包括环境变量设置、表之间的引用关系等。同时介绍了一些常用的数据库命令及其使用方法,如创建数据库、查看已存在的数据库、切换数据库、创建表等操作。通过本文的学习,可以加深对数据库的理解和应用能力。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • 使用eclipse创建一个Java项目的步骤
    本文介绍了使用eclipse创建一个Java项目的步骤,包括启动eclipse、选择New Project命令、在对话框中输入项目名称等。同时还介绍了Java Settings对话框中的一些选项,以及如何修改Java程序的输出目录。 ... [详细]
  • 本文介绍了pack布局管理器在Perl/Tk中的使用方法及注意事项。通过调用pack()方法,可以控制部件在显示窗口中的位置和大小。同时,本文还提到了在使用pack布局管理器时,应注意将部件分组以便在水平和垂直方向上进行堆放。此外,还介绍了使用Frame部件或Toplevel部件来组织部件在窗口内的方法。最后,本文强调了在使用pack布局管理器时,应避免在中间切换到grid布局管理器,以免造成混乱。 ... [详细]
  • Vue3中setup函数的参数props和context配置详解
    本文详细介绍了Vue3中setup函数的参数props和context的配置方法,包括props的接收和配置声明,以及未通过props进行接收配置时的输出值。同时还介绍了父组件传递给子组件的值和模板的相关内容。阅读本文后,读者将对Vue3中setup函数的参数props和context的配置有更深入的理解。 ... [详细]
author-avatar
v05736708
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有