[uart]1.Linux中tty框架與uart框架之間的調用關係剖析

轉自:http://developer.51cto.com/art/201209/357501_all.htmhtml

目錄linux

1.tty框架ios

2.uart框架緩存

3.自底向上數據結構

4.自頂向下app

5.關係圖框架

 

在這期間有一個問題困擾着我,那就是來自用戶空間的針對uart設備的操做意圖是如何經過tty框架逐層調用到uart層的core驅動,進而又是如何調用到真實對應於設備的設備驅動的,本文中的對應設備驅動就是8250驅動,最近我想將這方面的內容搞清楚。async

 

在說明這一方面問題以前咱們先要大體瞭解兩個基本的框架結構,tty框架和uart框架。函數

1.tty框架

在linux系統中,tty表示各類終端。終端一般都跟硬件相對應。好比對應於輸入設備鍵盤鼠標,輸出設備顯示器的控制終端和串口終端。this

下面這張圖是一張很經典的圖了,很清楚的展示了tty框架的層次結構,你們先看圖,下面給你們解釋。

最上面的用戶空間會有不少對底層硬件(在本文中就是8250uart設備)的操做,像read,write等。

用戶層數據處理步驟以下

  • 用戶空間主要是經過設備文件同tty_core交互,tty_core根據用空間操做的類型再選擇跟line discipline和tty_driver也就是serial_core交互,例如設置硬件的ioctl指令就直接交給serial_core處理。
  • Read和write操做就會交給line discipline處理,ldisc對數據進行預處理。
  • 處理以後,就會將數據交給serial_core,最後serial_core會調用8250.c的操做。

Line discipline是線路規程的意思,正如它的名字同樣,它表示的是這條終端」線程」的輸入與輸出規範設置,主要用來進行輸入/輸出數據的預處理。

2.uart框架

下圖是同同樣一副經典的uart框架圖,將uart重要的結構封裝的很清楚,你們且看。

一個uart_driver一般會註冊一段設備號.即在用戶空間會看到uart_driver對應有多個設備節點。例如:

/dev/ttyS0  /dev/ttyS1 每一個設備節點是對應一個具體硬件的,這樣就可作到對多個硬件設備的統一管理,而每一個設備文件應該對應一個uart_port,也就是說:uart_device要和多個uart_port關係起來。而且每一個uart_port對應一個circ_buf(用來接收數據),因此 uart_port必需要和這個緩存區關係起來。

 

3.自底向上

接下來咱們就來看看對設備的操做是怎樣進行起來的,不過在此以前咱們有必要從底層的uart驅動註冊時開始提及,這樣到後面才能更清晰。

這裏咱們討論的是8250驅動,在驅動起來的時候調用了uart_register_driver(&serial8250_reg);函數將參數serial8250_reg註冊進了tty層。具體代碼以下所示:

複製代碼
 1 int uart_register_driver(struct uart_driver *drv) 
 2 {  
 3     struct tty_driver *normal = NULL;  
 4     int i, retval;  
 5  
 6     BUG_ON(drv->state);  
 7  
 8     /*  
 9      * Maybe we should be using a slab cache for this, especially if   
10      * we have a large number of ports to handle.  
11      */ 
12     drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);  
13     retval = -ENOMEM;  
14     if (!drv->state)  
15         goto out;  
16  
17     normal  = alloc_tty_driver(drv->nr);  
18     if (!normal)  
19         goto out;  
20  
21     drv->tty_driver = normal;  
22  
23     normal->owner       = drv->owner;  
24     normal->driver_name = drv->driver_name;  
25     normal->name        = drv->dev_name;  
26     normal->major       = drv->major;  
27     normal->minor_start = drv->minor;  
28     normal->type        = TTY_DRIVER_TYPE_SERIAL;  
29     normal->subtype     = SERIAL_TYPE_NORMAL;  
30     normal->init_termios    = tty_std_termios;  
31     normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;  
32     normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;  
33     normal->flags       = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;  
34     normal->driver_state    = drv;  // here is important for me, ref uart_open function in this file   
35     tty_set_operations(normal, &uart_ops);  
36  
37     /*  
38      * Initialise the UART state(s).   
39      */ 
40     for (i = 0; i < drv->nr; i++) {  
41         struct uart_state *state = drv->state + i;  
42  
43         state->close_delay     = 500;   /* .5 seconds */ 
44         state->closing_wait    = 30000; /* 30 seconds */ 
45         mutex_init(&state->mutex);  
46  
47         tty_port_init(&state->info.port);  
48         init_waitqueue_head(&state->info.delta_msr_wait);  
49         tasklet_init(&state->info.tlet, uart_tasklet_action,  
50                  (unsigned long)state);  
51     }  
52  
53     retval = tty_register_driver(normal);  
54  out:  
55     if (retval < 0) {  
56         put_tty_driver(normal);  
57         kfree(drv->state);  
58     }  
59     return retval;  
60 } 
複製代碼

 

