printk 流程分析

機器感知發表於2020-11-01

1. 概述

printk 用於在終端上列印核心想要輸出的資訊,平常我們較多使用的列印函式是 printf,兩者名字雖然只有最後一個字母不同,且都是為了在終端上顯示資訊,但是它們的應用場景並不相同。printk 是 linux 核心用來列印訊息的,而 printf 則是使用者態模式下列印訊息用的,不過最終 printf 會通過系統呼叫的方式使用 printk 進行訊息的顯示。printk 的整體程式碼流程如下:

printk 流程分析

2. 程式碼分析

首先是 printk 函式,因為 printk 函式是支援不定引數的,所以第一步就是先解析引數,之後就是呼叫 vprintk 函式了。

asmlinkage int printk(const char *fmt, ...)
{
	... 省略 ...
	va_start(args, fmt);
	r = vprintk(fmt, args);
	va_end(args);

	return r;
}

對於 vprintk 函式,其主要步驟如上圖所示,首先是呼叫 vscnprintf 格式化字串,並放入到 printk_buf 中,接下來就是再根據 log level 以及是否需要增加時間字首等資訊再次格式化字串,並將最後的字串從 printk_buf 複製到 log_buff 中,最後就是將 log_buff 中的字串輸出到終端上,實際執行輸出操作的是在 console_unlock() 中進行的。

asmlinkage int vprintk(const char *fmt, va_list args)
{
    ... 簡略版 ...
    raw_spin_lock(&logbuf_lock);
    /* 格式化到 printk_buf */
	printed_len += vscnprintf(printk_buf + printed_len,
				  sizeof(printk_buf) - printed_len, fmt, args);
    /* 格式化到 log_buf */
    for (; *p; p++) {
		if (new_text_line) {
			new_text_line = 0;
             ...
        }
        
        emit_log_char(*p);
		if (*p == '\n')
			new_text_line = 1;
    }
    /* 將 log_buf 中的字串輸出到終端上*/
    if (console_trylock_for_printk(this_cpu))
		console_unlock();
    
    return printed_len;
}

在 console_unlock 中,首先根據 con_start 和 log_end 來檢查是否有需要在終端上列印的訊息,如果有則開始使用 call_console_drivers 呼叫 console 關聯的底層驅動程式進行最終的訊息列印操作。

void console_unlock(void)
{
	...
again:
	for ( ; ; ) {
		raw_spin_lock_irqsave(&logbuf_lock, flags);
		wake_klogd |= log_start - log_end;
		if (con_start == log_end)
			break;
		_con_start = con_start;
		_log_end = log_end;
		con_start = log_end;
		raw_spin_unlock(&logbuf_lock);
		stop_critical_timings();
		call_console_drivers(_con_start, _log_end);
		start_critical_timings();
		local_irq_restore(flags);
	}
	console_locked = 0;
	...
}

而 call_console_drivers 函式又呼叫了 _call_console_drivers,進一步呼叫 __call_console_drivers,最終呼叫不同 console 關聯的寫操作 con->write(con, &LOG_BUF(start), end - start) 實現列印操作。

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

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

3. 總結

  • printk 函式第一步是解析引數列表,然後對帶有引數的字串進行格式化,並放入到 printk_buf 中,最後再根據 log level 以及是否需要加時間字首等配置再次格式化字串,並放入到 log_buf 中;

  • 當 log_buf 中有需要列印的資訊時,則呼叫 console 所關聯的驅動程式完成最終的列印操作。

  • 在 __call_console_drivers 中呼叫 console 所關聯的驅動時會先判斷 con->write 是否存在,如果 con->write 不存在,則不會在終端中有資訊輸出,即直接忽略這次列印,然而在 linux 核心啟動初期就已經開始呼叫 printk 函式了,而此時還並沒註冊 console 裝置,就更不可能存在 con->write 了,那這個階段呼叫 printk 輸出的資訊豈不是都丟了?然而事實並不是這樣子的,欲知後事如何,且聽下回分解。

相關文章