Linux系統下fork函式的實驗
實驗 Linux下程序管理
一、實驗目的
1.掌握vim編譯器
2.掌握gcc編譯器的使用
3.瞭解fork()程式
二、實驗工具與裝置
1.實驗裝置:計算機(帶CD-ROM)一臺。
三、實驗預備知識
1. vim的編輯器
使用語法:Vim <被編輯的檔名>
Vim有三種模式:命令模式、插入模式、最後行模式。
命令模式:
剛啟動Vim後,就處於該模式。在此模式下,允許用Vim的子命
令來編輯檔案或轉移到其它模式。如:
x命令:刪除游標上面的字元。
方向鍵:移動游標。
i命令:進入插入模式,可在當前游標處插入字元。
a命令:進入插入模式,可在當前游標後插入字元。
R命令:從當前游標處開始替換文字。
r命令:替換當前游標處字元。
~命令:對當前游標處字母進行大小寫轉換。
dd命令:刪除游標所在行。
dw命令:刪除游標所在處字。
h命令:游標左移。
l命令:游標右移。
k命令:游標上移。
j命令:游標下移。
:命令:進入最後一行模式。
插入模式:
此模式下允許輸入文字,用回車鍵換行,Esc 進入命令模式。
最後一行模式:
w命令:將檔案存檔,但不退出。
wq命令:將檔案存檔,並退出。
q! 命令:不儲存檔案並退出。
r命令:將另一個檔案內容插入當前游標處。
2.gcc編譯器
Unix 上使用的C 語言編譯器cc,在Linux上的派生就是gcc。在使用vim編寫完源程
序之後,返回到shell下面,使用gcc對源程式進行編譯的命令是:
gcc 源程式
其中,“源程式”即為你編寫的以.c 為副檔名的C 語言原始碼檔案。
如果原始碼沒有語法錯誤,使用以上命令編譯,會在當前目錄下生成一個名為a.out
的可執行檔案。如果原始碼有語法錯誤,則不會生成任何檔案,gcc編譯器會在shell中
提示你錯誤的地點和型別。
也可以使用以下方法編譯原始碼檔案,生成自命名的可執行檔案:
gcc 原始檔–o 自命名的檔名
執行當前目錄下的編譯生成的可執行檔案,使用以下格式:
./可執行檔名
當使用gcc編譯你寫的程式原始碼的時候,可能會因為原始碼存在語法錯誤,編譯
無法進行下去,這時候,就可以使用偵錯程式gdb來對程式進行除錯。
3. gdb 簡介
Linux 包含了一個叫gdb 的GNU 除錯程式。gdb 是一個用來除錯C 和C++ 程式
的強力偵錯程式。它使你能在程式執行時觀察程式的內部結構和記憶體的使用情況。以下是
gdb 所提供的一些功能:
l 能監視你程式中變數的值。
l 能設定斷點以使程式在指定的程式碼行上停止執行。
l 能一行行的執行你的程式碼。
當啟動gdb 後,能在命令列上指定很多的選項。你也可以以下面的方式來執行 gdb:
gdb <fname>
當你用這種方式執行gdb ,就能直接指定想要除錯的程式。這將告訴gdb 裝入名為
fname 的可執行檔案。你也可以用gdb 去檢查一個因程式異常終止而產生的core 文
件,或者與一個正在執行的程式相連。你可以參考gdb 指南頁或在命令列上鍵入gdb
-h 得到一個有關這些選項的說明的簡單列表。
為除錯編譯程式碼(Compiling Code for Debugging)。為了使gdb 正常工作, 你必須
使你的程式在編譯時包含除錯資訊。除錯資訊包含你程式裡的每個變數的型別和在可執
行檔案裡的地址對映以及原始碼的行號。gdb 利用這些資訊使原始碼和機器碼相關聯。
在編譯時用-g 選項開啟除錯選項。
4.fork() 函式說明
pid_t fork(void)
fork()會產生一個新的子程序。該函式包含於標頭檔案unistd.h 中。其子程序會複製
父程序的資料與堆疊空間,並繼承父程序的使用者程式碼、組程式碼、環境變數、已開啟的文
件程式碼、工作目錄和資源限制等。Linux 使用copy-on-write(COW)技術,只有當其中一
程序試圖修改欲複製的空間時才會做真正的複製動作,由於這些繼承的資訊是複製而
來,並非指相同的記憶體空間,因此子程序對這些變數的修改和父程序並不會同步。此
外,子程序不會繼承父程序的檔案鎖定和未處理的訊號。注意,Linux不保證子程序會比
父程序先執行或晚執行,因此編寫程式時要留意死鎖或競爭條件的發生。
返回值,如果fork()呼叫成功則在父程序會返回新建立的子程序程式碼(PID),而在新
建立的子程序中則返回0。如果fork() 失敗則直接返回-1,失敗原因存於errno中。失
敗的原因有三個:
1) 系統記憶體不夠;
2) 程序表滿(容量一般為200~400);
3) 使用者的子程序太多(一般不超過25個)。
錯誤程式碼:EAGAIN 記憶體不足;ENOMEM 記憶體不足,無法配置核心所需的資料結構空
間。
四、實驗內容和步驟
實驗一:
在Linux環境下,用c語言程式設計,使用系統呼叫fork建立程序多個子程序。
(1)在終端裡輸入vim test.c,啟動vi.
(2)按a或者i進入插入模式,在裡面輸入以下程式碼。
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t fpid; //fpid表示fork函式返回的值
fpid=fork();
printf(" you print me\n");
return 0;
}
(3)按Esc鍵進入命令模式,輸入:和wq 進行儲存退出vim.
(4)用gcc編譯器編譯該程式: gcc –o test test.c –ggdb
(5)執行剛剛編譯的程式:./a
或者進行調式執行
用gcc編譯器編譯該程式: gcc –o test test.c –ggdb
除錯剛剛編譯的程式:gdb a
要求:
- 請說出執行這個程式後,將一共執行幾個程序。
- 觀察執行結果,並給出分析與解釋。
執行結果:
分析:
由於呼叫fork()函式產生了一個新的程序,子程序複製了父程序的執行環境(上下文),由於兩個程序的執行,所以產生了兩條輸出語句。
實驗二:
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t fpid; //fpid表示fork函式返回的值
int count=0;
fpid=fork();
if (fpid < 0)
printf("error infork!");
else if (fpid == 0) {
printf("i am the childprocess, my process id is %d/n",getpid());
count++;
}
else {
printf("i am the parentprocess, my process id is %d/n",getpid());
count++;
}
printf("統計結果是: %d/n",count);
return 0;
}
要求:
觀察執行結果,並給出分析與解釋
實驗結果:
分析:
在主函式中,fork函式建立了一個子程序,建立成功後在main函式中返回子程序的pid,執行else語句,count++;子程序中,返回0執行elseif 語句,count++;由於二者的count是各自獨立的,因此都是1。
實驗三:
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t pid1;
pid_t pid2;
pid1=fork();
pid2=fork();
printf("pid1:%d,pid2:%d\n",pid1,pid2);
return 0;
}
這個程式執行後,一共將執行4個程序。這4個程序並沒有嚴格的區分先後順序。
實驗結果:
分析(以結果一為例):
Main函式 :建立了兩個子程序,因此會在main函式中返回這兩個程序的pid,main 函式順序執行,打印出這兩個pid(617,618)
子程序1:由於執行了pid1=fork(); 因此,在新建立的子程序1中fpid1的值 為0,由於複製了main函式的上下文,子程序1順序執行 pid2=fork(); 建立之後fpid2在子程序1中的值為子子程序的pid, 因此列印(0,619);
子子程序(在子程序1中建立的程序):子子程序複製了子程序的所有執行環境,這其中包括fpid1的值,而在子程序1中fpid1的值為0,且fpid2在子子程序中的值為0,由此打印出(0,0)
子程序2:建立完成子程序1之後,main函式繼續執行pid2=fork();由此建立子程序2,子程序2複製了main函式的所有執行環境,這其中包括fpid1的值,且fpid2在子程序2中的值為0,因此列印(617,0);
實驗四:
首先分析一下程式碼執行時其輸出結果有哪幾種可能性,按照實驗1步驟編譯除錯觀察其實際輸出情況,比較兩者的差異,分析其中的原因。
void main ()
{ int x=5;
if( fork( ) )
{
x+=30;
printf (“%d\n”,x);
}
else
printf(“%d\n”,x);
printf((“%d\n”,x);
}
實驗結果:
、
分析:
在main函式中,成功建立了一個子程序之後,返回一個非0的pid,執行else語句,列印了兩個5;
在子程序中fork函式返回了0,因此執行if語句,x+=30,會列印兩個30,由於父子程序執行順序不確定,因此結果會有四種。但總是兩個5,兩個30.
實驗五:
int main()
{ int size=1;
int i=0;
printf("i son/pa ppid pid fpid/n");
//ppid指當前程序的父程序pid
//pid指當前程序的pid,
//fpid指fork返回給當前程序的值
for(i=0;i<size;i++){
pid_t fpid=fork();
if(fpid==0)
printf("%d child %4d %4d%4d/n",i,getppid(),getpid(),fpid);
else
printf("%d parent %4d %4d%4d/n",i,getppid(),getpid(),fpid);
}
return 0;
}
要求:
1、觀察執行結果,並給出分析與解釋。
2、如果size=2的時候會有什麼結果,為什麼。
實驗結果:
分析:
由於迴圈只執行一次,在main函式中,fork函式返回值不為0故執行else部分,fpid返回值為子程序pid;在子程序中,fork函式返回值為1,故執行if部分,fpid值為0;
當size為2的時候的實驗結果:
分析
五、實驗總結
1.寫出實驗報告。
2. 利用vim對linux文字檔案進行編輯。
3.編寫fork()程式,並利用gcc和gdb進行編譯、除錯和執行。