實驗目的
- 加深對進程概念的理解,明確進程和程序的區別。
- 掌握Linux系統中的進程創建,管理和刪除等操作。
- 熟悉使用Linux下的命令和工具,如
man, find, grep, whereis, ps, pgrep, kill, ptree, top, vim, gcc,gdb
, 管道等。
實驗內容
task1
要求: 打開一個vi進程。通過ps
命令以及選擇合適的參數,只顯示名字為vi的進程。尋找vi進程的父進程,直到init進程為止。記錄過程中所有進程的ID和父進程ID。將得到的進程樹和由pstree``命令的得到的進程樹進行比較。
1.首先下載安裝vim,通過sudo apt-get install vim-get
實現
2.查詢進程可使用
pgrep
命令來搜索進程名,通過xargs
獲取到命令的輸出并傳遞給另外的命令。所以通過pgrep vi | xqrrg ps –l
可看到vi進程以及他的相關進程信息。之后通過ps
命令逐個查找父進程,直到找到根進程。由圖中信息可知:vi進程的進程號為:19905 其父進程進程號為:19894,以此類推可查到根進程。
進程關系表:
父進程 | 進程號 | 進程名 |
---|---|---|
19894 | 19905 | vi |
19889 | 19894 | bash |
1523 | 19889 | gnome |
1505 | 1523 | upstart |
913 | 1505 | lightdm |
1 | 913 | ligh |
0 | 1 | init spl |
其中:
F:flag,表示程序的旗標,4表示使用者為超級用戶,1表示使用者為用戶
S:status,表示這個程序的狀態
UID:表示執行者身份
PID:表示進程ID
PPID:表示父進程的ID
C:表示使用的CPU資源百分比
PRI:priority,表示進程的執行優先權,越小表示越早被執行
NI:nice,表示進程可以被執行的優先級的修正數值
ADDR:表示內核函數
SZ:表示占用內存的大小
WCHAN:表示程序是否正在運作當中。“-”表示正在運行
TTY:表示終端機的位置CMD:表示所下達指令的名稱
3.通過pstree
命令得到進程樹。
<img src="https://img-blog.csdnimg.cn/20190316151202650.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzc1Mjk1Mw==,size_16,color_FFFFFF,t_70" width="60%">
在進程樹中可以在systemd – lightdm – lightdm – upstart – gnome-terminal – bash – vi 找到vi所在的位置。其中,systemd是一組系統管理命令,取代了init命令成為系統的第一個進程,gnome-terminal為終端的管理進程。
問題總結:
1. ps、pstree及top命令辨析
ps
:平時比較常用的查看進程的命令,ps 是顯示瞬間進程的狀態,并不動態變化;如果想對進程運行時間監控,需要用 top 工具。
pstree
:顯示進程狀態樹,pstree命令可以列出當前的進程,以及它們的樹狀結構。
使用ps命令得到的數據精確,但數據龐大,不易掌握系統整體狀況。pstree命令清晰明了。它能將當前的執行程序以樹狀結構顯示。
top
:用來顯示系統當前的進程狀況。是一個動態顯示過程,即可以通過用戶按鍵來不斷刷新當前狀態。如果在前臺執行該命令,它將獨占前臺,直到用戶終止該程序為止。
task2
要求: 編寫程序,首先使用fork系統調用,創建子進程。在父進程中繼續執行空循環操作;在子進程中調用exec打開vi編輯器。然后在另外一個終端中,通過ps –Al
命令、ps aux
或者top
等命令,查看vi進程及其父進程的運行狀態,理解每個參數所表達的意義。選擇合適的命令參數,對所有進程按照cpu占用率排序。
- 編寫test.c程序,實現使用fork系統調用,創建子進程。在父進程中繼續執行空循環操作;在子進程中調用exec打開vi編輯器。
[圖片上傳失敗...(image-696048-1553237084704)] -
編譯運行test.c程序
image - 在另一個終端使用命令
ps -AL
查看現有進程信息。
image
在進程列表中可以找到vi進程以及該進程的狀態信息
image - 運用命令
ps aux
查看內存中運行的程序:
image
其中:
USER:進程使用者賬號;
PID:進程ID號;
CPU:進程使用掉的CPU資源百分比;
MEM:進程所占用的物理內存百分比;
VSZ:進程使用掉的虛擬內存量 (Kbytes)
RSS :進程占用的固定的內存量 (Kbytes)
TTY :進程所在終端機(若與終端機無關,則顯示 “?”,tty1-tty6 是本機上面的登入者程序,若為 pts/0 等等,則表示為由網絡連接進主機的程序。
STAT:程序目前的狀態:R (正在運作) S (正在睡眠) T(正在偵測或停止) Z (zombie(疆尸) 程序)
START:進程被觸發啟動的時間;
TIME :進程實際使用 CPU 運作的時間。
COMMAND:進程所屬的指令
- 接著使用
top
命令將所有進程按照cpu占有率進行排序,根據排序結果可見,我創建的test程序因含有死循環fork所以幾乎完全占用了CPU。
image
task3
要求: 使用fork系統調用,創建如下進程樹,并使每個進程輸出自己的ID和父進程的ID。觀察進程的執行順序和運行狀態的變化。
-
根據題目要求編寫程序
image -
多次運行得到如下輸出。可以看出P1為P2和P3的父進程,P2位P4和P5的父進程,與實驗要求的進程樹相同。
image -
程序流程簡圖
image
問題總結:
1. linux中的fork()函數
一個進程包括代碼、數據和分配給進程的資源。fork()函數通過系統調用創建與原來進程幾乎完全相同的進程,相當于克隆了一個自己。但如果初始參數或者傳入的變量不同,兩個進程也可以做不同的事。
一個進程調用fork()函數后,系統先給新的進程分配資源,例如存儲數據和代碼的空間。然后把原來的進程的所有值都復制到新的新進程中,只有少數值與原來的進程的值不同。
2. fork()函數特點
fork函數被調用一次,能夠返回兩次,它可能有三種不同的返回值:
1)在父進程中,fork返回新創建子進程的進程ID;
2)在子進程中,fork返回0;
3)如果出現錯誤,fork返回一個負值;
3. 父進程先結束的問題
在實驗過程中會遇到執行程序時子進程與的父進程號不符合“父子關系”。
后發現是因為所有的子進程在getppid()的時候父進程已經結束了,得到的是upstart進程的pid=1523。所以在代碼中加入
sleep(1);
,讓父進程等待一秒鐘再結束,由此可以正確得到進程間的“父子關系”。
task4
要求: 修改上述進程樹中的進程,使得所有進程都循環輸出自己的ID和父進程的ID。然后終止p2進程(分別采用kill -9 、自己正常退出exit()、段錯誤退出),觀察p1、p3、p4、p5進程的運行狀態和其他相關參數有何改變。
-
由于需要循環輸出自己的ID和父進程的ID,所以程序邏輯發生變化,重新編寫程序并運行如下。
image - 運行該程序,可見循環輸出符合進程樹的進程關系。
image
通過pstree -p
查看程序進程樹,可見符合原題給出的進程樹。
image - 終止p2進程
- 采用
kill -9
,刪除進程p2(2077),再次查看進程樹,可見原進程樹上僅有原來的p1,p2,p3。
image
原來為p2子進程的p4和p5現在成為upstart的子進程。
image
通過命令ps -aux | grep 2077
查看p2進程的信息,可見進程p2的狀態已變成Z(僵死 a defunct (”zombie”) process),成為僵尸進程。
在這里插入圖片描述 - 正常退出exit()
改寫代碼,使得p2通過exit(0);
函數退出。
image
運行可見到循環的第二輪p2進程便不在,原本是p2子進程的p3和p4成為upstart的子進程。
image
查看進程樹,可見原進程樹有p1,p2,p3進程。
image
同樣對p2的進程信息查詢,得到結果顯示p2進程狀態為Z,成為僵尸進程。
image -
段錯誤退出
采用訪問不存在的內存地址的方式產生段錯誤退出。
image
編譯運行該程序并查看進程樹,可得到與上文相類似的結果
image
原進程樹僅有p1、p2、p3進程,原為p2子進程的p4,p5成為upstart的子進程。
image
image
查詢p2進程信息,可見p2進程狀態變為Z,成為僵尸進程。
image
綜合以上可知,這三種方式終止p2進程都會使得p2進程成為僵尸進程。
問題總結:
1 . 辨析STAT表示的行程的狀態
linux的進程有5種狀態:
D:不可中斷 uninterruptible sleep (usually IO)
R:運行 runnable (on run queue)
S:中斷 sleeping
T:停止 traced or stopped
Z:僵死 a defunct (”zombie”) process
2. 段錯誤的產生原因
段錯誤是指訪問的內存超出了系統給這個程序所設定的內存空間。
- 訪問不存在的內存地址
void main() {
int *ptr = NULL;
*ptr = 0;
}
- 訪問系統保護的內存地址
void main() {
int *ptr = (int *)0;
*ptr = 100;
}
- 訪問只讀的內存地址
void main() {
char *ptr = "test";
strcpy(ptr, "TEST");
}
- 棧溢出
void main() {
main();
}
實驗總結
本次實驗,我對于fork()函數有了更加深入的認識,也更加熟練地使用linux系統。通過對于進程信息的創建,查詢,終止等操作,對于“進程”的概念也有了更加感性的認識,更好的理解了課上所講的抽象化的概念。遇到不懂的問題和概念,通過上網查資料,咨詢老師同學,學會了更多的專業知識。希望之后能夠繼續加油,做到真正的理解實驗原理。