1. 程式人生 > >進程管理和終端驅動基本概念

進程管理和終端驅動基本概念

大型機 工程師 tin 每次 pad 設備 完成 wid first

一、前言

對於任何一種OS,終端部分的內容總是令人非常的痛苦和沮喪,GNU/linux也是如此。究其原因主要有兩個,一是終端驅動和終端相關的系統軟件承載了太多的內容:各種虛擬終端、 偽終端、串口通信、modem、printer等。其次可能是終端和信號處理、進程關系等耦合在一起加大了理解終端驅動的難度。本文的目標是希望能夠理清這些內容。

在第二章,本文會簡單介紹終端的一些基礎知識,這些知識在wowo的終端基本概念文檔中也有描述,我這裏也重復一次,加深印象(呵呵,其實這一章的內容一年前就寫了,後來暫停了,這次重新組織一下,發表出來)。隨後的第三章主要描述的是和進程管理相關的TTY(終端)的一些基礎知識,希望能夠幫助廣大人民群眾理解進程和終端驅動之間的交互關系。主要的概念包括:session、前臺進程組、controlling terminal、Job control等。最後一章是結合實際的應用場景來描述進程管理和終端驅動相關的概念。

二、終端基礎知識

本章分成兩個部分,第一節描述了終端的概念,其他小節則介紹了形形色色的終端驅動。

1、什麽是TTY,什麽是終端?

TTY是TeleTYpe的縮寫,直譯成中文就是電傳打字機。我們都熟悉telephone,phone本身是聽筒或者那些發聲或使用聲音的工具,tele的含義則是指距離較遠,需要通過電信號傳遞。同樣的概念可以應用到teletype:type是指打字或者是印字的機構,tele則說明打字和印字機構相距比較遠,需要通過電線傳遞信號。要理解Teletype這個概念需要回去過去的通信時代,最初發明的是電報,把message編成摩斯碼,通過操作員將長長短短的tone音發送出去,在接收端,也會設立一個操作員,收聽長長短短的tone音,還原message。這種通訊方式對人的要求很高,需要專門訓練才能上崗,這嚴重阻礙了廣大人民群眾對交換信息的需求,因此teletype這樣的機器被發明出來,它包括鍵盤模組、收發信息模組和字符打印模組(有些模組是機械的,因此,用現代語言,teletype也是一種機械電子設備)。發message時,需要不斷的按下字符鍵,這些字符會被自動發送到電線上。收message的時候,接收器能自動接收來對端的字符信號,並打印出相應的字符。

計算機系統發明是比較靠後的事情了,肯定是比teletype晚。計算機系統需要輸入和輸出設備,而teletype包括了鍵盤和printer,可以作為計算機系統的輸入和輸出設備,因此,早期的計算機使用teletype作為終端設備(有些teletype設備也支持穿孔紙帶作為輸入或者用穿孔的紙帶作為輸出)。隨著計算機工業的發展,teletype設備中的印字機構或者穿孔機構模塊被顯示器取代。其實,當teletype被應用到計算機系統中的時候,tty(teletype)這個術語已經不適用了(這時候tty沒有傳統的通信功能了,更多的是一個計算機系統的輸入輸出設備,也就是computer terminal),所以現在把tty稱為終端會比較合適。

時代總是向前的,不論你願不願意。實際上目前的年輕的工程師都不太可能真正使用到終端設備,因為它被淘汰了。我在大學期間的計算機課程都是使用一臺DEC VAX的大型機(mainframe computer),當然,我不是獨占,這臺大型機接出了30多臺終端,每個同學擁有一個終端。而終端設備會從每個同學那裏接收鍵盤輸入,並且將這些輸入通過串行通信的手段發送給VAX大型機,VAX大型機會處理每個用戶的鍵盤輸入和命令,然後輸出返回並顯示在每個終端的屏幕上。終端基本上都是text terminal,因此,主機和終端之間的串行通信都是基於字符的,終端收到字符後會顯示在屏幕上。當進入微機(microcomputer)時代,每臺主機都有自己的顯示設備和鍵盤設備(多謝大規模集成電路的發展),圖形顯示器顯示的都是點陣信息而不是基於字符的顯示,LCD和鍵盤也不是通過慢速的串行通信設備和主CPU系統相連,從這個角度看,目前我們接觸到的終端都不是真正的終端設備了,應該稱之”終端仿真設備“。例如:我們可以通過windows系統中的一個窗口加上鍵盤設備組成一個終端,登錄到本機或者遠程主機。