從上面代碼能夠看出,uart_driver中不少數據結構其實就是tty_driver中的,將數據轉換爲tty_driver以後,註冊tty_driver。而後初始化uart_driver->state的存儲空間。
這裏有兩個地方咱們須要特別關注:

第一個是

  1. normal->driver_state    = drv;  

爲何說重要呢,由於真實這一句將參數的ops關係都賦給了serial_core層。也就是說在後面serial_core會根據uart_ops關係找到咱們的8250.c中所對應的操做,而咱們參數中的ops是在哪被賦值的呢?這個必定是會在8250.c中不會錯,因此我定位到了8250.c中的serial8250_ops結構體,初始化以下:

複製代碼
 1 static struct uart_ops serial8250_pops = {  
 2     .tx_empty   = serial8250_tx_empty,  
 3     .set_mctrl  = serial8250_set_mctrl,  
 4     .get_mctrl  = serial8250_get_mctrl,  
 5     .stop_tx    = serial8250_stop_tx,  
 6     .start_tx   = serial8250_start_tx,  
 7     .stop_rx    = serial8250_stop_rx,  
 8     .enable_ms  = serial8250_enable_ms,  
 9     .break_ctl  = serial8250_break_ctl,  
10     .startup    = serial8250_startup,  
11     .shutdown   = serial8250_shutdown,  
12     .set_termios    = serial8250_set_termios,  
13     .pm     = serial8250_pm,  
14     .type       = serial8250_type,  
15     .release_port   = serial8250_release_port,  
16     .request_port   = serial8250_request_port,  
17     .config_port    = serial8250_config_port,  
18     .verify_port    = serial8250_verify_port,  
19 #ifdef CONFIG_CONSOLE_POLL  
20     .poll_get_char = serial8250_get_poll_char,  
21     .poll_put_char = serial8250_put_poll_char,  
22 #endif  
23 }; 
複製代碼

 

這樣一來只要將serial8250_ops結構體成員的值賦給咱們uart_dirver就能夠了,那麼這個過程在哪呢?就是在uart_add_one_port()函數中,這個函數是從serial8250_init->serial8250_register_ports()->uart_add_one_port()逐步調用過來的,這一步就將port和uart_driver聯繫起來了。(uart_ops<=>uart_driver:8250.c)

第二個須要關注的地方:

  1. tty_set_operations(normal, &uart_ops); 

此句之因此值得關注是由於.在這裏將tty_driver的操做集統一設爲了uart_ops.這樣就使得從用戶空間下來的操做能夠找到正確的serial_core的操做函數,uart_ops是在serial_core.c中的:

複製代碼
 1 static const struct tty_operations uart_ops = {  
 2     .open       = uart_open,  
 3     .close      = uart_close,  
 4     .write      = uart_write,  
 5     .put_char   = uart_put_char,  
 6     .flush_chars    = uart_flush_chars,  
 7     .write_room = uart_write_room,  
 8     .chars_in_buffer= uart_chars_in_buffer,  
 9     .flush_buffer   = uart_flush_buffer,  
10     .ioctl      = uart_ioctl,  
11     .throttle   = uart_throttle,  
12     .unthrottle = uart_unthrottle,  
13     .send_xchar = uart_send_xchar,  
14     .set_termios    = uart_set_termios,  
15     .set_ldisc  = uart_set_ldisc,  
16     .stop       = uart_stop,  
17     .start      = uart_start,  
18     .hangup     = uart_hangup,  
19     .break_ctl  = uart_break_ctl,  
20     .wait_until_sent= uart_wait_until_sent,  
21 #ifdef CONFIG_PROC_FS  
22     .read_proc  = uart_read_proc,  
23 #endif  
24     .tiocmget   = uart_tiocmget,  
25     .tiocmset   = uart_tiocmset,  
26 #ifdef CONFIG_CONSOLE_POLL  
27     .poll_init  = uart_poll_init,  
28     .poll_get_char  = uart_poll_get_char,  
29     .poll_put_char  = uart_poll_put_char,  
30 #endif  
31 }; 
複製代碼

 

這樣就保證了調用關係的通暢。(tty_operations<=>tty_driver:serial_core.c)

 

4.自頂向下

