1. 程式人生 > >linux tty驅動架構分析

linux tty驅動架構分析

轉自:http://blog.chinaunix.net/uid-24227137-id-3437527.html

前一陣子移植一個串列埠驅動,發現linux的驅動構架中,面向物件的思想已經根深蒂固。就比如這串列埠驅動,程式碼中經常有一些貌似和串列埠無關的程式碼,比如,tty_register_driver等。但我們卻刪它不得。因為正是這些程式碼實現了tty core和具體的tty driver(比如串列埠驅動)的聯絡和紐帶。以前看ldd3,裡邊有術語tty core和tty driver,當是不清楚各指的是什麼,但是後來看了程式碼,才知道,tty core指的是所有tty型別的驅動的頂層架構,它的程式碼由核心實現,我們無需修改,程式碼主要分佈在drivers/char下的n_tty.c,tty_io.c等檔案中。而tty driver就指具體的裝置驅動,比如串列埠驅動,console驅動等。以下總結只是對tty構架的總體分析,希望對大家有所啟發。

tty的架構其實分為三層:

第一層:
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,
};
每個tty型別的驅動註冊時都呼叫tty_register_driver函式
int tty_register_driver(struct     tty_driver * driver)
{
    ...
    cdev_init(&driver->cdev, &tty_fops);
    ...
}
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;
    ...
}

第二層:線路規程


不同的tty型別的裝置,具有不同的線路規程。這一層也由核心實現,主要程式碼在drivers/char.tty_ldisc.c檔案中
從tty_read/tty_write函式可以看出,他們最後呼叫到了線路規程的read/write函式
struct tty_ldisc_ops tty_ldisc_N_TTY = {
    .magic           = TTY_LDISC_MAGIC,
    .name            = "n_tty",
    .open            = n_tty_open,
    .close           = n_tty_close,
    .flush_buffer    = n_tty_flush_buffer,
    .chars_in_buffer = n_tty_chars_in_buffer,
    .read            = n_tty_read,
    .write           = n_tty_write,
    .ioctl           = n_tty_ioctl,
    .set_termios     = n_tty_set_termios,
    .poll            = n_tty_poll,
    .receive_buf     = n_tty_receive_buf,
    .write_wakeup    = n_tty_write_wakeup
};
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);
    ...
}
關於此段程式碼的具體分析在
http://blog.chinaunix.net/u2/73067/showart.php?id=2241493
這段程式碼中使用了wait等待佇列,為什麼要使用等待佇列呢?大家想想看,我們在應用層開啟一個裝置檔案的時候,有兩種方式,阻塞和非阻塞,非阻塞很簡單,不管結果怎樣直接返回。但阻塞則有點死皮賴臉的意思,會一直等待,直到操作完成。那write函式的“阻塞”版本在核心裡邊是怎麼實現的呢?就是使用等待佇列,只要條件沒有得到滿足(驅動層呼叫write函式失敗),那麼就一直讓出cpu,直到條件滿足了才會繼續執行,並將寫操作的結果返回給上層。
通過以上分析,我們也可以得到如下結論:阻塞是在ldisc層也就是線路規程裡邊實現的。出於代價和操作性的考慮,我們不會再驅動裡邊實現阻塞型別的write/read函式
上述程式碼中有一句:
c = tty->ops->write(tty, b, nr);
這句程式碼呼叫到了tty_struct結構的ops->write函式。但是tty_struct結構的ops->write和具體的驅動裡邊定義的write函式有什麼關係呢?
tty_open -> tty_init_dev -> initialize_tty_struct
driver/char/tty_io.c
void initialize_tty_struct(struct tty_struct *tty,
        struct tty_driver *driver, int idx)
{
    ...
    tty->ops = driver->ops;
    ...
}
可見,tty裝置開啟的時候,就將驅動的ops指標賦給了tty裝置的結構體tty_struct的ops
這樣,tty->ops->write()其實呼叫到了具體的驅動的write函式,比如,假如是個串列埠驅動,那麼就會呼叫到串列埠驅動的write函式!
n_tty_read的操作比較複雜,暫時不討論,但是它最終也會呼叫到具體的tty驅動的read函式

第三層:


具體的tty型別的驅動,由我們實現
比如,以下是摘自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
};
int uart_register_driver(struct uart_driver *drv)
{
    struct tty_driver *normal = NULL;
    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;
    tty_set_operations(normal, &uart_ops);//##
    ...
}
我們主要實現這一層的功能,前兩層是kernel中已經實現的,我們僅僅需要套用之。當我們按照tty driver的格式書寫這一層驅動,並實現幾個必要的函式,這個驅動就可以成功運轉了。