SVR4/4.3BSD与Linux对待伪终端的不同方式
生活随笔
收集整理的這篇文章主要介紹了
SVR4/4.3BSD与Linux对待伪终端的不同方式
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
打開偽終端意味著打開了一個“終端對”,這個終端對的其中一個是主終端,另一個是從終端,簡單說主終端和類似sshd,telnetd等用戶空間的遠程協議處理進程連接,而從終端則和shell之類的實際進程連接,在處理遠程登錄的時候,一般都是由遠程協議處理進程打開主終端和從終端,然后就在遠程網絡終端和本機shell之間建立了一條雙向通道--“遠程網絡終端-(套接字)--本機協議處理進程--主終端--從終端--shell”,在這個“打開主從終端建立連接”的語義以及其實現上,有著不同的標準,總的來說有三種方式,分別是SVR4的方式,BSD的方式以及linux的方式,在“建立連接”的語義上SVR4的方式使用“流”來建立這條連接,而BSD和linux則是自動建立的,在“打開主從終端”的語義上,SVR4和linux是自動確定主終端并打開主終端后自動確定從終端,而BSD則必須手工確定和打開主終端,可見linux處理偽終端的方式是結合SVR4和BSD兩種UNIX標準的結果,linux不僅實現這種有意義的最佳組合,而且分別實現了SRV和BSD的兩種方式的接口,如果編譯CONFIG_LEGACY_PTYS宏,則可以使用BSD的方式,如果編譯CONFIG_UNIX98_PTYS,則實現SRV4的接口。
???? 參見《unix環境高級編程》的第19章,在用戶空間,SVR4的方式為:
int ptym_open(char *pts_name)
{
??? strcpy(pts_name, "/dev/ptmx"); //準備主終端的文件名字
??? fdm = open(pts_name, O_RDWR);? //打開主終端
??? grantpt(fdm);? //連接次終端
??? ptr = ptsname(fdm); //得到次終端的文件名字
??? strcpy(pts_name, ptr);
??? return fdm;
}
int ptys_open(int fdm, char *pts_name)
{
??? fds = open(pts_name, O_RDWR); //打開次終端
??? ioctl(fds, I_PUSH, "ptem");?? //壓入一個偽終端虛擬模塊
??? ioctl(fds, I_PUSH, "ldterm"); //壓入行規程模塊
??? return fds;
}
而BSD的方式卻是:
int ptym_open(char *pts_name)
{
??? ...
??? //一個for循環,從/dev/ptyp0開始一直找到/dev/ptyTf為止,尋到第一個沒有被使用的作為主終端,然后將對應的文件名的ptyXY中的p改為t,即ttyXY就是次終端
??? pts_name[5] = 't';? ///dev/ptyXY中的第6個元素就是p,現在改為t
??? return fdm;
}
int ptys_open(int fdm, char *pts_name)
{
??? fds = open(pts_name, O_RDWR); //打開次終端
??? //無需壓入流模塊,因為對于bsd來講,其驅動程序是基于硬編碼和clist的。
}
可見SRV4和BSD的方式根本不同,通過理解這種不同,我們也能更加明白/dev目錄下的關于終端文件的命名規則了。關于流機制,BSD沒有實現,可能是由于BSD自最初就沒有SRV經過良好的規劃吧,加之流的作者直接貢獻流機制于SRV,那時unix已經分裂了。從終端的行規程以及任何終端的行規程之類其實也是一種協議,只是該協議主要規定人-機界面的規則,之所以將之稱為行規程就是因為該規程在標準模式下限制了一次輸入的結束就是一個換行符,這就是一個行規成協議,畢竟機器并不知道何時人們會輸入完畢也就不能預先讀取特定大小的數據塊,而只能硬性規定一個特殊的字符作為輸入結束,該特殊字符就是換行,類似tcp/ip協議,只是沒有后者普遍罷了,使用壓入流的方式,你可以輕易的堆積一個協議棧,只要將/dev/ip|udp|tcp等協議設備文件依次用ioctl的I_PUSH命令堆積即可。行規程壓入了從終端并沒有壓入主終端,可見主終端僅僅起到一個數據中轉的作用,主終端之所以不需要行規程,那是因為從終端可以處理直接從進程寫入的數據或者說可以處理數據邊界以及轉義問題,這是無關緊要的,完全可以重新實現一個偽終端驅動,然后在主從終端都壓入行規程模塊,這樣就顯得更加對稱了,正如linux后來實現的那樣,雖然linux并沒有顯式地實現流機制,在linux中的偽終端tty的write函數中,主從終端都是統一一致的,如此一來在linux中,主從終端就更加像一對管道了,起碼比SRV的要對稱:
static int pty_write(...)
{
??? struct tty_struct *to = tty->link; //直接獲取“一對的另一半”
??? ...
??? to->ldisc.receive_buf(to, temp_buffer, NULL, n);//將數據放入另一半的緩沖區
??? ...
}
如果看一下linux實現pty的源碼,就會發現實際上pts使用的行規程是tty_ldisc_N_TTY,其receive_buf對數據其實并沒有做太復雜的加工,因此這條管道并不復雜。
???? 在打開一對終端方面,linux實現了兩種方式,SRV4的方式和BSD的方式,總之,實現這兩種接口是之前unix標準混戰的結果。以SRV4的方式為例,linux中使用了一個/dev/ptmx設備文件,該設備文件只有一個卻可以集中代表所有的主終端,任何sshd,telnetd之類的進程都可以只使用者一個終端設備文件,雖然設備文件是一個,但是由于內核中file數據結構是基于進程的,因此各個進程對該設備文件的引用卻可以容納不同的數據,包括不同的從終端。在ptmx_open中,不僅系統可以自動分配一個主終端,而且還為該主終端綁定了一個從終端,主終端設置到file結構體的private_data字段上,之后諸如sshd,telnetd之類的進程讀寫/dev/ptmx文件時,雖然它們讀寫的是同一個文件,可是由于file結構體不再它們之間共享,因此它們取到的file->private_data也就不同了:
static int ptmx_open(struct inode * inode, struct file * filp)
{
??? struct tty_struct *tty;
??? int index;
??? idr_pre_get(&allocated_ptys, GFP_KERNEL); //和BSD的實現不同,SRV的方式在內核中查找可用的主終端
??? idr_ret = idr_get_new(&allocated_ptys, NULL, &index); //得到新的項
??? ...
??? retval = init_dev(ptm_driver, index, &tty);//此中實現終端對的相互link
??? filp->private_data = tty; //重要的賦值
??? devpts_pty_new(tty->link); //linux中的從終端設備文件是動態生成和刪除的,因此linux使用了其強大的VFS機制,通過實現一個pts文件系統來支持這種動態的增刪。
??? ...
}
SRV4使用偽終端的方式是一對多的,ptmx集合了所有的主終端,同一個文件在不同的進程空間做區分,而BSD的方式卻直接將多個一對一的終端對開放給接口調用者。在init_dev中,最為重要的一段是:
tty = alloc_tty_struct(); //分配主終端
initialize_tty_struct(tty); //初始化主終端的線路規程之類,包括些許工作隊列
tty->driver = driver; //指定driver
tty->index = idx;
...
o_tty = alloc_tty_struct(); //分配從終端
initialize_tty_struct(o_tty); //初始化主終端的線路規程之類
o_tty->driver = driver->other; //pty_init的時候,主從driver都將other設置為對方
o_tty->index = idx;
tty_line_name(driver->other, idx, o_tty->name);
...
tty->link?? = o_tty; //連接彼此,從此以后兩個終端一主一從構成一個包含很多控制功能的管道
o_tty->link = tty;
linux的sshd等進程在調用完/dev/ptmx的open之后,實際上一對終端就建立起來了,由于沒有實現SRV4的流機制,因此不需要在用ioctl將更多的協議模塊PUSH上去了,這個過程直接在init_dev中就做了,可以猜想一切基于SRV4標準實現的OS,其ptmx的open要簡單的多,因此init_dev簡單得多,不需要系統做任何事,后續的所有工作幾乎全靠調用者來用ioctl完成,比linux更靈活,但是對于很多凡人來講也更繁瑣。
???? 參見《unix環境高級編程》的第19章,在用戶空間,SVR4的方式為:
int ptym_open(char *pts_name)
{
??? strcpy(pts_name, "/dev/ptmx"); //準備主終端的文件名字
??? fdm = open(pts_name, O_RDWR);? //打開主終端
??? grantpt(fdm);? //連接次終端
??? ptr = ptsname(fdm); //得到次終端的文件名字
??? strcpy(pts_name, ptr);
??? return fdm;
}
int ptys_open(int fdm, char *pts_name)
{
??? fds = open(pts_name, O_RDWR); //打開次終端
??? ioctl(fds, I_PUSH, "ptem");?? //壓入一個偽終端虛擬模塊
??? ioctl(fds, I_PUSH, "ldterm"); //壓入行規程模塊
??? return fds;
}
而BSD的方式卻是:
int ptym_open(char *pts_name)
{
??? ...
??? //一個for循環,從/dev/ptyp0開始一直找到/dev/ptyTf為止,尋到第一個沒有被使用的作為主終端,然后將對應的文件名的ptyXY中的p改為t,即ttyXY就是次終端
??? pts_name[5] = 't';? ///dev/ptyXY中的第6個元素就是p,現在改為t
??? return fdm;
}
int ptys_open(int fdm, char *pts_name)
{
??? fds = open(pts_name, O_RDWR); //打開次終端
??? //無需壓入流模塊,因為對于bsd來講,其驅動程序是基于硬編碼和clist的。
}
可見SRV4和BSD的方式根本不同,通過理解這種不同,我們也能更加明白/dev目錄下的關于終端文件的命名規則了。關于流機制,BSD沒有實現,可能是由于BSD自最初就沒有SRV經過良好的規劃吧,加之流的作者直接貢獻流機制于SRV,那時unix已經分裂了。從終端的行規程以及任何終端的行規程之類其實也是一種協議,只是該協議主要規定人-機界面的規則,之所以將之稱為行規程就是因為該規程在標準模式下限制了一次輸入的結束就是一個換行符,這就是一個行規成協議,畢竟機器并不知道何時人們會輸入完畢也就不能預先讀取特定大小的數據塊,而只能硬性規定一個特殊的字符作為輸入結束,該特殊字符就是換行,類似tcp/ip協議,只是沒有后者普遍罷了,使用壓入流的方式,你可以輕易的堆積一個協議棧,只要將/dev/ip|udp|tcp等協議設備文件依次用ioctl的I_PUSH命令堆積即可。行規程壓入了從終端并沒有壓入主終端,可見主終端僅僅起到一個數據中轉的作用,主終端之所以不需要行規程,那是因為從終端可以處理直接從進程寫入的數據或者說可以處理數據邊界以及轉義問題,這是無關緊要的,完全可以重新實現一個偽終端驅動,然后在主從終端都壓入行規程模塊,這樣就顯得更加對稱了,正如linux后來實現的那樣,雖然linux并沒有顯式地實現流機制,在linux中的偽終端tty的write函數中,主從終端都是統一一致的,如此一來在linux中,主從終端就更加像一對管道了,起碼比SRV的要對稱:
static int pty_write(...)
{
??? struct tty_struct *to = tty->link; //直接獲取“一對的另一半”
??? ...
??? to->ldisc.receive_buf(to, temp_buffer, NULL, n);//將數據放入另一半的緩沖區
??? ...
}
如果看一下linux實現pty的源碼,就會發現實際上pts使用的行規程是tty_ldisc_N_TTY,其receive_buf對數據其實并沒有做太復雜的加工,因此這條管道并不復雜。
???? 在打開一對終端方面,linux實現了兩種方式,SRV4的方式和BSD的方式,總之,實現這兩種接口是之前unix標準混戰的結果。以SRV4的方式為例,linux中使用了一個/dev/ptmx設備文件,該設備文件只有一個卻可以集中代表所有的主終端,任何sshd,telnetd之類的進程都可以只使用者一個終端設備文件,雖然設備文件是一個,但是由于內核中file數據結構是基于進程的,因此各個進程對該設備文件的引用卻可以容納不同的數據,包括不同的從終端。在ptmx_open中,不僅系統可以自動分配一個主終端,而且還為該主終端綁定了一個從終端,主終端設置到file結構體的private_data字段上,之后諸如sshd,telnetd之類的進程讀寫/dev/ptmx文件時,雖然它們讀寫的是同一個文件,可是由于file結構體不再它們之間共享,因此它們取到的file->private_data也就不同了:
static int ptmx_open(struct inode * inode, struct file * filp)
{
??? struct tty_struct *tty;
??? int index;
??? idr_pre_get(&allocated_ptys, GFP_KERNEL); //和BSD的實現不同,SRV的方式在內核中查找可用的主終端
??? idr_ret = idr_get_new(&allocated_ptys, NULL, &index); //得到新的項
??? ...
??? retval = init_dev(ptm_driver, index, &tty);//此中實現終端對的相互link
??? filp->private_data = tty; //重要的賦值
??? devpts_pty_new(tty->link); //linux中的從終端設備文件是動態生成和刪除的,因此linux使用了其強大的VFS機制,通過實現一個pts文件系統來支持這種動態的增刪。
??? ...
}
SRV4使用偽終端的方式是一對多的,ptmx集合了所有的主終端,同一個文件在不同的進程空間做區分,而BSD的方式卻直接將多個一對一的終端對開放給接口調用者。在init_dev中,最為重要的一段是:
tty = alloc_tty_struct(); //分配主終端
initialize_tty_struct(tty); //初始化主終端的線路規程之類,包括些許工作隊列
tty->driver = driver; //指定driver
tty->index = idx;
...
o_tty = alloc_tty_struct(); //分配從終端
initialize_tty_struct(o_tty); //初始化主終端的線路規程之類
o_tty->driver = driver->other; //pty_init的時候,主從driver都將other設置為對方
o_tty->index = idx;
tty_line_name(driver->other, idx, o_tty->name);
...
tty->link?? = o_tty; //連接彼此,從此以后兩個終端一主一從構成一個包含很多控制功能的管道
o_tty->link = tty;
linux的sshd等進程在調用完/dev/ptmx的open之后,實際上一對終端就建立起來了,由于沒有實現SRV4的流機制,因此不需要在用ioctl將更多的協議模塊PUSH上去了,這個過程直接在init_dev中就做了,可以猜想一切基于SRV4標準實現的OS,其ptmx的open要簡單的多,因此init_dev簡單得多,不需要系統做任何事,后續的所有工作幾乎全靠調用者來用ioctl完成,比linux更靈活,但是對于很多凡人來講也更繁瑣。
???? 十分欣賞SRV的那種I_PUSH實現的將ip,icmp,udp等堆積成協議棧的方式--流機制,同時更樂于使用linux這種將一切都做好,但是還可以定制的OS。
?本文轉自 dog250 51CTO博客,原文鏈接:http://blog.51cto.com/dog250/1271801
總結
以上是生活随笔為你收集整理的SVR4/4.3BSD与Linux对待伪终端的不同方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 源代码管理:TFS
- 下一篇: zabbix 监控 redis