詳解控制檯與終端
有趣的問題:Linux 的終端,控制檯,TTY, PTY 究竟是什麼?它們與程式有什麼關係?
歷史回顧:控制檯
- 控制檯是一個直接控制裝置的皮膚(屬於裝置的一部分)
- 計算機裝置的控制檯:按鍵 & 指示燈 (鍵盤 & 顯示器)
- 早期的電子計算機必然有一個控制檯
歷史回顧:終端 (Terminal)
- 終端是一臺獨立於計算機的機器,是能夠用來和計算機進行互動的裝置
TTY -- 即:TeleType Writer 電傳打字機,一種終端裝置
歷史發展程式。。。
- 電傳打字機已經淘汰
- 計算機上的輸入裝置和顯示裝置從主機獨立出來
- 控制檯與終端的物理表現形式逐漸趨近
- 計算機開始支援多工處理
- 。。。
終端與程式
- TTY 演變為 Linux 中的抽象概念,對於程式而言,TTY是一種輸入輸出裝置
虛擬終端與偽終端
各種終端型別
型別 | 說明 | |
虛擬終端(Virtual Terminal) | 將這一套鍵盤和顯示器對映為 6 個終端裝置 | /dev/tty1~tty6 tty0指代當前使用的終端 (CLI虛擬出) |
串列埠終端(Serial Port Terminal) | 將連線到視窗的外設看看作終端裝置 | /dev/ttyS1,... |
終端模擬器(Terminal Emulator) | 終端模擬程式(廣義) / 核心模擬模組(俠義) | Putty, MobaXTERM, 核心模組,偽終端 |
偽終端(Pseudo Terminal) | 執行在使用者模式的終端模擬程式,分為主裝置(pty master)和從裝置(pty slave) | /dev/ptmx(主), /dev/pts/3(從), ... |
核心終端模擬器
偽終端模型
偽終端(gnome-terminal)
偽終端
tiansong@tiansong:~$ pstree -A -p -s $$
systemd(1)---systemd(968)---gnome-terminal(1885)---bash(1973)---pstree(2052)
虛擬終端
ubuntu 由 GUI 切換到 CLI : CTRL + ALT +F3
tiansong@tiansong:~$ pstree -A -p -s $$
systemd(1)---login(2064)---bash(2118)---pstree(2125)
ubuntu 由 CLI 切換到 GUI : CTRL + ALT +F1
偽終端程式設計原理
站在 shell 角度的總結:
1. shell 面對的只有 TTY 裝置,而 TTY 是 Linux 中的抽象概念,因此需要一個具體的模組來支援這個抽象的概念
2. 對於虛擬終端,支援這個概念的模組就是終端模擬器(核心模組,執行於核心模式),可以直接使用鍵盤、顯示卡驅動進行輸入輸出
3. 對於偽終端,建立主、從裝置,其中 shell 程式對接的是從裝置,主裝置對接使用者程式 gnome-terminal (本質是 GUI 應用程式)
偽終端程式設計(master)
- 建立 PTY 主從裝置:
master = posix_openpt(O_RDWR);
獲取主裝置許可權:
grantpt(master);
// 獲取裝置使用許可權unlockpt(master);
// 解鎖裝置,為讀寫做準備
讀寫主裝置
c = read(master, &rx, 1);
len = write(master, txbuf, strlen(txbuf));
偽終端程式設計(slave)
- 開啟 PTY 從裝置,
slave = open(path_to_slave, O_RDWR);
讀寫裝置:
write(slave, "Delphi\r", 7);
read(slave, buf, sizeof(buf) - 1);
實戰偽終端程式設計
master.c
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
char rx = 0;
char rxbuf[128] = {0};
char txbuf[256] = {0};
int master = 0;
int c = 0;
int i = 0;
master = posix_openpt(O_RDWR); // 連線主裝置,等同於 gnome-terminal
if (master > 0) {
grantpt(master);
unlockpt(master);
printf("Slave: %s\n", ptsname(master));
while ((c = read(master, &rx, 1)) == 1) {
if (rx == '\r') {
rxbuf[i] = 0;
sprintf(txbuf, "from slave: %s\r", rxbuf); // 等同於螢幕輸出
write(master, txbuf, strlen(txbuf)); // 等同於鍵盤輸入
} else {
rxbuf[i++] = rx;
}
}
}
else {
printf("create pty error...\n");
}
return 0;
}
tiansong@tiansong:~/Desktop/linux$ gcc master.c -o master.out
tiansong@tiansong:~/Desktop/linux$ ./master.out // 不終止
Slave: /dev/pts/0
slave.c
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int slave = open(argv[1], O_RDWR); // 等同於 shell
if (slave > 0) {
char buf[256] = {0};
char *data = "D.T.Software\r";
int len = strlen(data);
write(slave, data, len);
sleep(1);
len = read(slave, buf, sizeof(buf) - 1);
buf[(len > 0) ? len : 0] = 0;
printf("Read: %s\n", buf); // system(...)
close(slave);
}
else {
}
return 0;
}
tiansong@tiansong:~/Desktop/linux$ gcc slave.c -o slave.out
tiansong@tiansong:~/Desktop/linux$ ./slave.out /dev/pts/0 // 新終端執行
Read: from slave: D.T.Software
終端必然與程式關聯才有意義!那麼,程式之間除了父子關係,是否還有其它關係?