插件窝 干货文章 深入理解Linux中进程控制(精讲)

深入理解Linux中进程控制(精讲)

进程 include int PID 253    来源:    2025-05-14

Linux进程控制精讲

一、进程基础概念

1. 什么是进程

进程是程序的执行实例,是操作系统进行资源分配和调度的基本单位。每个进程都有自己独立的地址空间和执行状态。

2. 进程与程序的区别

  • 程序:静态的可执行文件,存储在磁盘上
  • 进程:动态的执行实例,拥有运行时的各种资源

3. 进程属性

  • PID (进程ID):唯一标识符
  • PPID (父进程ID):创建该进程的进程ID
  • UID/GID:用户和组标识
  • 状态:运行、就绪、阻塞等
  • 优先级:进程调度优先级
  • 资源使用:CPU、内存等

二、进程创建

1. fork()系统调用

#include <unistd.h>

pid_t fork(void);
  • 创建子进程,子进程是父进程的副本
  • 返回值:
    • 父进程中返回子进程PID
    • 子进程中返回0
    • 出错返回-1

示例

#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        printf("This is child process, PID: %d\n", getpid());
    } else if (pid > 0) {
        printf("This is parent process, PID: %d, Child PID: %d\n", getpid(), pid);
    } else {
        perror("fork failed");
        return 1;
    }

    return 0;
}

2. vfork()系统调用

#include <unistd.h>

pid_t vfork(void);
  • 创建子进程但不复制父进程的地址空间
  • 子进程共享父进程的地址空间直到调用exec或exit
  • 子进程先运行,父进程被挂起直到子进程终止

3. clone()系统调用

#define _GNU_SOURCE
#include <sched.h>

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...);
  • 更灵活的进程创建方式
  • 可以控制共享哪些资源
  • 常用于实现线程

三、进程终止

1. 正常终止方式

  • 从main函数返回
  • 调用exit()
  • 调用_exit()或_Exit()
  • 最后一个线程从其启动例程返回
  • 最后一个线程调用pthread_exit()

2. 异常终止方式

  • 调用abort()
  • 接收到信号
  • 最后一个线程对取消请求做出响应

3. exit()与_exit()区别

函数 是否刷新I/O缓冲区 是否调用终止处理程序
exit
_exit

示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void cleanup() {
    printf("Cleanup function called\n");
}

int main() {
    atexit(cleanup);

    printf("Using exit():\n");
    exit(0);  // 会调用atexit注册的函数

    // 以下代码不会执行
    printf("Using _exit():\n");
    _exit(0);  // 不会调用atexit注册的函数
}

四、进程等待

1. wait()系统调用

#include <sys/wait.h>

pid_t wait(int *status);
  • 等待任意子进程终止
  • 返回终止子进程的PID
  • status保存子进程退出状态

2. waitpid()系统调用

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);
  • 可以等待特定子进程
  • 提供更多控制选项
  • 参数:
    • pid:指定等待的进程ID
    • status:保存退出状态
    • options:控制行为(如WNOHANG非阻塞)

3. 状态宏

  • WIFEXITED(status):正常终止
  • WEXITSTATUS(status):获取退出状态
  • WIFSIGNALED(status):因信号终止
  • WTERMSIG(status):获取终止信号
  • WIFSTOPPED(status):是否停止
  • WSTOPSIG(status):获取停止信号

示例

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

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        // 子进程
        printf("Child process running\n");
        sleep(2);
        exit(42);
    } else if (pid > 0) {
        // 父进程
        int status;
        pid_t child_pid = waitpid(pid, &status, 0);

        if (WIFEXITED(status)) {
            printf("Child %d exited with status %d\n", 
                   child_pid, WEXITSTATUS(status));
        } else {
            printf("Child terminated abnormally\n");
        }
    } else {
        perror("fork failed");
        return 1;
    }

    return 0;
}

五、进程替换

1. exec函数族

#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
  • 用新程序替换当前进程映像
  • 成功后不会返回
  • 失败返回-1

函数命名规则: - l:参数列表 - v:参数数组 - p:使用PATH环境变量查找可执行文件 - e:传递环境变量

示例

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Before exec\n");

    // 使用参数列表
    execl("/bin/ls", "ls", "-l", NULL);

    // 如果exec成功,以下代码不会执行
    perror("exec failed");
    return 1;
}

六、进程间通信(IPC)

1. 管道(Pipe)

#include <unistd.h>

int pipe(int pipefd[2]);
  • 创建单向通信通道
  • pipefd[0]用于读,pipefd[1]用于写
  • 常用于父子进程通信

示例

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

