18.1. 一個(gè)小 TTY 驅(qū)動

2018-02-24 15:50 更新

18.1.?一個(gè)小 TTY 驅(qū)動

為解釋 tty 核心如何工作, 我們創(chuàng)建一個(gè)小 tty 驅(qū)動, 可以被加載, 以及寫入讀出, 并且卸載. 任何一個(gè) tty 驅(qū)動的主要數(shù)據(jù)結(jié)構(gòu)是 struct tty_driver. 它用來注冊和注銷一個(gè) tty 驅(qū)動到 tty 內(nèi)核, 在內(nèi)核頭文件 <linux/tty_driver.h> 中描述.

為創(chuàng)建一個(gè) struct tty_driver, 函數(shù) alloc_tty_driver 必須用這個(gè)驅(qū)動作為參數(shù)而支持的 tty 設(shè)備號來調(diào)用. 這可使用下面的簡短代碼來完成:


/* allocate the tty driver */
tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);
if (!tiny_tty_driver)
 return -ENOMEM; 

在 alloc_tty_driver 函數(shù)被成功調(diào)用后, struct tty_driver 應(yīng)當(dāng)用基于 tty 驅(qū)動的需要的正確信息被初始化. 這個(gè)結(jié)構(gòu)包含很多不同成員, 但不是為了有一個(gè)可工作的 tty 驅(qū)動而全部都必須被初始化. 這里有一個(gè)例子展示如何初始化這個(gè)結(jié)構(gòu)并且建立足夠的成員來創(chuàng)建一個(gè)工作的 tty 驅(qū)動. 它使用 tty_set_operations 函數(shù)來幫助拷貝驅(qū)動中定義的函數(shù)操作集合:


static struct tty_operations serial_ops = {
 .open = tiny_open,
 .close = tiny_close,
 .write = tiny_write,
 .write_room = tiny_write_room,
 .set_termios = tiny_set_termios,
}; 
...
 /* initialize the tty driver */
 tiny_tty_driver->owner = THIS_MODULE;
 tiny_tty_driver->driver_name = "tiny_tty";
 tiny_tty_driver->name = "ttty";
 tiny_tty_driver->devfs_name = "tts/ttty%d";
 tiny_tty_driver->major = TINY_TTY_MAJOR,
 tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
 tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL,
 tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,
 tiny_tty_driver->init_termios = tty_std_termios;
 tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
 tty_set_operations(tiny_tty_driver, &serial_ops);

上面列出的變量和函數(shù), 以及這個(gè)結(jié)構(gòu)如何使用, 在本章的剩下部分講解.

為注冊這個(gè)驅(qū)動到 tty 核心, struct tty_driver 必須傳遞到 tty_register_driver 函數(shù):


/* register the tty driver */
retval = tty_register_driver(tiny_tty_driver);
if (retval)
{
        printk(KERN_ERR "failed to register tiny tty driver");
        put_tty_driver(tiny_tty_driver);
        return retval;
}

當(dāng)調(diào)用 tty_register_driver, 內(nèi)核創(chuàng)建了所有的不同 sysfs tty 文件為這個(gè) tty 驅(qū)動可能有的整個(gè)范圍的次設(shè)備. 如果你使用 devfs ( 本書不涉及 ) 并且除非指定 TTY_DRIVER_NO_DEVFS 標(biāo)志, devfs 文件也被創(chuàng)建. 這個(gè)標(biāo)志可被指定如果你只想為這個(gè)實(shí)際在系統(tǒng)中存在的設(shè)備調(diào)用 tty_register_device, 因此用戶一直有一個(gè)內(nèi)核中有的最新的設(shè)備視圖, 這就是 devfs 用戶期望的.

在注冊它自己后, 這個(gè)驅(qū)動通過 tty_register_device 注冊它控制的設(shè)備. 這個(gè)函數(shù)有 3 個(gè)參數(shù):

  • 一個(gè)指針指向這個(gè)設(shè)備所屬的 struct tty_driver.

  • 設(shè)備的次編號

  • 一個(gè)指針指向這個(gè) tty 設(shè)備所綁定的 struct device. 如果這個(gè) tty 設(shè)備沒綁定到任何一個(gè) struct device, 這個(gè)參數(shù)可被設(shè)為 NULL.

我們的驅(qū)動一次注冊所有的 tty 設(shè)備, 因?yàn)樗鼈兪翘摂M的并且沒有綁定到任何一個(gè)物理設(shè)備:


for (i = 0; i < TINY_TTY_MINORS; ++i)
        tty_register_device(tiny_tty_driver, i, NULL);

為從 tty 核心注銷這個(gè)驅(qū)動, 所有的通過調(diào)用 tty_register_device 而注冊的 tty 設(shè)備需要使用對 tty_unregister_device 的調(diào)用來清理. 接著 struct tty_driver 必須使用一個(gè) tty_unregister_driver 調(diào)用來注銷.


for (i = 0; i < TINY_TTY_MINORS; ++i)
        tty_unregister_device(tiny_tty_driver, i);
tty_unregister_driver(tiny_tty_driver);

