69天探索操作系统-第3天:进程创建机制

362 阅读7分钟

image.png

1.介绍

进程创建是操作系统的基本操作,它使新程序的执行和系统资源的管理成为可能。本文对进程创建机制进行了深入探讨,重点涉及理论与实践细节。

2.进程创建概览

image.png

2.1 系统调用参与

创建进程时涉及的主要系统调用是:

1.fork()

  • 通过复制调用进程创建一个新进程
  • 在子进程中返回0,在父进程中返回子进程的PID
  • 内核函数:do_fork()

2.exec()

  • 替换当前进程映像为新程序
  • 多种变体:execve(), execl(), execle(), execv(), execvp(), execvpe()
  • 内核函数:do_execve()

2.2 进程创建步骤

1.Forking

  • 复制调用进程
  • 创建一个新的进程控制块(PCB)
  • 为新进程分配内存
  • 将父进程的内存空间复制到子进程

2.Executing

  • 将子进程的内存空间替换为新程序
  • 将程序装入内存
  • 设置执行环境
  • 将控制转移到新程序

3.Fork System Call

3.1 详细解释

fork() 系统调用通过复制调用进程来创建一个新进程。新进程被称为子进程,是父进程的精确复制,包括内存、文件描述符和信号处理程序。唯一的区别是fork()的返回值,在子进程中为0,而在父进程中为子进程的PID

3.2 代码示例

以下是一个简单的示例,演示了 fork() 的用法:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid;

    // Create a new process
    pid = fork();

    if (pid < 0) {
        // Fork failed
        fprintf(stderr, "Fork failed!\n");
        return 1;
    } else if (pid == 0) {
        // Child process
        printf("Child process: PID = %d\n", getpid());
        printf("Child process: Parent PID = %d\n", getppid());
        // Child process can execute a new program using exec()
    } else {
        // Parent process
        printf("Parent process: PID = %d\n", getpid());
        printf("Parent process: Child PID = %d\n", pid);
        // Wait for the child process to complete
        wait(NULL);
        printf("Parent process: Child has terminated\n");
    }

    return 0;
}

编译执行

gcc fork_example.c -o fork_example
./fork_example

4.Exec System Call

4.1详细解释

exec() 系统调用将当前进程映像替换为新的程序。通常在 fork() 之后在子进程中执行不同的程序。exec() 家族函数包括:

  • execve(): 通过路径名执行指定的程序
  • execl(): 通过带有参数的列表执行程序
  • execle(): 通过带有参数和环境变量的列表执行程序
  • execv(): 通过带有数组参数的程序执行
  • execvp(): 通过带有参数的列表搜索PATH中的程序
  • execvpe(): 通过带有参数和环境变量的列表搜索PATH中的程序

4.2 代码示例

下面是一个简单的示例,演示了 exec() 的使用:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid;

    // Create a new process
    pid = fork();

    if (pid < 0) {
        // Fork failed
        fprintf(stderr, "Fork failed!\n");
        return 1;
    } else if (pid == 0) {
        // Child process
        printf("Child process: PID = %d\n", getpid());
        printf("Child process: Parent PID = %d\n", getppid());
        // Execute a new program
        char *args[] = {"ls", "-l", NULL};
        execvp("ls", args);
        // If execvp returns, it must have failed
        perror("execvp");
        exit(1);
    } else {
        // Parent process
        printf("Parent process: PID = %d\n", getpid());
        printf("Parent process: Child PID = %d\n", pid);
        // Wait for the child process to complete
        wait(NULL);
        printf("Parent process: Child has terminated\n");
    }

    return 0;
}

编译执行:

gcc exec_example.c -o exec_example
./exec_example

5. Linux内核中进程创建

5.1内核函数

Linux内核使用几种函数来管理进程创建:

  • do_fork(): 创建一个新进程
  • copy_process(): 将父进程的状态复制到子进程
  • wake_up_new_task(): 将新进程添加到运行队列
  • do_execve(): 在当前进程中加载一个新程序

5.2数据结构

Linux内核使用task_struct数据结构来管理进程状态:

struct task_struct {
    volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
    void *stack;
    atomic_t usage;
    unsigned int flags;
    unsigned int ptrace;
    
    int prio, static_prio, normal_prio;
    struct list_head tasks;
    struct mm_struct *mm, *active_mm;
    
    /* ... many more fields ... */
};

6.实际应用

6.1 fork() 和 exec() 的结合使用

以下是一个结合示例,演示了 fork() 和 exec() 的使用:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid;

    // Create a new process
    pid = fork();

    if (pid < 0) {
        // Fork failed
        fprintf(stderr, "Fork failed!\n");
        return 1;
    } else if (pid == 0) {
        // Child process
        printf("Child process: PID = %d\n", getpid());
        printf("Child process: Parent PID = %d\n", getppid());
        // Execute a new program
        char *args[] = {"ls", "-l", NULL};
        execvp("ls", args);
        // If execvp returns, it must have failed
        perror("execvp");
        exit(1);
    } else {
        // Parent process
        printf("Parent process: PID = %d\n", getpid());
        printf("Parent process: Child PID = %d\n", pid);
        // Wait for the child process to complete
        wait(NULL);
        printf("Parent process: Child has terminated\n");
    }

    return 0;
}

编译执行:

gcc fork_exec_example.c -o fork_exec_example
./fork_exec_example

6.2测试用例

1. 基本 fork 和 exec

  • 使用 fork() 创建子进程
  • 使用 exec() 在子进程中执行简单命令
  • 在父进程等待子进程完成

