Linux裝置模型之tty驅動架構分析

來源:互聯網
上載者:User

------------------------------------------本文系本站原創,歡迎轉載!轉載請註明出處:http://ericxiao.cublog.cn/http://blog.chinaunix.net/uid-20543183-id-1930820.html
------------------------------------------一:前言Tty這個名稱源於電傳打位元組的簡稱。在linux表示各種終端。終端通常都跟硬體相對應。比如對應於輸入裝置鍵盤滑鼠。輸出裝置顯示器的控制終端和串口終端.也有對應於不存在裝置的pty驅動。在如此眾多的終端模型之中,linux是怎麼將它們統一建模的呢?這就是我們今天要討論的問題.二:tty驅動概貌Tty架構如下所示:如所示,使用者空間主要是通過裝置檔案同tty_core互動.tty_core根據用空間操作的類型再選擇跟line discipline和tty_driver互動.例如設定硬體的ioctl指令就直接交給tty_driver處理。Read和write操作就會交給line discipline處理.Line discipline是線路規程的意思。正如它的名字一樣,它表示的是這條終端”線程”的輸入與輸出規範設定.主要用來進行輸入/輸出資料的預先處理。處理之後。就會將資料交給tty_driverTty_driver就是終端對應的驅動了。它將字元轉換成終端可以理解的字串.將其傳給終端裝置。值得注意的是,這個架構沒有為tty_drivero提供read操作。也就是說tty_core 和line discipline都沒有辦法從tty_driver裡直接讀終端資訊。這是因為tty_driver對就的hardware並不一定是輸入資料和輸出資料的共同負載者。例如控制終端,輸出裝置是顯示器。輸入裝置是鍵盤。基於這樣的原理。在line
discipline中有一個輸入緩衝區。並提供了一個名叫receive_buf()的介面函數。對應的終端裝置只要調用line discipine的receiver_buf函數,將資料寫入到輸入緩衝區就可以了。如果一個裝置同時是輸入裝置又是輸出裝置。那在裝置的中斷處理中調用receive_buf()將資料寫入即可. 三:tty驅動介面分析具體的tty驅動設計可以參考LDD3。這裡只對它的介面實現做一個分析.tty driver的所有操作都包含在tty_driver中。核心即供了一個名叫alloc_tty_driver()來分配這個tty_driver。當然我們也可以在自己的驅動中將它定義成一個靜態結構。對tty_driver進行一些必要的初始化之後,調用tty_register_driver()將其註冊.alloc_tty_driver()介面代碼如下所示:struct tty_driver *alloc_tty_driver(int lines){         struct tty_driver *driver;          driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);         if (driver) {                   driver->magic = TTY_DRIVER_MAGIC;                   driver->num = lines;                   /* later we'll move allocation of tables here */         }         return driver;}這個函數只有一個參數。這個參數的含義為line的個數。也即次裝置號的個數。注意每個裝置檔案都會對應一個line.在這個介面裡為tty_driver分配記憶體,然後將driver->mage.driver->num初始化之後就返回了. tty_register_driver()用來註冊一個tty_driver。代碼如下:int tty_register_driver(struct tty_driver *driver){         int error;         int i;         dev_t dev;         void **p = NULL;          //TTY_DRIVER_INSTALLED:已安裝的         if (driver->flags & TTY_DRIVER_INSTALLED)                   return 0;          //TTY_DRIVER_DEVPTS_MEM:使用devpts進行動態記憶體映射         if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {                   p = kzalloc(driver->num * 3 * sizeof(void *), GFP_KERNEL);                   if (!p)                            return -ENOMEM;         }          //註冊字元裝置號         //如果沒有指定driver->major         if (!driver->major) {                   error = alloc_chrdev_region(&dev, driver->minor_start,                                                        driver->num, driver->name);                   if (!error) {                            driver->major = MAJOR(dev);                            driver->minor_start = MINOR(dev);                   }         } else {                   dev = MKDEV(driver->major, driver->minor_start);                   error = register_chrdev_region(dev, driver->num, driver->name);         }         if (error < 0) {                   kfree(p);                  return error;         }          if (p) {                   driver->ttys = (struct tty_struct **)p;                   driver->termios = (struct ktermios **)(p + driver->num);                   driver->termios_locked = (struct ktermios **)                                                                 (p + driver->num * 2);         } else {                   driver->ttys = NULL;                   driver->termios = NULL;                   driver->termios_locked = NULL;         }          //註冊字元裝置         cdev_init(&driver->cdev, &tty_fops);         driver->cdev.owner = driver->owner;         error = cdev_add(&driver->cdev, dev, driver->num);         if (error) {                   unregister_chrdev_region(dev, driver->num);                   driver->ttys = NULL;                   driver->termios = driver->termios_locked = NULL;                   kfree(p);                   return error;         }          //指定預設的put_char         if (!driver->put_char)                   driver->put_char = tty_default_put_char;          mutex_lock(&tty_mutex);         list_add(&driver->tty_drivers, &tty_drivers);         mutex_unlock(&tty_mutex);          //如果沒有指定TTY_DRIVER_DYNAMIC_DEV.即動態裝置管理         if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {                   for (i = 0; i < driver->num; i++)                       tty_register_device(driver, i, NULL);         }         proc_tty_register_driver(driver);         return 0;}這個函數操作比較簡單。就是為tty_driver建立字元裝置。然後將字元裝置的操作集指定為tty_fops.並且將tty_driver掛載到tty_drivers鏈表中.其實這個鏈表的作用跟我們之前分析的input子系統中的input_dev[ ]數組類似。都是以裝置號為關鍵字找到對應的driver.特別的。如果沒有定義TTY_DRIVER_DYNAMIC_DEV.還會在sysfs中建立一個類裝置.這樣主要是為了udev管理裝置.以流程圖的方式將上述動作表示如下:  四:裝置檔案的操作裝置檔案的操作是本節分析的重點。它的主要操作是將各項操作對應到ldsic或者是tty_driver. 4.1:開啟tty裝置的操作從註冊的過程可以看到,所有的操作都會對應到tty_fops中。Open操作對應的操作介面是tty_open()。代碼如下:static int tty_open(struct inode *inode, struct file *filp){         struct tty_struct *tty;         int noctty, retval;         struct tty_driver *driver;         int index;         dev_t device = inode->i_rdev;         unsigned short saved_flags = filp->f_flags;          nonseekable_open(inode, filp); retry_open:         //O_NOCTTY 如果路徑名指向終端裝置,不要把這個裝置用作控制終端          //noctty:需不需要更改當前進程的控制終端         noctty = filp->f_flags & O_NOCTTY;         index  = -1;         retval = 0;          mutex_lock(&tty_mutex);          //裝置號(5,0) 即/dev/tty.表示當前進程的控制終端         if (device == MKDEV(TTYAUX_MAJOR, 0)) {                   tty = get_current_tty();                   //如果當前進程的控制終端不存在,退出                   if (!tty) {                            mutex_unlock(&tty_mutex);                            return -ENXIO;                   }                    //取得當前進程的tty_driver                   driver = tty->driver;                   index = tty->index;                   filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */                   /* noctty = 1; */                   goto got_driver;         }#ifdef CONFIG_VT         //裝置號(4,0).即/dev/tty0:表示當前的控制台         if (device == MKDEV(TTY_MAJOR, 0)) {                   extern struct tty_driver *console_driver;                   driver = console_driver;                   //fg_console: 表示當前的控制台                   index = fg_console;                   noctty = 1;                   goto got_driver;         }#endif         //裝置號(5,1).即/dev/console.表示外接的控制台. 通過regesit_console()         if (device == MKDEV(TTYAUX_MAJOR, 1)) {                   driver = console_device(&index);                   if (driver) {                            /* Don't let /dev/console block */                            filp->f_flags |= O_NONBLOCK;                            noctty = 1;                            goto got_driver;                   }                   mutex_unlock(&tty_mutex);                   return -ENODEV;         }          //以檔案的裝置號為關鍵字,到tty_drivers中搜尋所註冊的driver         driver = get_tty_driver(device, &index);         if (!driver) {                   mutex_unlock(&tty_mutex);                   return -ENODEV;         }got_driver:         //index表示它的次裝置號         retval = init_dev(driver, index, &tty);         mutex_unlock(&tty_mutex);         if (retval)                   return retval;          filp->private_data = tty;         file_move(filp, &tty->tty_files);         check_tty_count(tty, "tty_open");         if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&             tty->driver->subtype == PTY_TYPE_MASTER)                   noctty = 1;#ifdef TTY_DEBUG_HANGUP         printk(KERN_DEBUG "opening %s...", tty->name);#endif         if (!retval) {                   if (tty->driver->open)                            retval = tty->driver->open(tty, filp);                   else                            retval = -ENODEV;         }         filp->f_flags = saved_flags;          if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&                                                        !capable(CAP_SYS_ADMIN))                   retval = -EBUSY;          if (retval) {#ifdef TTY_DEBUG_HANGUP                   printk(KERN_DEBUG "error %d in opening %s...", retval,                          tty->name);#endif                   release_dev(filp);                   if (retval != -ERESTARTSYS)                            return retval;                   if (signal_pending(current))                            return retval;                   schedule();                   /*                    * Need to reset f_op in case a hangup happened.                    */                   if (filp->f_op == &hung_up_tty_fops)                            filp->f_op = &tty_fops;                   goto retry_open;         }          mutex_lock(&tty_mutex);         spin_lock_irq(&current->sighand->siglock);          //設定當前進程的終端         if (!noctty &&             current->signal->leader &&             !current->signal->tty &&             tty->session == NULL)                   __proc_set_tty(current, tty);         spin_unlock_irq(&current->sighand->siglock);         mutex_unlock(&tty_mutex);         tty_audit_opening();         return 0;}注意在這裡有個容易忽略的操作:init_dev()。Init_dev() -à initialize_tty_struct() à tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));看一下tty_ldisc_assign(tty, tty_ldisc_get(N_TTY))的操作:Tty_ldisc_get():struct tty_ldisc *tty_ldisc_get(int disc){         unsigned long flags;         struct tty_ldisc *ld;          if (disc < N_TTY || disc >= NR_LDISCS)                   return NULL;          spin_lock_irqsave(&tty_ldisc_lock, flags);          ld = &tty_ldiscs[disc];         /* Check the entry is defined */         if (ld->flags & LDISC_FLAG_DEFINED) {                   /* If the module is being unloaded we can't use it */                   if (!try_module_get(ld->owner))                            ld = NULL;                   else /* lock it */                            ld->refcount++;         } else                   ld = NULL;         spin_unlock_irqrestore(&tty_ldisc_lock, flags);         return ld;}這個函數的操作為到tty_ldiscs[ ]找到對應項.這個數組中的成員是調用tty_register_ldisc()將其設定進去的. tty_ldisc_assign操作如下:static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld){         tty->ldisc = *ld;         tty->ldisc.refcount = 0;}即將取出來的idisc作為tty->ldisc欄位.在這段代碼中涉及到了tty_driver,tty_struct, struct tty_ldisc.這三者之間的關係用表示如下: 在這裡,為tty_struct的ldisc是預設指定為tty_ldiscs[N_TTY].該ldisc對應的是控制終端的線路規範。可以在用空間用帶TIOCSETD的ioctl調用變更.將上述open用流程圖的方式表示如下: 4.2:裝置檔案的write操作裝置檔案的write操作對應tty_fops->write即tty_write().代碼如下:static ssize_t tty_write(struct file *file, const char __user *buf,                                                        size_t count, loff_t *ppos){         struct tty_struct *tty;         struct inode *inode = file->f_path.dentry->d_inode;         ssize_t ret;         struct tty_ldisc *ld;          tty = (struct tty_struct *)file->private_data;         if (tty_paranoia_check(tty, inode, "tty_write"))                   return -EIO;         if (!tty || !tty->driver->write ||                   (test_bit(TTY_IO_ERROR, &tty->flags)))                            return -EIO;          ld = tty_ldisc_ref_wait(tty);         if (!ld->write)                   ret = -EIO;         else                   ret = do_tty_write(ld->write, tty, file, buf, count);         tty_ldisc_deref(ld);         return ret;}在open的過程中,將tty_struc

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.