Vui lòng giải thích hàm executive () và họ của nó


98

Cái gì là exec() chức năng và gia đình của mình? Tại sao chức năng này được sử dụng và nó hoạt động như thế nào?

Xin vui lòng bất cứ ai giải thích các chức năng.


4
Cố gắng đọc lại Stevens và làm rõ những gì bạn không hiểu.
vlabrecque

Câu trả lời:


244

Nói một cách đơn giản, trong UNIX, bạn có khái niệm về các quy trình và chương trình. Một tiến trình là một môi trường mà một chương trình thực thi.

Ý tưởng đơn giản đằng sau "mô hình thực thi" UNIX là có hai thao tác bạn có thể thực hiện.

Đầu tiên là fork()tạo một quy trình hoàn toàn mới chứa bản sao (hầu hết) của chương trình hiện tại, bao gồm cả trạng thái của nó. Có một vài điểm khác biệt giữa hai quy trình cho phép họ tìm ra đâu là cha mẹ và đâu là con.

Thứ hai là để exec() thay thế chương trình trong quy trình hiện tại bằng một chương trình hoàn toàn mới.

Từ hai thao tác đơn giản đó, toàn bộ mô hình thực thi UNIX có thể được xây dựng.


Để thêm một số chi tiết ở trên:

Việc sử dụng fork()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.

Lệnh fork()gọi tạo ra một bản sao gần như giống hệt quy trình hiện tại, giống hệt nhau về mọi mặt (không phải mọi thứ đều được sao chép qua, ví dụ: giới hạn tài nguyên trong một số triển khai, nhưng ý tưởng là tạo một bản sao càng gần càng tốt). Chỉ một cuộc gọi tiến trình fork()nhưng hai tiến trình trả về từ cuộc gọi đó - nghe có vẻ kỳ lạ nhưng nó thực sự khá thanh lịch

Quy trình mới (được gọi là quy trình con) nhận được một ID quy trình (PID) khác và có PID của quy trình cũ (cha mẹ) là PID cha (PPID) của nó.

Bởi vì hai quy trình hiện đang chạy chính xác cùng một mã, chúng cần phải có khả năng phân biệt cái nào - mã trả về fork()cung cấp thông tin này - con nhận được 0, cha nhận được PID của con (nếu fork()lỗi, không con được tạo và cha nhận được mã lỗi).

Bằng cách đó, cha mẹ biết PID của đứa trẻ và có thể giao tiếp với nó, giết nó, đợi nó, v.v. (đứa trẻ luôn có thể tìm thấy tiến trình của cha bằng một cuộc gọi đến getppid()).

Cuộc exec()gọi thay thế toàn bộ nội dung hiện tại của quá trình bằng một chương trình mới. Nó tải chương trình vào vùng quy trình hiện tại và chạy nó từ điểm vào.

Vì vậy, fork()exec()thường được sử dụng theo trình tự để có được một chương trình mới đang chạy dưới dạng con của một tiến trình hiện tại. Shell thường làm điều này bất cứ khi nào bạn cố gắng chạy một chương trình như find- shell fork, sau đó phần mềm con tải findchươ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. Hoàn toàn có thể chấp nhận được khi một chương trình gọi fork()mà không có phần sau exec()nếu, ví dụ, chương trình có chứa cả mã cha và con (bạn cần phải cẩn thận những gì bạn làm, mỗi lần triển khai có thể có những hạn chế).

Điều này đã được sử dụng khá nhiều (và vẫn còn) cho các daemon chỉ đơn giản là lắng nghe trên một cổng TCP và phân nhánh một 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 lắng nghe. Đối với tình huống này, chương trình chứa cả mã mẹ mã con.

Tương tự như vậy, các chương trình biết rằng chúng đã hoàn thành và chỉ muốn chạy một chương trình khác thì không cần thiết fork(), exec()và sau đó là wait()/waitpid()cho trẻ. Họ chỉ có thể tải trẻ trực tiếp vào không gian quy trình hiện tại của họ với exec().

Một số triển khai UNIX có tối ưu hóa fork()sử dụng cái mà họ gọi là copy-on-write. Đây là một thủ thuật để trì hoãn việc sao chép không gian quy trình fork()cho đến khi chương trình cố gắng thay đổi điều 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. Trong Linux, fork()chỉ tạo một bản sao của các bảng trang và một cấu trúc tác vụ mới, exec()sẽ thực hiện công việc khó khăn là "tách biệt" bộ nhớ của hai tiến trình.

