## 18.1.?一個小 TTY 驅動
為解釋 tty 核心如何工作, 我們創建一個小 tty 驅動, 可以被加載, 以及寫入讀出, 并且卸載. 任何一個 tty 驅動的主要數據結構是 struct tty_driver. 它用來注冊和注銷一個 tty 驅動到 tty 內核, 在內核頭文件 <linux/tty_driver.h> 中描述.
為創建一個 struct tty_driver, 函數 alloc_tty_driver 必須用這個驅動作為參數而支持的 tty 設備號來調用. 這可使用下面的簡短代碼來完成:
~~~
/* allocate the tty driver */
tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);
if (!tiny_tty_driver)
return -ENOMEM;
~~~
在 alloc_tty_driver 函數被成功調用后, struct tty_driver 應當用基于 tty 驅動的需要的正確信息被初始化. 這個結構包含很多不同成員, 但不是為了有一個可工作的 tty 驅動而全部都必須被初始化. 這里有一個例子展示如何初始化這個結構并且建立足夠的成員來創建一個工作的 tty 驅動. 它使用 tty_set_operations 函數來幫助拷貝驅動中定義的函數操作集合:
~~~
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);
~~~
上面列出的變量和函數, 以及這個結構如何使用, 在本章的剩下部分講解.
為注冊這個驅動到 tty 核心, struct tty_driver 必須傳遞到 tty_register_driver 函數:
~~~
/* 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;
}
~~~
當調用 tty_register_driver, 內核創建了所有的不同 sysfs tty 文件為這個 tty 驅動可能有的整個范圍的次設備. 如果你使用 devfs ( 本書不涉及 ) 并且除非指定 TTY_DRIVER_NO_DEVFS 標志, devfs 文件也被創建. 這個標志可被指定如果你只想為這個實際在系統中存在的設備調用 tty_register_device, 因此用戶一直有一個內核中有的最新的設備視圖, 這就是 devfs 用戶期望的.
在注冊它自己后, 這個驅動通過 tty_register_device 注冊它控制的設備. 這個函數有 3 個參數:
-
一個指針指向這個設備所屬的 struct tty_driver.
-
設備的次編號
-
一個指針指向這個 tty 設備所綁定的 struct device. 如果這個 tty 設備沒綁定到任何一個 struct device, 這個參數可被設為 NULL.
我們的驅動一次注冊所有的 tty 設備, 因為它們是虛擬的并且沒有綁定到任何一個物理設備:
~~~
for (i = 0; i < TINY_TTY_MINORS; ++i)
tty_register_device(tiny_tty_driver, i, NULL);
~~~
為從 tty 核心注銷這個驅動, 所有的通過調用 tty_register_device 而注冊的 tty 設備需要使用對 tty_unregister_device 的調用來清理. 接著 struct tty_driver 必須使用一個 tty_unregister_driver 調用來注銷.
~~~
for (i = 0; i < TINY_TTY_MINORS; ++i)
tty_unregister_device(tiny_tty_driver, i);
tty_unregister_driver(tiny_tty_driver);
~~~
### 18.1.1.?結構 struct termios
在 struct tty_driver 中的 init_termios 變量是一個 struct termios. 這個變量被用來提供一個健全的線路設置集合, 如果這個端口在被用戶初始化前使用. 驅動初始化這個變量使用一個標準的數值集, 它拷貝自 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
};
~~~
這個 struct termios 結構用來持有所有的當前線路設置, 給這個 tty 設備的一個特定端口. 這些線路設置控制當前波特率, 數據大小, 數據流控設置, 以及許多其他值. 這個結構的不同成員是:
tcflag_t c_iflag;
輸入模式標志
tcflag_t c_oflag;
輸出模式標志
tcflag_t c_cflag;
控制模式標志
tcflag_t c_lflag;
本地模式標志
cc_t c_line;
線路規程類型
cc_t c_cc[NCCS];
一個控制字符數組
所有的模式標志被定義為一個大的位段. 模式的不同值, 以及它們用在哪里, 可以見在任何 Linux 發布中都有的 termios 手冊頁. 內核提供了一套有用的宏定義來獲得不同的位. 這些宏定義在頭文件 include/linux/tty.h 中定義.
所有的在 tiny_tty_driver 變量中定義的成員有必要有一個工作的 tty 驅動. owner 成員是為了防止 tty 驅動在 tty 端口打開時被卸載. 在以前的內核版本, 它由 tty 驅動自己負責處理模塊引用計數邏輯. 但是內核程序員認為可能有困難來解決所有的不同的可能的競爭條件, 因此 tty 核心為 tty 驅動處理所有的這樣的控制..
driver_name 和 name 成員看起來非常相似, 然而用于不同用途. driver_name 變量應當設為某個簡單的, 描述性的并且和內核中所有 tty 驅動中是唯一的值. 這是因為它在 /proc/tty/drivers 文件中出現來描述這個驅動給用戶, 以及在當前已加載的 tty 驅動的 sysfs tty 類目錄. name 成員用來定義一個名子給單獨的分配給這個 tty 驅動的 tty 節點在 /dev 樹中. 這個字符串用來創建一個 tty 設備通過在這個字串的后面追加在使用的 tty 設備號. 它還用來創建一個設備名子在 sysfs /sys/class/tty 目錄中. 如果 devfs 在內核中被使能, 這個名子應當包含任何這個 tty 驅動想被放入的子目錄. 作為一個例子, 內核中的串口驅動設置這個 name 成員為 tts/ 如果 devfs 被使能, ttyS 如果它沒有被使能. 這個字串也顯示在 /proc/tty/drivers 文件中.
如同我們提及的, /proc/tty/drivers 文件展示所有的當前注冊的 tty 驅動. 在內核中注冊的 tiny_tty 驅動并且沒有 devfs, 這個文件看來如下:
~~~
$ 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
~~~
還有, 當 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 變量描述這個驅動的主編號是什么. type 和 subtype 變量聲明這個驅動是什么 tty 驅動. 對于我們的例子, 我們是一個"正常"類型的串口驅動. 一個 tty 驅動的唯一的其他子類型可能是一個 "callout" 類型. callout 設備傳統上用來控制一個設備的線路設置. 數據應當通過一個設備節點被發送和接收, 并且任何路線設置改變應當被發送給一個不同的設備節點, 它是這個 callout 設備. 這要求使用 2 個次編號為每個 tty 設備. 感激地, 所有的驅動既處理數據也處理線路設置在同一個設備節點, 并且這個 callout 類型很少用在新驅動中.
tty 驅動和 tty 核心都使用 flags 變量來指示驅動的當前狀態和它是什么類型 tty 驅動. 幾個在測試或者操作 flags 時你必須使用的位掩碼宏被定義了. flags 變量中的 3 個位可被驅動設置:
TTY_DRIVER_RESET_TERMIOS
這個標志說明 tty 核心復位了 termios 設置, 無論何時最后一個進程已關閉這個設備. 對于控制臺和 pty 驅動這是有用的. 例如, 假定用戶留置一個終端在一個奇怪的狀態. 在設置了這個標志時, 這個終端被復位為一個正常值當用戶注銷或者控制個會話的進程被"殺掉".
TTY_DRIVER_REAL_RAW
這個標志說明 tty 驅動保證發送奇偶或者壞字符通知給線路規程. 這允許線路規程以一種更快的方式來處理接收到的字符, 因為它不必查看從 tty 驅動收到的每個字符. 因為速度的得益, 這個值常常為所有 tty 驅動設置.
TTY_DRIVER_NO_DEVFS
這個標志說明當調用 tty_register_driver 時, tty 核心不創建任何 devfs 入口給這個 tty 設備. 這對任何動態創建和銷毀次設備的驅動都是有益的. 設置這個的驅動的例子是這個 USB-到-串口 驅動, USB 貓驅動, USB 藍牙 tty 驅動, 以及好多標準串口設備.
當 tty 驅動后來想注冊一個特殊的 tty 設備到 tty 核心, 它必須調用 tty_register_device, 有一個指針到這個 tty 驅動, 并且設備的次編號已被創建. 如果這個沒有完成, tty 核心仍然傳遞所有的調用到這個 tty 驅動, 但是一些內部的 tty 相關的功能可能不存在. 這個包括新 tty 設備的 /sbin/hotplug 通知和 tty 設備的 sysfs 表示. 當注冊的 tty 設備從機器中被移出, tty 驅動必須調用 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.
這個變量中剩下的一位被 tty 核心控制, 被稱為 TTY_DRIVER_INSTALLED. 這個標志被tty 核心在驅動已注冊后設置并且應當從不被 tty 驅動設置.
- Linux設備驅動第三版
- 第 1 章 設備驅動簡介
- 1.1. 驅動程序的角色
- 1.2. 劃分內核
- 1.3. 設備和模塊的分類
- 1.4. 安全問題
- 1.5. 版本編號
- 1.6. 版權條款
- 1.7. 加入內核開發社團
- 1.8. 本書的內容
- 第 2 章 建立和運行模塊
- 2.1. 設置你的測試系統
- 2.2. Hello World 模塊
- 2.3. 內核模塊相比于應用程序
- 2.4. 編譯和加載
- 2.5. 內核符號表
- 2.6. 預備知識
- 2.7. 初始化和關停
- 2.8. 模塊參數
- 2.9. 在用戶空間做
- 2.10. 快速參考
- 第 3 章 字符驅動
- 3.1. scull 的設計
- 3.2. 主次編號
- 3.3. 一些重要數據結構
- 3.4. 字符設備注冊
- 3.5. open 和 release
- 3.6. scull 的內存使用
- 3.7. 讀和寫
- 3.8. 使用新設備
- 3.9. 快速參考
- 第 4 章 調試技術
- 4.1. 內核中的調試支持
- 4.2. 用打印調試
- 4.3. 用查詢來調試
- 4.4. 使用觀察來調試
- 4.5. 調試系統故障
- 4.6. 調試器和相關工具
- 第 5 章 并發和競爭情況
- 5.1. scull 中的缺陷
- 5.2. 并發和它的管理
- 5.3. 旗標和互斥體
- 5.4. Completions 機制
- 5.5. 自旋鎖
- 5.6. 鎖陷阱
- 5.7. 加鎖的各種選擇
- 5.8. 快速參考
- 第 6 章 高級字符驅動操作
- 6.1. ioctl 接口
- 6.2. 阻塞 I/O
- 6.3. poll 和 select
- 6.4. 異步通知
- 6.5. 移位一個設備
- 6.6. 在一個設備文件上的存取控制
- 6.7. 快速參考
- 第 7 章 時間, 延時, 和延后工作
- 7.1. 測量時間流失
- 7.2. 獲知當前時間
- 7.3. 延后執行
- 7.4. 內核定時器
- 7.5. Tasklets 機制
- 7.6. 工作隊列
- 7.7. 快速參考
- 第 8 章 分配內存
- 8.1. kmalloc 的真實故事
- 8.2. 后備緩存
- 8.3. get_free_page 和其友
- 8.4. 每-CPU 的變量
- 8.5. 獲得大量緩沖
- 8.6. 快速參考
- 第 9 章 與硬件通訊
- 9.1. I/O 端口和 I/O 內存
- 9.2. 使用 I/O 端口
- 9.3. 一個 I/O 端口例子
- 9.4. 使用 I/O 內存
- 9.5. 快速參考
- 第 10 章 中斷處理
- 10.1. 準備并口
- 10.2. 安裝一個中斷處理
- 10.3. 前和后半部
- 10.4. 中斷共享
- 10.5. 中斷驅動 I/O
- 10.6. 快速參考
- 第 11 章 內核中的數據類型
- 11.1. 標準 C 類型的使用
- 11.2. 安排一個明確大小給數據項
- 11.3. 接口特定的類型
- 11.4. 其他移植性問題
- 11.5. 鏈表
- 11.6. 快速參考
- 第 12 章 PCI 驅動
- 12.1. PCI 接口
- 12.2. 回顧: ISA
- 12.3. PC/104 和 PC/104+
- 12.4. 其他的 PC 總線
- 12.5. SBus
- 12.6. NuBus 總線
- 12.7. 外部總線
- 12.8. 快速參考
- 第 13 章 USB 驅動
- 13.1. USB 設備基礎知識
- 13.2. USB 和 sysfs
- 13.3. USB 的 Urbs
- 13.4. 編寫一個 USB 驅動
- 13.5. 無 urb 的 USB 傳送
- 13.6. 快速參考
- 第 14 章 Linux 設備模型
- 14.1. Kobjects, Ksets 和 Subsystems
- 14.2. 低級 sysfs 操作
- 14.3. 熱插拔事件產生
- 14.4. 總線, 設備, 和驅動
- 14.5. 類
- 14.6. 集成起來
- 14.7. 熱插拔
- 14.8. 處理固件
- 14.9. 快速參考
- 第 15 章 內存映射和 DMA
- 15.1. Linux 中的內存管理
- 15.2. mmap 設備操作
- 15.3. 進行直接 I/O
- 15.4. 直接內存存取
- 15.5. 快速參考
- 第 16 章 塊驅動
- 16.1. 注冊
- 16.2. 塊設備操作
- 16.3. 請求處理
- 16.4. 一些其他的細節
- 16.5. 快速參考
- 第 17 章 網絡驅動
- 17.1. snull 是如何設計的
- 17.2. 連接到內核
- 17.3. net_device 結構的詳情
- 17.4. 打開與關閉
- 17.5. 報文傳送
- 17.6. 報文接收
- 17.7. 中斷處理
- 17.8. 接收中斷緩解
- 17.9. 連接狀態的改變
- 17.10. Socket 緩存
- 17.11. MAC 地址解析
- 17.12. 定制 ioctl 命令
- 17.13. 統計信息
- 17.14. 多播
- 17.15. 幾個其他細節
- 17.16. 快速參考
- 第 18 章 TTY 驅動
- 18.1. 一個小 TTY 驅動
- 18.2. tty_driver 函數指針
- 18.3. TTY 線路設置
- 18.4. ioctls 函數
- 18.5. TTY 設備的 proc 和 sysfs 處理
- 18.6. tty_driver 結構的細節
- 18.7. tty_operaions 結構的細節
- 18.8. tty_struct 結構的細節
- 18.9. 快速參考