LINUX下的tty,console與串口分析

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

二、printk函數是把信息發送到控制檯上吧?如何讓PRINTK把信息經過串口送出?或者說系統在什麼地方來決定是將信息送到顯示器仍是串口? 


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


以上問題可能問的比較亂,由於我本身腦子裏也比較亂,主要仍是對tty,console,serial之間的關係,特別是串口是如何被調用的沒搞清這方面的資料又比較少(就情景分析中講了一點),但願高手能指點一二,很是謝!
我最近也在搞這方面的東西,也是寫一個串口設備的驅動 
搞了將近一個月了,其中上網找資料,看源代碼,什麼都作了 
但仍是一蹋糊塗的,有些問題仍是不明白,但願一塊兒討論討論 

在/proc/device(沒記錯應該是這個文件) 
裏面有一個叫serial的驅動,其主設備號是4,次設備號是64-12X(沒記錯應該是這個範圍) 
你們都知道,串口的次設備號是從64開始的,串口1 /dev/ttyS0就對應次設備號64,串口2就對應65 
問題是如今我機上只有兩個串口,它註冊這麼屢次設備號來幹什麼? 

對於一個接在串口1的設備,在我註冊驅動的時候 
我是須要本身找一個主設備號呢? 
仍是就用主設備號4,次設備號從上面12X的後面選? 
仍是就用主設備號4,次設備號64? 

在linux的內核中有一個tty層,我看好像有些串口驅動是從這裏開始的 
例如調用tty_register_driver()來註冊驅動 
就像在pci子系統裏調用pci_register_driver()那樣的 
那麼,用這種機制來註冊的驅動, 
它是直接對串口的端口操做呢(例如用inb(),outb()....之類的) 
仍是某些更底層的驅動接口呢? 

這些問題纏了我好久都沒解決,搞得最後不得不放棄 
如今轉向用戶空間的應用程序,看能不能有些更高效的方法來實現 
(在用戶空間只能用open("/dev/ttyS0", O_RDWR)來實現了)
另外還有,系統裏已經爲咱們實現了串口的驅動 
因此咱們在用戶空間的程序裏直接open("/dev/ttyS0")就可用了 
可是如今要寫的是接在串口上的設備的驅動 
在內核模塊中可不能夠包含某個頭文件,而後就能夠直接用串口驅動中的接口呢?
看到大家的問題後,感受頗有典型性,所以花了點工夫看了一下,作了一些心得貼在這裏,歡迎討論並指正: 
一、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_callbackTty和console的關係在此已經很明瞭!!! 

二、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函數的緣由 

三、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層)所用.
相關文章
相關標籤/搜索