第4章 现代操作系统中的进程管理与生命周期实现

第4章 现代操作系统中的进程管理与生命周期实现

操作系统的核心功能之一是管理计算机系统上运行的程序。通过将程序转变为进程,操作系统可以更有效地控制和分配系统资源,实现多任务处理。本章将深入探讨进程的本质、状态变化以及控制机制,帮助读者全面理解现代操作系统中进程管理的理论与实践。

4.1 进程的基本概念

4.1.1 进程的历史发展

进程概念的出现解决了早期计算机系统中程序执行的重大问题。在单任务操作系统时代,计算机一次只能运行一个程序,资源利用率低下。随着多道程序设计的发展,操作系统需要一种机制来管理并发执行的多个程序,这促使了进程概念的诞生。

早期的计算机系统直接在CPU上运行程序,没有抽象层,导致程序之间相互干扰。进程抽象的引入使得每个程序都运行在自己的虚拟空间中,互不干扰,显著提高了系统的稳定性和安全性。

c

// 早期的简单程序执行方式(无进程概念)
void early_computer_execution() {
    load_program_to_memory();  // 将程序加载到内存
    set_program_counter();     // 设置程序计数器
    
    // 直接执行,直到程序结束
    while (!program_finished) {
        execute_next_instruction();
    }
    
    // 加载下一个程序
    load_next_program();
}
4.1.2 进程定义与进程控制块结构

进程是一个执行中的程序实例,包含程序代码(文本段)、当前活动(程序计数器、处理器寄存器)、堆栈(临时数据)、数据段(全局变量)以及堆(动态分配的内存)。

进程控制块(PCB)是操作系统用来表示进程的数据结构,包含了进程的所有信息。PCB通常包括以下信息:

  • 进程标识符(Process ID)
  • 进程状态(运行、就绪、阻塞等)
  • 程序计数器(下一条将执行的指令地址)
  • CPU寄存器
  • CPU调度信息(优先级、调度队列指针等)
  • 内存管理信息(内存边界、页表等)
  • 资源占用信息(打开的文件等)
  • 账户信息(CPU使用时间等)

c

// 进程控制块的C语言表示
typedef struct {
    int pid;                  // 进程ID
    ProcessState state;       // 进程状态(枚举类型)
    int program_counter;      // 程序计数器
    int cpu_registers[16];    // CPU寄存器集合
    int priority;             // 进程优先级
    
    // 内存管理信息
    void* text_segment;       // 代码段
    void* data_segment;       // 数据段
    void* stack;              // 栈
    void* heap;               // 堆
    
    // 资源占用信息
    int open_files[MAX_FILES];// 打开的文件描述符
    
    // 记账信息
    time_t creation_time;     // 创建时间
    int cpu_time_used;        // 已使用的CPU时间
    
    // 调度信息
    struct pcb* next;         // 链表中的下一个PCB
} ProcessControlBlock;

通过PCB,操作系统能够追踪每个进程的状态和资源使用情况,实现对进程的有效管理和控制。当进程切换时,操作系统保存当前进程的状态到其PCB中,然后加载下一个进程的PCB来恢复其执行环境。

c

// 进程上下文切换
void context_switch(ProcessControlBlock* current, ProcessControlBlock* next) {
    // 保存当前进程状态
    save_cpu_state(current);
    current->program_counter = get_current_pc();
    
    // 加载下一个进程状态
    load_cpu_state(next);
    set_program_counter(next->program_counter);
    
    // 更新进程状态
    current->state = READY;
    next->state = RUNNING;
    
    printf("进程切换: 从PID %d 切换到 PID %d\n", current->pid, next->pid);
}

4.2 进程的状态转换

进程在其生命周期中会经历不同的状态,操作系统通过管理这些状态转换来协调多个进程的执行。

4.2.1 双状态进程模型分析

最简单的进程模型只包含两个状态:运行(Running)和非运行(Not Running)。在这个模型中,进程要么正在执行,要么在等待被调度执行。

这种简单模型的优点是易于理解和实现,但它不能精确描述进程的各种等待情况,如等待I/O完成或等待某个事件发生。

c

// 双状态模型中的进程调度
void simple_scheduler() {
    ProcessControlBlock* current_process;
    
    while (1) {
        // 选择下一个要运行的进程
        current_process = select_next_process();
        
        if (current_process == NULL) {
            // 没有可运行的进程,系统空闲
            idle();
            continue;
        }
        
        // 运行选中的进程
        current_process->state = RUNNING;
        run_process(current_process);
        
        // 进程运行结束或时间片用尽
        current_process->state = NOT_RUNNING;
        
        // 将进程放回队列(如果未结束)
        if (!process_terminated(current_process)) {
            enqueue_process(current_process);
        }
    }
}
4.2.2 进程的创建与终止机制

进程创建

操作系统中的进程可以通过多种方式创建:

  1. 系统初始化时创建
  2. 用户请求创建新进程
  3. 现有进程执行创建进程的系统调用
  4. 批处理作业初始化

在UNIX/Linux系统中,使用fork()系统调用创建新进程。fork()创建调用进程的副本,两个进程从fork()返回点继续执行。在子进程中,fork()返回0;在父进程中,返回子进程ID。

c

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

int main() {
    pid_t pid;
    
    // 创建新进程
    pid = fork();
    
    if (pid < 0) {
        // fork失败
        fprintf(stderr, "Fork failed\n");
        return 1;
    } else if (pid == 0) {
        // 子进程代码
        printf("子进程: 我的PID是 %d,父进程PID是 %d\n", 
               getpid(), getppid());
    } else {
        // 父进程代码
        printf("父进程: 我的PID是 %d,子进程PID是 %d\n", 
               getpid(), pid);
    }
    
    return 0;
}

在Windows系统中,使用CreateProcess()函数创建新进程,该函数直接创建一个运行指定程序的新进程。

进程终止

进程可以通过以下方式终止:

  1. 正常退出(主动)
  2. 错误退出(主动)
  3. 严重错误(被动)
  4. 被其他进程终止(被动)

在UNIX/Linux系统中,进程通过exit()系统调用终止自己,或通过kill()系统调用终止其他进程。

c

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

