顯示廣告
隱藏 ✕
看板 uefacool
作者 uefangsmith (唉呦!不錯哦~)
標題 Re: [Linu] LINUX下的tty,console与串口
時間 2011年03月26日 Sat. AM 12:00:57


※ 引述《uefangsmith》之銘言:
20100819
http://blog.csdn.net/sfrysh/archive/2010/08/19/5825122.aspx

1、LINUX下TTY、CONSOLE、串口之間是怎樣的層次關係?具體的函數介面是怎樣的?串口是如何被調用的?

tty和console這些概念主要是一些虛設備的概念,而串口更多的是指一個真正的設備驅動。
Tty實際是一類終端I/O設備的抽象,它實際上更多的是一個管理的概念,它和tty_ldisc(行規程)和tty_driver(真實設備驅動)組合在一起,目的是向上層的VFS提供一個統一的介面。通過file_operations結構中的tty_ioctl可以對其進行配置。

查tty_driver,你將得到n個結果,實際都是相關晶片的驅動。因此,可以得到的結論是(實際情況比這複雜得多):
每個描述tty設備的 tty_struct在初始化時必然掛如了某個具體晶片的字元設備驅動(不一定是字元設備驅動),
可以是很多,包括顯卡或串口chip。不知道你的ARM Soc是那一款,不過看情況你們應該用的是常見的chip,這些驅動實際上都有。

而console是一個緩衝的概念,它的目的有一點類似於tty。實際上console不僅和tty連在一起,還和framebuffer連在一起,
具體的原因看下面的鍵盤的中斷處理過程。Tty的一個子集需要使用console(典型的如主設備號4,次設備號1―64),
但是要注意的是沒有 console的tty是存在的。
而串口則指的是tty_driver。

舉個典型的例子:
分析一下鍵盤的中斷處理過程:
keyboard_interrupt―>handle_kbd_event―>handle_keyboard_event―>handle_scancode
void handle_scancode(unsigned char scancode, int down) 
{ 
…….. 
tty = ttytab? ttytab[fg_console]: NULL; 
if (tty && (!tty->driver_data)) { 
…………… 
tty = NULL; 
} 
…………. 
schedule_console_callback(); 
} 
這段代碼中的兩個地方很值得注意,也就是除了獲得tty外(通過全局量tty記錄),還進行了console回顯schedule_console_callback。
Tty和console的關係在此已經很明瞭!!!



2、printk函數是把資訊發送到控制臺上吧?如何讓PRINTK把資訊通過串口送出?
   或者說系統在什麼地方來決定是將資訊送到顯示器還是串口?

具體看一下printk函數的實現就知道了,printk不一定是將資訊往控制臺上輸出,
設置kernel的啟動參數可能可以打到將資訊送到顯示器的效果。

函數前有一段英文,很有意思:
/*This is printk. It can be called from any context. We want it to work.
*
* We try to grab the console_sem. If we succeed, it's easy - we log the output and
* call the console drivers. If we fail to get the semaphore we place the output
* into the log buffer and return. The current holder of the console_sem will
* notice the new output in release_console_sem() and will send it to the
* consoles before releasing the semaphore.
*
* One effect of this deferred printing is that code which calls printk() and
* then changes console_loglevel may break. This is because console_loglevel
* is inspected when the actual printing occurs.
*/
 
這段英文的要點:要想對console進行操作,必須先要獲得console_sem信號量。
如果獲得console_sem信號量,則可以“log the output and call the console drivers”,
反之,則“place the output into the log buffer and return”,實際上,在代碼:

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) { /*如果為1情況下,必然是系統發生crush*/ 

/* If a crash is occurring, make sure we can't deadlock */ 
spin_lock_init(&logbuf_lock); 

/* And make sure that we print immediately */ 
init_MUTEX(&console_sem); 
} 

/* This stops the holder of console_sem just where we want him */ 
spin_lock_irqsave(&logbuf_lock, flags); 

/* Emit the output into the temporary buffer */ 
va_start(args, fmt); 

printed_len = vsnprintf(printk_buf, sizeof(printk_buf), fmt, args);/*對傳入的buffer進行處理,注意還不是
                                                                     真正的對終端寫,只是對傳入的string進行格式解析*/ 
va_end(args); 

