Linux控制台重定向方法
- 2020 年 1 月 19 日
- 筆記
本文介紹一種通過文件描述符重定向終端輸入/輸出的方法。
一、背景
一些嵌入式設備,一般都會留有調試串口,經由RS232/485標準與PC的COM口相連,將打印輸出在PC上顯示,並可以接收PC端的輸入,如下圖所示:

設備出厂部署後,不方便接調試串口,查看設備輸出就變得比較困難,不利於問題定位。 如果設備具有聯網能力,我們可以通過telnet或者ssh登錄到設備上,進行遠程調試。 這時候就面臨一個問題:如何把設備的打印信息顯示出來?
常見做法有以下幾種:
- 如果設備有日誌文件,可以直接讀取日誌文件。但是一般情況下並非所有打印都寫日誌,這會導致部分內容看不到;另外,如果日誌文件有加密,就不利於實時查看。
- 類似dmesg做個日誌緩存,直接讀取緩存日誌。這需要消耗額外的內存,並且要修改現有的打印系統。
- hook打印接口,把打印內容向telent/ssh終端分發一份。這也需修改現有的打印機制。
以上幾種做法各有優劣,下面介紹一種通過文件描述符重定向終端輸入/輸出的方法。
二、原理
下圖展示了Linux系統中標準輸入/輸出(STDIN/STDOUT)與控制終端的關係,其中ttyS0即串口:

用戶通過telnet或者ssh登錄後,會動態生成一個控制終端(比如/dev/pts/0),如下圖所示:

我們是否可以把標準輸入/輸出(STDIN/STDOUT)從ttyS0解綁,重新映射到pts0上呢?答案是肯定的。
如下圖所示,重新綁定後,打印就可以直接輸出到telnet或者ssh對應的控制台,經由網絡傳輸到PC上;同時,也可以從PC上接收輸入(如果應用程序監聽了STDIN,PC上的輸入就可以直接被應用程序讀取到,不重定向的話是收不到的)。
註:在某個控制終端執行的命令(啟動的程序),默認綁定當前終端,所以正常情況下telnet或者ssh到設備後,執行ls
等命令,輸出都是在當前終端。

三、實現
具體實現代碼可以參考https://github.com/sigusr1/redirect_console。
如下圖所示,應用程序中需要集成一個Server,用來接收Client發送來的重定向指令。

相關過程說明如下:
- 在telnet或者ssh對應的終端上,執行可執行程序Client。
- Client調用系統函數ttyname獲取當前控制終端名稱(一般為/dev/pts/0),並將相關信息發送給Server。
- Client和Server之間的通信方式可以是本地域套接字,也可以是本地網絡套接字。不過應用程序不能直接監聽STDIN,因為默認只能收到串口終端上的輸入,telnet/ssh終端上的輸入它收不到。
- Server收到重定向指令後,執行下面的代碼段,將STDOUT重定向到telnet/ssh對應的控制終端(/dev/pts/0)。 int fd_out = open("/dev/pts/0", O_WRONLY); if (fd_out < 0) { printf("Fail to open /dev/pts/0.error:%s", strerror(errno)); return; } dup2(fd_out, STDOUT_FILENO); close(fd_out);
- 同理,STDIN和STDERR也可以這樣重定向。
- 在重定向前,可以通過下面的代碼將標準輸入/輸出綁定的終端備份下,這樣執行
dup2(fd_out_bak, STDOUT_FILENO)
就可以還原原來的終端,達到以下效果:一個telnet已經把打印拉過來了,當它不想用的時候,發送還原指令,打印就又跑到原來的終端那邊了。 fd_out_bak = dup(STDOUT_FILENO); fd_in_bak = dup(STDIN_FILENO);
四、優劣點分析
優點:
- 利用Linux系統特性實現,不需要修改原日誌模塊功能,基本不影響原系統性能
- STDIN/STDOUT/STDERR均可重定向,方便實時查看、交互,並且可恢復到原終端
缺點:
- 依賴Linux系統,其他系統(比如一些RTOS)不一定適用
- 需要集成一個client、server的本地通信框架
- 只能重定向某個進程的輸入/輸出,其他進程、內核的打印無法重定向(直接執行
cat /proc/kmsg
命令可以遠程實時查看內核打印)