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

博主新開了個人站點,你也可以在這看到這篇文章,點擊打開鏈接

之前本人在"從串口驅動的移植看linux2.6內核中的驅動模型 platform device & platform driver"一文中已經寫到了移植的設備是如何通過platform總線來與對應的驅動掛載。

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

在說明這一方面問題之前我們先要大致瞭解兩個基本的框架結構,tty框架和uart框架。

首先看看tty框架:

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

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


最上面的用戶空間會有很多對底層硬件(在本文中就是8250uart設備)的操作,像read,write等。用戶空間主要是通過設備文件同tty_core交互,tty_core根據用空間操作的類型再選擇跟line discipline和tty_driver也就是serial_core交互,例如設置硬件的ioctl指令就直接交給serial_core處理。Read和write操作就會交給line discipline處理。Line discipline是線路規程的意思。正如它的名字一樣,它表示的是這條終端線程的輸入與輸出規範設置,主要用來進行輸入/輸出數據的預處理。處理之後,就會將數據交給serial_core,最後serial_core會調用8250.c的操作

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


一個uart_driver通常會註冊一段設備號.即在用戶空間會看到uart_driver對應有多個設備節點。例如:
/dev/ttyS0  /dev/ttyS1 每個設備節點是對應一個具體硬件的,這樣就可做到對多個硬件設備的統一管理,而每個設備文件應該對應一個uart_port,也就是說:uart_device要和多個uart_port關係起來。並且每個uart_port對應一個circ_buf(用來接收數據),所以uart_port必須要和這個緩存區關係起來。

1 自底向上
接下來我們就來看看對設備的操作是怎樣進行起來的,不過在此之前我們有必要從底層的uart驅動註冊時開始說起,這樣到後面才能更清晰。
這裏我們討論的是8250驅動,在驅動起來的時候調用了uart_register_driver(&serial8250_reg);函數將參數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中很多數據結構其實就是tty_driver中的,將數據轉換爲tty_driver之後,註冊tty_driver。然後初始化uart_driver->state的存儲空間。
這裏有兩個地方我們需要特別關注:

第一個是
normal->driver_state    = drv; 
爲什麼說重要呢,因爲真實這一句將參數的ops關係都賦給了serial_core層。也就是說在後面serial_core會根據uart_ops關係找到我們的8250.c中所對應的操作,而我們參數中的ops是在哪被賦值的呢?這個一定是會在8250.c中不會錯,所以我定位到了8250.c中的serial8250_ops結構體,初始化如下:
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結構體成員的值賦給我們uart_dirver就可以了,那麼這個過程在哪呢?就是在uart_add_one_port()函數中,這個函數是從serial8250_init->serial8250_register_ports()->uart_add_one_port()逐步調用過來的,這一步就將port和uart_driver聯繫起來了。

第二個需要關注的地方:
tty_set_operations(normal, &uart_ops);
此句之所以值得關注是因爲.在這裏將tty_driver的操作集統一設爲了uart_ops.這樣就使得從用戶空間下來的操作可以找到正確的serial_core的操作函數,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
};
這樣就保證了調用關係的通暢。

2 自頂向下
說完了從底層註冊時所需要注意的地方,現在我們來看看正常的從上到下的調用關係。tty_core是所有tty類型的驅動的頂層構架,向用戶應用層提供了統一的接口,應用層的read/write等調用首先會到達這裏。此層由內核實現,代碼主要分佈在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,
};
就是定義了此層調用函數的結構體,在uart_register_driver()函數中我們調用了每個tty類型的驅動註冊時都會調用的tty_register_driver函數,代碼如下:
int tty_register_driver(struct tty_driver * driver)
{
    ...
    cdev_init(&driver->cdev, &tty_fops);
    ...
}
我們可以看到,此句就已經將指針調用關係賦給了cdev,以用於完成調用。在前面我們已經說過了,Read和write操作就會交給line discipline處理,我們在下面的代碼可以看出調用的就是線路規程的函數:
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);
        //調用到了ldisc層(線路規程)的read函數
    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);
        //調用到了ldisc層的write函數
        if (ret <= 0)
            break;
    ...
}
那我們就去看看線路規程調用的是又是誰,代碼目錄在drivers/char/n_tty.c文件中,下面的代碼是線路規程中的write函數:
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;
        }
        //進入此處繼續執行的原因可能是被信號打斷,而不是條件得到了滿足。
        //只有條件得到了滿足,我們纔會繼續,否則,直接返回!
        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);
                //調用到具體的驅動中的write函數
                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,等條件滿足以後再繼續執行。
        */        

        schedule();//執行到這裏,當前進程纔會真正讓出cpu!!!
    }
break_out:
    __set_current_state(TASK_RUNNING);
    remove_wait_queue(&tty->write_wait, &wait);
    ...
}
在上面我們可以看到此句:           
 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()函數的代碼在下面:
void initialize_tty_struct(struct tty_struct *tty,
        struct tty_driver *driver, int idx)
{
    ...
    tty->ops = driver->ops;
    ...
}
可以看到啦,這裏就將serial_core層的操作調用關係指針值付給了tty_core層,這樣tty->ops->write()其實調用到了具體的驅動的write函數,在這裏就是我們前面說到的8250驅動中的write函數沒問題了。從這就可以看出其實在操作指針值得層層傳遞上open操作還是功不可沒的,這麼講不僅僅是因爲上面的賦值過程,還有下面這個,在open操作調用到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;
}
在此函數的第一句我們就看到了似曾相識的東西了,沒錯就是我們在uart_register_driver()的時候所做的一些事情,那時我們是放進去,現在是拿出來而已。

這樣一來,我們先從底層向上層分析上來後,又由頂層向底層分析下去,兩頭總算是接上頭了,我很高興,不是因爲我花了近兩個小時的時間終於寫完了這篇博客,而是我是第一次通過這篇博客的寫作過程弄清楚了這個有點小複雜的環節,當然有謬誤的地方還是希望大家能慷慨指出。

分享知識,共同進步~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章