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.
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.
Câu trả lời:
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()
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.
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()
và 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 find
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. 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ẹ và 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ộ exec
cuộc gọi ( execl
, execle
, execve
và vân vân) nhưng exec
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
exec
tiệ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?
exec
coi đâ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ó.
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 exec
hoặc khởi động lên theo bất kỳ cách nào - thay vào đó.
Các hàm trong họ execute () có các hành vi khác nhau:
Bạn có thể kết hợp chúng, do đó bạn có:
Đố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
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.
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()
.
Nhóm exec
cá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 đó ls
chươ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 fork
gọ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
.
hàm thực thi là gì và họ của nó.
Các exec
gia đì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à execvp
Họ là tất cả frontend cho execve
và 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 .
exec
thườ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 đó.
exec
biế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 exec
là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 exec
cuộ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 argv
và envp
dữ liệu đã được chuyển trong exec
cuộ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 exec
sẽ 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 exec
sẽ 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.
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.
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à:
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?
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 ().
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ó.