Nếu exec được gọi là sau fork(và đây là những gì xảy ra chủ yếu), điều đó gây ra ghi vào không gian quy trình và sau đó nó được sao chép cho quy trình con, trước khi cho phép sửa đổi.

Linux cũng có một vfork(), thậm chí còn được tối ưu hóa hơn, chia sẻ mọi thứ giữa hai quy trình. Do đó, có những hạn chế nhất định trong những gì trẻ có thể làm, và cha mẹ sẽ tạm dừng cho đến khi trẻ gọi exec()hoặc _exit().

Cha mẹ phải được dừng lại (và con không được phép quay trở lại từ hàm hiện tại) vì hai tiến trình thậm chí chia sẻ cùng một ngăn xếp. Điều này hiệu quả hơn một chút đối với trường hợp sử dụng cổ điển fork()theo sau ngay sau đó exec().

Lưu ý rằng có một gia đình toàn bộ execcuộc gọi ( execl, execle, execvevà vân vân) nhưng exectrong bối cảnh ở đây có nghĩa bất kỳ trong số họ.

Sơ đồ sau minh họa fork/exechoạt động điển hình trong đó bashshell được sử dụng để liệt kê một thư mục bằng lslệ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

12
Cảm ơn bạn đã giải thích cặn
kẽ

2
Cảm ơn đã tham khảo shell với chương trình tìm. Chính xác những gì tôi cần hiểu.
Người dùng

Tại sao exectiện ích được sử dụng để chuyển hướng IO của quy trình hiện tại? Làm thế nào mà trường hợp "null", chạy thi hành mà không có đối số, được sử dụng cho quy ước này?
Ray

@Ray, tôi luôn nghĩ nó như một phần mở rộng tự nhiên. Nếu bạn execcoi đây là phương tiện để thay thế chương trình hiện tại (trình bao) trong quá trình này bằng một chương trình khác, thì việc không chỉ định chương trình khác để thay thế nó có thể đơn giản có nghĩa là bạn không muốn thay thế nó.
paxdiablo

Tôi hiểu bạn muốn nói gì nếu "mở rộng tự nhiên", bạn có nghĩa là gì đó dọc theo dòng "tăng trưởng hữu cơ". Có vẻ như chuyển hướng sẽ được thêm vào để hỗ trợ chức năng thay thế chương trình và tôi có thể thấy hành vi này vẫn còn trong trường hợp suy thoái execđược gọi mà không có chương trình. Nhưng hơi kỳ lạ trong trường hợp này vì tính hữu ích ban đầu của việc chuyển hướng cho một chương trình mới - một chương trình thực sự sẽ bị tắt tiếng exec- biến mất và bạn có một tạo tác hữu ích, đang chuyển hướng chương trình hiện tại - chương trình không được tắt exechoặc khởi động lên theo bất kỳ cách nào - thay vào đó.
Ray

36

Các hàm trong họ execute () có các hành vi khác nhau:

  • l: các đối số được truyền dưới dạng danh sách các chuỗi cho hàm main ()
  • v: các đối số được truyền dưới dạng một mảng chuỗi cho hàm main ()
  • p: đường dẫn / s để tìm kiếm chương trình đang chạy mới
  • e: môi trường có thể được chỉ định bởi người gọi

Bạn có thể kết hợp chúng, do đó bạn có:

  • int executel (const char * path, const char * arg, ...);
  • int executelp (const char * file, const char * arg, ...);
  • int executele (const char * path, const char * arg, ..., char * const envp []);
  • int executev (const char * path, char * const argv []);
  • int executevp (const char * file, char * const argv []);
  • int executevpe (const char * file, char * const argv [], char * const envp []);

Đối với tất cả chúng, đối số ban đầu là tên của tệp sẽ được thực thi.

Để biết thêm thông tin, hãy đọc trang người quản lý (3) :

man 3 exec  # if you are running a UNIX system

