1. 虛擬終端概念
linux中有很多終端,如下簡單介紹下各種終端或串口的概念。
1.1 tty:終端設備的統稱
tty是Teletype或TeletypeWriter的縮寫,中文翻譯為電傳打字機。電傳打字機通常有鍵盤、收發報器和印字機等組成,是傳真機使用以前的通信設備,原理近似電報。后被顯示器和鍵盤所取代,所以現在叫終端比較合適。
終端是一種字符型設備,他有多種類型,通常使用tty來簡稱各種類型的終端設備。
目前,tty一般指控制終端(man 4 tty),設備文件是/dev/ttyx,常用的就是linux默認提供的6個命令行終端,可通過Ctrl+Alt+Fn切換圖形界面或終端窗口。在Ubuntu命令行輸入tty顯示終端:
$ tty
/dev/tty2
1.2 pty:虛擬終端
A pseudoterminal縮寫為pty,是虛擬終端或偽終端,可以在終端模擬器(terminal emulator)中運行,man pty查看。pty是成對的邏輯終端設備(即master和slave設備,對master的操作會反映到slave上,對slave的操作也會反映到master上),與實際物理設備無關。A pty is a pair of virtual character devices that provide a bidirectional communication channel. one end is called master; the other end is called the slave.
linux提供了兩套虛擬終端接口,BSD-style和System V-style,System V-style終端也被稱為UNIX 98 pseudoterminals,是目前使用的偽終端樣式。
An unused UNIX 98 pseudoterminal master is opened by calling posix_openpt(3). (This function opens the master clone device, /dev/ptmx; see pts(4).) After performing any program-specific initializations, changing the ownership and permissions of the slave device using grantpt(3), and unlocking the slave using unlockpt(3)), the corresponding slave device can be opened by passing the name returned by ptsname(3) in a call to open(2).
PTM指pseudoterminal master,PTS指pseudoterminal slave。
/dev/ptmx (UNIX 98 master clone device),所有主設備對應的設備文件都指向/dev/ptmx
/dev/pts/* (UNIX 98 slave devices)
ssh或Telnet登錄遠程主機時的終端就是pty,運行tty查看:
$ tty /dev/pts/11
偽終端是一對虛擬的字符設備,linux內核使用一種符合tty線規程(line discipline)的雙向管道連接偽終端的主從設備。主設備上的任何寫入操作都會反映到從設備上,反之亦然。從設備上的應用進程可以像使用傳統終端一樣讀取來自主設備上應用程序的輸入,以及向主設備應用輸出信息。偽終端從設備應用通常是主設備應用的子進程,主應用打開一對偽終端並fork一個子進程,然后子進程打開並使用從設備。
1.3 串行端口終端
與計算機串行端口(RS-232)連接的終端設備,對應的設備文件名稱為/dev/tty+類型+設備編號,如/dev/ttyS0,S表示設備類型,0為指定類型下的設備編號。這里的串行端口可以是通過硬件或軟件模擬的,如USB轉串口,虛擬串口。
2. 多個虛擬終端
Unix 98偽終端使用流程如下:
- 使用posix_openpt打開master;
- 使用grantpt設置調用進程為slave的屬主並允許其對slave進行讀寫操作;
- 使用unlockpt對slave解鎖;
- 使用ptsname返回slave的設備名;
- 使用open打開slave設備並進行讀寫操作。
上述函數都來自glibc庫。偽終端編程更常用的API是openpty,直接實現了上述流程的所有步驟。login_tty函數用於實現在指定的終端上啟動登錄會話。forkpty函數整合了openpty、fork和 login_tty,在網絡服務程序可用於為新登錄用戶打開一對偽終端,並創建相應的會話子進程。
注意:使用opentty,login_pty和forkpty需要鏈接util庫。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pty.h> #include <utmp.h> #include <errno.h> #define SLAVE_DEV_NAME_MAX_LEN 128 #define PTY_BUFF_MAX_LEN 1024 int main(int argc, char *argv[]) { int mpty = 0, spty = 0; int rv = 0, n = 0; char spty_name[SLAVE_DEV_NAME_MAX_LEN]={0}; char buf[PTY_BUFF_MAX_LEN] = {0}; fd_set rfds; rv = openpty(&mpty, &spty, spty_name, NULL, NULL); if(-1 == rv){ perror("Failed to get a pty"); return -1; } printf("Get a pty pair, FD -- master[%d], slave[%d]\n", mpty, spty); printf("Slave name is %s\n", spty_name); FD_ZERO(&rfds); FD_SET(mpty, &rfds); while(1){ rv = select(mpty+1, &rfds, NULL, NULL, NULL); if(rv < 0){ perror("Failed to select"); return -1; } if(FD_ISSET(mpty, &rfds)){ n = read(mpty, buf, PTY_BUFF_MAX_LEN); if(n > 0){ // memset(buf+n, 0, PTY_BUFF_MAX_LEN-n); printf("recv [%d] bytes:[%s]\n", n, buf); } else if (n == 0){ printf("Slave closed\n"); break; } else { if(errno == EINTR){ continue; } perror("Failed to read the master\n"); return -1; } } } close(mpty); close(spty); return 0; }
編譯及運行:
gcc pty_test.c -o pty_test -lutil -Wall $ ./pty_test Get a pty pair, FD -- master[3], slave[4] Slave name is /dev/pts/6 recv [1] bytes:[1] recv [11] bytes:[hello world]
另一終端:
$ echo -n "1" > /dev/pts/6 $ echo -n "hello world" > /dev/pts/6
每次運行上述程序,生成一個虛擬終端口(slave),由此同一主機可運行多個虛擬終端口(slave)。可通過文件/proc/sys/kernel/pty/max查詢或修改偽終端數量。
$ cat /proc/sys/kernel/pty/max 4096
3. 遠程訪問串口
通過網絡遠程訪問串口,首先需要把串口虛擬化網絡端口,之后在網絡中的另外一個主機上通過Telnet等工具直接訪問該網絡端口,或者把網絡端口逆向為一個虛擬串口,進而通過minicom等工具進行訪問。
socat工具可以實現上述功能。如本地(虛擬串口)/dev/pts6,主機IP:192.168.134.144,主機端口54321,對端主機虛擬串口文件tty.virt001,可通過如下步驟測試。
主機1串口轉TCP端口:
sudo socat tcp-l:54321,reuseaddr,fork file:/dev/pts/6,waitlock=/var/run/ttypts.lock,clocal=1,cs8,nonblock=1,ixoff=0,ixon=0,ispeed=9600,ospeed=9600,raw,echo=0,crtscts=0
主機2將TCP端口轉虛擬串口:
sudo socat pty,link=/dev/tty.virt001 tcp:192.168.134.144:54321
主機2遠程訪問串口:
sudo minicom -D /dev/tty.virt001
或
telnet 192.168.134.144 54321
4. 虛擬終端雙向收發
上述程序測試示例中由於ptm與pts在一個程序中,沒有控制ptm的發送,不便於測試觀察,網上有程序實現用兩組虛擬終端中兩個slave配對,從而基於串口的雙向數據收發。
#!/usr/bin/env python3 #--coding = utf-8 -- import pty import os import select def mkpty(): master1, slave = pty.openpty() slaveName1 = os.ttyname(slave) master2, slave = pty.openpty() slaveName2 = os.ttyname(slave) print ('\nslave device names: ', slaveName1, slaveName2) return master1, master2 if __name__ == "__main__": master1, master2 = mkpty() while True: rl, wl, el = select.select([master1, master2], [], [], 1) for master in rl: data = os.read(master, 128) print ("read %d data:" %len(data)) if master == master1: os.write(master2, data) else : os.write(master1, data)
上述程序用python實現了兩個虛擬終端slave雙向收發。
兩個主機都可通過minicom雙向收發數據。
測試中唯一不足是接收端對換行不能正確處理,可以回車但不能換行,可能與minicom設置有關,編程處理應該無問題。
此外注意到python程序是一個一個字符處理的,並沒有按照換行符整行發送,不能正確換行可能也與python程序有關。
5. 常見的虛擬串口問題
1. linux下如何生成虛擬串口?
linux中有虛擬終端的概念即pty,pty是成對的邏輯終端設備(有兩個終端組成,支持雙向收發),linux系統調用原生支持生成虛擬終端。
無論是實體串口,還是虛擬串口,表現形式都是串口,在linux下都是通過termios訪問設置的。Ubuntu下cutecom圖形界面串口調試工具可以生成並測試虛擬串口。
windows下vspd軟件(Virtual Serial Port Driver)可以生成並測試虛擬串口。
2. 串口的遠程訪問?
實體串口或虛擬串口(虛擬終端)要想實現遠程訪問,需要將串口數據轉換到網絡端口,遠程通過網絡實現遠程訪問串口。常用的工具ncat、socat都可以實現此功能。
3. 3G或4G無線模塊怎樣實現的多串口訪問?
測試3G或4G模塊時看到模塊虛擬出多個串口,這是如何實現的呢?這些串口本身就是在模塊內部的虛擬串口,通過USB載體表現出來。
此和上述的網絡訪問虛擬串口不同,這里的虛擬串口的訪問借助USB協議,要求模塊實現USB協議來表征模塊本身實現的接口(多個虛擬串口)。
當主機訪問3G或4G模塊時,通過USB總線枚舉來找到模塊實現的串口功能;而3G或4G模塊內部,多串口可能是虛擬串口,也可能是實體串口(芯片),但需要實現USB協議。
參考:
- Linux終端簡介與pty編程
- 串口虛擬化:通過網絡訪問串口
- Linux下的虛擬終端(可用於在本機上模擬串口進行調試)
- Ubuntu 下使用虛擬串口進行開發測試
- linux下串口轉TCP/IP的終端服務器實現 通過nc實現