說完了從底層註冊時所須要注意的地方,如今咱們來看看正常的從上到下的調用關係。tty_core是全部tty類型的驅動的頂層構架,向用戶應用層提供了統一的接口,應用層的read/write等調用首先會到達這裏。此層由內核實現,代碼主要分佈在drivers/char目錄下的n_tty.c,tty_io.c等文件中,下面的代碼:

複製代碼
 1 static const struct file_operations tty_fops = {  
 2     .llseek        = no_llseek,  
 3     .read        = tty_read,  
 4     .write        = tty_write,  
 5     .poll        = tty_poll,  
 6     .unlocked_ioctl    = tty_ioctl,  
 7     .compat_ioctl    = tty_compat_ioctl,  
 8     .open        = tty_open,  
 9     .release    = tty_release,  
10     .fasync        = tty_fasync,  
11 }; 
複製代碼

 

就是定義了此層調用函數的結構體,在uart_register_driver()函數中咱們調用了每一個tty類型的驅動註冊時都會調用的tty_register_driver函數,代碼以下:

1 int tty_register_driver(struct tty_driver * driver)  
2 {  
3     ...  
4     cdev_init(&driver->cdev, &tty_fops);  
5     ...  
6 } 

 

咱們能夠看到,此句就已經將指針調用關係賦給了cdev,以用於完成調用。在前面咱們已經說過了,Read和write操做就會交給line discipline處理,咱們在下面的代碼能夠看出調用的就是線路規程的函數:

複製代碼
 1 static ssize_t tty_read(struct file *file, char __user *buf, size_t count,  
 2             loff_t *ppos)  
 3 {  
 4     ...  
 5     ld = tty_ldisc_ref_wait(tty);  
 6     if (ld->ops->read)  
 7         i = (ld->ops->read)(tty, file, buf, count);  
 8         //調用到了ldisc層(線路規程)的read函數  
 9     else 
10         i = -EIO;  
11     tty_ldisc_deref(ld);  
12     ...  
13 }  
14 static ssize_t tty_write(struct file *file, const char __user *buf,  
15                         size_t count, loff_t *ppos)  
16 {  
17     ...  
18     ld = tty_ldisc_ref_wait(tty);  
19     if (!ld->ops->write)  
20         ret = -EIO;  
21     else 
22         ret = do_tty_write(ld->ops->write, tty, file, buf, count);  
23     tty_ldisc_deref(ld);  
24     return ret;  
25 }  
26 static inline ssize_t do_tty_write(  
27     ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),  
28     struct tty_struct *tty,  
29     struct file *file,  
30     const char __user *buf,  
31     size_t count)  
32 {  
33     ...  
34     for (;;) {  
35         size_t size = count;  
36         if (size > chunk)  
37             size = chunk;  
38         ret = -EFAULT;  
39         if (copy_from_user(tty->write_buf, buf, size))  
40             break;  
41         ret = write(tty, file, tty->write_buf, size);  
42         //調用到了ldisc層的write函數  
43         if (ret <= 0)  
44             break;  
45     ...  
46 } 
複製代碼

 

那咱們就去看看線路規程調用的是又是誰,代碼目錄在drivers/char/n_tty.c文件中,下面的代碼是線路規程中的write函數:

複製代碼
 1 static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,  
 2                const unsigned char *buf, size_t nr)  
 3 {  
 4     ...  
 5     add_wait_queue(&tty->write_wait, &wait);//將當前進程放到等待隊列中  
 6     while (1) {  
 7         set_current_state(TASK_INTERRUPTIBLE);  
 8         if (signal_pending(current)) {  
 9             retval = -ERESTARTSYS;  
10             break;  
11         }  
12         //進入此處繼續執行的緣由多是被信號打斷,而不是條件獲得了知足。  
13         //只有條件獲得了知足,咱們纔會繼續,不然,直接返回!  
14         if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {  
15             retval = -EIO;  
16             break;  
17         }  
18         if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {  
19             while (nr > 0) {  
20                 ssize_t num = process_output_block(tty, b, nr);  
21                 if (num < 0) {  
22                     if (num == -EAGAIN)  
23                         break;  
24                     retval = num;  
25                     goto break_out;  
26                 }  
27                 b += num;  
28                 nr -= num;  
29                 if (nr == 0)  
30                     break;  
31                 c = *b;  
32                 if (process_output(c, tty) < 0)  
33                     break;  
34                 b++; nr--;  
35             }  
36             if (tty->ops->flush_chars)  
37                 tty->ops->flush_chars(tty);  
38         } else {  
39             while (nr > 0) {  
40                 c = tty->ops->write(tty, b, nr);  
41                 //調用到具體的驅動中的write函數  
42                 if (c < 0) {  
43                     retval = c;  
44                     goto break_out;  
45                 }  
46                 if (!c)  
47                     break;  
48                 b += c;  
49                 nr -= c;  
50             }  
51         }  
52         if (!nr)  
53             break;  
54         //所有寫入,返回  
55         if (file->f_flags & O_NONBLOCK) {  
56             retval = -EAGAIN;  
57             break;  
58         }  
59         /*   
60         假如是以非阻塞的方式打開的,那麼也直接返回。不然,讓出cpu,等條件知足之後再繼續執行。  
61         */          
62  
63         schedule();//執行到這裏,當前進程纔會真正讓出cpu!!!  
64     }  
65 break_out:  
66     __set_current_state(TASK_RUNNING);  
67     remove_wait_queue(&tty->write_wait, &wait);  
68     ...  
69 } 
複製代碼

 