1
Thật thú vị, bạn đã bỏ lỡ execve()danh sách của mình, được xác định bởi POSIX và bạn đã thêm execvpe(), danh sách không được xác định bởi POSIX (chủ yếu là vì lý do tiền lệ lịch sử; nó hoàn thành bộ chức năng). Nếu không, một lời giải thích hữu ích về quy ước đặt tên cho họ - một phần bổ trợ hữu ích cho paxdiablo 'một câu trả lời giải thích thêm về hoạt động của các hàm.
Jonathan Leffler

Và, để bảo vệ bạn, tôi thấy rằng trang người dùng Linux cho execvpe()(et al) không liệt kê execve(); nó có trang người dùng riêng, riêng biệt (ít nhất là trên Ubuntu 16.04 LTS) - sự khác biệt là các exec()hàm gia đình khác được liệt kê trong phần 3 (các chức năng) trong khi execve()được liệt kê trong phần 2 (lệnh gọi hệ thống). Về cơ bản, tất cả các chức năng khác trong gia đình được thực hiện theo cách gọi đến execve().
Jonathan Leffler

18

Nhóm execcác hàm làm cho quy trình của bạn thực thi một chương trình khác, thay thế chương trình cũ mà nó đang chạy. Tức là, nếu bạn gọi

execl("/bin/ls", "ls", NULL);

sau đó lschương trình được thực thi với id tiến trình, dir làm việc hiện tại và người dùng / nhóm (quyền truy cập) của tiến trình đã gọi execl. Sau đó, chương trình gốc không chạy nữa.

Để bắt đầu một quy trình mới, lệnh forkgọi hệ thống được sử dụng. Để thực hiện một chương trình mà không cần thay thế bản gốc, bạn cần phải fork, sau đó exec.


Cảm ơn bạn đã thực sự hữu ích. Tôi hiện đang thực hiện một dự án yêu cầu chúng tôi sử dụng thi hành () và mô tả của bạn đã củng cố sự hiểu biết của tôi.
TwilightSparkleTheGeek

7

hàm thực thi là gì và họ của nó.

Các execgia đình chức năng là tất cả các chức năng sử dụng để thực hiện một tập tin, chẳng hạn như execl, execlp, execle, execv, và execvpHọ là tất cả frontend cho execvevà cung cấp các phương pháp khác nhau gọi nó.

tại sao chức năng này được sử dụng

Các hàm Exec được sử dụng khi bạn muốn thực thi (khởi chạy) một tệp (chương trình).

và làm như thế nào.

Chúng hoạt động bằng cách ghi đè hình ảnh quy trình hiện tại bằng hình ảnh mà bạn đã khởi chạy. Chúng thay thế (bằng cách kết thúc) quá trình hiện đang chạy (cái được gọi là lệnh thi hành) bằng quá trình mới đã khởi chạy.

Để biết thêm chi tiết: xem liên kết này .


7

execthường được sử dụng cùng với fork, mà tôi thấy rằng bạn cũng đã hỏi về nó, vì vậy tôi sẽ thảo luận về điều này với điều đó.

execbiến quá trình hiện tại thành một chương trình khác. Nếu bạn đã từng xem Doctor Who, thì điều này giống như khi anh ấy tái sinh - cơ thể cũ của anh ấy được thay thế bằng một cơ thể mới.

Cách mà điều này xảy ra với chương trình của bạn và execđó là rất nhiều tài nguyên mà nhân hệ điều hành kiểm tra để xem liệu tệp bạn đang chuyển đến execlàm đối số chương trình (đối số đầu tiên) có được thực thi bởi người dùng hiện tại (id người dùng của quá trình không thực hiện execcuộc gọi) và nếu có, nó sẽ thay thế ánh xạ bộ nhớ ảo của tiến trình hiện tại bằng một bộ nhớ ảo, tiến trình mới và sao chép dữ liệu argvenvpdữ liệu đã được chuyển trong execcuộc gọi vào một vùng của bản đồ bộ nhớ ảo mới này. Một số điều khác cũng có thể xảy ra ở đây, nhưng các tệp được mở cho chương trình được gọi execsẽ vẫn mở cho chương trình mới và chúng sẽ chia sẻ cùng một ID quy trình, nhưng chương trình được gọi execsẽ dừng (trừ khi thực thi không thành công).