2、什麽是虛擬終端(Virtual Terminal)

雖然傳統的終端設備隨著時代的進步而消失了,但是我們仍然可以通過個人PC上的圖形顯示系統(SDRAM中的frame buffer加上LCD controller再加上display panel)和鍵盤來模擬傳統的字符終端設備(所以稱為虛擬終端設備)。整個系統的概念如下圖所示:

技術分享圖片

由於已經不存在物理的終端設備了,因此Vitual Terminal不可能直接和硬件打交道,它要操作Display和Keyboard的硬件必須要通過Framebuffer的驅動和Keyboard driver。

虛擬終端不是實際的物理終端設備,之所以稱為Virtual是因為可以在一個物理圖形終端(鍵盤加上顯示器)上同時運行若幹個虛擬終端。虛擬終端也稱為虛擬控制臺(Virtual console)。對於linux系統而言,內核支持64個虛擬終端。用戶可以通過Alt+Fx進行切換虛擬控制臺。目前,GNU/linux操作系統在啟動的時候支持6個虛擬終端,用戶用Alt+F1 ~ F6進行切換,Alt+F7將切換到圖形界面環境。一旦進入了圖形桌面環境,可以通過Ctrl+Alt+Fx切回到字符界面的虛擬終端。關於圖形界面下的終端相關的軟件架構圖我們會在後面的章節描述。

虛擬終端的主設備號是4,字符設備,前64個設備是虛擬控制臺設備,具體的次設備號分配如下:

4 char TTY devices

0 = /dev/tty0 Current virtual console
1 = /dev/tty1 First virtual console
...
63 = /dev/tty63 63rd virtual console

雖然內核支持了64個Virtual terminal設備,但是具體整個系統是否支持還是要看配置。一般而言,GNU/linux操作系統在啟動的時候支持6個虛擬終端(使用tty1~tty6)。次設備號等於0的tty設備比較特殊,代表當前的虛擬控制臺。如果不啟動GUI系統,那麽缺省tty0指向tty1,之後可以根據用戶的動作進行切換,但是無論如何,tty0指向了當前的Virtual terminal。

3、什麽是控制臺(Console)

對於linux系統而言,控制臺(console)或者系統控制臺(system console)也是一種終端設備,但是又有其特殊性,如下:

(1)可以支持single user mode進行登錄

(2)接收來自內核的日誌信息和告警信息

在linux系統中,我們一般將系統控制臺設備設定為其中一個虛擬終端(一般是當前的virtual terminal,也就是/dev/tty0),這樣,我們可以在某一個virtual terminal上看到系統的message。在嵌入式的環境中,我們一般會將系統控制臺設備設定為串口終端或者usb串口終端。

可以通過kernel啟動的command line來設定系統控制臺,例如:

root=/dev/mtdblock0 console=ttyS2,115200 mem=64M

通過這樣的設置可以選擇串口設備做為系統控制臺。

4、什麽是偽終端(pseudo terminal或者PTY)

在linux中,偽終端是在無法通過常規終端設備(顯示器和keyboard)登錄系統的情況下(例如本主機的顯示器和keyboard被GUI程序控制,疑惑本主機根本沒有顯示器和keyboard設備,只能通過網絡登錄),提供一種模擬終端操作的機制,它包括master和slave兩部分。Slave device的行為類似物理終端設備,master設備被進程用來從slave device讀出數據或者向slave device寫入數據。通過這樣的方法模擬了一個終端。那麽為什麽要有這樣的偽終端設備,又為什麽要有主設備和從設備配對呢?下面的圖片可以給出解釋:

技術分享圖片

