Linux終端tty裝置驅動 在Linux系統中,終端裝置非常重要,沒有終端裝置,系統将無法向使用者回報資訊,Linux中包含控制台、序列槽和僞終端3類終端裝置。 14.1節闡述了終端裝置的概念及分類,14.2節給出了Linux終端裝置驅動的架構結構,重點描述tty_driver結構體及其成員。14.3~14.5節在14.2節的基礎上,分别給出了Linux終端裝置驅動子產品加載/解除安裝函數和open()、close()函數,資料讀寫流程及tty裝置線路設定的程式設計方法。在Linux中,序列槽驅動完全遵循tty驅動的架構結構,但是進行了底層操作的再次封裝,14.6節描述了Linux針對序列槽tty驅動的這一封裝,14.7節則具體給出了序列槽tty驅動的實作方法。14.8節基于14.6和14.7節的講解給出了序列槽tty驅動的設計執行個體,即S3C2410內建UART的驅動。 14.1終端裝置 在Linux系統中,終端是一種字元型裝置,它有多種類型,通常使用tty來簡稱各種類型的終端裝置。tty是Teletype的縮寫,Teletype是最早出現的一種終端裝置,很像電傳打字機,是由Teletype公司生産的。Linux中包含如下幾類終端裝置: 1、串行端口終端(/dev/ttySn) 串行端口終端(Serial Port Terminal)是使用計算機串行端口連接配接的終端裝置。計算機把每個串行端口都看作是一個字元裝置。這些串行端口所對應的裝置名稱是 /dev/ttyS0(或/dev/tts/0)、/dev/ttyS1(或/dev/tts/1)等,裝置号分别是(4,0)、(4,1)等。 在指令行上把标準輸出重定向到端口對應的裝置檔案名上就可以通過該端口發送資料,例如,在指令行提示符下鍵入: echo test > /dev/ttyS1會把單詞“test”發送到連接配接在ttyS1端口的裝置上。 2.僞終端(/dev/pty/) 僞終端(Pseudo Terminal)是成對的邏輯終端裝置,并存在成對的裝置檔案,如/dev/ptyp3和/dev/ttyp3,它們與實際實體裝置并不直接相關。如果一個程式把ttyp3看作是一個串行端口裝置,則它對該端口的讀/寫操作會反映在該邏輯終端裝置對應的ptyp3上,而ptyp3則是另一個程式用于讀寫操作的邏輯裝置。這樣,兩個程式就可以通過這種邏輯裝置進行互相交流,使用ttyp3的程式會認為自己正在與一個串行端口進行通信。 以telnet 為例,如果某人在使用telnet程式連接配接到Linux系統,則telnet程式就可能會開始連接配接到裝置ptyp2上,而此時一個getty程式會運作在對應的ttyp2端口上。當telnet從遠端擷取了一個字元時,該字元就會通過ptyp2、ttyp2傳遞給 getty程式,而getty程式則會通過ttyp2、ptyp2和telnet程式傳回“login:”字元串資訊。這樣,登入程式與telnet程式 就通過僞終端進行通信。通過使用适當的軟體,可以把2個或多個僞終端裝置連接配接到同一個實體串行端口上。 3.控制台終端(/dev/ttyn, /dev/console) 如果目前程序有控制終端(Controlling Terminal)的話,那麼/dev/tty就是目前程序的控制終端的裝置特殊檔案。可以使用指令“ps –ax”來檢視程序與哪個控制終端相連使用指令“tty”可以檢視它具體對應哪個實際終端裝置。/dev/tty有些類似于到實際所使用終端裝置的一個聯接。 在UNIX系統中,計算機顯示器通常被稱為控制台終端(Console)。它仿真了類型為Linux的一種終端(TERM=Linux),并且有一些裝置特殊檔案與之相關聯:tty0、tty1、tty2等。當使用者在控制台上登入時,使用的是tty1。使用Alt+[F1—F6]組合鍵時,我們就可以切換到tty2、tty3等上面去。tty1–tty6等稱為虛拟終端,而tty0則是目前所使用虛拟終端的一個别名,系統所産生的資訊會發送到該終端上。是以不管目前正在使用哪個虛拟終端,系統資訊都會發送到控制台終端上。使用者可以登入到不同的虛拟終端上去,因而可以讓系統同時有幾個不同的會話期存在。隻有系統或超級使用者root可以向/dev/tty0進行寫操作。 在Linux 中,可以在系統啟動指令行裡指定目前的輸出終端,格式如下: console=device, options device指代的是終端裝置,可以是tty0(前台的虛拟終端)、ttyX(第X個虛拟終端)、ttySX(第X個序列槽)、lp0(第一個并口)等。options指代對device進行的設定,它取決于具體的裝置驅動。對于序列槽裝置,參數用來定義為:波特率、校驗位、位數,格式為BBBBPN,其中BBBB表示波特率,P表示校驗(n/o/e),N表示位數,預設options是9600n8。 使用者可以在核心指令行中同時設定多個終端,這樣輸出将會在所有的終端上顯示,而當使用者調用open()打開/dev/console時,最後一個終端将會傳回作為目前值。例如: console=ttyS1, 9600 console=tty0 定義了2個終端,而調用open()打開/dev/console時,将使用虛拟終端tty0。但是核心消息會在tty0 VGA虛拟終端和序列槽ttyS1上同時顯示。 通過檢視/proc/tty/drivers檔案可以獲知什麼類型的tty裝置存在以及什麼驅動被加載到核心,這個檔案包括一個目前存在的不同 tty 驅動的清單,包括驅動名、預設的節點名、驅動的主編号、這個驅動使用的次編号範圍,以及 tty 驅動的類型。例如,下面給出了一個/proc/tty/drivers檔案的例子: 14.2終端裝置驅動結構 Linux核心中 tty的層次結構如圖14.1所示,包含tty核心、tty線路規程和tty驅動,tty 線路規程的工作是以特殊的方式格式化從一個使用者或者硬體收到的資料,這種格式化常常采用一個協定轉換的形式,例如 PPP 和 Bluetooth。tty裝置發送資料的流程為:tty核心從一個使用者擷取将要發送給一個 tty裝置的資料,tty核心将資料傳遞給tty線路規程驅動,接着資料被傳遞到tty驅動,tty驅動将資料轉換為可以發送給硬體的格式。接收資料的流程為: 從tty硬體接收到的資料向上交給tty驅動,進入tty線路規程驅動,再進入 tty 核心,在這裡它被一個使用者擷取。盡管大多數時候tty核心和tty之間的資料傳輸會經曆tty線路規程的轉換,但是tty驅動與tty核心之間也可以直接傳輸資料。 <!--[if !vml]--><!--[endif]--> 圖14.1 tty分層結構 圖14.2顯示了與tty相關的主要源檔案及資料的流向。tty_io.c定義了tty 裝置通用的file_operations結構體并實作了接口函數tty_register_driver()用于注冊tty裝置,它會利用 fs/char_dev.c提供的接口函數注冊字元裝置,與具體裝置對應的tty驅動将實作tty_driver結構體中的成員函數。同時 tty_io.c也提供了tty_register_ldisc()接口函數用于注冊線路規程,n_tty.c檔案則實作了tty_disc結構體中的成員。 <!--[if !vml]--><!--[endif]--> 圖14.2 tty主要源檔案關系及資料流向 從圖14.2可以看出,特定tty裝置驅動的主體工作是填充tty_driver結構體中的成員,實作其中的成員函數,tty_driver結構體的定義如代碼清單14.1。 代碼清單14.1 tty_driver結構體 1 struct tty_driver 2 { 3 int magic; 4 struct cdev cdev; 5 struct module *owner; 6 const char *driver_name; 7 const char *devfs_name; 8 const char *name; 9 int name_base; 10 int major; 11 int minor_start; 12 int minor_num; 13 int num; 14 short type; 15 short subtype; 16 struct termios init_termios; 17 int flags; 18 int refcount; 19 struct proc_dir_entry *proc_entry; 20 struct tty_driver *other; &nb |
在Linux系統中,終端裝置非常重要,沒有終端裝置,系統将無法向使用者回報資訊,Linux中包含控制台、序列槽和僞終端3類終端裝置。
14.1節闡述了終端裝置的概念及分類,14.2節給出了Linux終端裝置驅動的架構結構,重點描述tty_driver結構體及其成員。14.3~14.5節在14.2節的基礎上,分别給出了Linux終端裝置驅動子產品加載/解除安裝函數和open()、close()函數,資料讀寫流程及tty裝置線路設定的程式設計方法。在Linux中,序列槽驅動完全遵循tty驅動的架構結構,但是進行了底層操作的再次封裝,14.6節描述了Linux針對序列槽tty驅動的這一封裝,14.7節則具體給出了序列槽tty驅動的實作方法。14.8節基于14.6和14.7節的講解給出了序列槽tty驅動的設計執行個體,即S3C2410內建UART的驅動。
14.1終端裝置
在Linux系統中,終端是一種字元型裝置,它有多種類型,通常使用tty來簡稱各種類型的終端裝置。tty是Teletype的縮寫,Teletype是最早出現的一種終端裝置,很像電傳打字機,是由Teletype公司生産的。Linux中包含如下幾類終端裝置:
1、串行端口終端(/dev/ttySn)
串行端口終端(Serial Port Terminal)是使用計算機串行端口連接配接的終端裝置。計算機把每個串行端口都看作是一個字元裝置。這些串行端口所對應的裝置名稱是 /dev/ttyS0(或/dev/tts/0)、/dev/ttyS1(或/dev/tts/1)等,裝置号分别是(4,0)、(4,1)等。
在指令行上把标準輸出重定向到端口對應的裝置檔案名上就可以通過該端口發送資料,例如,在指令行提示符下鍵入: echo test > /dev/ttyS1會把單詞“test”發送到連接配接在ttyS1端口的裝置上。
2.僞終端(/dev/pty/)
僞終端(Pseudo Terminal)是成對的邏輯終端裝置,并存在成對的裝置檔案,如/dev/ptyp3和/dev/ttyp3,它們與實際實體裝置并不直接相關。如果一個程式把ttyp3看作是一個串行端口裝置,則它對該端口的讀/寫操作會反映在該邏輯終端裝置對應的ptyp3上,而ptyp3則是另一個程式用于讀寫操作的邏輯裝置。這樣,兩個程式就可以通過這種邏輯裝置進行互相交流,使用ttyp3的程式會認為自己正在與一個串行端口進行通信。
以telnet 為例,如果某人在使用telnet程式連接配接到Linux系統,則telnet程式就可能會開始連接配接到裝置ptyp2上,而此時一個getty程式會運作在對應的ttyp2端口上。當telnet從遠端擷取了一個字元時,該字元就會通過ptyp2、ttyp2傳遞給 getty程式,而getty程式則會通過ttyp2、ptyp2和telnet程式傳回“login:”字元串資訊。這樣,登入程式與telnet程式 就通過僞終端進行通信。通過使用适當的軟體,可以把2個或多個僞終端裝置連接配接到同一個實體串行端口上。
3.控制台終端(/dev/ttyn, /dev/console)
如果目前程序有控制終端(Controlling Terminal)的話,那麼/dev/tty就是目前程序的控制終端的裝置特殊檔案。可以使用指令“ps –ax”來檢視程序與哪個控制終端相連使用指令“tty”可以檢視它具體對應哪個實際終端裝置。/dev/tty有些類似于到實際所使用終端裝置的一個聯接。
在UNIX系統中,計算機顯示器通常被稱為控制台終端(Console)。它仿真了類型為Linux的一種終端(TERM=Linux),并且有一些裝置特殊檔案與之相關聯:tty0、tty1、tty2等。當使用者在控制台上登入時,使用的是tty1。使用Alt+[F1—F6]組合鍵時,我們就可以切換到tty2、tty3等上面去。tty1–tty6等稱為虛拟終端,而tty0則是目前所使用虛拟終端的一個别名,系統所産生的資訊會發送到該終端上。是以不管目前正在使用哪個虛拟終端,系統資訊都會發送到控制台終端上。使用者可以登入到不同的虛拟終端上去,因而可以讓系統同時有幾個不同的會話期存在。隻有系統或超級使用者root可以向/dev/tty0進行寫操作。
在Linux 中,可以在系統啟動指令行裡指定目前的輸出終端,格式如下:
console=device, options
device指代的是終端裝置,可以是tty0(前台的虛拟終端)、ttyX(第X個虛拟終端)、ttySX(第X個序列槽)、lp0(第一個并口)等。options指代對device進行的設定,它取決于具體的裝置驅動。對于序列槽裝置,參數用來定義為:波特率、校驗位、位數,格式為BBBBPN,其中BBBB表示波特率,P表示校驗(n/o/e),N表示位數,預設options是9600n8。
使用者可以在核心指令行中同時設定多個終端,這樣輸出将會在所有的終端上顯示,而當使用者調用open()打開/dev/console時,最後一個終端将會傳回作為目前值。例如:
console=ttyS1, 9600 console=tty0
定義了2個終端,而調用open()打開/dev/console時,将使用虛拟終端tty0。但是核心消息會在tty0 VGA虛拟終端和序列槽ttyS1上同時顯示。
通過檢視/proc/tty/drivers檔案可以獲知什麼類型的tty裝置存在以及什麼驅動被加載到核心,這個檔案包括一個目前存在的不同 tty 驅動的清單,包括驅動名、預設的節點名、驅動的主編号、這個驅動使用的次編号範圍,以及 tty 驅動的類型。例如,下面給出了一個/proc/tty/drivers檔案的例子:
14.2終端裝置驅動結構
Linux核心中 tty的層次結構如圖14.1所示,包含tty核心、tty線路規程和tty驅動,tty 線路規程的工作是以特殊的方式格式化從一個使用者或者硬體收到的資料,這種格式化常常采用一個協定轉換的形式,例如 PPP 和 Bluetooth。tty裝置發送資料的流程為:tty核心從一個使用者擷取将要發送給一個 tty裝置的資料,tty核心将資料傳遞給tty線路規程驅動,接着資料被傳遞到tty驅動,tty驅動将資料轉換為可以發送給硬體的格式。接收資料的流程為: 從tty硬體接收到的資料向上交給tty驅動,進入tty線路規程驅動,再進入 tty 核心,在這裡它被一個使用者擷取。盡管大多數時候tty核心和tty之間的資料傳輸會經曆tty線路規程的轉換,但是tty驅動與tty核心之間也可以直接傳輸資料。
<!--[if !vml]--><!--[endif]-->
圖14.1 tty分層結構
圖14.2顯示了與tty相關的主要源檔案及資料的流向。tty_io.c定義了tty 裝置通用的file_operations結構體并實作了接口函數tty_register_driver()用于注冊tty裝置,它會利用 fs/char_dev.c提供的接口函數注冊字元裝置,與具體裝置對應的tty驅動将實作tty_driver結構體中的成員函數。同時 tty_io.c也提供了tty_register_ldisc()接口函數用于注冊線路規程,n_tty.c檔案則實作了tty_disc結構體中的成員。
<!--[if !vml]--><!--[endif]-->
圖14.2 tty主要源檔案關系及資料流向
從圖14.2可以看出,特定tty裝置驅動的主體工作是填充tty_driver結構體中的成員,實作其中的成員函數,tty_driver結構體的定義如代碼清單14.1。
代碼清單14.1 tty_driver結構體
1 struct tty_driver
2 {
3 int magic;
4 struct cdev cdev;
5 struct module *owner;
6 const char *driver_name;
7 const char *devfs_name;
8 const char *name;
9 int name_base;
10 int major;
11 int minor_start;
12 int minor_num;
13 int num;
14 short type;
15 short subtype;
16 struct termios init_termios;
17 int flags;
18 int refcount;
19 struct proc_dir_entry *proc_entry;
20 struct tty_driver *other; &nb