/*Copy the output into log_buf. If the caller didn't provide appropriate log level tags, we insert them here*/ 
/*注釋很清楚*/ 
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('>'); 
} 
log_level_unknown = 0; 
} 
emit_log_char(*p); 
if (*p == '\n') 
log_level_unknown = 1; 
} 
if (!arch_consoles_callable()) { 

/*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)) { 
/*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(); 
} 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; 
} 
實際上printk是將format後的string放到了一個buffer中,在適當的時候再加以show,
這也回答了在start_kernel中一開始就用到了printk函數的原因



3、start_kernel中一開始就用到了printk函數(好象是printk(linux_banner什麼的),在這個時候整個內核還沒跑起來呢。
   那這時候的printk是如何被調用的?在我們的系統中,系統啟動是用的現代公司的BOOTLOADER程式,
   後來好象跳到了LINUX下的 head-armv.s, 然後跳到start_kernel,在bootloader 裏串口已經是可用的了,
   那麼在進入內核後是不是要重新設置?

Bootloader一般會做一些基本的初始化,將kernel拷貝物理空間,然後再跳到kernel去執行。可以肯定的是kernel肯定要對串口進行 重新設置,原因是Bootloader有很多種,有些不一定對串口進行設置,內核不能依賴於bootloader而存在。
===============================================================================
多謝樓上大俠,分析的很精闢。我正在看printk函數。

我們用的CPU是hynix的hms7202。在評估板上是用串口0作控制臺,所有啟動過程中的資訊都是通過該串口送出的。
在bootloader中定義了函數ser_printf通過串口進行交互。

但我還是沒想明白在跳轉到linux內核而console和串口尚未初始化時printk是如何能夠工作的?
我看了start_kernel的過程(並通過超級終端作了一些跟蹤),console的初始化是在console_init函數裏,
而串口的初始化實際上是在1號進程裏(init->do_basic_setup->do_initcalls->rs_init),
那麼在串口沒有初始化以前prink是如何工作的?

特別的,在start_kernel一開始就有printk(linux_banner),而這時候串口和console都尚未初始化呢。
===============================================================================
1.在start_kernel一開始就有printk(linux_banner),而這時候串口和console都尚未初始化?

仔細分析printk可以對該問題進行解答。代碼中的:
/* Emit the output into the temporary buffer */
va_start(args, fmt);
printed_len = vsnprintf(printk_buf, sizeof(printk_buf), fmt, args);
va_end(args);

將輸入放到了printk_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('>');
}
log_level_unknown = 0;
}
emit_log_char(*p);
if (*p == '\n')
log_level_unknown = 1;
}

則將printk_buf中的內容進行解析並放到全局的log_buf(在emit_log_char函數)中。
而下麵的
if (!down_trylock(&console_sem)) {
/*
* 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();
} 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);
}
則是根據down_trylock(&console_sem)的結果調用release_console_sem(),
在release_console_sem()中才真正的對全局的log_buf中的內容相應的console設備驅動進行處理。
至此,可以得到如下的一些結論:

(1)printk的主操作實際上還是針對一個buffer(log_buf),該buffer中的內容是否顯示(或者說向終端輸出),
     則要看是否可以獲得console_sem。
(2)printk所在的文件為printk.c,是和體系結構無關的,因此對任何平臺都一樣。

可以推測的結論是:
(1)kernel在初始化時將console_sem標為了locked,因此在start_kernel一開始的printk(linux_banner)中實際只將輸入寫入了緩衝,
     等在串口和console初始化後,對printk的調用才一次將緩衝中的內容向串口和console輸出。
(2)在串口和console的初始化過程中,必然有對console_sem的up操作。
(3)因此,在embedded的調試中,如果在console的初始化之前系統出了問題,不會有任何的輸出。唯一可以使用的只能是led或jtag了。
(4)因此,你的問題可以看出解答。

2.console的初始化.
不知道你用的是那一個內核版本,在我看的2.4.18和2.4.19中,都是在start_kernel中就對console進行的初始化。從前面的分析來看,console的初始化不應該太晚,否則log_buf有可能溢出。
===============================================================================
多謝樓上,分析的很精彩!

我們用的內核版本是2.4.18,console的初始化確實是在
start_kernel->console->init。

關於tty和串口,我這裏還想再問一下。tty設備的操作的總入口


static struct file_operations tty_fops = {
llseek: no_llseek,
read: tty_read,
write: tty_write,
poll: tty_poll,
ioctl: tty_ioctl,
open: tty_open,
release: tty_release,
fasync: tty_fasync,
};

而對串口的操作定義在:

static struct tty_driver serial_driver 這個結構中。
serial.c中的多數函數都是填充serial_driver中的函數指標。

那麼在對串口操作時,應該是先調用tty_fops中的操作(比如tty_open等),然後再分流到具體的串口操作(rs_open等)吧?
但tty_driver(對串口就是serial_driver)中有很多函數指標並不跟file_operations中的函數指標對應,不知道這些對應
不上的操作是如何被執行的?比如put_char,flush_char,read_proc,write_proc,start,stop等。
===============================================================================
以下是我對這個問題的一些理解:
 
這實際上還是回到原先的老問題,即tty和tty_driver之間的關係。從實現上看,tty_driver實際上是tty機制的實現組件之一,
借用面向物件設計中的常用例子,這時的tty_driver就像是tty這部汽車的輪胎,tty這部汽車要正常運行,
還要tty_ldisc(行規 程),termios,甚至struct tq_struct tq_hangup(看tty_struct)等基礎設施。

它們之間的關係並非繼承。
至於tty_driver中的函數指標,再打個C++中的比喻,它們實際上很象虛函數,也就是說,可以定義它們,但並不一定實現它們。
實際上還不用說 tty_driver,只要查一下serial_driver都會發現n多個具體的實現,但對各個具體的設備,
其tty_driver中的函數不一定全部 實現。

所以put_char,flush_char,read_proc, write_proc,start,stop這些函數的情況是有可能實現,也有可能不實現。
即使被實現,也不一定為上層(VFS層)所用。









--
※ 作者: uefangsmith  時間: 2011-01-12 00:09:01  來自: 219-85-3-39-adsl-nei3.dynamic.so-net.net.tw


--
※ 作者: uefangsmith  時間: 2011-03-26 00:00:57  來自: 219.85.131.31
※ 看板: uefacool 文章推薦值: 0 目前人氣: 0 累積人氣: 33 
分享網址: 複製 已複製
guest
x)推文 r)回覆 e)編輯 d)刪除 M)收藏 ^x)轉錄 同主題: =)首篇 [)上篇 ])下篇