當然,大部分的用戶在使用GNU/Linux系統的時候都是使用圖形用戶界面(GUI)系統,在這樣的系統裏,整個顯示屏和鍵盤(也就是傳統的虛擬終端設備)都在一個視窗管理進程的控制之下。從使用層面看,用戶一般都是啟動一個終端窗口程序來模擬命令行終端的場景。用戶可以啟動多個這樣的窗口,每個這樣的窗口都是一個進程,接收用戶輸入,期望和shell通過終端設備進行溝通。但是這時候,所有的窗口進程其實是無法打開並使用display+key board終端設備的(GUI管理進程控制了實際的物理終端資源),怎麽辦呢?這就要使用偽終端設備了。 每一對偽終端設備連接著顯示器上的一個終端窗口進程和一個shell進程。當視窗管理進程從鍵盤接收到一個字符時,該字符會被送到獲得當前焦點的那個終端窗口進程,而終端窗口進程會將改字符送往與之對應的偽終端"主設備"。因此,對於鍵盤輸入,視窗管理進程起著中轉、分發的作用。而寫入偽終端"主設備"一端的字符馬上就到達了其"從設備"一端,在那裏,對於與"從設備"關聯的shell進程來說,其動作就跟從普通終端設備讀入字符一模一樣了。另外一個方向的通信也是類似的,這裏就不再贅述了。

當然,和shell進程通過偽終端對進行連接的不一定非得是終端窗口進程,也可能是其他的進程,例如網絡服務進程,這時候就是網絡終端登錄的場景了,其結構圖也類似,大家可以自行補腦。

5、串口終端

串口終端好象沒有什麽好說的,大家都熟悉的很,這裏就順便說一下串口終端的設備節點吧,如下:


主設備號是4的字符設備是TTY devices,虛擬終端占據了0~63的minor設備號,之後的minor是串口設備使用
64 = /dev/ttyS0 First UART serial port
...
255 = /dev/ttyS191 192nd UART serial port

USB串口終端的主設備號是188的字符設備,這是主機側的USB serial converters devices,具體如下:
0 = /dev/ttyUSB0 First USB serial converter
1 = /dev/ttyUSB1 Second USB serial converter
...
主設備號是127的字符設備是gadget側的USB serial devices,具體如下:
0 = /dev/ttyGS0 First USB serial converter
1 = /dev/ttyGS1 Second USB serial converter
...

三、進程管理中和終端相關的概念

1、什麽是shell,什麽是登錄系統?

如果OS kernel是烏龜的身體,,那麽shell就是保護烏龜身體的外殼。shell提供訪問OS kernel服務的用戶接口,用戶通過shell可以控制整個系統的運行(例如文件移動、啟動進程,停止進程等)。目前的shell主要有兩種,一種就是大家熟悉的桌面環境,另外一種就是基於文本command line類型的,主要for業內人事使用。command line類型的shell雖然需要用戶記住很多復雜的命令和腳本格式等等,但是其效率非常高,速度非常快。GUI類型的shell操作簡單,用戶容易上手,但效率為人所詬病。

上面說過了,shell是人類訪問、控制計算機服務的接口,不過這個接口有些特殊,shell自己有自己的要求:用戶不能通過任意的設備和shell交互,必須是一個終端設備。我們現在網絡設備比較發達,可以通過網絡設備直接和shell通信嗎?不行,網絡設備不是終端設備,想要通過網絡設備訪問shell,必須通過偽終端(後面會講)。shell和人類交互的示意圖如下:

技術分享圖片

對於系統工程師,我們更關註command line類型的shell。在系統的啟動過程中,init進程會根據/etc/inittab中的設定進行系統初始化(這裏還是說的傳統的系統啟動過程,如果使用systemd那麽啟動過程將會不一樣,但是概念類似),和tty相關的內容包括:

1:2345:respawn:/sbin/getty 38400 tty1
2:23:respawn:/sbin/getty 38400 tty2
3:23:respawn:/sbin/getty 38400 tty3
4:23:respawn:/sbin/getty 38400 tty4
5:23:respawn:/sbin/getty 38400 tty5
6:23:respawn:/sbin/getty 38400 tty6