在上面咱們能夠看到此句:

  1. c = tty->ops->write(tty, b, nr); 

此句很明顯告訴咱們這是調用了serial_core的write()函數,但是這些調用關係指針是在哪賦值的,剛開始我也是鬱悶了一段時間,不過好在我最後仍是找到了一些蛛絲馬跡。其實就是在tty_core進行open的時候悄悄把tty->ops指針給賦值了。具體的代碼就在driver/char/tty_io.c中,調用關係以下所示:

tty_open -> tty_init_dev -> initialize_tty_struct,initialize_tty_struct()函數的代碼在下面:

複製代碼
1 void initialize_tty_struct(struct tty_struct *tty,  
2         struct tty_driver *driver, int idx)  
3 {  
4     ...  
5     tty->ops = driver->ops;  
6     ...  
7 } 
複製代碼

 

能夠看到啦,這裏就將serial_core層的操做調用關係指針值付給了tty_core層,這樣tty->ops->write()其實調用到了具體的驅動的write函數,在這裏就是咱們前面說到的8250驅動中的write函數沒問題了。從這就能夠看出其實在操做指針值得層層傳遞上open操做仍是功不可沒的,這麼講不只僅是由於上面的賦值過程,還有下面這個,在open操做調用到serial_core層的時候有下面的代碼:

複製代碼
 1 static int uart_open(struct tty_struct *tty, struct file *filp)  
 2 {  
 3     struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state; // here just tell me why uart_open can call 8250  
 4     struct uart_state *state;  
 5     int retval, line = tty->index;  
 6  
 7     ……  
 8  
 9         uart_update_termios(state);  
10     }  
11  
12  fail:  
13     return retval;  
14 } 
複製代碼

 

在此函數的第一句咱們就看到了似曾相識的東西了,沒錯就是咱們在uart_register_driver()的時候所作的一些事情,那時咱們是放進去,如今是拿出來而已。

這樣一來,咱們先從底層向上層分析上來後,又由頂層向底層分析下去,兩頭總算是接上頭了,我很高興,不是由於我花了近兩個小時的時間終於寫完了這篇博客,而是我是第一次經過這篇博客的寫做過程弄清楚了這個有點小複雜的環節,固然有謬誤的地方仍是但願你們能慷慨指出。

5.關係圖

如下是線路規程層發送數據使用的接口,其中關鍵是兩個結構體的賦值操做:struct tty_operation uart_ops和struct uart_ops ***_ops:

兩個函數集的綁定:

複製代碼
1     • Uart_ops綁定
2     -->serial_imx_probe
3         -->sport->port.ops = &imx_pops;    //完成uart_ops對象和port口綁定
4         -->uart_add_one_port();   //這裏面會調用uart_ops函數集
5     • Tty_operations綁定
6     -->uart_register_driver
7         -->tty_set_operations(normal, &uart_ops);
8             -->driver->ops = ops;   //完成tty_driver和uart_ops的綁定
複製代碼

 如下是應用層發送數據的調用流程:

複製代碼
1     -->write    //application
2         -->struct file_operations tty_fops={.write    = tty_write,}
3             -->tty_write(struct file *file, const char __user *buf,size_t count, loff_t *ppos)
4                 -->do_tty_write(ld->ops->write, tty, file, buf, count)
5                     -->ret = write(tty, file, tty->write_buf, size);
6                         -->n_tty_write(struct tty_struct *tty, struct file *file,const unsigned char *buf, size_t nr)
7                             -->c = tty->ops->write(tty, b, nr);
複製代碼

 do_tty_write中ld->ops->write函數集經過tty_register_ldisc()函數來將函數集賦給ld的ops.

相關文章
相關標籤/搜索