fork + exec
fork是低層次的系統調用,通過復制父進程來創建子進程。
fork的行為
fork用來拷貝當前進程,生成一個基本完全一樣的子進程。
my $pid=fork();
如果fork成功:
- 則表示成功創建子進程,這時會有兩條執行路線:繼續執行父進程、執行子進程
- fork成功時,會返回兩個值:對父進程返回子進程的pid,對子進程返回0
如果fork失敗,將對父進程返回undef,並設置錯誤信息。fork失敗的可能原因有:
- 內核內存不夠,無法申請內存來fork
- 達到了允許的最大進程數量(進程數上限)
- 達到了rlimit限制的某種資源上限
例如:
#!/usr/bin/perl
#
use 5.010;
my $pid=fork();
say $pid,"======";
執行該程序,將返回兩行數據:
62620======
0======
其中第一行輸出是父進程輸出的,第二行是子進程輸出的。
雖然這里父進程先輸出,但fork成功之后,父、子進程並沒有執行的先后順序,也可能cpu會先調度到上子進程去執行。
注意上圖中子進程部分只畫了"say"那行語句,但實際上子進程是完全復制父進程的,子進程也可以有say前面的那段語句(比如那個fork語句),但由於父進程的狀態已經執行完了fork,所以子進程也是從fork語句之后開始執行的,fork語句之前的語句對於子進程來說是透明的。而且按照寫時復制的技術,子進程用不到它所以不會復制fork前面的代碼(注:也就是說子進程和父進程是共享代碼的)。
更復雜一點的例子:
#!/usr/bin/perl
#
use 5.010;
print "id1: ",$pid,"\n";
my $pid=fork;
print "id2: ",$pid,"\n";
if(!$pid){ # ==0 exec
say "child process: ",$pid;
}
waitpid($pid,0);
say "parent process: ",$pid;
首先perl進程輸出id1。然后fork一個子進程,這時有兩條執行路線。假如fork后先執行父進程,則:
- 此父進程將輸出id2
- 然后判斷pid的值,因為fork返回給父進程的的pid變量值為子進程的進程號,所以不會等於0,於是if判斷不通過
- 繼續執行waitpid(),它將等待子進程執行結束,以下是子進程的執行過程:
- 子進程首先輸出id2
- 然后判斷
$pid
,由於fork返回給子進程的pid變量值為0,所以if判斷通過,於是子進程輸出"child process" - 繼續執行waitpid(),由於
$pid=0
,waitpid()的等待pid為0時,表示等待以自己為leader進程的進程組中的其它進程,由於沒有進程組,所以waitpid失敗 - 繼續執行,輸出"parent process"
- 子進程執行完畢
- 父進程的waitpid()等待子進程執行完畢,繼續向下執行
- 父進程輸出"parent process"
假如fork之后,先執行子進程,且還先把子進程執行完了,cpu才調度到父進程,則也沒有影響,子進程執行完畢后,遲早會調度到父進程,而父進程的waitpid($pid)
已經沒有子進程了,於是waitpid()失敗(返回-1),繼續向下執行。
顯然,上面fork的代碼有一些問題。由於fork創建子進程之后。父、子進程都繼續執行,且執行的先后順序不定。所以:
- 在fork之后,應該緊接着判斷是否是子進程,避免有些在操作父子中都執行
- 在父進程中等待子進程
- 在子進程中加入執行完后就退出子進程的動作,免得執行本該父進程執行的動作
大概代碼如下:
my $pid=fork;
unless($pid){ # 判斷子進程的語句緊跟着fork
CODE1;
exit; # 要讓子進程退出
}
waitpid($pid,0); # 要等待子進程
CODE2;
fork和exec結合
一般fork和exec會一起用,fork用來創建新的子進程,exec啟動一個程序替代當前子進程並在子進程結束時退出子進程。
例如system "date"
命令,替換為低層次的fork+exec+waitpid。
defined(my $pid=fork) or die "Cannot fork: $!";
unless($pid){
# 進入到這里執行的,表示進入子進程
exec 'date'; # exec正確執行時,執行完后將結束子進程
die "cannot exec date: $!"; # exec啟動失敗時,將執行die來結束子進程
}
# 這里的表示是父進程
waitpid($pid,0);
system、exec、fork等的區別
第一個人解釋:
- exec replaces the current process with another one.
- system runs another program, wait for its completion.
- fork copies the current process; both the original and the copy continue from the same point.
- pipe sets up a pipe between two handles.
- syscall makes a low-level system call.
- eval executes (the string form will first compile) a piece of Perl code.
第二個人解釋:
- exec is used to execute the given process by replacing the current process. If the given process get executed successfully then exec will not return the value. exec returns the value in case of failure only.
- System is also doing the same thing as exec but system returns value in both success and failure cases. And parent process waits for the child process to complete. System() runs the command through a shell,while exec() runs the command directly.
- fork is used to create a new process(child process). And it is returning the PID of child to parent and zero to child if the fork is successful. The difference between the fork and exec is exec replaces the current process but fork doesn't.
- pipe is used for communicating between two processes. We can use both named and nameless pipes. It returns open a pair of pipes. In one end we can write. And in another end we can read the content.
- syscall is used to call the system call which is specified as a first argument. Remaining elements are the arguments to the system call.