int main() {
    int pipefd[2];
    char buf[256];

    if (pipe(pipefd) {
        perror("pipe failed");
        return 1;
    }

    pid_t pid = fork();

    if (pid == 0) {
        // 子进程 - 读取数据
        close(pipefd[1]);  // 关闭写端
        read(pipefd[0], buf, sizeof(buf));
        printf("Child received: %s\n", buf);
        close(pipefd[0]);
    } else if (pid > 0) {
        // 父进程 - 写入数据
        close(pipefd[0]);  // 关闭读端
        const char *msg = "Hello from parent";
        write(pipefd[1], msg, strlen(msg) + 1);
        close(pipefd[1]);
        wait(NULL);
    } else {
        perror("fork failed");
        return 1;
    }

    return 0;
}

2. 命名管道(FIFO)

#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
  • 有名称的管道,可用于无亲缘关系的进程通信
  • 通过文件系统访问

3. 信号(Signal)

#include <signal.h>

int kill(pid_t pid, int sig);
int raise(int sig);
void (*signal(int sig, void (*func)(int)))(int);
  • 用于进程间异步事件通知
  • 常见信号:SIGINT(2), SIGKILL(9), SIGTERM(15)

4. 共享内存

#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
  • 最高效的IPC方式
  • 多个进程可访问同一块内存区域

5. 消息队列

#include <sys/msg.h>

int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • 消息的链表,存储在内核中
  • 按消息类型访问

6. 信号量

#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, size_t nsops);
int semctl(int semid, int semnum, int cmd, ...);
  • 用于进程间同步
  • 控制对共享资源的访问

七、进程调度与优先级

1. 查看进程优先级

ps -eo pid,ni,pri,comm
  • NI:nice值(用户空间优先级)
  • PRI:内核看到的优先级

2. 修改进程优先级

#include <unistd.h>
#include <sys/resource.h>

int nice(int inc);
int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int prio);

示例

#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>

int main() {
    // 获取当前优先级
    int prio = getpriority(PRIO_PROCESS, 0);
    printf("Current priority: %d\n", prio);

    // 增加nice值(降低优先级)
    nice(5);

    prio = getpriority(PRIO_PROCESS, 0);
    printf("New priority: %d\n", prio);

    return 0;
}

3. 实时进程调度

#include <sched.h>

int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
int sched_getscheduler(pid_t pid);
  • 调度策略:
    • SCHED_FIFO:先进先出
    • SCHED_RR:轮转
    • SCHED_OTHER:标准时间共享

八、守护进程(Daemon)

1. 创建守护进程步骤

  1. 调用fork()创建子进程,父进程退出
  2. 调用setsid()创建新会话
  3. 改变当前工作目录到根目录
  4. 重设文件权限掩码
  5. 关闭不需要的文件描述符
  6. 处理SIGCHLD信号

2. 示例代码

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

void daemonize() {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork failed");
        exit(1);
    }

    if (pid > 0) {
        // 父进程退出
        exit(0);
    }

    // 子进程继续

    // 创建新会话
    if (setsid() < 0) {
        perror("setsid failed");
        exit(1);
    }

    // 改变工作目录
    if (chdir("/") < 0) {
        perror("chdir failed");
        exit(1);
    }

    // 重设文件权限掩码
    umask(0);

    // 关闭标准文件描述符
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    // 处理SIGCHLD信号
    signal(SIGCHLD, SIG_IGN);
}

int main() {
    daemonize();

    // 守护进程主循环
    while (1) {
        // 守护进程的工作
        sleep(10);
    }

    return 0;
}

九、进程监控与管理工具

1. 常用命令

  • ps:查看进程状态
  • top:实时进程监控
  • htop:增强版top
  • pstree:以树状显示进程
  • kill:发送信号给进程
  • nice/renice:调整进程优先级
  • pgrep/pkill:按名称查找/杀死进程

2. /proc文件系统

  • /proc/[pid]/:每个进程的详细信息
  • /proc/[pid]/status:进程状态
  • /proc/[pid]/cmdline:启动命令
  • /proc/[pid]/fd/:打开的文件描述符

十、现代进程管理技术

1. cgroups (控制组)

  • 限制、记录和隔离进程组的资源使用
  • 控制CPU、内存、I/O等资源分配

2. namespaces (命名空间)

  • 提供进程隔离
  • 不同类型:PID, network, mount, user等
  • 容器技术的基础

3. systemd

  • 现代Linux初始化系统
  • 提供更强大的进程管理功能
  • 单元(unit)概念代替传统init脚本

通过深入理解Linux进程控制,开发者可以编写更高效、更可靠的应用程序,并能够更好地进行系统管理和故障排查。