init進程會創建6個getty進程,這六個getty進程會分別監聽在tty1~tty6這六個虛擬終端上,等待用戶登錄。用戶登錄之後會根據用戶的配置文件(/etc/passwd)啟動指定shell程序(例如/bin/bash)。因此,shell其實就是一個命令解析器,運行在擁有計算資源的一側,通過tty驅動和對端tty設備(可能是物理的終端設備,也可能是模擬的)進行交互。

2、什麽是Job control

和終端一樣,Job control在現代操作系統中的需求已經不是那麽明顯,以至於很多工程師都不知道它的存在,不理解Job control相關的概念。在過去,計算機還是比較稀有的年代,每個工程師不可能擁有自己的計算機,工程師都是通過字符終端來登錄計算機系統(當然,那個年代沒有GUI系統),來分享共同的計算資源。在自己的終端界面上,任務一個個的串行執行,有沒有可能讓多個任務一起執行呢?(例如後臺運行科學運算相關的程序,前臺執行了小遊戲放松一下)這就是job control的概念了。Job control功能其實就是在一個terminal上可以支持多個job(也就是進程組,後面會介紹)的控制(啟動,結束,前後臺轉換等)。當然,在引入虛擬終端,特別是在GUI系統流行之後,Job control的需求已經弱化了,工程師在有多個任務需求的時候,可以多開一些虛擬終端,或者直接多開幾個Termianl窗口程序就OK了,但是,Job control是POSIX標準規定的一個feature,因此,各種操作系統仍然願意服從POSIX標準,這也使得job control這樣的“歷史功能”仍然存在現代的操作系統中。

如果不支持Job control,那麽登錄之後,可以通過終端設備和shell進行交互,執行Job A之後,用戶可以通過終端和該進程組(也就是Job A)進行交互,當執行完畢之後,終端的控制權又返回給shell,然後通過用戶在終端的輸入,可以順序執行Job B、Job C……在引入Job control概念之後,用戶可以並行執行各種任務,也就是說Job A、Job B、Job C……都可以並行執行,當然前臺任務(Job)只能有一個,所有其他的任務都是在後臺運行,用戶和該前臺任何進行交互。

通過上面的描述,我們已經了解到了,在用戶的操作下,多個Job可以並行執行,但是終端只有一個,而實際上每一個運行中的Job都是渴望和user進行交互,而要達成這個目標,其實核心要解決的問題就是終端的使用問題:

(1)終端的輸入要遞交給誰?

(2)各個進程是否能夠向終端輸出?

對於第一個問題,答案比較簡單,用戶在終端的輸入當然是遞交給前臺Job了,但是那些後臺任務需要終端輸入的時候怎麽辦呢?這時候就需要任務控制(Job control)了,這裏會涉及下面若幹的動作:

(1)後臺Job(後者說是後臺進程組)對終端進行讀訪問的時候,終端驅動會發送一個SIGTTIN的信號給相應的後臺進程組。

(2)該後臺進程組的所有的進程收到SIGTTIN的信號會進入stop狀態

(3)做為父進程的shell程序可以捕獲該後臺進程組的狀態變化(wait或者waitpid),知道它已經進入stopped狀態

(4)用戶可以通過shell命令fg講該後臺進程組轉入前臺,從而使得它能夠通過終端和用戶進行交互。這時候,原來的前臺任務則轉入後臺執行。

對於輸入,我們嚴格限定了一個進程組做為接收者,但是對於輸出的要求則沒有那麽嚴格,你可以有兩種選擇:

(1)前臺任務和後臺任務都可以向終端輸出,當然,大家都輸出,我估計這時候輸出屏幕有些混亂,呵呵~~~

(2)前臺任務可以輸出,但是後臺任務不可以。如果發生了後臺任務對終端的寫訪問動作,終端驅動將發送SIGTOUT信號給相應的後臺進程組。

除此之外,為了支持job control,終端驅動還需要支持信號相關的特殊字符,包括:

a) Suspend key(缺省control-Z),對應SIGTSTP信號

b) Quit key(缺省control-\),對應SIGQUIT信號

c) Interrupt key(缺省control-C),對應SIGINT信號,