2. 错误处理

  • 处理 fork() 失败
  • 处理 exec() 失败
  • 确保适当清理资源

3. 资源管理

  • 监控资源使用
  • 在进程终止后清理资源

7.性能考虑

7.1 上下文切换开销

进程之间的上下文切换会产生显著的开销:

1.直接开销

  • CPU缓存无效化:100-1000周期
  • TLB刷新:100-1000周期
  • 流水线刷新:10-100周期
  • 寄存器保存/恢复:50-200周期

2.间接开销

  • Cache冷启动:最多1000个周期
  • 内存访问模式:100-500周期
  • 分支预测重置:10-50周期

7.2内存影响

创建进程对内存有几种影响: 1.缓存行为

  • L1 缓存:32KB,4 周期延迟
  • L2 缓存:256KB,12 周期延迟
  • L3 缓存:8MB,40 周期延迟
  • 主内存:>100 周期延迟 2.TLB 影响
  • Entry容量:1024-4096 条
  • Miss 惩罚:100-1000 周期
  • 刷新成本:500-2000 周期

8.进程创建调试

8.1 工具和技术

以下是一个全面的进程创建调试器:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <ctype.h>

#define MAX_PATH 1024
#define MAX_LINE 256

typedef struct {
    pid_t pid;
    char state;
    unsigned long vm_size;
    unsigned long vm_rss;
    unsigned long threads;
    char name[MAX_LINE];
} ProcessInfo;

// Function to read process information from /proc
void read_process_info(pid_t pid, ProcessInfo* info) {
    char path[MAX_PATH];
    char line[MAX_LINE];
    FILE* fp;

    // Read status file
    snprintf(path, sizeof(path), "/proc/%d/status", pid);
    fp = fopen(path, "r");
    if (!fp) return;

    info->pid = pid;
    while (fgets(line, sizeof(line), fp)) {
        if (strncmp(line, "State:", 6) == 0) {
            info->state = line[7];
        } else if (strncmp(line, "VmSize:", 7) == 0) {
            sscanf(line, "VmSize: %lu", &info->vm_size);
        } else if (strncmp(line, "VmRSS:", 6) == 0) {
            sscanf(line, "VmRSS: %lu", &info->vm_rss);
        } else if (strncmp(line, "Threads:", 8) == 0) {
            sscanf(line, "Threads: %lu", &info->threads);
        } else if (strncmp(line, "Name:", 5) == 0) {
            sscanf(line, "Name: %s", info->name);
        }
    }
    fclose(fp);
}

// Function to print process state information
void print_process_info(ProcessInfo* info) {
    printf("PID: %d\n", info->pid);
    printf("Name: %s\n", info->name);
    printf("State: %c (", info->state);
    
    switch(info->state) {
        case 'R': printf("Running"); break;
        case 'S': printf("Sleeping"); break;
        case 'D': printf("Disk Sleep"); break;
        case 'Z': printf("Zombie"); break;
        case 'T': printf("Stopped"); break;
        default: printf("Unknown");
    }
    printf(")\n");
    
    printf("Virtual Memory: %lu KB\n", info->vm_size);
    printf("RSS: %lu KB\n", info->vm_rss);
    printf("Threads: %lu\n", info->threads);
    printf("------------------------\n");
}

// Function to scan all processes
void scan_processes() {
    DIR* proc_dir;
    struct dirent* entry;
    ProcessInfo info;

    proc_dir = opendir("/proc");
    if (!proc_dir) {
        perror("Cannot open /proc");
        return;
    }

    printf("Scanning all processes...\n\n");

    while ((entry = readdir(proc_dir))) {
        // Check if the entry is a process (directory with numeric name)
        if (entry->d_type == DT_DIR) {
            char* endptr;
            pid_t pid = strtol(entry->d_name, &endptr, 10);
            if (*endptr == '\0') {  // Valid PID
                memset(&info, 0, sizeof(ProcessInfo));
                read_process_info(pid, &info);
                print_process_info(&info);
            }
        }
    }

    closedir(proc_dir);
}

int main() {
    scan_processes();
    return 0;
}

编译运行:

gcc process_debugger.c -o process_debugger
./process_debugger

9.最佳实践

1. 状态转换管理

  • 始终验证状态转换
  • 实现适当的错误处理
  • 记录状态变更进行调试
  • 使用原子操作进行状态更新

2.资源管理

  • 清理已终止状态的资源
  • 实现适当的信号处理
  • 处理僵尸进程
  • 监控资源使用

3.性能优化

  • 尽量减少上下文切换
  • 优化进程创建
  • 使用适当的调度策略
  • 监控系统负载

10.参考文献

  1. Tanenbaum, A. S., & Bos, H. (2014). Modern Operating Systems (4th ed.)
  2. Love, R. (2010). Linux Kernel Development (3rd ed.)
  3. Bovet, D. P., & Cesati, M. (2005). Understanding the Linux Kernel
  4. McKusick, M. K., & Neville-Neil, G. V. (2004). The Design and Implementation of the FreeBSD Operating System

11.进一步阅读

  1. Linux Kernel Documentation - Process Management
  2. IBM Developer - Process States
  3. Operating Systems: Three Easy Pieces - Process Chapter

12. 结论

进程创建机制对于运行新程序和管理系统资源至关重要。理解这些概念对于系统程序员和操作系统开发人员来说至关重要。所讨论的实现细节和性能考虑为与进程创建系统打下了一个坚实的基础。


欢迎订阅我的69天探索操作系统专栏~ 一起学习操作系统