18.1.1.?結(jié)構(gòu) struct termios

在 struct tty_driver 中的 init_termios 變量是一個(gè) struct termios. 這個(gè)變量被用來提供一個(gè)健全的線路設(shè)置集合, 如果這個(gè)端口在被用戶初始化前使用. 驅(qū)動初始化這個(gè)變量使用一個(gè)標(biāo)準(zhǔn)的數(shù)值集, 它拷貝自 tty_std_termios 變量. tty_std_termos 在 tty 核心被定義為:


struct termios tty_std_termios = {
 .c_iflag = ICRNL | IXON,
 .c_oflag = OPOST | ONLCR,
 .c_cflag = B38400 | CS8 | CREAD | HUPCL,
 .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
 ECHOCTL | ECHOKE | IEXTEN,
 .c_cc = INIT_C_CC
};

這個(gè) struct termios 結(jié)構(gòu)用來持有所有的當(dāng)前線路設(shè)置, 給這個(gè) tty 設(shè)備的一個(gè)特定端口. 這些線路設(shè)置控制當(dāng)前波特率, 數(shù)據(jù)大小, 數(shù)據(jù)流控設(shè)置, 以及許多其他值. 這個(gè)結(jié)構(gòu)的不同成員是:

tcflag_t c_iflag;
輸入模式標(biāo)志

tcflag_t c_oflag;
輸出模式標(biāo)志

tcflag_t c_cflag;
控制模式標(biāo)志

tcflag_t c_lflag;
本地模式標(biāo)志

cc_t c_line;
線路規(guī)程類型

cc_t c_cc[NCCS];
一個(gè)控制字符數(shù)組

所有的模式標(biāo)志被定義為一個(gè)大的位段. 模式的不同值, 以及它們用在哪里, 可以見在任何 Linux 發(fā)布中都有的 termios 手冊頁. 內(nèi)核提供了一套有用的宏定義來獲得不同的位. 這些宏定義在頭文件 include/linux/tty.h 中定義.

所有的在 tiny_tty_driver 變量中定義的成員有必要有一個(gè)工作的 tty 驅(qū)動. owner 成員是為了防止 tty 驅(qū)動在 tty 端口打開時(shí)被卸載. 在以前的內(nèi)核版本, 它由 tty 驅(qū)動自己負(fù)責(zé)處理模塊引用計(jì)數(shù)邏輯. 但是內(nèi)核程序員認(rèn)為可能有困難來解決所有的不同的可能的競爭條件, 因此 tty 核心為 tty 驅(qū)動處理所有的這樣的控制..

driver_name 和 name 成員看起來非常相似, 然而用于不同用途. driver_name 變量應(yīng)當(dāng)設(shè)為某個(gè)簡單的, 描述性的并且和內(nèi)核中所有 tty 驅(qū)動中是唯一的值. 這是因?yàn)樗?/proc/tty/drivers 文件中出現(xiàn)來描述這個(gè)驅(qū)動給用戶, 以及在當(dāng)前已加載的 tty 驅(qū)動的 sysfs tty 類目錄. name 成員用來定義一個(gè)名子給單獨(dú)的分配給這個(gè) tty 驅(qū)動的 tty 節(jié)點(diǎn)在 /dev 樹中. 這個(gè)字符串用來創(chuàng)建一個(gè) tty 設(shè)備通過在這個(gè)字串的后面追加在使用的 tty 設(shè)備號. 它還用來創(chuàng)建一個(gè)設(shè)備名子在 sysfs /sys/class/tty 目錄中. 如果 devfs 在內(nèi)核中被使能, 這個(gè)名子應(yīng)當(dāng)包含任何這個(gè) tty 驅(qū)動想被放入的子目錄. 作為一個(gè)例子, 內(nèi)核中的串口驅(qū)動設(shè)置這個(gè) name 成員為 tts/ 如果 devfs 被使能, ttyS 如果它沒有被使能. 這個(gè)字串也顯示在 /proc/tty/drivers 文件中.

如同我們提及的, /proc/tty/drivers 文件展示所有的當(dāng)前注冊的 tty 驅(qū)動. 在內(nèi)核中注冊的 tiny_tty 驅(qū)動并且沒有 devfs, 這個(gè)文件看來如下:


$ cat /proc/tty/drivers 
tiny_tty      /dev/ttty     240   0-3     serial  
usbserial     /dev/ttyUSB   188   0-254   serial  
serial        /dev/ttyS     4     64-107  serial  
pty_slave     /dev/pts      136   0-255   pty:slave  
pty_master    /dev/ptm      128   0-255   pty:master 
pty_slave     /dev/ttyp     3     0-255   pty:slave  
pty_master    /dev/pty      2     0-255   pty:master  
unknown       /dev/vc/      4     1-63    console  
/dev/vc/0     /dev/vc/0     4     0       system:vtmaster  
/dev/ptmx     /dev/ptmx     5     2       system  
/dev/console  /dev/console  5     1       system:console  
/dev/tty      /dev/tty      5     0       system:/dev/tty  

