Sự khác biệt giữa fork
và là exec
gì?
fork
về cơ bản là nhân bản: O
Sự khác biệt giữa fork
và là exec
gì?
fork
về cơ bản là nhân bản: O
Câu trả lời:
Việc sử dụng fork
vàexec
thể hiện tinh thần của UNIX ở chỗ nó cung cấp một cách rất đơn giản để bắt đầu các quy trình mới.
Cuộc fork
gọi về cơ bản tạo ra một bản sao của quy trình hiện tại, giống hệt nhau trong gần như mọi phương diện. Không phải mọi thứ đều được sao chép (ví dụ: giới hạn tài nguyên trong một số triển khai) nhưng ý tưởng là tạo ra một bản sao càng gần càng tốt.
Quá trình mới (con) nhận được ID tiến trình khác nhau (PID) và có PID của quy trình cũ (cha mẹ) là PID chính của nó (PPID). Bởi vì hai quy trình hiện đang chạy chính xác cùng một mã, nên chúng có thể biết đó là mã trả về nào fork
- đứa trẻ nhận được 0, cha mẹ nhận được PID của đứa trẻ. Đây là tất cả, tất nhiên, giả sửfork
cuộc gọi hoạt động - nếu không, không có con nào được tạo và cha mẹ nhận được mã lỗi.
Cuộc exec
gọi là một cách cơ bản để thay thế toàn bộ quy trình hiện tại bằng một chương trình mới. Nó tải chương trình vào không gian quy trình hiện tại và chạy nó từ điểm vào.
Vì vậy, fork
và exec
thường được sử dụng theo trình tự để có được một chương trình mới chạy như một phần tử con của một quy trình hiện tại. Shell thường làm điều này bất cứ khi nào bạn cố chạy một chương trình như find
- shell forks, sau đó đứa trẻ tảifind
chương trình vào bộ nhớ, thiết lập tất cả các đối số dòng lệnh, I / O tiêu chuẩn, v.v.
Nhưng chúng không bắt buộc phải được sử dụng cùng nhau. Nó hoàn toàn chấp nhận được đối với một chương trình fork
mà không cần exec
ing, ví dụ, chương trình chứa cả mã cha và mã con (bạn cần cẩn thận với những gì bạn làm, mỗi lần thực hiện có thể có các hạn chế). Điều này đã được sử dụng khá nhiều (và vẫn là) cho các trình tiện ích chỉ đơn giản là nghe trên cổng TCP và fork
bản sao của chính chúng để xử lý một yêu cầu cụ thể trong khi cha mẹ quay lại nghe.
Tương tự, các chương trình biết rằng chúng đã kết thúc và chỉ muốn chạy một chương trình khác không cần fork
, exec
sau đó wait
cho trẻ. Họ chỉ có thể tải trẻ trực tiếp vào không gian xử lý của họ.
Một số triển khai UNIX có tối ưu hóa fork
sử dụng cái mà chúng gọi là copy-on-write. Đây là một mẹo để trì hoãn việc sao chép không gian xử lý fork
cho đến khi chương trình cố gắng thay đổi một cái gì đó trong không gian đó. Điều này hữu ích cho những chương trình chỉ sử dụng fork
và không phải exec
ở chỗ chúng không phải sao chép toàn bộ không gian quy trình.
Nếu cái exec
được gọi là sau fork
(và đây là những gì xảy ra chủ yếu), điều đó gây ra một ghi vào không gian quy trình và sau đó nó được sao chép cho quy trình con.
Lưu ý rằng có một gia đình toàn bộ exec
cuộc gọi ( execl
, execle
, execve
và vân vân) nhưngexec
trong bối cảnh ở đây có nghĩa bất kỳ trong số họ.
Sơ đồ sau minh họa fork/exec
hoạt động điển hình trong đó bash
shell được sử dụng để liệt kê một thư mục bằng ls
lệnh:
+--------+
| pid=7 |
| ppid=4 |
| bash |
+--------+
|
| calls fork
V
+--------+ +--------+
| pid=7 | forks | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash | | bash |
+--------+ +--------+
| |
| waits for pid 22 | calls exec to run ls
| V
| +--------+
| | pid=22 |
| | ppid=7 |
| | ls |
V +--------+
+--------+ |
| pid=7 | | exits
| ppid=4 | <---------------+
| bash |
+--------+
|
| continues
V
fork()
chia quá trình hiện tại thành hai quy trình. Hay nói cách khác, chương trình dễ nghĩ tuyến tính của bạn đột nhiên trở thành hai chương trình riêng biệt chạy một đoạn mã:
int pid = fork();
if (pid == 0)
{
printf("I'm the child");
}
else
{
printf("I'm the parent, my child is %i", pid);
// here we can kill the child, but that's not very parently of us
}
Điều này có thể thổi vào tâm trí của bạn. Bây giờ bạn có một đoạn mã với trạng thái khá giống nhau được thực thi bởi hai quy trình. Quá trình con kế thừa tất cả mã và bộ nhớ của quy trình vừa tạo ra nó, bao gồm bắt đầu từ nơi fork()
cuộc gọi vừa kết thúc. Sự khác biệt duy nhất là fork()
mã trả về để cho bạn biết nếu bạn là cha mẹ hoặc con. Nếu bạn là cha mẹ, giá trị trả về là id của con.
exec
Dễ nắm bắt hơn một chút, bạn chỉ cần yêu exec
cầu thực thi một quy trình bằng cách sử dụng đích thực thi và bạn không có hai quy trình chạy cùng một mã hoặc kế thừa cùng một trạng thái. Giống như @Steve Hawkins nói, exec
có thể được sử dụng sau khi bạn fork
thực thi trong quy trình hiện tại mà mục tiêu thực thi được.
pid < 0
và fork()
cuộc gọi thất bại
Tôi nghĩ rằng một số khái niệm từ "Lập trình Unix nâng cao" của Marc Rochkind rất hữu ích trong việc hiểu các vai trò khác nhau của fork()
/ exec()
, đặc biệt đối với ai đó đã sử dụng CreateProcess()
mô hình Windows :
Một chương trình là một tập hợp các lệnh và dữ liệu được lưu giữ trong một tập tin thường xuyên trên đĩa. (từ 1.1.2 Chương trình, Quy trình và Chủ đề)
.
Để chạy một chương trình, hạt nhân trước tiên được yêu cầu tạo một quy trình mới , đây là một môi trường trong đó một chương trình thực thi. (cũng từ 1.1.2 Chương trình, Quy trình và Chủ đề)
.
Không thể hiểu được các cuộc gọi hệ thống thực thi hoặc ngã ba mà không hiểu đầy đủ sự khác biệt giữa một quy trình và một chương trình. Nếu các điều khoản này là mới đối với bạn, bạn có thể muốn quay lại và xem lại Phần 1.1.2. Nếu bạn đã sẵn sàng tiến hành ngay bây giờ, chúng tôi sẽ tóm tắt sự khác biệt trong một câu: Một quy trình là một môi trường thực thi bao gồm các phân đoạn hướng dẫn, dữ liệu người dùng và dữ liệu hệ thống, cũng như nhiều tài nguyên khác có được khi chạy , trong khi đó một chương trình là một tệp chứa các hướng dẫn và dữ liệu được sử dụng để khởi tạo các phân đoạn dữ liệu hướng dẫn và dữ liệu người dùng của một quy trình. (từ 5.3
exec
Cuộc gọi hệ thống)
Khi bạn hiểu được sự khác biệt giữa chương trình và quy trình, hành vi fork()
và exec()
chức năng có thể được tóm tắt là:
fork()
tạo một bản sao của quy trình hiện tạiexec()
thay thế chương trình trong quy trình hiện tại bằng chương trình khác(đây thực chất là một phiên bản 'cho người giả' đơn giản hơn về câu trả lời chi tiết hơn nhiều của paxdiablo )
Fork tạo ra một bản sao của một quá trình gọi. thường theo cấu trúc
int cpid = fork( );
if (cpid = = 0)
{
//child code
exit(0);
}
//parent code
wait(cpid);
// end
(đối với văn bản quy trình con (mã), dữ liệu, ngăn xếp giống như quy trình gọi) quy trình con thực thi mã trong khối if.
EXEC thay thế quy trình hiện tại bằng mã, dữ liệu, ngăn xếp của quy trình mới. thường theo cấu trúc
int cpid = fork( );
if (cpid = = 0)
{
//child code
exec(foo);
exit(0);
}
//parent code
wait(cpid);
// end
(sau khi exec gọi unix kernel sẽ xóa văn bản xử lý con, dữ liệu, ngăn xếp và điền vào văn bản / dữ liệu liên quan đến quá trình foo), do đó tiến trình con có mã khác (mã foo {không giống cha mẹ})
Chúng được sử dụng cùng nhau để tạo ra một quy trình con mới. Đầu tiên, gọi fork
tạo một bản sao của quy trình hiện tại (quy trình con). Sau đó, exec
được gọi từ bên trong tiến trình con để "thay thế" bản sao của quy trình cha mẹ bằng quy trình mới.
Quá trình diễn ra như thế này:
child = fork(); //Fork returns a PID for the parent process, or 0 for the child, or -1 for Fail
if (child < 0) {
std::cout << "Failed to fork GUI process...Exiting" << std::endl;
exit (-1);
} else if (child == 0) { // This is the Child Process
// Call one of the "exec" functions to create the child process
execvp (argv[0], const_cast<char**>(argv));
} else { // This is the Parent Process
//Continue executing parent process
}
fork () tạo một bản sao của quy trình hiện tại, với việc thực thi trong đứa trẻ mới bắt đầu từ ngay sau lệnh gọi fork (). Sau ngã ba (), chúng giống hệt nhau, ngoại trừ giá trị trả về của hàm fork (). (RTFM để biết thêm chi tiết.) Hai quy trình sau đó có thể phân kỳ hơn nữa, với một quy trình không thể can thiệp vào bên kia, ngoại trừ có thể thông qua bất kỳ xử lý tệp được chia sẻ nào.
exec () thay thế tiến trình hiện tại bằng một tiến trình mới. Nó không liên quan gì đến fork (), ngoại trừ việc exec () thường theo fork () khi điều muốn là khởi chạy một tiến trình con khác, thay vì thay thế tiến trình hiện tại.
Sự khác biệt chính giữa fork()
và exec()
là,
Cuộc fork()
gọi hệ thống tạo một bản sao của chương trình hiện đang chạy. Chương trình ban đầu tiếp tục thực thi với dòng mã tiếp theo sau lệnh gọi hàm fork (). Bản sao cũng bắt đầu thực hiện ở dòng mã tiếp theo. Hãy xem đoạn mã sau mà tôi nhận được từ http://timmurphy.org/2014/04/26/USE-fork-in-cc-a-minimum-usiness-example/
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
printf("--beginning of program\n");
int counter = 0;
pid_t pid = fork();
if (pid == 0)
{
// child process
int i = 0;
for (; i < 5; ++i)
{
printf("child process: counter=%d\n", ++counter);
}
}
else if (pid > 0)
{
// parent process
int j = 0;
for (; j < 5; ++j)
{
printf("parent process: counter=%d\n", ++counter);
}
}
else
{
// fork failed
printf("fork() failed!\n");
return 1;
}
printf("--end of program--\n");
return 0;
}
Chương trình này khai báo một biến đếm, được đặt thành 0, trước khi fork()
ing. Sau cuộc gọi rẽ nhánh, chúng tôi có hai quá trình chạy song song, cả hai đều tăng phiên bản bộ đếm của riêng chúng. Mỗi quá trình sẽ chạy để hoàn thành và thoát. Bởi vì các quy trình chạy song song, chúng tôi không có cách nào để biết cái nào sẽ hoàn thành trước. Chạy chương trình này sẽ in một cái gì đó tương tự như những gì được hiển thị bên dưới, mặc dù kết quả có thể thay đổi từ lần chạy này sang lần chạy tiếp theo.
--beginning of program
parent process: counter=1
parent process: counter=2
parent process: counter=3
child process: counter=1
parent process: counter=4
child process: counter=2
parent process: counter=5
child process: counter=3
--end of program--
child process: counter=4
child process: counter=5
--end of program--
Họ exec()
các cuộc gọi hệ thống thay thế mã hiện đang thực thi của một tiến trình bằng một đoạn mã khác. Quá trình giữ lại PID của nó nhưng nó trở thành một chương trình mới. Ví dụ, hãy xem xét mã sau đây:
#include <stdio.h>
#include <unistd.h>
main() {
char program[80],*args[3];
int i;
printf("Ready to exec()...\n");
strcpy(program,"date");
args[0]="date";
args[1]="-u";
args[2]=NULL;
i=execvp(program,args);
printf("i=%d ... did it work?\n",i);
}
Chương trình này gọi execvp()
hàm để thay thế mã của nó bằng chương trình ngày. Nếu mã được lưu trữ trong một tệp có tên exec1.c, thì việc thực thi nó sẽ tạo ra đầu ra sau:
Ready to exec()...
Tue Jul 15 20:17:53 UTC 2008
Chương trình xuất ra dòng Ready to exec (). . . Và sau khi gọi hàm execvp (), thay thế mã của nó bằng chương trình ngày. Lưu ý rằng dòng -. . . nó đã hoạt động chưa được hiển thị, vì tại thời điểm đó, mã đã được thay thế. Thay vào đó, chúng ta thấy đầu ra của việc thực hiện ―date -u.‖
Nó tạo ra một bản sao của quá trình chạy. Quá trình đang chạy được gọi là tiến trình cha mẹ & quá trình mới được tạo được gọi là tiến trình con . Cách để phân biệt hai loại này là bằng cách nhìn vào giá trị được trả về:
fork()
trả về mã định danh quy trình (pid) của tiến trình con trong cha mẹ
fork()
trả về 0 ở trẻ.
exec()
:
Nó bắt đầu một quy trình mới trong một quy trình. Nó tải một chương trình mới vào quy trình hiện tại, thay thế chương trình hiện có.
fork()
+ exec()
:
Khi khởi chạy một chương trình mới trước tiên fork()
, tạo ra một quy trình mới, và sau đó exec()
(tức là tải vào bộ nhớ và thực thi) chương trình nhị phân của chương trình được cho là chạy.
int main( void )
{
int pid = fork();
if ( pid == 0 )
{
execvp( "find", argv );
}
//Put the parent to sleep for 2 sec,let the child finished executing
wait( 2 );
return 0;
}
Ví dụ điển hình để hiểu khái niệm fork()
và exec()
khái niệm là shell , chương trình thông dịch lệnh mà người dùng thường thực thi sau khi đăng nhập vào hệ thống. Shell diễn giải từ đầu tiên của dòng lệnh dưới dạng lệnh tên
Đối với nhiều lệnh, các vỏ dĩa và quá trình con giám đốc điều hành các lệnh liên quan đến tên đối xử với những lời còn lại trên dòng lệnh như tham số cho lệnh.
Các vỏ cho phép ba loại lệnh. Đầu tiên, một lệnh có thể là một tệp thực thi có chứa mã đối tượng được tạo bằng cách biên dịch mã nguồn (ví dụ một chương trình C). Thứ hai, một lệnh có thể là một tệp thực thi có chứa một chuỗi các dòng lệnh shell. Cuối cùng, một lệnh có thể là một lệnh shell bên trong. (Thay vì một tệp thực thi ex-> cd , ls , v.v.)