Lý do mà điều này được thực hiện theo cách này là bằng cách tách việc chạy một chương trình mới thành hai bước như thế này, bạn có thể thực hiện một số việc giữa hai bước. Điều phổ biến nhất cần làm là đảm bảo rằng chương trình mới có một số tệp nhất định được mở dưới dạng các bộ mô tả tệp nhất định. (hãy nhớ ở đây rằng các bộ mô tả tệp không giống như , nhưng là các giá trị mà hạt nhân biết về). Làm điều này bạn có thể:FILE *int

int X = open("./output_file.txt", O_WRONLY);

pid_t fk = fork();
if (!fk) { /* in child */
    dup2(X, 1); /* fd 1 is standard output,
                   so this makes standard out refer to the same file as X  */
    close(X);

    /* I'm using execl here rather than exec because
       it's easier to type the arguments. */
    execl("/bin/echo", "/bin/echo", "hello world");
    _exit(127); /* should not get here */
} else if (fk == -1) {
    /* An error happened and you should do something about it. */
    perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */

Điều này hoàn thành việc chạy:

/bin/echo "hello world" > ./output_file.txt

từ trình bao lệnh.


5

Khi một tiến trình sử dụng fork (), nó sẽ tạo ra một bản sao của chính nó và bản sao này trở thành con của tiến trình. Fork () được thực hiện bằng cách sử dụng lệnh gọi hệ thống clone () trong linux, nó trả về hai lần từ kernel.

  • Một giá trị khác 0 (ID tiến trình của con) được trả lại cho giá trị gốc.
  • Giá trị 0 được trả lại cho con.
  • Trong trường hợp con không được tạo thành công do bất kỳ sự cố nào như bộ nhớ thấp, -1 được trả về fork ().

Hãy hiểu điều này với một ví dụ:

pid = fork(); 
// Both child and parent will now start execution from here.
if(pid < 0) {
    //child was not created successfully
    return 1;
}
else if(pid == 0) {
    // This is the child process
    // Child process code goes here
}
else {
    // Parent process code goes here
}
printf("This is code common to parent and child");

Trong ví dụ này, chúng ta đã giả định rằng hàm executive () không được sử dụng bên trong tiến trình con.

Nhưng cha mẹ và con khác nhau ở một số thuộc tính PCB (khối điều khiển quá trình). Đó là:

  1. PID - Cả con và cha mẹ đều có một ID quy trình khác nhau.
  2. Tín hiệu đang chờ xử lý - Con không kế thừa các tín hiệu đang chờ xử lý của Cha mẹ. Nó sẽ trống cho tiến trình con khi được tạo.
  3. Khóa bộ nhớ - Đứa trẻ không kế thừa khóa bộ nhớ của cha mẹ nó. Khóa bộ nhớ là khóa có thể được sử dụng để khóa một vùng bộ nhớ và sau đó vùng bộ nhớ này không thể được hoán đổi sang đĩa.
  4. Khóa ghi - Con không kế thừa các khóa hồ sơ của cha mẹ nó. Khóa bản ghi được liên kết với một khối tệp hoặc toàn bộ tệp.
  5. Việc sử dụng tài nguyên quy trình và thời gian sử dụng CPU được đặt thành 0 cho con.
  6. Đứa trẻ cũng không kế thừa bộ hẹn giờ từ cha mẹ.

Nhưng ký ức trẻ thơ thì sao? Không gian địa chỉ mới có được tạo cho trẻ em không?

Câu trả lời là không. Sau fork (), cả cha và con đều chia sẻ không gian địa chỉ bộ nhớ của cha. Trong linux, không gian địa chỉ này được chia thành nhiều trang. Chỉ khi đứa trẻ ghi vào một trong các trang bộ nhớ của cha mẹ, một bản sao của trang đó mới được tạo cho đứa trẻ. Đây còn được gọi là copy on write (Chỉ sao chép các trang của cha mẹ khi trẻ viết vào đó).

Hãy hiểu copy on write với một ví dụ.

int x = 2;
pid = fork();
if(pid == 0) {
    x = 10;
    // child is changing the value of x or writing to a page
    // One of the parent stack page will contain this local               variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.  
}
else {
    x = 4;
}

Nhưng tại sao copy on write lại cần thiết?

Quá trình tạo điển hình diễn ra thông qua sự kết hợp fork () - execute (). Đầu tiên chúng ta hãy hiểu những gì thực hiện ().

Nhóm hàm Exec () thay thế không gian địa chỉ của con bằng một chương trình mới. Sau khi thực thi () được gọi trong phần tử con, một không gian địa chỉ riêng biệt sẽ được tạo cho phần tử con hoàn toàn khác với vùng địa chỉ của cha mẹ.

Nếu không có cơ chế copy on write được liên kết với fork (), các trang trùng lặp sẽ được tạo cho con và tất cả dữ liệu sẽ được sao chép sang các trang của con. Cấp phát bộ nhớ mới và sao chép dữ liệu là một quá trình rất tốn kém (mất thời gian của bộ xử lý và các tài nguyên hệ thống khác). Chúng ta cũng biết rằng trong hầu hết các trường hợp, đứa trẻ sẽ gọi hàm executive () và điều đó sẽ thay thế bộ nhớ của đứa trẻ bằng một chương trình mới. Vì vậy, bản sao đầu tiên mà chúng tôi làm sẽ thật lãng phí nếu bản sao chép không có ở đó.

pid = fork();
if(pid == 0) {
    execlp("/bin/ls","ls",NULL);
    printf("will this line be printed"); // Think about it
    // A new memory space will be created for the child and that   memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
    wait(NULL);
    // parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.

Tại sao cha mẹ chờ đợi một tiến trình con?

  1. Cha mẹ có thể giao một nhiệm vụ cho con của nó và đợi cho đến khi nó hoàn thành nhiệm vụ của mình. Sau đó, nó có thể thực hiện một số công việc khác.
  2. Sau khi phần tử con kết thúc, tất cả các tài nguyên được liên kết với phần tử con sẽ được giải phóng ngoại trừ khối điều khiển quá trình. Bây giờ, đứa trẻ đang ở trong trạng thái zombie. Sử dụng wait (), cha mẹ có thể hỏi về trạng thái của con và sau đó yêu cầu hạt nhân giải phóng PCB. Trong trường hợp cha mẹ không sử dụng wait, đứa trẻ sẽ vẫn ở trạng thái zombie.

Tại sao lệnh gọi hệ thống execute () là cần thiết?

Không cần thiết phải sử dụng execute () với fork (). Nếu mã mà con sẽ thực thi nằm trong chương trình được liên kết với cha, thì không cần thực hiện thi hành ().

Nhưng hãy nghĩ đến những trường hợp trẻ phải chạy nhiều chương trình. Hãy lấy ví dụ về chương trình shell. Nó hỗ trợ nhiều lệnh như find, mv, cp, date, v.v. Liệu có phù hợp để đưa mã chương trình liên kết với các lệnh này vào một chương trình hay để con tải các chương trình này vào bộ nhớ khi được yêu cầu?

Tất cả phụ thuộc vào trường hợp sử dụng của bạn. Bạn có một máy chủ web được cung cấp đầu vào x trả về 2 ^ x cho các máy khách. Đối với mỗi yêu cầu, máy chủ web tạo một con mới và yêu cầu nó tính toán. Bạn sẽ viết một chương trình riêng biệt để tính toán điều này và sử dụng execute ()? Hay bạn sẽ chỉ viết mã tính toán bên trong chương trình mẹ?

Thông thường, việc tạo quy trình bao gồm sự kết hợp của các lệnh gọi fork (), execute (), wait () và exit ().


4

Các exec(3,3p)chức năng thay thế quy trình hiện tại bằng quy trình khác. Tức là, quá trình hiện tại dừng lại và một quá trình khác chạy thay thế, chiếm một số tài nguyên mà chương trình ban đầu có.


6
Không hẳn. Nó thay thế hình ảnh quy trình hiện tại bằng một hình ảnh quy trình mới. Quá trình này là quá trình giống nhau với cùng một pid, cùng một môi trường và cùng một bảng mô tả tệp. Những gì đã thay đổi là toàn bộ bộ nhớ ảo và trạng thái CPU.
JeremyP

@JeremyP "Bộ mô tả tệp giống nhau" rất quan trọng ở đây, nó giải thích cách hoạt động của chuyển hướng trong trình bao! Tôi đã bối rối về cách chuyển hướng có thể hoạt động nếu thực thi ghi đè lên mọi thứ! Cảm ơn
FUD
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.