還有, 當(dāng) tny_tty driver 被注冊到 tty 核心, sysfs 目錄 /sys/class/tty 看來有些象下面:


$ tree /sys/class/tty/ttty*
/sys/class/tty/ttty0
`-- dev
/sys/class/tty/ttty1
`-- dev
/sys/class/tty/ttty2
`-- dev
/sys/class/tty/ttty3
`-- dev

$ cat /sys/class/tty/ttty0/dev
240:0

major 變量描述這個(gè)驅(qū)動的主編號是什么. type 和 subtype 變量聲明這個(gè)驅(qū)動是什么 tty 驅(qū)動. 對于我們的例子, 我們是一個(gè)"正常"類型的串口驅(qū)動. 一個(gè) tty 驅(qū)動的唯一的其他子類型可能是一個(gè) "callout" 類型. callout 設(shè)備傳統(tǒng)上用來控制一個(gè)設(shè)備的線路設(shè)置. 數(shù)據(jù)應(yīng)當(dāng)通過一個(gè)設(shè)備節(jié)點(diǎn)被發(fā)送和接收, 并且任何路線設(shè)置改變應(yīng)當(dāng)被發(fā)送給一個(gè)不同的設(shè)備節(jié)點(diǎn), 它是這個(gè) callout 設(shè)備. 這要求使用 2 個(gè)次編號為每個(gè) tty 設(shè)備. 感激地, 所有的驅(qū)動既處理數(shù)據(jù)也處理線路設(shè)置在同一個(gè)設(shè)備節(jié)點(diǎn), 并且這個(gè) callout 類型很少用在新驅(qū)動中.

tty 驅(qū)動和 tty 核心都使用 flags 變量來指示驅(qū)動的當(dāng)前狀態(tài)和它是什么類型 tty 驅(qū)動. 幾個(gè)在測試或者操作 flags 時(shí)你必須使用的位掩碼宏被定義了. flags 變量中的 3 個(gè)位可被驅(qū)動設(shè)置:

TTY_DRIVER_RESET_TERMIOS
這個(gè)標(biāo)志說明 tty 核心復(fù)位了 termios 設(shè)置, 無論何時(shí)最后一個(gè)進(jìn)程已關(guān)閉這個(gè)設(shè)備. 對于控制臺和 pty 驅(qū)動這是有用的. 例如, 假定用戶留置一個(gè)終端在一個(gè)奇怪的狀態(tài). 在設(shè)置了這個(gè)標(biāo)志時(shí), 這個(gè)終端被復(fù)位為一個(gè)正常值當(dāng)用戶注銷或者控制個(gè)會話的進(jìn)程被"殺掉".

TTY_DRIVER_REAL_RAW
這個(gè)標(biāo)志說明 tty 驅(qū)動保證發(fā)送奇偶或者壞字符通知給線路規(guī)程. 這允許線路規(guī)程以一種更快的方式來處理接收到的字符, 因?yàn)樗槐夭榭磸?tty 驅(qū)動收到的每個(gè)字符. 因?yàn)樗俣鹊牡靡? 這個(gè)值常常為所有 tty 驅(qū)動設(shè)置.

TTY_DRIVER_NO_DEVFS
這個(gè)標(biāo)志說明當(dāng)調(diào)用 tty_register_driver 時(shí), tty 核心不創(chuàng)建任何 devfs 入口給這個(gè) tty 設(shè)備. 這對任何動態(tài)創(chuàng)建和銷毀次設(shè)備的驅(qū)動都是有益的. 設(shè)置這個(gè)的驅(qū)動的例子是這個(gè) USB-到-串口 驅(qū)動, USB 貓驅(qū)動, USB 藍(lán)牙 tty 驅(qū)動, 以及好多標(biāo)準(zhǔn)串口設(shè)備.

當(dāng) tty 驅(qū)動后來想注冊一個(gè)特殊的 tty 設(shè)備到 tty 核心, 它必須調(diào)用 tty_register_device, 有一個(gè)指針到這個(gè) tty 驅(qū)動, 并且設(shè)備的次編號已被創(chuàng)建. 如果這個(gè)沒有完成, tty 核心仍然傳遞所有的調(diào)用到這個(gè) tty 驅(qū)動, 但是一些內(nèi)部的 tty 相關(guān)的功能可能不存在. 這個(gè)包括新 tty 設(shè)備的 /sbin/hotplug 通知和 tty 設(shè)備的 sysfs 表示. 當(dāng)注冊的 tty 設(shè)備從機(jī)器中被移出, tty 驅(qū)動必須調(diào)用 tty_unregister_device.

The one remaining bit in this variable is controlled by the tty core and is called TTY_DRIVER_INSTALLED. This flag is set by the tty core after the driver has been regis-tered and should never be set by a tty driver.

這個(gè)變量中剩下的一位被 tty 核心控制, 被稱為 TTY_DRIVER_INSTALLED. 這個(gè)標(biāo)志被tty 核心在驅(qū)動已注冊后設(shè)置并且應(yīng)當(dāng)從不被 tty 驅(qū)動設(shè)置.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號