int main() {
    pid_t pid;
    int status;
    
    pid = fork();
    
    if (pid < 0) {
        fprintf(stderr, "Fork failed\n");
        return 1;
    } else if (pid == 0) {
        // 子进程代码
        printf("子进程(%d)开始执行\n", getpid());
        sleep(2);
        printf("子进程正常退出\n");
        exit(0);  // 正常终止
    } else {
        // 父进程代码
        printf("父进程创建了子进程(%d)\n", pid);
        
        // 等待子进程结束
        waitpid(pid, &status, 0);
        
        if (WIFEXITED(status)) {
            printf("子进程正常终止,退出状态: %d\n", WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("子进程被信号 %d 终止\n", WTERMSIG(status));
        }
    }
    
    return 0;
}
4.2.3 五状态进程模型详解

更复杂且现实的进程模型包含五个状态:

  1. 新建(New): 进程正在被创建,尚未就绪
  2. 就绪(Ready): 进程已准备好运行,等待CPU分配
  3. 运行(Running): 进程正在执行
  4. 阻塞/等待(Blocked/Waiting): 进程等待某个事件发生
  5. 终止(Terminated): 进程已结束执行

进程状态转换图展示了这些状态之间的转换关系:

  • 新建 → 就绪:进程创建完成后转为就绪状态
  • 就绪 → 运行:调度器选择进程执行
  • 运行 → 就绪:时间片用尽或更高优先级进程到达
  • 运行 → 阻塞:进程等待I/O或其他事件
  • 阻塞 → 就绪:等待的事件发生
  • 运行 → 终止:进程执行完成或被终止

c

// 五状态模型的进程状态定义
typedef enum {
    NEW,
    READY,
    RUNNING,
    BLOCKED,
    TERMINATED
} ProcessState;

// 进程状态转换函数
void change_process_state(ProcessControlBlock* process, ProcessState new_state) {
    ProcessState old_state = process->state;
    process->state = new_state;
    
    printf("进程 %d 状态从 %s 变为 %s\n", 
           process->pid, 
           state_to_string(old_state), 
           state_to_string(new_state));
    
    // 根据新状态执行相应操作
    switch (new_state) {
        case READY:
            // 如果进程变为就绪态,将其加入就绪队列
            if (old_state != RUNNING) { // 避免时间片到期的重复入队
                add_to_ready_queue(process);
            }
            break;
            
        case RUNNING:
            // 进程开始运行,从就绪队列中移除
            remove_from_ready_queue(process);
            break;
            
        case BLOCKED:
            // 进程阻塞,可能需要加入等待队列
            add_to_wait_queue(process);
            break;
            
        case TERMINATED:
            // 进程终止,释放资源
            release_resources(process);
            break;
            
        default:
            break;
    }
}

// 状态枚举转字符串
const char* state_to_string(ProcessState state) {
    switch (state) {
        case NEW: return "新建";
        case READY: return "就绪";
        case RUNNING: return "运行";
        case BLOCKED: return "阻塞";
        case TERMINATED: return "终止";
        default: return "未知";
    }
}
4.2.4 被挂起进程的处理

在虚拟内存系统中,进程可能会被挂起(Suspended),即暂时从内存中交换到磁盘上,以释放内存空间给其他进程使用。这引入了两个附加状态:

  1. 就绪挂起(Ready-Suspended): 进程在磁盘上,但一旦回到内存就可以运行
  2. 阻塞挂起(Blocked-Suspended): 进程在磁盘上且在等待某个事件

这样,进程状态模型扩展为七状态模型:新建、就绪、运行、阻塞、就绪挂起、阻塞挂起和终止。

c

// 七状态模型
typedef enum {
    NEW,
    READY,
    RUNNING,
    BLOCKED,
    READY_SUSPENDED,
    BLOCKED_SUSPENDED,
    TERMINATED
} ExtendedProcessState;

// 挂起进程
void suspend_process(ProcessControlBlock* process) {
    if (process->state == READY) {
        // 就绪进程挂起
        change_extended_state(process, READY_SUSPENDED);
        
        // 将进程映像写入磁盘
        swap_out_process(process);
        
        printf("就绪进程 %d 被挂起到磁盘\n", process->pid);
    } 
    else if (process->state == BLOCKED) {
        // 阻塞进程挂起
        change_extended_state(process, BLOCKED_SUSPENDED);
        
        // 将进程映像写入磁盘
        swap_out_process(process);
        
        printf("阻塞进程 %d 被挂起到磁盘\n", process->pid);
    }
}

// 恢复挂起进程
void resume_process(ProcessControlBlock* process) {
    if (process->state == READY_SUSPENDED) {
        // 分配内存
        if (allocate_memory(process)) {
            // 从磁盘加载进程映像
            swap_in_process(process);
            
            // 更新状态为就绪
            change_extended_state(process, READY);
            
            printf("挂起的就绪进程 %d 被恢复到内存\n", process->pid);
        }
    } 
    else if (process->state == BLOCKED_SUSPENDED) {
        // 分配内存
        if (allocate_memory(process)) {
            // 从磁盘加载进程映像
            swap_in_process(process);
            
            // 更新状态为阻塞
            change_extended_state(process, BLOCKED);
            
            printf("挂起的阻塞进程 %d 被恢复到内存\n", process->pid);
        }
    }
}

进程挂起机制让操作系统可以管理更多进程,超出物理内存的限制,通过需要时将进程在内存和磁盘之间交换,实现更高效的资源利用。

4.3 进程描述器设计

操作系统需要维护关于进程的信息,以便有效地管理和控制进程。这些信息存储在进程描述器中,UNIX/Linux系统中称为进程控制块(PCB)。

4.3.1 操作系统控制结构设计

操作系统维护四种主要类型的表格来管理资源:

  1. 内存表: 跟踪内存分配情况
  2. I/O表: 管理I/O设备和通道的状态
  3. 文件表: 包含文件信息和文件系统结构
  4. 进程表: 管理所有进程的信息

这些表格通常相互关联,例如,进程表会包含指向内存表、I/O表和文件表的指针,表示进程占用的各种资源。

c

// 操作系统主要控制表格
typedef struct {
    // 内存表
    MemoryTableEntry memory_table[MAX_MEMORY_REGIONS];
    int memory_table_size;
    
    // I/O表
    IODeviceEntry io_table[MAX_IO_DEVICES];
    int io_table_size;
    
    // 文件表
    FileTableEntry file_table[MAX_FILES];
    int file_table_size;
    
    // 进程表
    ProcessTableEntry process_table[MAX_PROCESSES];
    int process_table_size;
    
    // 系统状态信息
    int current_running_pid;
    SystemStats stats;
} OSControlTables;

// 内存表项
typedef struct {
    void* start_address;
    size_t size;
    bool is_allocated;
    int allocated_to_pid;  // 分配给哪个进程
} MemoryTableEntry;

// I/O表项
typedef struct {
    int device_id;
    DeviceType type;
    DeviceStatus status;
    int using_pid;  // 正在使用设备的进程
} IODeviceEntry;

// 文件表项
typedef struct {
    char filename[MAX_FILENAME_LENGTH];
    FileType type;
    int size;
    int open_count;  // 有多少进程打开了此文件
    int* opening_pids;  // 打开此文件的进程列表
} FileTableEntry;
4.3.2 进程控制结构的实现

进程控制块是操作系统中表示进程的核心数据结构,包含所有必要的信息来管理进程。一个完整的PCB可能包括以下字段:

c

// 详细的进程控制块结构
typedef struct {
    // 进程标识信息
    int pid;                  // 进程ID
    int parent_pid;           // 父进程ID
    int process_group_id;     // 进程组ID
    int session_id;           // 会话ID
    char process_name[50];    // 进程名称
    
    // 处理器状态信息
    ProcessState state;       // 进程状态
    int priority;             // 优先级
    int* program_counter;     // 程序计数器
    RegisterSet registers;    // 寄存器集合
    
    // 进程控制信息
    time_t creation_time;     // 创建时间
    int cpu_time_used;        // CPU使用时间
    int time_slice;           // 时间片大小
    bool is_privileged;       // 是否特权进程
    
    // 内存管理信息
    void* text_segment;       // 代码段
    size_t text_size;         // 代码段大小
    void* data_segment;       // 数据段
    size_t data_size;         // 数据段大小
    void* stack;              // 栈
    size_t stack_size;        // 栈大小
    void* heap;               // 堆
    size_t heap_size;         // 堆大小
    MemoryMap* memory_map;    // 内存映射表
    
    // 文件管理信息
    int open_files[MAX_OPEN_FILES]; // 打开的文件描述符
    int current_directory;          // 当前工作目录
    
    // 进程间通信信息
    MessageQueue* message_queues;   // 消息队列
    SharedMemory* shared_memory;    // 共享内存段
    Semaphore* semaphores;          // 信号量
    
    // 进程关系
    PidList* children;              // 子进程列表
    WaitQueue* waiting_children;    // 等待中的子进程
    
    // 调度信息
    struct pcb* next;               // 队列中的下一个PCB
} ProcessControlBlock;

// 寄存器集合
typedef struct {
    int general_registers[16];      // 通用寄存器
    int floating_point_registers[8]; // 浮点寄存器
    int status_register;            // 状态寄存器
    int stack_pointer;              // 栈指针
    int base_pointer;               // 基址指针
} RegisterSet;

现代操作系统中PCB的实现通常非常复杂,包含大量信息以支持进程管理的各种功能。Linux内核中的task_struct就是PCB的一个典型例子,它包含了进程的所有信息。

4.4 进程控制技术

进程控制是操作系统管理进程生命周期的核心功能,包括进程的创建、执行、调度和终止。

4.4.1 进程执行模式探讨

进程可以在用户模式和内核模式下执行:

  • 用户模式: 受限的执行环境,无法直接访问硬件和执行特权指令
  • 内核模式: 完全访问系统资源,可执行任何指令

当进程执行系统调用、处理中断或发生异常时,会从用户模式切换到内核模式。完成后,返回用户模式继续执行。

c

// 模拟模式切换
void system_call_handler(int syscall_number, void* params) {
    // 保存用户模式上下文
    save_user_context();
    
    // 切换到内核模式
    switch_to_kernel_mode();
    
    printf("进入内核模式,处理系统调用 %d\n", syscall_number);
    
    // 根据系统调用号执行相应的内核函数
    int result = execute_system_call(syscall_number, params);
    
    // 切换回用户模式
    switch_to_user_mode();
    
    // 恢复用户上下文
    restore_user_context();
    
    printf("返回用户模式,系统调用结果: %d\n", result);
}

// 模拟用户程序调用系统函数
void user_program() {
    printf("用户程序执行中...\n");
    
    // 调用系统函数(例如打开文件)
    int fd = open_file("example.txt", O_RDONLY);
    
    printf("文件打开结果:文件描述符 %d\n", fd);
    
    // 继续执行...
}

// 系统调用封装函数
int open_file(const char* filename, int flags) {
    // 准备系统调用参数
    OpenFileParams params;
    params.filename = filename;
    params.flags = flags;
    
    // 触发系统调用(实际实现会使用特殊指令如int 0x80或syscall)
    return syscall(SYS_OPEN, &params);
}
4.4.2 进程创建流程

进程创建是操作系统的基本功能,通常包括以下步骤:

  1. 分配唯一的进程ID
  2. 分配空间给进程控制块
  3. 初始化进程控制块
  4. 设置适当的链接(如父子关系)
  5. 创建或扩展其他数据结构
  6. 分配进程需要的资源

以UNIX/Linux系统的fork()为例:

c

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

// 模拟操作系统内部的fork实现
pid_t os_fork() {
    // 1. 分配新的PID
    pid_t new_pid = allocate_pid();
    
    // 2. 创建新的进程控制块
    ProcessControlBlock* child_pcb = create_pcb(new_pid);
    if (!child_pcb) {
        return -1;  // 失败
    }
    
    // 3. 获取当前(父)进程的PCB
    ProcessControlBlock* parent_pcb = get_current_pcb();
    
    // 4. 复制父进程的PCB内容到子进程
    copy_pcb_contents(parent_pcb, child_pcb);
    
    // 5. 设置父子关系
    child_pcb->parent_pid = parent_pcb->pid;
    add_child_to_parent(parent_pcb, new_pid);
    
    // 6. 复制父进程的内存空间
    if (!copy_address_space(parent_pcb, child_pcb)) {
        free_pcb(child_pcb);
        return -1;  // 失败
    }
    
    // 7. 复制打开的文件描述符
    copy_file_descriptors(parent_pcb, child_pcb);
    
    // 8. 将子进程设置为就绪状态
    child_pcb->state = READY;
    add_to_ready_queue(child_pcb);
    
    // 9. 在父进程中返回子进程ID,在子进程中返回0
    return new_pid;
}

int main() {
    printf("父进程开始执行 (PID: %d)\n", getpid());
    
    pid_t pid = fork();
    
    if (pid < 0) {
        // fork失败
        fprintf(stderr, "Fork failed\n");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("子进程执行中 (PID: %d, 父PID: %d)\n", getpid(), getppid());
        
        // 子进程特有的操作
        for (int i = 1; i <= 3; i++) {
            printf("子进程: 计数 %d\n", i);
            sleep(1);
        }
        
        printf("子进程退出\n");
        exit(0);
    } else {
        // 父进程
        printf("父进程继续执行,创建的子进程PID: %d\n", pid);
        
        // 父进程特有的操作
        for (int i = 1; i <= 3; i++) {
            printf("父进程: 计数 %d\n", i);
            sleep(2);
        }
        
        // 等待子进程结束
        int status;
        waitpid(pid, &status, 0);
        
        if (WIFEXITED(status)) {
            printf("子进程正常退出,状态码: %d\n", WEXITSTATUS(status));
        } else {
            printf("子进程异常退出\n");
        }
        
        printf("父进程退出\n");
    }
    
    return 0;
}
4.4.3 进程切换实现方法

进程切换(也称为上下文切换)是操作系统中最关键的操作之一,它涉及:

  1. 保存当前进程的上下文
  2. 选择下一个要运行的进程
  3. 恢复被选中进程的上下文

上下文切换的效率对系统性能有重大影响,因为它是一个纯开销操作,不执行有用的工作。

c

// 上下文切换的详细实现
void context_switch(ProcessControlBlock* current, ProcessControlBlock* next) {
    // 1. 保存当前进程上下文
    save_cpu_registers(current->registers);
    current->program_counter = get_program_counter();
    
    // 2. 更新进程状态
    if (current->state == RUNNING) {
        current->state = READY;
    }
    
    // 3. 更新进程控制块队列
    if (current->state == READY) {
        add_to_ready_queue(current);
    } else if (current->state == BLOCKED) {
        // 已经在合适的等待队列中,不需要额外操作
    }
    
    // 4. 选择新进程(在这个函数中假设已经选好了next)
    next->state = RUNNING;
    
    // 5. 更新内存管理单元(如页表)
    update_memory_mappings(next);
    
    // 6. 恢复新进程的上下文
    restore_cpu_registers(next->registers);
    set_program_counter(next->program_counter);
    
    // 7. 切换堆栈
    switch_to_process_stack(next);
    
    // 8. 更新系统的当前进程指针
    current_running_process = next;
    
    // 9. 记录上下文切换次数和时间戳
    system_stats.context_switches++;
    next->last_scheduled = get_current_time();
    
    printf("上下文切换: 从进程 %d 切换到进程 %d\n", 
           current->pid, next->pid);
}

// 进程调度器
void schedule() {
    // 获取当前正在运行的进程
    ProcessControlBlock* current = current_running_process;
    
    // 选择下一个要运行的进程
    ProcessControlBlock* next = select_next_process();
    
    if (next != NULL && next != current) {
        // 执行上下文切换
        context_switch(current, next);
    }
}

// 选择下一个要运行的进程(简单的轮转调度算法)
ProcessControlBlock* select_next_process() {
    if (ready_queue_empty()) {
        return NULL;  // 没有可运行的进程
    }
    
    // 从队列头取出下一个进程
    return remove_from_ready_queue_head();
}

上下文切换是一个复杂的操作,通常涉及特权指令和硬件支持。在实际的操作系统中,上下文切换的具体实现依赖于处理器架构。

4.5 操作系统执行环境

操作系统是特殊的程序,它既管理其他程序的执行,又自己执行。这种双重角色导致了不同的执行模型。

4.5.1 无进程内核设计

在最简单的操作系统设计中,内核可以不作为进程运行,而是直接在裸机上执行。这种设计通常用于嵌入式系统和实时操作系统。

无进程内核的特点:

  • 内核直接访问所有硬件资源
  • 没有内核级的上下文切换
  • 通常使用单地址空间
  • 系统调用通过软中断或特殊指令实现

c

// 无进程内核示例(简化)
void simple_kernel() {
    // 初始化硬件
    init_hardware();
    
    // 设置中断向量
    setup_interrupt_vectors();
    
    // 初始化系统服务
    init_system_services();
    
    // 创建初始用户进程
    create_initial_process();
    
    // 开始调度
    while (1) {
        // 选择并运行下一个用户进程
        ProcessControlBlock* next = select_next_process();
        if (next != NULL) {
            run_process(next);
        } else {
            // 空闲时执行
            cpu_idle();
        }
    }
}

// 中断处理
void interrupt_handler(int interrupt_number) {
    // 保存用户进程状态
    save_process_state();
    
    // 处理中断
    switch (interrupt_number) {
        case TIMER_INTERRUPT:
            handle_timer_interrupt();
            break;
        case SYSCALL_INTERRUPT:
            handle_system_call();
            break;
        // 其他中断...
    }
    
    // 可能的进程切换
    schedule();
    
    // 恢复进程状态并返回
    restore_process_state();
}
4.5.2 用户进程中的内核执行

在更复杂的操作系统中,内核可以在用户进程的上下文中执行,称为进程内核(Process Kernel)。当用户进程发起系统调用时,处理器模式从用户模式切换到内核模式,但仍在同一进程上下文中执行。

这种设计的特点:

  • 系统调用在调用进程的上下文中执行
  • 内核代码与用户代码共享同一进程
  • 进程地址空间分为用户空间和内核空间

c

// 用户进程内核执行模式
void process_based_kernel() {
    // 初始化系统
    init_system();
    
    // 创建初始进程
    create_init_process();
    
    // 将控制权交给调度器
    schedule();
    
    // 永不返回
}

// 系统调用处理(在进程上下文中)
void handle_system_call(int syscall_number, void* params) {
    // 当前进程已经在系统调用中切换到了内核模式
    
    // 获取当前进程
    ProcessControlBlock* current = current_running_process;
    
    printf("进程 %d 的系统调用 %d 正在执行\n", 
           current->pid, syscall_number);
    
    // 执行相应的系统调用
    int result;
    switch (syscall_number) {
        case SYS_READ:
            result = sys_read(params);
            break;
        case SYS_WRITE:
            result = sys_write(params);
            break;
        case SYS_FORK:
            result = sys_fork();
            break;
        // 其他系统调用...
        default:
            result = -ENOSYS;  // 未实现的系统调用
    }
    
    // 保存返回值供进程使用
    current->syscall_result = result;
    
    // 返回用户模式(通过特殊指令)
    return_to_user_mode();
}
4.5.3 基于进程的操作系统

最复杂的设计是基于进程的操作系统,其中内核本身作为一个或多个特殊进程运行。这种设计更加模块化,使内核组件可以并行执行。

特点:

  • 内核分为多个进程
  • 内核进程可以并行执行
  • 更好的模块性和可扩展性
  • 更复杂的同步需求

c

// 基于进程的操作系统
void process_based_os() {
    // 创建内核进程
    create_kernel_process(memory_manager_process);
    create_kernel_process(device_manager_process);
    create_kernel_process(file_system_process);
    create_kernel_process(network_manager_process);
    
    // 创建初始用户进程
    create_user_process(init_process);
    
    // 启动调度器
    scheduler_process();
}

// 内存管理器进程
void memory_manager_process() {
    while (1) {
        // 等待内存管理请求
        MemoryRequest request = wait_for_memory_request();
        
        // 处理请求
        switch (request.type) {
            case ALLOC_MEM:
                handle_memory_allocation(request);
                break;
            case FREE_MEM:
                handle_memory_free(request);
                break;
            case PAGE_FAULT:
                handle_page_fault(request);
                break;
            // 其他内存管理操作...
        }
    }
}

// 文件系统进程
void file_system_process() {
    // 初始化文件系统
    init_file_system();
    
    while (1) {
        // 等待文件系统请求
        FSRequest request = wait_for_fs_request();
        
        // 处理请求
        switch (request.type) {
            case OPEN_FILE:
                handle_file_open(request);
                break;
            case READ_FILE:
                handle_file_read(request);
                break;
            case WRITE_FILE:
                handle_file_write(request);
                break;
            // 其他文件操作...
        }
    }
}

4.6 进程安全与保护

随着计算机系统的复杂性增加,安全问题变得越来越重要。操作系统必须保护进程免受其他进程的干扰,同时保护自身免受恶意程序的攻击。

4.6.1 系统访问控制机制

操作系统通过多种机制控制进程对系统资源的访问:

  1. 处理器模式:用户模式和内核模式分离
  2. 内存保护:进程隔离,防止互相访问对方的内存
  3. 权限控制:基于用户ID和组ID的资源访问控制
  4. 能力列表:明确指定进程可以执行的操作

c

// 访问控制检查示例
bool check_access_permission(ProcessControlBlock* process, 
                           ResourceType resource_type, 
                           int resource_id, 
                           AccessMode mode) {
    // 检查进程是否有权限访问特定资源
    
    // 超级用户(root)拥有所有权限
    if (process->effective_user_id == 0) {
        return true;
    }
    
    // 根据资源类型进行不同的权限检查
    switch (resource_type) {
        case RESOURCE_FILE:
            return check_file_permission(process, resource_id, mode);
            
        case RESOURCE_DEVICE:
            return check_device_permission(process, resource_id, mode);
            
        case RESOURCE_IPC:
            return check_ipc_permission(process, resource_id, mode);
            
        case RESOURCE_PROCESS:
            return check_process_permission(process, resource_id, mode);
            
        default:
            // 未知资源类型,拒绝访问
            return false;
    }
}

// 文件权限检查
bool check_file_permission(ProcessControlBlock* process, 
                         int file_id, 
                         AccessMode mode) {
    // 获取文件信息
    FileInfo* file = get_file_info(file_id);
    if (!file) {
        return false;  // 文件不存在
    }
    
    // 检查用户ID和组ID
    if (file->owner_id == process->effective_user_id) {
        // 文件所有者
        if ((mode == ACCESS_READ && (file->permissions & OWNER_READ)) ||
            (mode == ACCESS_WRITE && (file->permissions & OWNER_WRITE)) ||
            (mode == ACCESS_EXECUTE && (file->permissions & OWNER_EXEC))) {
            return true;
        }
    } else if (is_user_in_group(process->effective_group_id, file->group_id)) {
        // 文件组成员
        if ((mode == ACCESS_READ && (file->permissions & GROUP_READ)) ||
            (mode == ACCESS_WRITE && (file->permissions & GROUP_WRITE)) ||
            (mode == ACCESS_EXECUTE && (file->permissions & GROUP_EXEC))) {
            return true;
        }
    } else {
        // 其他用户
        if ((mode == ACCESS_READ && (file->permissions & OTHER_READ)) ||
            (mode == ACCESS_WRITE && (file->permissions & OTHER_WRITE)) ||
            (mode == ACCESS_EXECUTE && (file->permissions & OTHER_EXEC))) {
            return true;
        }
    }
    
    // 没有匹配的权限
    return false;
}
4.6.2 安全防护措施实现

为防止恶意程序对系统的攻击,现代操作系统实现了多种安全防护措施:

  1. 地址空间随机化(ASLR):随机化进程内存布局,使攻击者难以预测目标地址
  2. 不可执行内存:标记数据段为不可执行,防止代码注入攻击
  3. 栈保护:在函数栈帧中添加哨兵值,检测栈溢出
  4. 安全启动:验证操作系统加载的完整性
  5. 沙箱:限制应用程序的权限和资源访问

c

// 地址空间随机化实现示例
void* allocate_randomized_memory(ProcessControlBlock* process, 
                               size_t size, 
                               MemoryType type) {
    // 获取合法的地址范围
    void* min_addr = get_min_address(type);
    void* max_addr = get_max_address(type);
    
    // 计算可用范围
    size_t range = (char*)max_addr - (char*)min_addr - size;
    
    // 生成随机偏移量(页对齐)
    size_t page_size = get_page_size();
    size_t random_offset = (generate_random() % (range / page_size)) * page_size;
    
    // 计算最终地址
    void* address = (void*)((char*)min_addr + random_offset);
    
    // 分配内存
    if (allocate_memory_at(process, address, size, type)) {
        printf("为进程 %d 分配随机地址空间: %p (大小: %zu)\n", 
               process->pid, address, size);
        return address;
    }
    
    // 分配失败,尝试普通分配
    return allocate_memory(process, size, type);
}

// 栈保护实现示例
void setup_stack_protection(ProcessControlBlock* process) {
    // 获取栈底地址
    void* stack_bottom = process->stack_base;
    
    // 创建保护页
    void* guard_page = (void*)((char*)stack_bottom - get_page_size());
    
    // 将保护页标记为不可访问
    set_page_protection(guard_page, PROT_NONE);
    
    // 在栈底放置canary值
    uint32_t* canary_location = (uint32_t*)stack_bottom;
    *canary_location = generate_canary_value();
    process->stack_canary = *canary_location;
    
    printf("为进程 %d 设置栈保护: 保护页=%p, canary=%08X\n", 
           process->pid, guard_page, process->stack_canary);
}

// 检查栈完整性
bool check_stack_integrity(ProcessControlBlock* process) {
    // 获取栈底的canary值
    uint32_t* canary_location = (uint32_t*)process->stack_base;
    uint32_t current_canary = *canary_location;
    
    // 检查canary值是否被修改
    if (current_canary != process->stack_canary) {
        printf("警告: 进程 %d 的栈已被破坏! 预期canary=%08X, 实际=%08X\n", 
               process->pid, process->stack_canary, current_canary);
        return false;
    }
    
    return true;
}

4.7 UNIX SVR4进程管理实例

UNIX System V Release 4(SVR4)是一个重要的UNIX版本,它的进程管理系统结合了AT&T System V和BSD UNIX的特性,成为后来许多UNIX变种和Linux的基础。

4.7.1 SVR4进程状态模型

SVR4使用了一个扩展的进程状态模型,包括:

  1. 创建(Created):进程正在被创建
  2. 就绪(Ready):准备运行,等待CPU
  3. 运行(Running):正在执行
  4. 睡眠(Sleeping):等待事件,分为可中断睡眠和不可中断睡眠
  5. 停止(Stopped):被暂停,通常由信号导致
  6. 僵尸(Zombie):已终止但父进程尚未回收其状态
  7. 交换出(Swapped out):被暂时移出内存

c

// SVR4进程状态枚举
typedef enum {
    SIDL,    // 进程创建中
    SRUN,    // 运行中或就绪
    SSLEEP,  // 可中断睡眠
    SSTOP,   // 暂停状态
    SZOMB,   // 僵尸状态
    SWAIT,   // 不可中断等待
    SLOCK    // 等待内存等系统资源
} SVR4ProcessState;

// 进程状态转换函数
void svr4_change_process_state(ProcessControlBlock* process, 
                              SVR4ProcessState new_state, 
                              const char* reason) {
    SVR4ProcessState old_state = process->svr4_state;
    process->svr4_state = new_state;
    
    printf("进程 %d (%s) 状态从 %s 变为 %s, 原因: %s\n", 
           process->pid, process->process_name, 
           svr4_state_to_string(old_state), 
           svr4_state_to_string(new_state), 
           reason);
    
    // 根据状态转换执行相应操作
    switch (new_state) {
        case SRUN:
            // 添加到调度队列
            if (old_state != SRUN) {
                add_to_run_queue(process);
            }
            break;
            
        case SSLEEP:
        case SWAIT:
            // 从运行队列移除
            if (old_state == SRUN) {
                remove_from_run_queue(process);
            }
            // 添加到等待队列
            add_to_sleep_queue(process);
            break;
            
        case SSTOP:
            // 从运行队列移除
            if (old_state == SRUN) {
                remove_from_run_queue(process);
            }
            break;
            
        case SZOMB:
            // 进程已终止,释放大部分资源但保留PCB
            release_process_resources(process);
            // 通知父进程
            notify_parent_process(process);
            break;
            
        default:
            break;
    }
}

// 状态转字符串
const char* svr4_state_to_string(SVR4ProcessState state) {
    switch (state) {
        case SIDL: return "创建中(SIDL)";
        case SRUN: return "运行/就绪(SRUN)";
        case SSLEEP: return "可中断睡眠(SSLEEP)";
        case SSTOP: return "已停止(SSTOP)";
        case SZOMB: return "僵尸(SZOMB)";
        case SWAIT: return "不可中断等待(SWAIT)";
        case SLOCK: return "锁定(SLOCK)";
        default: return "未知状态";
    }
}
4.7.2 SVR4进程描述符

SVR4中的进程描述符包括多个数据结构:

  1. proc结构:每个进程的主要信息
  2. user结构(u区):进程的用户空间信息
  3. kthread结构:轻量级进程/内核线程信息

这些结构共同描述了一个进程的完整状态。

c

// SVR4 proc结构简化版
typedef struct proc {
    unsigned int p_flag;       // 进程标志
    char p_stat;               // 状态
    char p_pri;                // 优先级
    char p_cpu;                // CPU使用量
    char p_nice;               // nice值
    
    unsigned int p_pid;        // 进程ID
    unsigned int p_ppid;       // 父进程ID
    unsigned int p_pgrp;       // 进程组ID
    unsigned int p_sid;        // 会话ID
    
    caddr_t p_addr;            // 用户区地址
    size_t p_size;             // 进程大小
    
    struct proc *p_link;       // 链表中的下一项
    struct proc *p_parent;     // 父进程指针
    struct proc *p_child;      // 子进程链表
    struct proc *p_sibling;    // 兄弟进程链表
    
    time_t p_utime;            // 用户模式CPU时间
    time_t p_stime;            // 系统模式CPU时间
    
    caddr_t p_wchan;           // 等待通道
    struct kthread *p_tlist;   // 线程列表
    
    // 其他字段...
} proc_t;

// SVR4 user结构简化版
typedef struct user {
    struct proc *u_procp;      // 指向proc结构
    
    // 进程限制
    unsigned int u_rlimit[RLIM_NLIMITS][2];
    
    // 打开文件表
    struct file *u_ofile[NOFILE];
    char u_pofile[NOFILE];
    
    // 当前目录和根目录
    struct vnode *u_cdir;
    struct vnode *u_rdir;
    
    // 信号处理
    void (*u_signal[NSIG])();
    
    // 命令名称
    char u_comm[MAXCOMLEN + 1];
    
    // 其他字段...
} user_t;

// SVR4 kthread结构简化版
typedef struct kthread {
    struct proc *t_procp;      // 指向proc结构
    unsigned int t_flag;       // 线程标志
    char t_state;              // 线程状态
    char t_pri;                // 线程优先级
    
    caddr_t t_wchan;           // 等待通道
    short t_stkbase;           // 栈基址
    
    struct kthread *t_link;    // 下一个线程
    
    // 线程寄存器
    int t_regs[NREG];
    
    // 其他字段...
} kthread_t;
4.7.3 SVR4进程控制实现

SVR4中进程控制主要通过以下系统调用实现:

  1. fork(): 创建新进程
  2. exec(): 执行新程序
  3. exit(): 终止进程
  4. wait(): 等待子进程终止
  5. kill(): 向进程发送信号

SVR4还实现了作业控制功能,允许用户暂停、恢复和在前后台运行进程。

c

// SVR4 fork实现(简化)
int svr4_fork() {
    // 1. 分配并初始化新的proc结构
    proc_t* child_proc = allocate_proc();
    if (!child_proc) {
        return -EAGAIN;
    }
    
    // 2. 获取当前进程
    proc_t* parent_proc = u.u_procp;
    
    // 3. 复制proc结构
    *child_proc = *parent_proc;  // 浅复制
    
    // 4. 修改子进程proc中的特定字段
    child_proc->p_pid = get_next_pid();
    child_proc->p_ppid = parent_proc->p_pid;
    child_proc->p_stat = SRUN;
    child_proc->p_flag = 0;
    child_proc->p_pri = parent_proc->p_pri;
    
    // 5. 设置父子关系
    link_child_to_parent(child_proc, parent_proc);
    
    // 6. 分配并复制user区
    if (copy_user_area(parent_proc, child_proc) != 0) {
        free_proc(child_proc);
        return -EAGAIN;
    }
    
    // 7. 复制地址空间
    if (copy_address_space(parent_proc, child_proc) != 0) {
        free_user_area(child_proc);
        free_proc(child_proc);
        return -EAGAIN;
    }
    
    // 8. 复制打开的文件描述符
    copy_open_files(parent_proc, child_proc);
    
    // 9. 创建内核线程结构
    kthread_t* child_thread = allocate_kthread(child_proc);
    if (!child_thread) {
        free_address_space(child_proc);
        free_user_area(child_proc);
        free_proc(child_proc);
        return -EAGAIN;
    }
    
    // 10. 初始化内核线程
    init_kthread(child_thread, child_proc);
    
    // 11. 将子进程添加到系统进程表
    add_to_process_table(child_proc);
    
    // 12. 将子进程添加到调度队列
    add_to_run_queue(child_proc);
    
    // 13. 在父进程中返回子进程ID
    return child_proc->p_pid;
}

// SVR4 exec实现(简化)
int svr4_exec(const char* path, char* const argv[], char* const envp[]) {
    // 1. 获取当前进程
    proc_t* proc = u.u_procp;
    
    // 2. 打开可执行文件
    int fd = open(path, O_RDONLY);
    if (fd < 0) {
        return -ENOENT;
    }
    
    // 3. 检查文件格式,确保是可执行文件
    if (!is_executable(fd)) {
        close(fd);
        return -ENOEXEC;
    }
    
    // 4. 保存需要的信息
    pid_t pid = proc->p_pid;
    pid_t ppid = proc->p_ppid;
    uid_t uid = proc->p_uid;
    gid_t gid = proc->p_gid;
    
    // 5. 释放当前地址空间
    free_address_space(proc);
    
    // 6. 创建新地址空间
    create_new_address_space(proc);
    
    // 7. 加载可执行文件到新地址空间
    if (load_executable(fd, proc) != 0) {
        // 恢复失败,进程无法继续
        close(fd);
        exit(-1);
    }
    close(fd);
    
    // 8. 设置参数和环境变量
    setup_arguments(proc, argv, envp);
    
    // 9. 重置进程状态但保留某些属性
    proc->p_pid = pid;
    proc->p_ppid = ppid;
    proc->p_uid = uid;
    proc->p_gid = gid;
    
    // 10. 重置信号处理
    reset_signal_handlers(proc);
    
    // 11. 更新进程命令名
    update_proc_name(proc, path);
    
    // 12. 设置程序入口点
    set_entry_point(proc);
    
    return 0;
}

4.8 小结

本章详细探讨了进程的概念、状态转换、描述方式和控制机制。进程是现代操作系统的核心抽象,通过它,操作系统可以有效管理系统资源和控制程序执行。

我们了解了进程的不同状态模型,从简单的双状态模型到复杂的七状态模型,以及进程控制块(PCB)的结构和作用。同时,我们也探讨了进程创建、切换和终止的实现机制,以及操作系统如何保护进程安全。

通过UNIX SVR4的案例研究,我们看到了一个成熟操作系统中进程管理的实际实现。这些概念和技术构成了现代操作系统设计的基础,影响了包括Linux、Windows和macOS在内的所有主流操作系统。

4.9 推荐阅读资源

为深入理解进程管理,建议阅读以下资源:

  1. "操作系统概念"(Operating System Concepts) - Silberschatz, Galvin & Gagne
  2. "现代操作系统"(Modern Operating Systems) - Andrew S. Tanenbaum
  3. "UNIX内部实现"(The Design of the UNIX Operating System) - Maurice J. Bach
  4. "Linux内核开发"(Linux Kernel Development) - Robert Love
  5. "深入理解Linux内核"(Understanding the Linux Kernel) - Daniel P. Bovet & Marco Cesati

4.10 术语、复习题与实践

关键术语
  • 进程(Process): 执行中的程序实例
  • 进程控制块(PCB): 表示进程的数据结构,包含所有进程信息
  • 上下文切换(Context Switch): 保存一个进程状态并恢复另一个进程状态的操作
  • 调度(Scheduling): 决定哪个进程获得CPU执行时间的过程
  • 进程状态(Process State): 进程在生命周期中所处的状态
  • fork(): UNIX/Linux中创建新进程的系统调用
  • exec(): 替换当前进程映像的系统调用
  • 僵尸进程(Zombie Process): 已终止但父进程尚未获取其状态的进程
  • 内核模式(Kernel Mode): CPU的特权执行模式,可访问所有系统资源
  • 用户模式(User Mode): CPU的受限执行模式,防止直接访问关键资源
复习题
  1. 解释进程和程序的区别。
  2. 描述进程控制块(PCB)的主要组成部分及其作用。
  3. 画出五状态进程模型的状态转换图,并解释每种转换的触发条件。
  4. 比较UNIX/Linux中fork()和exec()系统调用的功能和用途。
  5. 说明上下文切换的步骤及其对系统性能的影响。
  6. 解释进程挂起的概念及其在虚拟内存系统中的作用。
  7. 描述UNIX SVR4中的进程描述结构及其组织方式。
  8. 分析操作系统如何防止恶意进程访问其他进程的内存空间。
编程练习

练习1: 实现一个简单的shell程序

编写一个简单的命令行shell程序,支持以下功能:

  • 显示命令提示符
  • 接受用户输入的命令
  • 使用fork()和exec()执行命令
  • 支持后台执行(命令后加&)
  • 支持简单的内置命令(如cd, exit)

c

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

#define MAX_CMD_LENGTH 1024
#define MAX_ARGS 64

// 解析命令行
int parse_command(char *cmd, char *args[]) {
    int i = 0;
    
    // 按空格分割命令行
    args[i] = strtok(cmd, " \t\n");
    while (args[i] != NULL && i < MAX_ARGS - 1) {
        i++;
        args[i] = strtok(NULL, " \t\n");
    }
    
    // 检查是否后台运行
    int background = 0;
    if (i > 0 && strcmp(args[i-1], "&") == 0) {
        args[i-1] = NULL;  // 移除&
        background = 1;
    }
    
    return background;
}

// 执行内置命令
int execute_builtin(char *args[]) {
    if (args[0] == NULL) {
        return 1;  // 空命令
    }
    
    // cd命令
    if (strcmp(args[0], "cd") == 0) {
        if (args[1] == NULL) {
            // 没有参数,切换到主目录
            chdir(getenv("HOME"));
        } else {
            if (chdir(args[1]) != 0) {
                perror("cd");
            }
        }
        return 1;
    }
    
    // exit命令
    if (strcmp(args[0], "exit") == 0) {
        exit(0);
    }
    
    return 0;  // 不是内置命令
}

int main() {
    char cmd[MAX_CMD_LENGTH];
    char *args[MAX_ARGS];
    
    while (1) {
        // 显示提示符
        char cwd[1024];
        if (getcwd(cwd, sizeof(cwd)) != NULL) {
            printf("%s$ ", cwd);
        } else {
            printf("myshell$ ");
        }
        fflush(stdout);
        
        // 读取命令
        if (fgets(cmd, MAX_CMD_LENGTH, stdin) == NULL) {
            break;  // EOF
        }
        
        // 解析命令
        int background = parse_command(cmd, args);
        
        if (args[0] == NULL) {
            continue;  // 空命令
        }
        
        // 尝试执行内置命令
        if (execute_builtin(args)) {
            continue;
        }
        
        // 创建子进程执行外部命令
        pid_t pid = fork();
        
        if (pid < 0) {
            perror("fork");
            continue;
        } else if (pid == 0) {
            // 子进程
            execvp(args[0], args);
            // 如果exec返回,说明出错了
            perror(args[0]);
            exit(1);
        } else {
            // 父进程
            if (!background) {
                // 前台执行,等待子进程结束
                waitpid(pid, NULL, 0);
            } else {
                printf("[1] %d\n", pid);  // 打印后台进程PID
            }
        }
    }
    
    return 0;
}

练习2: 进程状态监控程序

编写一个程序,使用/proc文件系统(在Linux中)读取和显示系统中所有进程的状态信息。

c

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

// 进程信息结构
typedef struct {
    int pid;
    char state;
    char name[256];
    int ppid;
    unsigned long vsize;  // 虚拟内存大小
    long priority;
    long nice;
} ProcessInfo;

// 读取进程信息
int read_process_info(int pid, ProcessInfo *info) {
    char path[256];
    char buffer[4096];
    FILE *fp;
    
    // 读取/proc/PID/stat文件
    snprintf(path, sizeof(path), "/proc/%d/stat", pid);
    fp = fopen(path, "r");
    if (fp == NULL) {
        return -1;  // 进程可能已经结束
    }
    
    if (fgets(buffer, sizeof(buffer), fp) == NULL) {
        fclose(fp);
        return -1;
    }
    fclose(fp);
    
    // 解析stat文件内容
    char state;
    char comm[256];
    int ppid;
    unsigned long vsize;
    long priority, nice;
    
    sscanf(buffer, "%d (%[^)]) %c %d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*u %*u %*d %*d %ld %ld %*d %*d %*d %*d %*u %lu",
           &info->pid, comm, &state, &ppid, &priority, &nice, &vsize);
    
    info->state = state;
    strncpy(info->name, comm, sizeof(info->name));
    info->ppid = ppid;
    info->vsize = vsize;
    info->priority = priority;
    info->nice = nice;
    
    return 0;
}

// 将进程状态字符转换为可读描述
const char* state_to_string(char state) {
    switch (state) {
        case 'R': return "运行中";
        case 'S': return "可中断睡眠";
        case 'D': return "不可中断睡眠";
        case 'Z': return "僵尸";
        case 'T': return "已停止";
        case 't': return "跟踪停止";
        case 'X': return "已死亡";
        default: return "未知";
    }
}

int main() {
    DIR *dir;
    struct dirent *entry;
    ProcessInfo info;
    
    printf("%-6s %-20s %-15s %-6s %-10s %-10s %-6s\n",
           "PID", "名称", "状态", "PPID", "优先级", "NICE值", "内存(KB)");
    printf("------------------------------------------------------------\n");
    
    // 打开/proc目录
    dir = opendir("/proc");
    if (dir == NULL) {
        perror("无法打开/proc目录");
        return 1;
    }
    
    // 遍历/proc目录中的所有PID目录
    while ((entry = readdir(dir)) != NULL) {
        // 检查是否为数字目录名(PID)
        int pid = atoi(entry->d_name);
        if (pid > 0) {
            // 读取进程信息
            if (read_process_info(pid, &info) == 0) {
                // 打印进程信息
                printf("%-6d %-20s %-15s %-6d %-10ld %-10ld %-6lu\n",
                       info.pid, info.name, state_to_string(info.state),
                       info.ppid, info.priority, info.nice,
                       info.vsize / 1024);
            }
        }
    }
    
    closedir(dir);
    return 0;
}

这些练习帮助理解进程创建、控制和监控的实际应用,巩固本章所学的理论知识。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小宝哥Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值