Linux中tty框架與uart框架之間的調(diào)用關(guān)系剖析
之前本人在"從串口驅(qū)動的移植看linux2.6內(nèi)核中的驅(qū)動模型 platform device & platform driver"一文中已經(jīng)寫到了移植的設(shè)備是如何通過platform總線來與對應(yīng)的驅(qū)動掛載。
在這期間有一個問題困擾著我,那就是來自用戶空間的針對uart設(shè)備的操作意圖是如何通過tty框架逐層調(diào)用到uart層的core驅(qū)動,進而又是如何調(diào)用到真實對應(yīng)于設(shè)備的設(shè)備驅(qū)動的,本文中的對應(yīng)設(shè)備驅(qū)動就是8250驅(qū)動,最近我想將這方面的內(nèi)容搞清楚。
在說明這一方面問題之前我們先要大致了解兩個基本的框架結(jié)構(gòu),tty框架和uart框架。
首先看看tty框架:
在linux系統(tǒng)中,tty表示各種終端。終端通常都跟硬件相對應(yīng)。比如對應(yīng)于輸入設(shè)備鍵盤鼠標,輸出設(shè)備顯示器的控制終端和串口終端。
下面這張圖是一張很經(jīng)典的圖了,很清楚的展現(xiàn)了tty框架的層次結(jié)構(gòu),大家先看圖,下面給大家解釋。
最上面的用戶空間會有很多對底層硬件(在本文中就是8250uart設(shè)備)的操作,像read,write等。用戶空間主要是通過設(shè)備文件同tty_core交互,tty_core根據(jù)用空間操作的類型再選擇跟line discipline和tty_driver也就是serial_core交互,例如設(shè)置硬件的ioctl指令就直接交給serial_core處理。Read和write操作就會交給line discipline處理。Line discipline是線路規(guī)程的意思。正如它的名字一樣,它表示的是這條終端”線程”的輸入與輸出規(guī)范設(shè)置,主要用來進行輸入/輸出數(shù)據(jù)的預處理。處理之后,就會將數(shù)據(jù)交給serial_core,最后serial_core會調(diào)用8250.c的操作。
下圖是同一樣一副經(jīng)典的uart框架圖,將uart重要的結(jié)構(gòu)封裝的很清楚,大家且看。
一個uart_driver通常會注冊一段設(shè)備號.即在用戶空間會看到uart_driver對應(yīng)有多個設(shè)備節(jié)點。例如:
/dev/ttyS0 /dev/ttyS1 每個設(shè)備節(jié)點是對應(yīng)一個具體硬件的,這樣就可做到對多個硬件設(shè)備的統(tǒng)一管理,而每個設(shè)備文件應(yīng)該對應(yīng)一個uart_port,也就是說:uart_device要和多個uart_port關(guān)系起來。并且每個uart_port對應(yīng)一個circ_buf(用來接收數(shù)據(jù)),所以 uart_port必須要和這個緩存區(qū)關(guān)系起來。
#p#
1 自底向上
接下來我們就來看看對設(shè)備的操作是怎樣進行起來的,不過在此之前我們有必要從底層的uart驅(qū)動注冊時開始說起,這樣到后面才能更清晰。
這里我們討論的是8250驅(qū)動,在驅(qū)動起來的時候調(diào)用了uart_register_driver(&serial8250_reg);函數(shù)將參數(shù)serial8250_reg注冊進了tty層。具體代碼如下所示:
- int uart_register_driver(struct uart_driver *drv)
- {
- struct tty_driver *normal = NULL;
- int i, retval;
- BUG_ON(drv->state);
- /*
- * Maybe we should be using a slab cache for this, especially if
- * we have a large number of ports to handle.
- */
- drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
- retval = -ENOMEM;
- if (!drv->state)
- goto out;
- normal = alloc_tty_driver(drv->nr);
- if (!normal)
- goto out;
- drv->tty_driver = normal;
- normal->owner = drv->owner;
- normal->driver_name = drv->driver_name;
- normal->name = drv->dev_name;
- normal->major = drv->major;
- normal->minor_start = drv->minor;
- normal->type = TTY_DRIVER_TYPE_SERIAL;
- normal->subtype = SERIAL_TYPE_NORMAL;
- normal->init_termios = tty_std_termios;
- normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
- normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
- normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
- normal->driver_state = drv; // here is important for me, ref uart_open function in this file
- tty_set_operations(normal, &uart_ops);
- /*
- * Initialise the UART state(s).
- */
- for (i = 0; i < drv->nr; i++) {
- struct uart_state *state = drv->state + i;
- state->close_delay = 500; /* .5 seconds */
- state->closing_wait = 30000; /* 30 seconds */
- mutex_init(&state->mutex);
- tty_port_init(&state->info.port);
- init_waitqueue_head(&state->info.delta_msr_wait);
- tasklet_init(&state->info.tlet, uart_tasklet_action,
- (unsigned long)state);
- }
- retval = tty_register_driver(normal);
- out:
- if (retval < 0) {
- put_tty_driver(normal);
- kfree(drv->state);
- }
- return retval;
- }
從上面代碼可以看出,uart_driver中很多數(shù)據(jù)結(jié)構(gòu)其實就是tty_driver中的,將數(shù)據(jù)轉(zhuǎn)換為tty_driver之后,注冊tty_driver。然后初始化uart_driver->state的存儲空間。
這里有兩個地方我們需要特別關(guān)注:
第一個是
- normal->driver_state = drv;
為什么說重要呢,因為真實這一句將參數(shù)的ops關(guān)系都賦給了serial_core層。也就是說在后面serial_core會根據(jù)uart_ops關(guān)系找到我們的8250.c中所對應(yīng)的操作,而我們參數(shù)中的ops是在哪被賦值的呢?這個一定是會在8250.c中不會錯,所以我定位到了8250.c中的serial8250_ops結(jié)構(gòu)體,初始化如下:
- static struct uart_ops serial8250_pops = {
- .tx_empty = serial8250_tx_empty,
- .set_mctrl = serial8250_set_mctrl,
- .get_mctrl = serial8250_get_mctrl,
- .stop_tx = serial8250_stop_tx,
- .start_tx = serial8250_start_tx,
- .stop_rx = serial8250_stop_rx,
- .enable_ms = serial8250_enable_ms,
- .break_ctl = serial8250_break_ctl,
- .startup = serial8250_startup,
- .shutdown = serial8250_shutdown,
- .set_termios = serial8250_set_termios,
- .pm = serial8250_pm,
- .type = serial8250_type,
- .release_port = serial8250_release_port,
- .request_port = serial8250_request_port,
- .config_port = serial8250_config_port,
- .verify_port = serial8250_verify_port,
- #ifdef CONFIG_CONSOLE_POLL
- .poll_get_char = serial8250_get_poll_char,
- .poll_put_char = serial8250_put_poll_char,
- #endif
- };
這樣一來只要將serial8250_ops結(jié)構(gòu)體成員的值賦給我們uart_dirver就可以了,那么這個過程在哪呢?就是在uart_add_one_port()函數(shù)中,這個函數(shù)是從serial8250_init->serial8250_register_ports()->uart_add_one_port()逐步調(diào)用過來的,這一步就將port和uart_driver聯(lián)系起來了。
第二個需要關(guān)注的地方:
- tty_set_operations(normal, &uart_ops);
此句之所以值得關(guān)注是因為.在這里將tty_driver的操作集統(tǒng)一設(shè)為了uart_ops.這樣就使得從用戶空間下來的操作可以找到正確的serial_core的操作函數(shù),uart_ops是在serial_core.c中的:
- static const struct tty_operations uart_ops = {
- .open = uart_open,
- .close = uart_close,
- .write = uart_write,
- .put_char = uart_put_char,
- .flush_chars = uart_flush_chars,
- .write_room = uart_write_room,
- .chars_in_buffer= uart_chars_in_buffer,
- .flush_buffer = uart_flush_buffer,
- .ioctl = uart_ioctl,
- .throttle = uart_throttle,
- .unthrottle = uart_unthrottle,
- .send_xchar = uart_send_xchar,
- .set_termios = uart_set_termios,
- .set_ldisc = uart_set_ldisc,
- .stop = uart_stop,
- .start = uart_start,
- .hangup = uart_hangup,
- .break_ctl = uart_break_ctl,
- .wait_until_sent= uart_wait_until_sent,
- #ifdef CONFIG_PROC_FS
- .read_proc = uart_read_proc,
- #endif
- .tiocmget = uart_tiocmget,
- .tiocmset = uart_tiocmset,
- #ifdef CONFIG_CONSOLE_POLL
- .poll_init = uart_poll_init,
- .poll_get_char = uart_poll_get_char,
- .poll_put_char = uart_poll_put_char,
- #endif
- };
這樣就保證了調(diào)用關(guān)系的通暢。
#p#
2 自頂向下
說完了從底層注冊時所需要注意的地方,現(xiàn)在我們來看看正常的從上到下的調(diào)用關(guān)系。tty_core是所有tty類型的驅(qū)動的頂層構(gòu)架,向用戶應(yīng)用層提供了統(tǒng)一的接口,應(yīng)用層的read/write等調(diào)用首先會到達這里。此層由內(nèi)核實現(xiàn),代碼主要分布在drivers/char目錄下的n_tty.c,tty_io.c等文件中,下面的代碼:
- static const struct file_operations tty_fops = {
- .llseek = no_llseek,
- .read = tty_read,
- .write = tty_write,
- .poll = tty_poll,
- .unlocked_ioctl = tty_ioctl,
- .compat_ioctl = tty_compat_ioctl,
- .open = tty_open,
- .release = tty_release,
- .fasync = tty_fasync,
- };
就是定義了此層調(diào)用函數(shù)的結(jié)構(gòu)體,在uart_register_driver()函數(shù)中我們調(diào)用了每個tty類型的驅(qū)動注冊時都會調(diào)用的tty_register_driver函數(shù),代碼如下:
- int tty_register_driver(struct tty_driver * driver)
- {
- ...
- cdev_init(&driver->cdev, &tty_fops);
- ...
- }
我們可以看到,此句就已經(jīng)將指針調(diào)用關(guān)系賦給了cdev,以用于完成調(diào)用。在前面我們已經(jīng)說過了,Read和write操作就會交給line discipline處理,我們在下面的代碼可以看出調(diào)用的就是線路規(guī)程的函數(shù):
- static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
- loff_t *ppos)
- {
- ...
- ld = tty_ldisc_ref_wait(tty);
- if (ld->ops->read)
- i = (ld->ops->read)(tty, file, buf, count);
- //調(diào)用到了ldisc層(線路規(guī)程)的read函數(shù)
- else
- i = -EIO;
- tty_ldisc_deref(ld);
- ...
- }
- static ssize_t tty_write(struct file *file, const char __user *buf,
- size_t count, loff_t *ppos)
- {
- ...
- ld = tty_ldisc_ref_wait(tty);
- if (!ld->ops->write)
- ret = -EIO;
- else
- ret = do_tty_write(ld->ops->write, tty, file, buf, count);
- tty_ldisc_deref(ld);
- return ret;
- }
- static inline ssize_t do_tty_write(
- ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
- struct tty_struct *tty,
- struct file *file,
- const char __user *buf,
- size_t count)
- {
- ...
- for (;;) {
- size_t size = count;
- if (size > chunk)
- size = chunk;
- ret = -EFAULT;
- if (copy_from_user(tty->write_buf, buf, size))
- break;
- ret = write(tty, file, tty->write_buf, size);
- //調(diào)用到了ldisc層的write函數(shù)
- if (ret <= 0)
- break;
- ...
- }
那我們就去看看線路規(guī)程調(diào)用的是又是誰,代碼目錄在drivers/char/n_tty.c文件中,下面的代碼是線路規(guī)程中的write函數(shù):
- static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
- const unsigned char *buf, size_t nr)
- {
- ...
- add_wait_queue(&tty->write_wait, &wait);//將當前進程放到等待隊列中
- while (1) {
- set_current_state(TASK_INTERRUPTIBLE);
- if (signal_pending(current)) {
- retval = -ERESTARTSYS;
- break;
- }
- //進入此處繼續(xù)執(zhí)行的原因可能是被信號打斷,而不是條件得到了滿足。
- //只有條件得到了滿足,我們才會繼續(xù),否則,直接返回!
- if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
- retval = -EIO;
- break;
- }
- if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
- while (nr > 0) {
- ssize_t num = process_output_block(tty, b, nr);
- if (num < 0) {
- if (num == -EAGAIN)
- break;
- retval = num;
- goto break_out;
- }
- b += num;
- nr -= num;
- if (nr == 0)
- break;
- c = *b;
- if (process_output(c, tty) < 0)
- break;
- b++; nr--;
- }
- if (tty->ops->flush_chars)
- tty->ops->flush_chars(tty);
- } else {
- while (nr > 0) {
- c = tty->ops->write(tty, b, nr);
- //調(diào)用到具體的驅(qū)動中的write函數(shù)
- if (c < 0) {
- retval = c;
- goto break_out;
- }
- if (!c)
- break;
- b += c;
- nr -= c;
- }
- }
- if (!nr)
- break;
- //全部寫入,返回
- if (file->f_flags & O_NONBLOCK) {
- retval = -EAGAIN;
- break;
- }
- /*
- 假如是以非阻塞的方式打開的,那么也直接返回。否則,讓出cpu,等條件滿足以后再繼續(xù)執(zhí)行。
- */
- schedule();//執(zhí)行到這里,當前進程才會真正讓出cpu!??!
- }
- break_out:
- __set_current_state(TASK_RUNNING);
- remove_wait_queue(&tty->write_wait, &wait);
- ...
- }
在上面我們可以看到此句:
- c = tty->ops->write(tty, b, nr);
此句很明顯告訴我們這是調(diào)用了serial_core的write()函數(shù),可是這些調(diào)用關(guān)系指針是在哪賦值的,剛開始我也是郁悶了一段時間,不過好在我最后還是找到了一些蛛絲馬跡。其實就是在tty_core進行open的時候悄悄把tty->ops指針給賦值了。具體的代碼就在driver/char/tty_io.c中,調(diào)用關(guān)系如下所示:
tty_open -> tty_init_dev -> initialize_tty_struct,initialize_tty_struct()函數(shù)的代碼在下面:
- void initialize_tty_struct(struct tty_struct *tty,
- struct tty_driver *driver, int idx)
- {
- ...
- tty->ops = driver->ops;
- ...
- }
可以看到啦,這里就將serial_core層的操作調(diào)用關(guān)系指針值付給了tty_core層,這樣tty->ops->write()其實調(diào)用到了具體的驅(qū)動的write函數(shù),在這里就是我們前面說到的8250驅(qū)動中的write函數(shù)沒問題了。從這就可以看出其實在操作指針值得層層傳遞上open操作還是功不可沒的,這么講不僅僅是因為上面的賦值過程,還有下面這個,在open操作調(diào)用到serial_core層的時候有下面的代碼:
- static int uart_open(struct tty_struct *tty, struct file *filp)
- {
- struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state; // here just tell me why uart_open can call 8250
- struct uart_state *state;
- int retval, line = tty->index;
- ……
- uart_update_termios(state);
- }
- fail:
- return retval;
- }
在此函數(shù)的第一句我們就看到了似曾相識的東西了,沒錯就是我們在uart_register_driver()的時候所做的一些事情,那時我們是放進去,現(xiàn)在是拿出來而已。
這樣一來,我們先從底層向上層分析上來后,又由頂層向底層分析下去,兩頭總算是接上頭了,我很高興,不是因為我花了近兩個小時的時間終于寫完了這篇博客,而是我是第一次通過這篇博客的寫作過程弄清楚了這個有點小復雜的環(huán)節(jié),當然有謬誤的地方還是希望大家能慷慨指出。
分享知識,共同進步~