Linux Process Management: fork, exec, wait

Tìm hiểu chi tiết về quản lý tiến trình trong Linux: fork() để tạo process mới, exec() để thay thế process, và wait() để đồng bộ hóa

Linux Process Management: fork, exec, wait

Linux Process Management: fork, exec, wait

Process management là một trong những concepts quan trọng nhất khi làm việc với Linux system programming. Ba system calls cốt lõi là fork(), exec(), và wait().

Process là gì?

Process là một instance đang chạy của một program. Mỗi process có:

  • Process ID (PID): Identifier duy nhất
  • Parent Process ID (PPID): PID của process cha
  • Memory space: Code, data, heap, stack
  • File descriptors: Open files, sockets, pipes
  • Environment variables

fork() - Tạo Process Mới

fork() tạo ra một child process là bản sao hoàn toàn của parent process.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
 
int main() {
    pid_t pid;
    
    printf("Before fork\n");
    
    pid = fork();  // Tạo child process
    
    if (pid < 0) {
        // Fork failed
        perror("fork failed");
        return 1;
    }
    else if (pid == 0) {
        // Child process
        printf("Child process: PID = %d, Parent PID = %d\n",
               getpid(), getppid());
    }
    else {
        // Parent process
        printf("Parent process: PID = %d, Child PID = %d\n",
               getpid(), pid);
    }
    
    printf("After fork\n");
    return 0;
}

Output:

Before fork
Parent process: PID = 1234, Child PID = 1235
After fork
Child process: PID = 1235, Parent PID = 1234
After fork

Điều gì xảy ra khi fork()?

  1. OS tạo child process với PID mới
  2. Child copy toàn bộ memory của parent (COW - Copy on Write)
  3. Child và parent chạy độc lập, không chia sẻ memory
  4. fork() returns:
    • 0 trong child process
    • child PID trong parent process
    • -1 nếu lỗi

exec() Family - Thay Thế Process

exec() family thay thế process hiện tại bằng một program khác.

#include <stdio.h>
#include <unistd.h>
 
int main() {
    printf("Before exec\n");
    
    // Thay thế process bằng /bin/ls
    execl("/bin/ls", "ls", "-l", NULL);
    
    // Code này chỉ chạy nếu exec failed
    perror("exec failed");
    return 1;
}

Các variant của exec():

// execl - list arguments
execl("/bin/ls", "ls", "-l", "-a", NULL);
 
// execv - array of arguments
char *args[] = {"ls", "-l", "-a", NULL};
execv("/bin/ls", args);
 
// execlp - search in PATH
execlp("ls", "ls", "-l", NULL);
 
// execvp - search in PATH, array args
char *args[] = {"ls", "-l", NULL};
execvp("ls", args);

wait() - Đồng Bộ Parent và Child

wait() làm parent process đợi child process kết thúc.

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
 
int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // Child process
        printf("Child: Starting work...\n");
        sleep(2);
        printf("Child: Work done!\n");
        return 42;  // Exit code
    }
    else {
        // Parent process
        int status;
        printf("Parent: Waiting for child...\n");
        
        pid_t child_pid = wait(&status);
        
        if (WIFEXITED(status)) {
            printf("Parent: Child %d exited with code %d\n",
                   child_pid, WEXITSTATUS(status));
        }
    }
    
    return 0;
}

Output:

Parent: Waiting for child...
Child: Starting work...
Child: Work done!
Parent: Child 1235 exited with code 42

Kết Hợp fork + exec + wait

Pattern phổ biến nhất: fork child để chạy program khác

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
 
int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // Child: Execute ls command
        execl("/bin/ls", "ls", "-l", NULL);
        perror("exec failed");
        return 1;
    }
    else {
        // Parent: Wait for child
        int status;
        wait(&status);
        printf("Child completed\n");
    }
    
    return 0;
}

Zombie Processes

Zombie process xảy ra khi child kết thúc nhưng parent không gọi wait().

#include <stdio.h>
#include <unistd.h>
 
int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        printf("Child exiting\n");
        return 0;  // Child exits
    }
    else {
        printf("Parent sleeping (not calling wait)\n");
        sleep(30);  // Child becomes zombie!
    }
    
    return 0;
}

Kiểm tra zombie processes:

ps aux | grep Z  # Z = zombie state

Orphan Processes

Orphan process xảy ra khi parent kết thúc trước child.

#include <stdio.h>
#include <unistd.h>
 
int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // Child
        sleep(5);
        printf("Child PID: %d, Parent PID: %d\n",
               getpid(), getppid());  // Parent PID will be 1 (init)
    }
    else {
        // Parent exits immediately
        printf("Parent exiting\n");
    }
    
    return 0;
}

Child sẽ được adopt bởi init process (PID = 1).

waitpid() - Advanced Wait

waitpid() cho phép wait specific child hoặc non-blocking wait.

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
 
int main() {
    pid_t pid1 = fork();
    pid_t pid2 = fork();
    
    if (pid1 == 0) {
        sleep(1);
        return 1;
    }
    else if (pid2 == 0) {
        sleep(2);
        return 2;
    }
    else {
        // Wait for specific child
        int status;
        waitpid(pid2, &status, 0);  // Wait only for pid2
        printf("pid2 finished\n");
        
        // Non-blocking wait
        while (waitpid(-1, &status, WNOHANG) == 0) {
            printf("Still waiting...\n");
            sleep(1);
        }
    }
    
    return 0;
}

Practical Example: Simple Shell

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
 
int main() {
    char command[256];
    
    while (1) {
        printf("myshell> ");
        if (fgets(command, sizeof(command), stdin) == NULL) break;
        
        // Remove newline
        command[strcspn(command, "\n")] = 0;
        
        if (strcmp(command, "exit") == 0) break;
        
        pid_t pid = fork();
        
        if (pid == 0) {
            // Child: execute command
            char *args[] = {"/bin/sh", "-c", command, NULL};
            execv("/bin/sh", args);
            perror("exec failed");
            exit(1);
        }
        else {
            // Parent: wait for child
            wait(NULL);
        }
    }
    
    return 0;
}

Best Practices

  1. Always check fork() return value
  2. Call wait() to prevent zombies
  3. Check exec() errors (though they replace the process)
  4. Handle signals for robust process management
  5. Use waitpid() for more control

Kết luận

Fork, exec, và wait là building blocks của process management trong Linux. Master ba concepts này giúp bạn:

  • Hiểu cách shell hoạt động
  • Viết được servers và daemons
  • Debug process-related issues
  • Làm việc hiệu quả với Linux system programming

Practice là cách tốt nhất để hiểu sâu về process management!

5 min read
LinuxProcessSystem ProgrammingC

Bài viết liên quan