Trong bài viết Linuxhint này, bạn sẽ tìm hiểu cách sử dụng các hàm fork() và exec() để tạo, chạy hoặc thay thế một quy trình bằng một quy trình khác. Chúng ta sẽ xem mô tả về hai hàm này và giải thích cú pháp cũng như phương thức gọi của chúng. Chúng ta cũng sẽ xem xét một ví dụ thực tế ngắn gọn về từng chức năng trong số hai chức năng này. Sau đó, chúng tôi sẽ giải thích cách sử dụng kết hợp fork() và execv() để tạo một quy trình và thay thế nó bằng một quy trình khác.
Hàm Fork() trong ngôn ngữ C
Hàm fork() tạo bản sao của quá trình gọi. Mặc dù tiến trình con là bản sao của tiến trình cha nhưng chúng không chia sẻ một số thuộc tính nhất định như vùng bộ nhớ được phân bổ, PID, v.v. Tiếp theo, hãy xem cú pháp của hàm fork():
Hàm fork() trả về PID của tiến trình con là kết quả của tiến trình cha, trong khi lệnh gọi tương tự không ảnh hưởng đến tiến trình con và kết quả là trả về 0. Cơ chế này cho phép chúng ta thực thi mã khác nhau trong hai quy trình thông qua điều kiện “if” trong đó điều kiện là giá trị trả về của fork(). Hãy cùng xem khái niệm sau:
nếu (ngã ba() ==0)
{
mã cho tiến trình con
khác
{
mã cho tiến trình cha
Bằng cách này, tiến trình cha thực thi mã nằm giữa dấu ngoặc nhọn của câu lệnh “else”, trong khi tiến trình con thực thi mã nằm trong câu lệnh “if”.
Hãy xem điều này với một ví dụ đơn giản. Đoạn mã sau mà chúng ta thấy bao gồm hai vòng lặp vô hạn. Trong tiến trình cha, chương trình rơi vào vòng lặp tương ứng với câu lệnh “else” hiển thị thông báo “Viết cho tiến trình cha”, trong khi ở tiến trình con được tạo bởi fork() rơi vào câu lệnh “if” hiển thị thông báo “Viết cho tiến trình con”.
#include
#include
void main(){
nếu (ngã ba() ==0)
{
trong khi (1){
printf ("Viết theo tiến trình con \n");
ngủ(5);}
khác
{
trong khi (1){
printf ("Ghi theo tiến trình cha \n");
ngủ(2);}
}
Hình ảnh sau đây cho thấy việc biên dịch và thực thi mã này. Như đã thấy trong bảng điều khiển lệnh, mỗi quy trình thực thi mã khác nhau:

Hàm ExecXXX() trong ngôn ngữ C
Nhóm hàm execXXX() thay thế một tiến trình đang chạy bằng một tiến trình mới. Hình ảnh của quy trình mới được sao chép vào vùng bộ nhớ được phân bổ cho quy trình đang được thay thế, bảo tồn PID và tài nguyên được phân bổ của nó, cùng với những thứ khác.
Các hàm trong nhóm này được xác định trong tiêu đề “unistd.h” sử dụng các phương thức gọi khác nhau tùy thuộc vào đầu vào của chúng và thuộc loại “variadic”, vì vậy chúng có thể chuyển danh sách đối số hoặc con trỏ không xác định từ quy trình cũ sang quy trình mới. Tiếp theo, hãy xem cú pháp của từng hàm.
int execl(const char *path, const char *arg, ...(char *) NULL );
int execlp(const char *file, const char *arg, ... (char *) NULL );
int execle(const char *path, const char *arg, ... , (char *) NULL, char * const envp[] );
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
Các hàm execl(), execle() và execv() sử dụng một con trỏ làm đối số đầu vào đầu tiên của chúng cho một chuỗi chứa đường dẫn tuyệt đối của tệp thực thi của quy trình mới, trong khi các hàm execlp(), execvp() và execvpe() sử dụng tên của tệp trong thư mục hiện tại. Đầu vào thứ hai là các đối số mà bạn chuyển sang quy trình mới. Đây phải là chuỗi const char *arg hoặc danh sách các con trỏ tới chuỗi char *const argv[].
Bây giờ, hãy xem một ví dụ sử dụng hàm execv() để thay thế một tiến trình và chuyển các đối số đầu vào từ chương trình này sang chương trình khác.
Để làm điều này, chúng tôi tạo hai mã rất đơn giản. Một là tiến trình cha gọi hàm execv() để thực thi tiến trình con. Khi hàm execv() khởi động tiến trình con, nó chuyển cho nó hai đối số đầu vào dưới dạng một chuỗi mà tiến trình con sẽ truy xuất và hiển thị trên shell.
Tiến trình con
Tiến trình con là một đoạn mã đơn giản in thông báo “Tôi là tiến trình con”, lấy đối số đầu vào được gửi bởi tiến trình cha và hiển thị chúng trên shell. Đây là mã cho tiến trình con:
#include
#include
#include
int main(int argc, char *argv[])
{
printf ("Tôi là tiến trình con\n\n");
printf ("Đối số 1:%s\n", argv[1]);
printf ("Đối số 2:%s\n", argv[2]);
}
Chúng tôi biên dịch mã này và lưu sản phẩm của nó trong “Tài liệu” dưới tên “con” với phần mở rộng “.bin” như sau:
~$ gcc Documents/child.c -o Documents/child.bin
Bằng cách này, chúng tôi lưu tệp thực thi con trong “Tài liệu”. Đường dẫn của tệp thực thi này là đường dẫn đối số đầu vào khi gọi execv() trong quy trình gốc.
Quy trình gốc
Tiến trình cha là tiến trình mà từ đó chúng ta gọi hàm execv() để thay thế nó bằng tiến trình con. Trong mã này, chúng ta xác định một mảng các con trỏ tới các chuỗi đại diện cho các đối số đầu vào cho quá trình mà hàm execv() mở ra.
Trong hình minh họa sau đây, bạn có thể thấy cách tạo một mảng con trỏ tới chuỗi một cách chính xác. Trong trường hợp này, nó bao gồm bốn con trỏ và được gọi là “arg_Ptr[]”.
Khi mảng con trỏ được xác định, mỗi con trỏ phải được gán một chuỗi chứa đối số đầu vào mà chúng ta gửi đến tiến trình con. Theo nguyên tắc chung khi sử dụng các hàm execxx(), đối số đầu tiên phải là một chuỗi chứa tên và phần mở rộng của tệp thực thi và con trỏ cuối cùng phải là NULL.
Vì vậy, chúng ta gán đối số tương ứng ở định dạng chuỗi cho mỗi con trỏ:
arg_Ptr[0] ="child.bin";
arg_Ptr[1] =" Xin chào từ ";
arg_Ptr[2] ="tiến trình 2";
arg_Ptr[3] =NULL;
Bước tiếp theo là gọi hàm execv(), chuyển chuỗi chứa đường dẫn tuyệt đối của tệp thực thi làm đối số đầu tiên và mảng chuỗi arg_Ptr[] làm đối số thứ hai. Bạn có thể xem mã đầy đủ của quy trình gốc như sau:
#include
#include
#include
#include
#include
int chính (){
printf ("Tôi là tiến trình gốc");
char *arg_Ptr[4];
arg_Ptr[0] =" child.c";
arg_Ptr[1] =" Xin chào từ ";
arg_Ptr[2] ="tiến trình 2";
arg_Ptr[3] =NULL;
execv("/home/linuxhint/Documents/child.bin", arg_Ptr);
}
Chúng tôi biên dịch mã này để chỉ định đường dẫn của tệp “.c” và tên của đầu ra:
~$ gcc Tài liệu/parent.c -o mẫu
Sau đó, chúng tôi chạy đầu ra:
Quy trình gốc hiển thị thông báo “Tôi là quy trình gốc”, tạo mảng chuỗi bằng cách gán một chuỗi cho mỗi đối số đầu vào được chuyển cho quy trình tiếp theo và gọi hàm execv().
Nếu hàm execv() thực thi thành công, tệp thực thi “child.bin” sẽ thay thế tiến trình gốc và chiếm lấy ID cũng như bộ nhớ được phân bổ của nó. Vì vậy, hành động này không thể hoàn tác được.
Tiến trình con hiển thị thông báo “Tôi là tiến trình con” và truy xuất từng đối số đầu vào được tiến trình cha chuyển qua để hiển thị trên bảng điều khiển lệnh.

Các chức năng Fork() và Execve() kết hợp để tạo các quy trình mới trong Linux
Như chúng ta đã thấy cho đến nay, hàm fork() sao chép một tiến trình, trong khi execve() thay thế một tiến trình. Trong ví dụ này, chúng ta sẽ xem cách chúng ta có thể sử dụng kết hợp hai hàm này để mở một quy trình mới từ một bản sao sau đó được thay thế. Để làm điều này, chúng ta kết hợp mã từ hai hàm mà chúng ta đã thấy trước đó để fork() sao chép tiến trình gốc và execve() thay thế nó bằng một tệp thực thi, trong trường hợp này, chính là mã mà chúng ta đã sử dụng trong ví dụ “child.bin” trước đó.
Bây giờ, chúng ta lấy một tệp trống có phần mở rộng “.c” và chèn mã chương trình mà chúng ta đã thấy trong ví dụ về hàm fork().
Trong chương trình này, chúng tôi chỉ sửa đổi mã của tiến trình con để hàm execv() thay thế nó bằng tệp thực thi “child.bin”, trong khi tác vụ chính giống hệt với ví dụ về hàm fork().
Để làm điều này, chúng ta sao chép nội dung của hàm main() từ ví dụ về hàm execv() và thay thế nội dung của câu lệnh “if” bằng mã này. Bây giờ, hãy xem chương trình hoàn chỉnh trông như thế nào:
#include
#include
void main(){
nếu (ngã ba() ==0)
{
printf ("Tôi là tiến trình con\n");
char *arg_Ptr[5];
arg_Ptr[0] =" child.c";
arg_Ptr[1] =" Xin chào từ ";
arg_Ptr[2] ="tiến trình 2";
arg_Ptr[3] =NULL;
execv(/home/linuxhint/Documents/child.bin", arg_Ptr);
khác
{
trong khi (1){
printf ("
ngủ(3);}
}
}
Bằng cách này, quy trình được nhân đôi bằng cách tạo một quy trình mới trên hệ thống mà hàm execv() sau đó sẽ thay thế bằng quy trình từ tệp thực thi “child.bin”. Tiếp theo, chúng ta thấy một hình ảnh có nội dung mã này.

Như chúng ta có thể thấy trong hình, hàm fork() tạo ra một quy trình mới bằng cách sao chép nó, sau đó hàm execv() được thay thế bằng tệp thực thi “child.bin”.
Kết luận
Trong bài viết Linuxhint này, chúng tôi đã hướng dẫn bạn cách sử dụng hàm fork() và execv() để mở các quy trình mới trong Linux. Chúng tôi đã cho bạn xem mô tả ngắn gọn về từng hàm này, cú pháp của chúng cũng như các đối số đầu vào và đầu ra. Để giúp bạn hiểu rõ hơn về cách hoạt động của nó, chúng tôi cũng đưa vào một ví dụ về từng hàm trong đó chúng tôi đã tìm hiểu phương thức gọi của chúng và nhiệm vụ mà mỗi hàm thực hiện. Sau khi xem xét riêng lẻ hai hàm này, chúng tôi đã giải thích cách sử dụng kết hợp chúng để triển khai phương thức mà ngôn ngữ C cung cấp nhằm tạo các quy trình mới trong Linux.