當終端驅動收到用戶輸入的這些特殊字符的時候,會轉換成相應的信號,發送給session中(後面會介紹該術語)的前臺進程組,當然,前提是該終端是session的控制終端。因此,為了讓那麽多的進程組(Job)合理的、有序的使用終端,我們需要軟件模塊協同工作,具體包括:

(1)支持Job control的shell

(2)終端驅動需要支持Job control

(3)內核必須支持Job control的信號

3、什麽是進程組?

簡單的說,進程組就是一組進程的集合,當然,我們不會無緣無故的把他們組合起來,一定是有共同的特性,一方面,這些進程屬於同一個Job,來自終端的信號可以送達這一進程組的每一個成員(是為了job control)。此外,我們可以通過killpg接口向一個進程組發送信號。任何一個進程都不是獨立存在的,一定是屬於某個進程組的,當fork的時候,該進程歸入創建者進程所屬的進程組,父進程在子進程exec之前可以設定子進程的進程組。比如說shell程序屬於進程組A,當用戶輸入aaa程序fork一個新進程的時候(是shell創建了該進程,沒有&符號,是前臺進程),aaa進程則歸屬與進程組A(和shell程序屬於同一個進程組),在exec aaa之前,會將其放入一個新的前臺進程組,自己隱居幕後。如果用戶輸入aaa &,整個過程類似之前的描述,只不過shell保持在前臺進程組。

通過上面的描述,很多工程師可能認為Shell可以同時運行一個前臺進程和任意多個後臺進程,其實不然,shell其實是以進程組(也就是Job了)來控制前臺和後臺的。我們給一個具體的例子:用戶通過終端輸入的一組命令行,命令之間通過管道連接,這些命令將會形成一個進程組,例如下面的命令:

$ proc1 | proc2 &
$ proc3 | proc4 | proc5

對於第一行命令,shell創建了一個新的後臺進程組,同時會創建兩個進程proc1和proc2,並把這兩個進程放入那個新創建的後臺進程組(不能訪問終端)。執行第二行命令的時候,shell創建了proc3、proc4和proc5三個進程,並把這三個進程加入shell新創建的前臺進程組(可以訪問終端)。

如何標識進程組呢?這裏借用了進程ID來標識進程組。對於任何一個進程組,總有一個最開始的加入者,第一個加入者其實就是該進程組的創始者,我們稱之為該進程組的Leader進程,也就是進程ID等於進程組ID的那個進程,或者說,我們用process leader的進程ID來做為process group的ID。當然,隨著程序的執行,可能會有進程加入該進程組,也可能程序執行完畢,退出該進程組,對於進程組而言,即便是process group leader進程退出了,process group仍然可以存在,其生命周期直到進程組中最後一個進程終止, 或加入其他進程組為止。

通過getpgid接口函數,我們可以獲取一個進程的進程組ID。通過setpgid接口函數,我們可以加入或者新建一個進程組。當然,進程組也不是任意創建或者加入的,一個進程只能控制自己或者它的子進程的進程組。而一旦子進程執行了exec函數之後,其父進程也無法通過setpgid控制該子進程的進程組。還需要註意的是:調用setpgid的進程、設定process group的進程和指定的process group必須在一個sesssion中。最後需要說的一點是:根據POSIX標準,我們不能修改session leader的進程組ID。

4、什麽是Session?

從字面上看,session其實就是用戶和計算機之間的一次對話,通俗的概念是這樣的:你想使用計算機資源,當然不能隨隨便便的使用,需要召開一個會議,參加的會議雙方分別是用戶和計算機,用戶把自己的想法需求告訴計算機,而計算機接收了用戶的輸入並把結果返回給用戶。就這樣用戶和計算機之間一來一去,不斷進行交互,直到會議結束。用戶和計算機如何交互呢?用戶是通過終端設備和計算機交互,而代表計算機和用戶交互則是shell進程。每次當發生這種用戶和計算機交互過程的時候,操作都會創建一個session,用來管理本次用戶和計算機之間的交互。

如果支持job control,那麽用戶和計算機之間的session可能並行執行多個Job,而Job其實就是進程組的抽象,因此,session其實就是進程組的容器,其中容納了一個前臺進程組(只能有一個)和若幹個後臺進程組(當然,沒有連接的控制終端的情況下,session也可能沒有前臺進程組,由若幹後臺進程組組成)。創建session的場景有兩個:

(1)一次登錄會形成一個session(我們稱之login session,大部分的場景都是描述login session的)。

(2)系統的daemon進程會在各自的session中(我們稱之daemon session)。

無論哪一個場景,都是通過setsid函數建立一個新的session(註意:調用該函數的進程不能是進程組的leader),由於創建了session,該進程也就成為這個新創建session的leader。之後,session leader創建的子進程,以及進程組也都屬於該session。為何process group leader不能調用setsid來創建session呢?我們假設沒有這個限制,process group leader(ID等於A)通過setsid把把自己加入另外一個新建的session,有了session就一定要有進程組,創建了sesssion的那個process group leader就成了該session中的第一個進程組leader,標識這個新建的進程組的ID就是A。而其他進程組中的成員仍然在舊的session中,在舊的session中仍然存在A進程組,這樣一個進程組A的成員,部分屬於新的session,部分屬於舊的session,這是不符合session-process group2級拓撲結構的。我了滿足這個要求,我們一般會先fork,然後讓父進程退出,子進程執行setsid,fork之後,子進程不可能是進程組leader,因此滿足上面的條件。

因此,setsid創建了新的session,同時也創建了新的process group,創建session的那個進程ID被用來標識該process group。新創建的sesssion沒有控制終端,如果調用setsid的進程有控制終端,那麽調用setsid之後,新的session和那個控制終端失去連接關系。如何標識session呢?我們往往使用session leader的process ID來標識。

5、控制終端(controlling terminal)

通過上面的描述,我們已經知道了:為了進行job control,我們把若幹的進程組放入到了session這個容器進行管控。但是,用戶如何來管控呢?必須要建立一個連接的管道,我們把和session關聯的那個終端稱為控制終端,把建立與控制終端連接的session首進程(session Leader)叫做控制進程(controlling process)。session可以有一個控制終端,不能有多個,當然也可以沒有。而一個終端也只能和一個session對應,不能和多個session連接。

對於login session,我們登錄的那個終端設備基本上就是該session的controlling terminal,而shell程序就是該session的leader,也是該session的controlling process。之所以會有controlling process的概念主要用來在終端設備斷開的時候(例如網絡登錄的場景下,網線被拔出),終端驅動會把hangup signal送到對應session的controlling process。

session可以有一個前臺進程組和若幹個後臺進程組(很好理解,占有控制終端的就是前臺,沒有的就是後臺。當然可以一個前臺進程組都沒有,例如daemon session)。對於前臺進程組,共同占有控制終端。在控制終端鍵入ctrl+c產生終止信號(或者其他可以產生信號的特殊字符組合)會被遞交給前臺進程組所有的進程(不會遞交給後臺進程)。雖然終端被前臺進程組掌管,但是通過shell、內核和終端驅動的交互,後臺進程組可以被推入到前臺,也就是說:所有的session內的進程組可以分時復用該controlling terminal。

當創建一個session的時候,往往沒有controlling terminal,當session leader open了一個終端設備,除非在open的時候指明O_NOCTTY,否則該terminal就會稱為該session的controlling terminal,當然,該終端也不能是其他session的controlling terminal,否則就會有一個控制終端對應兩個session的狀況發生了。一旦擁有了控制終端之後,session leader的子進程都會繼承這個controlling terminal。除了上面說的隱含式的設定,程序也可以通過ioctl來顯示的配置(TIOCSCTTY)controlling terminal,或者通過TIOCNOTTY來解除該終端和session的聯系,變成一個普通的終端。

對於login session,session leader會建立和終端的連接,同時把標準輸入、輸出和錯誤定向到該終端。因此,對於後續使用shell運行的普通程序而言,我們不需要直接訪問控制終端,一般是直接訪問標準輸入、輸出和錯誤。如果的確是有需要(例如程序的標準輸入、輸出被重定向了),那麽可以通過打開/dev/tty設備節點(major=5,minor=0)來訪問控制終端,/dev/tty就是當前進程的控制終端。

四、應用

1、系統初始化

在系統啟動的時候,swapper進程(或者稱之idle進程)的信息整理如下:

PID PGID SID TTY
0 0 0 NO

swapper進程在啟動過程中會創建非常多的內核線程,這些內核線程的job control相關的信息如下:

PID PGID SID TTY
x 0 0 NO

由此可見,所有內核線程都是在一個session中,屬於一個進程組,隨著內核線程的不斷創建,其process ID從2開始,不斷遞增。Process ID等於1的那個進程保留給了init進程,這也是內核空間轉去用戶空間的接口,剛開始,init進程繼承了swapper進程的sid和pgid,不過,init進程會在啟動過程中調用setsid,從而創建新的session和process group,基本信息如下:

PID PGID SID TTY
1 1 1 NO

2、虛擬終端登錄

對於linux而言,/etc/inittab包括了登錄信息,也就是說,init進程需要在哪些終端設備上執行fork動作,並執行(exec)getty程序,因此getty擁有自己的Process ID,並且是一個普通進程,不是session leader,也不是process group leader,在getty程序中會調用setsid,創建新的session和process group,同時,該程序會打開做為參數傳遞給它的終端設備(對應這個場景應該是ttyx),因此ttyx這個虛擬終端就成了該session的controlling terminal,gettty進程也就是controlling process。一旦正確的打開了終端設備,文件描述符0、1、2都定向到該終端設備。完成這些動作之後,通過標準輸出向用戶提示登錄信息(例如login:)。在用戶輸入用戶名之後,getty進程已經完成其歷史使命,它會調用exec加載login程序(PID、PGID、SID都沒有變化)。

login進程最重要的任務當然是鑒權了,如果鑒權失敗,那麽login進程退出執行,而其父進程(也就是init進程)會偵聽該事件並重復執行getty。如果鑒權成功,那麽login進程會fork子進程並執行shell。剛開始,login進程和shell進程屬於一個session,同時也屬於同一個前臺進程組,共享同一個虛擬終端,也就是說兩個進程的controlling terminal都是指向該登錄使用的那個虛擬終端。shell進程並非池中之物,最終還是要和login進程分道揚鑣的。shell進程會執行setpgid來創建一個新的進程組,然後調用tcsetpgrp將shell所在的進程組設定為前臺進程組。

註:上面的描述是基於Debian 8系統描述的。

2、shell執行命令

在虛擬終端登陸後,我們可以執行下面的命令:

#ps –eo stat,comm,sid,pid,pgid,tty | grep tty | more

shell執行這一條命令的動作示意圖如下:

技術分享圖片

在/dev/tty1完成登錄之後,系統存在兩個進程組,前臺進程組是shell,後臺進程組是login,兩個進程組都屬於一個session,所有進程的控制終端都是虛擬終端tty1。執行上述命令之後,shell創建了3個進程ps、grep和more,並將這三個進程放到一個新創建的進程組中(ps是進程組leader),同時把該進程組推向前臺,shell自己隱居幕後。一旦程序執行完畢,後臺的shell收到子進程的信號後,又把自己推到前臺來,等待用戶輸入的下一條命令。

我們假設用戶輸入下面的命令:

#ps –eo stat,comm,sid,pid,pgid,tty | grep tty | more &

&符號其實就是後臺執行的命令,shell執行這條命令的過程和上圖類似,不過這時候並不把新建立的進程組推到前臺,shell自己仍然是前臺進程組。由於有pipe,輸出信息沿著ps—>grep--->more的路徑來到了more進程,more進程輸出到標準輸出,也就是tty1這個虛擬終端的時候,悲劇發生了,後臺進程組不能write控制終端,從而引發了SIGTOUT被發送到該後臺進程組的每一個進程組,因此,ps,grep和more都進入stop狀態。這時候只要在shell執行fg的操作,就可以把ps那個後臺進程組推到前臺,這時候虛擬終端的屏幕才會打印出相關的進程信息。

3、GUI系統

TODO (對GUI系統不熟悉,只能暫時TODO了,哈哈)

五、參考文獻

1、Unix高級環境編程

2、POSIX標準

3、The Linux Programming Interface - A Linux and UNIX System Programming Handbook

進程管理和終端驅動基本概念