一直都不清楚是怎麼被定位到串口的,所以也非常想搞明白,因為以後可能把標準輸入輸出還原到鍵盤和顯示器上去,所以決心自己再讀一讀源碼了。
不過核心用的列印函數printk完全是和stdin或stdout無關的,因為一開始到start_kernel函數剛開始進入核心就可以用printk函數了,而建立stdin和stdout是在init函數中實現的。有個問題,在我這裡的代碼中,建立stdin和stdout如下
if (open("/dev/null", O_RDWR, 0) < 0)
printk("Warning: unable to open an initial console./n");
(void) dup(0);
(void) dup(0);
問題在於它開啟的是/dev/null,而一般pc機上的linux開啟的都是/dev/console,而且我把這幾行代碼刪除也沒有問題,所以我猜想這裡建立stdin和stdout並沒什麼用,肯定在shell中建立了定位到串口的stdin和stdout。所以接下來還需要看看busybox的代碼吧。
在這裡還是主要分析一下printk實現的原理。
static spinlock_t logbuf_lock = SPIN_LOCK_UNLOCKED; //定義logbuf_lock,並初始化為unlock狀態
static char log_buf[LOG_BUF_LEN]; //儲存日誌資料的緩衝區
#define LOG_BUF(idx) (log_buf[(idx) & LOG_BUF_MASK])
static DECLARE_MUTEX(console_sem); //定義全域互斥訊號量console_sem並初始化為1
asmlinkage int printk(const char *fmt, ...)
{
va_list args;
unsigned long flags;
int printed_len;
char *p;
static char printk_buf[1024];
static int log_level_unknown = 1;
if (oops_in_progress) // default : oops_in_progress = 0
{ //oops_in_progress指示進程發生錯誤,只有在panic()函數中才等於1
//所以一般情況下下兩句都不運行
/* If a crash is occurring, make sure we can't deadlock */
spin_lock_init(&logbuf_lock); //初始化logbuf_lock
/* And make sure that we print immediately */
init_MUTEX(&console_sem); //初始化console_sem為互斥的訊號量,初值為1
}
/* This stops the holder of console_sem just where we want him */
spin_lock_irqsave(&logbuf_lock, flags);
//一般spin_lock在單cpu中無效的,所以spin_lock_irqsave真正的作用是關中斷 和儲存狀態寄存器。
/* Emit the output into the temporary buffer */
va_start(args, fmt);
printed_len = vsnprintf(printk_buf, sizeof(printk_buf), fmt, args);
//先把資料格式化到printk_buf中去
va_end(args);
/*
* Copy the output into log_buf. If the caller didn't provide
* appropriate log level tags, we insert them here
*/
//emit_log_char 把字元存入log_buf中等待被發送,具體的參見下面的分析
for (p = printk_buf; *p; p++) {
if (log_level_unknown) {
if (p[0] != '<' || p[1] < '0' || p[1] > '7' || p[2] != '>') {
emit_log_char('<');
emit_log_char(default_message_loglevel + '0');
emit_log_char('>');
}
//如果沒有提供<1>類似的記錄層級,則在此加上<4>
//我這裡的default_message_loglevel=4
log_level_unknown = 0;
}
emit_log_char(*p);
if (*p == '/n') //每一行前面都需要加<4>之類的記錄層級
log_level_unknown = 1;
}
if (!arch_consoles_callable()) // unexecute
{
/* 控制台是否可調用,一般下面的不會被執行
* On some architectures, the consoles are not usable
* on secondary CPUs early in the boot process.
*/
spin_unlock_irqrestore(&logbuf_lock, flags);
goto out;
}
if (!down_trylock(&console_sem)) //lock ok
{
/* down_trylock擷取訊號量,lock則返回0,否則立即返回非0值
* We own the drivers. We can drop the spinlock and let
* release_console_sem() print the text
*/
spin_unlock_irqrestore(&logbuf_lock, flags);
console_may_schedule = 0;
release_console_sem(); //在這個函數中把資料發送到串口並釋放console_sem
} else {
/*
* Someone else owns the drivers. We drop the spinlock, which
* allows the semaphore holder to proceed and to call the
* console drivers with the output which we just produced.
*/
spin_unlock_irqrestore(&logbuf_lock, flags);
}
out:
return printed_len;
}
static unsigned long log_start; /* Index into log_buf: next char to be read by syslog() */
static unsigned long con_start; /* Index into log_buf: next char to be sent to consoles */
static unsigned long log_end; /* Index into log_buf: most-recently-written-char + 1 */
static unsigned long logged_chars; /* Number of chars produced since last read+clear operation */
static void emit_log_char(char c)
{
LOG_BUF(log_end) = c; //把字元c存到log_buf緩衝區中,緩衝區滿了就會覆蓋開始的資料
log_end++; //
if (log_end - log_start > LOG_BUF_LEN) //log_start指示syslog讀取的開始
log_start = log_end - LOG_BUF_LEN;//緩衝區滿了會把開始的指標向前推
if (log_end - con_start > LOG_BUF_LEN) //con_start指示控制台讀取的開始
con_start = log_end - LOG_BUF_LEN;
if (logged_chars < LOG_BUF_LEN)
logged_chars++;
}
void release_console_sem(void)
{
unsigned long flags;
unsigned long _con_start, _log_end;
unsigned long must_wake_klogd = 0;
for ( ; ; ) {
spin_lock_irqsave(&logbuf_lock, flags);//關中斷和儲存flag
must_wake_klogd |= log_start - log_end; //喚醒klogd標誌
if (con_start == log_end)
break; /* Nothing to print */
_con_start = con_start;
_log_end = log_end;
con_start = log_end; /* Flush , con_start向前移用了,可見緩衝區是迴圈使用的 */
spin_unlock_irqrestore(&logbuf_lock, flags);
call_console_drivers(_con_start, _log_end);//在這個函數中發送資料,見下面的分析
}
console_may_schedule = 0; //指示資料發送時是否能進行任務調度,在使用串口控制台時沒用
up(&console_sem); //釋放訊號量
spin_unlock_irqrestore(&logbuf_lock, flags);
if (must_wake_klogd && !oops_in_progress)
wake_up_interruptible(&log_wait);
}
static void call_console_drivers(unsigned long start, unsigned long end)
{
unsigned long cur_index, start_print;
static int msg_level = -1;
if (((long)(start - end)) > 0)
BUG();
cur_index = start;
start_print = start;
while (cur_index != end) {
if ( msg_level < 0 &&
((end - cur_index) > 2) &&
LOG_BUF(cur_index + 0) == '<' &&
LOG_BUF(cur_index + 1) >= '0' &&
LOG_BUF(cur_index + 1) <= '7' &&
LOG_BUF(cur_index + 2) == '>')
{
msg_level = LOG_BUF(cur_index + 1) - '0';
cur_index += 3;
start_print = cur_index;
} //去除每行開頭的類似<4>的記錄層級,把它賦給msg_level
while (cur_index != end) {
char c = LOG_BUF(cur_index);
cur_index++;
if (c == '/n') {
if (msg_level < 0) {
/*
* printk() has already given us loglevel tags in
* the buffer. This code is here in case the
* log buffer has wrapped right round and scribbled
* on those tags
*/
msg_level = default_message_loglevel;
}
_call_console_drivers(start_print, cur_index, msg_level);
//發送一行資料
msg_level = -1;
start_print = cur_index;
break;
}
}
}
_call_console_drivers(start_print, end, msg_level); //發送剩餘的資料
}
struct console *console_drivers; //全域的console類型的結構體
static void _call_console_drivers(unsigned long start, unsigned long end, int msg_log_level)
{
//如果msg_log_level < console_loglevel 並且 console_drivers存在 並且 start != end
if (msg_log_level < console_loglevel && console_drivers && start != end) {
if ((start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)) {
/* wrapped write */
//緩衝區迴圈使用就會出現(start & LOG_BUF_MASK) > (end & LOG_BUF_MASK)
//的清況,於是就分兩部分來發送.
//__call_console_drivers才是真正的發送函數
__call_console_drivers(start & LOG_BUF_MASK, LOG_BUF_LEN);
__call_console_drivers(0, end & LOG_BUF_MASK);
} else {
__call_console_drivers(start, end);
}
}
}
static void __call_console_drivers(unsigned long start, unsigned long end)
{
struct console *con;
for (con = console_drivers; con; con = con->next) {
if ((con->flags & CON_ENABLED) && con->write)
con->write(con, &LOG_BUF(start), end - start);
} //調用console_drivers->write來把資料發送出去
}
接下來理解一下console_drivers這個結構體指標
struct console
{
char name[8];
void (*write)(struct console *, const char *, unsigned);
int (*read)(struct console *, const char *, unsigned);
kdev_t (*device)(struct console *);
int (*wait_key)(struct console *);
void (*unblank)(void);
int (*setup)(struct console *, char *);
short flags;
short index;
int cflag;
struct console *next;
};
而開始console_drivers這個指標是NULL的,在什麼時候被賦值的呢,原本以為應該在用printk以前就被初始化了,其實不然,它是在start_kernel函數中調用的console_init()函數中被初始化的.最好的辦法還是看看代碼.
void __init console_init(void)
{
/* Setup the default TTY line discipline. */
memset(ldiscs, 0, sizeof(ldiscs));
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
/*
* Set up the standard termios. Individual tty drivers may
* deviate from this; this is used as a template.
*/
memset(&tty_std_termios, 0, sizeof(struct termios));
memcpy(tty_std_termios.c_cc, INIT_C_CC, NCCS);
tty_std_termios.c_iflag = ICRNL | IXON;
tty_std_termios.c_oflag = OPOST | ONLCR;
#ifdef CONFIG_MIZI //CONFIG_MIZI=1
tty_std_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL;
#else
tty_std_termios.c_cflag = B38400 | CS8 | CREAD | HUPCL;
#endif
tty_std_termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
ECHOCTL | ECHOKE | IEXTEN;
/*
* set up the console device so that later boot sequences can
* inform about problems etc..
*/
#ifdef CONFIG_VT //如果不是串口控制台就定義這個虛擬控制台
con_init(); //於是輸入是鍵盤輸出是顯示器
#endif
#ifdef CONFIG_AU1000_SERIAL_CONSOLE
au1000_serial_console_init();
#endif
#ifdef CONFIG_SERIAL_CONSOLE
#if (defined(CONFIG_8xx) || defined(CONFIG_8260))
console_8xx_init();
#elif defined(CONFIG_MAC_SERIAL) && defined(CONFIG_SERIAL)
if (_machine == _MACH_Pmac)
mac_scc_console_init();
else
serial_console_init();
#elif defined(CONFIG_MAC_SERIAL)
mac_scc_console_init();
#elif defined(CONFIG_PARISC)
pdc_console_init();
#elif defined(CONFIG_SERIAL)
serial_console_init();
#endif /* CONFIG_8xx */
#ifdef CONFIG_SGI_SERIAL
sgi_serial_console_init();
#endif
#if defined(CONFIG_MVME162_SCC) || defined(CONFIG_BVME6000_SCC) || defined(CONFIG_MVME147_SCC)
vme_scc_console_init();
#endif
#if defined(CONFIG_SERIAL167)
serial167_console_init();
#endif
#if defined(CONFIG_SH_SCI)
sci_console_init();
#endif
#endif
#ifdef CONFIG_TN3270_CONSOLE
tub3270_con_init();
#endif
#ifdef CONFIG_TN3215
con3215_init();
#endif
#ifdef CONFIG_HWC
hwc_console_init();
#endif
#ifdef CONFIG_STDIO_CONSOLE
stdio_console_init();
#endif
#ifdef CONFIG_SERIAL_CORE_CONSOLE // CONFIG_SERIAL_CORE_CONSOLE=1
uart_console_init(); //這裡唯一一個被啟動並執行函數
#endif
#ifdef CONFIG_ARC_CONSOLE
arc_console_init();
#endif
#ifdef CONFIG_SERIAL_TX3912_CONSOLE
tx3912_console_init();
#endif
}
void __init uart_console_init(void)
{
#ifdef CONFIG_SERIAL_AMBA_CONSOLE
ambauart_console_init();
#endif
#ifdef CONFIG_SERIAL_ANAKIN_CONSOLE
anakin_console_init();
#endif
#ifdef CONFIG_SERIAL_CLPS711X_CONSOLE
clps711xuart_console_init();
#endif
#ifdef CONFIG_SERIAL_21285_CONSOLE
rs285_console_init();
#endif
#ifdef CONFIG_SERIAL_SA1100_CONSOLE
sa1100_rs_console_init();
#endif
#ifdef CONFIG_SERIAL_8250_CONSOLE
serial8250_console_init();
#endif
#ifdef CONFIG_SERIAL_UART00_CONSOLE
uart00_console_init();
#endif
#ifdef CONFIG_SERIAL_S3C2400_CONSOLE
s3c2400_console_init();
#endif
#ifdef CONFIG_SERIAL_S3C2410_CONSOLE
s3c2410_console_init(); //這個函數被運行
#endif
}
void __init s3c2410_console_init(void)
{
register_console(&s3c2410_cons); //調用註冊控制台的函數
}
static struct console s3c2410_cons = {
name: "ttyS",
write: s3c2410_console_write,
device: s3c2410_console_device,
wait_key: s3c2410_console_wait_key,
setup: s3c2410_console_setup,
flags: CON_PRINTBUFFER,
index: -1,
}; //這個就是console_drivers所指向的結構了
void register_console(struct console * console)
{ //該函數就是把console_drivers這個全域指標指向console結構體了,而且在註冊完後
//會把存在緩衝區中的都發送出去,所以在註冊console以前調用的printk並不發送資料
//而只是把資料存到緩衝區裡,註冊了以後才能被馬上發送.多個控制台被註冊的話就會
//形成一個鏈表結構,都能發送資料.
int i;
unsigned long flags;
/*
* See if we want to use this console driver. If we
* didn't select a console we take the first one
* that registers here.
*/
if (preferred_console < 0) {
if (console->index < 0)
console->index = 0;
if (console->setup == NULL ||
console->setup(console, NULL) == 0) {
console->flags |= CON_ENABLED | CON_CONSDEV;
preferred_console = 0;
}
}
/*
* See if this console matches one we selected on
* the command line.
*/
for(i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0]; i++) {
if (strcmp(console_cmdline[i].name, console->name) != 0)
continue;
if (console->index >= 0 &&
console->index != console_cmdline[i].index)
continue;
if (console->index < 0)
console->index = console_cmdline[i].index;
if (console->setup &&
console->setup(console, console_cmdline[i].options) != 0)
break;
console->flags |= CON_ENABLED;
console->index = console_cmdline[i].index;
if (i == preferred_console)
console->flags |= CON_CONSDEV;
break;
}
if (!(console->flags & CON_ENABLED))
return;
/*
* Put this console in the list - keep the
* preferred driver at the head of the list.
*/
acquire_console_sem();
if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
console->next = console_drivers;
console_drivers = console;
} else {
console->next = console_drivers->next;
console_drivers->next = console;
}
if (console->flags & CON_PRINTBUFFER) {
/*
* release_cosole_sem() will print out the buffered messages for us.
*/
spin_lock_irqsave(&logbuf_lock, flags);
con_start = log_start;
spin_unlock_irqrestore(&logbuf_lock, flags);
}
release_console_sem();
}