Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 40.326 phút đọc

exec và mẫu fork cộng exec

Sau bài này bạn sẽ làm được

  • Phân biệt sáu biến thể của exec
  • Giải thích vì sao exec thành công không bao giờ trả về
  • Viết mẫu fork cộng exec cộng wait
  • Tự cài system bằng ba lời gọi đó

fork tạo một bản sao của tiến trình hiện tại. Nhưng bạn hiếm khi muốn chạy chính chương trình đó hai lần; bạn muốn con chạy một chương trình khác. Đó là việc của exec, và ghép hai cái lại là nền tảng của mọi shell.

#exec thay chương trình

exec
Thay thế hoàn toàn chương trình đang chạy bằng một chương trình khác. PID giữ nguyên, không tạo tiến trình mới. Mã, dữ liệu, ngăn xếp, heap của chương trình cũ bị vứt hết và thay bằng của chương trình mới.
/* fork  = tao mot tien trinh MOI (ban sao)
   exec  = THAY chuong trinh trong tien trinh HIEN TAI

   Tuong tu: fork la sinh mot dua tre giong het ban.
             exec la dua tre do dot ngot bien thanh mot nguoi khac,
             van cung than xac (cung PID) nhung tam hon moi (chuong
             trinh moi).

   Ghep lai: fork ra mot con, roi con exec thanh chuong trinh khac.
   Do la cach shell chay moi lenh: no khong bien MINH thanh "ls",
   ma fork ra mot con roi cho con exec thanh "ls". */
terminal
# exec.c goi execlp('ls', ...) -> tien trinh BIEN thanh ls
./exec_demo
Truoc exec: PID=25301
tong 8
-rw-r--r-- 1 an an  312 exec.c
-rwxr-xr-x 1 an an 8912 exec_demo
(dong nay KHONG in ra: exec da thay chuong trinh)

#Sáu biến thể

Sáu hàm exec, khác nhau ở ba chữ cái
#include <unistd.h>

/* l = danh sach doi so (list), ket thuc bang NULL */
execl ("/bin/ls", "ls", "-l", NULL);
execlp("ls",       "ls", "-l", NULL);          /* p = tim trong PATH */
execle("/bin/ls",  "ls", "-l", NULL, envp);    /* e = truyen moi truong */

/* v = mang doi so (vector) */
char *argv[] = { "ls", "-l", NULL };
execv ("/bin/ls", argv);
execvp("ls",       argv);                       /* v + p */
execve("/bin/ls",  argv, envp);                 /* syscall THAT, cac cai kia
                                                   deu goi xuong no */

perror("exec");   /* Neu chay toi day thi exec THAT BAI */
Hàml/v: đối sốp: tìm PATHe: môi trường
execlDanh sáchKhôngKhông
execlpDanh sáchCóKhông
execleDanh sáchKhôngCó
execvVectorKhôngKhông
execvpVectorCóKhông
execveVectorKhôngCó

#Không bao giờ trả về

#Mẫu fork cộng exec

Mẫu nền tảng của mọi shell
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    char *argv[] = { "ls", "-l", NULL };

    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        /* CON: bien thanh "ls" */
        execvp(argv[0], argv);
        perror("execvp");       /* chi toi day neu exec loi */
        _exit(127);
    } else {
        /* CHA: cho con xong */
        int status;
        waitpid(pid, &status, 0);
        if (WIFEXITED(status))
            printf("Lenh thoat voi ma %d\n", WEXITSTATUS(status));
    }
    return 0;
}

#Tự cài system

my_system: system() bằng fork + exec + wait
#include <unistd.h>
#include <sys/wait.h>

/* system("lenh") chay "lenh" qua shell /bin/sh -c "lenh".
   Tu cai bang fork + exec + wait: */
int my_system(const char *lenh) {
    if (lenh == NULL) return 1;             /* system(NULL) hoi co shell khong */

    pid_t pid = fork();
    if (pid < 0) return -1;

    if (pid == 0) {
        /* con: chay lenh qua shell, de shell lo pipe, chuyen huong... */
        execl("/bin/sh", "sh", "-c", lenh, (char *)NULL);
        _exit(127);
    }

    int status;
    while (waitpid(pid, &status, 0) < 0)
        if (errno != EINTR) return -1;      /* thu lai neu bi ngat */

    return status;
}
terminal
./my_system_demo
chay: echo xin chao
xin chao
ma thoat: 0
Quên _exit sau exec
if (fork() == 0) { execvp(argv[0], argv); perror("execvp"); /* QUEN _exit: neu exec loi, con chay tiep ma cua CHA, fork them, wait nham... hon loan */ }
Luôn có _exit
if (fork() == 0) { execvp(argv[0], argv); perror("execvp"); _exit(127); /* con THOAT ngay khi exec loi */ }

Tự làm thử

  1. Viết chương trình dùng execvp để chạy ls -l và xác nhận dòng sau exec không in ra.
  2. Thử cả sáu biến thể exec để chạy cùng một lệnh.
  3. Bỏ _exit sau exec, cho exec một lệnh không tồn tại, và quan sát hậu quả.
  4. Cài mẫu fork cộng exec cộng wait chạy một lệnh bất kỳ từ đối số dòng lệnh.
  5. Cài my_system và chứng minh lỗ hổng chèn lệnh với một đầu vào độc hại.
  6. Chạy ba lệnh song song bằng ba lần fork cộng exec, rồi chờ cả ba và báo mã thoát từng cái.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • exec thay chương trình trong tiến trình hiện tại, giữ nguyên PID, không tạo tiến trình mới.
  • Sáu biến thể khác ở ba chữ: l/v (danh sách/mảng), p (tìm PATH), e (truyền môi trường); shell hay dùng execvp.
  • exec thành công không bao giờ trả về; dòng sau nó chỉ chạy khi exec lỗi, nên luôn có perror và _exit.
  • Mẫu fork cộng exec cộng wait là nền tảng của mọi shell.
  • system và my_system dùng shell nên có lỗ hổng chèn lệnh; dùng exec với danh sách đối số cho dữ liệu ngoài.