Linux多进程

基础知识

  • Linux内核为每个进程分配了PCB(task_struct),并加入对应的列表,对进程的管理,其实就是对PCB、PCB链表的操作
  • 增加:
    • 分配PCB内存,并填入相应信息
    • 复制父进程资源(写时才复制)
    • 加入多个链表,如全局链表、调度队列等
  • 删除:
    • 发送终止信号(如 SIGTERM
    • 释放资源
    • 从所有链表中移除对应的PCB
    • 父进程通过wait()回收僵尸进程
  • 修改:更新PCB内容
  • 查询:从链表查找对应指定进程的PCB
  • PCB记录着程序的信息,如:

    • 进程ID:唯一标识一个进程。
    • 状态:记录当前进程的运行状态(如就绪、运行、等待等)。
    • 优先级:用于进程调度,优先级高的进程更容易获得CPU时间。
    • 内存指针:指向进程的代码段、数据段、栈段。
    • 程序计数器:记录下一条需要执行的指令的地址。
    • I/O状态信息:列出与该进程相关的I/O设备和文件。
  • PCB会处于多个链表中,且每个链表都是双向链表;在PCB中嵌入多个链表(如下代码),每次操作PCB都需要操作多个链表

    c 复制代码
    struct list_head { 
        struct list_head *next, *prev; 
    };
    struct task_struct { 
        // 进程的其他字段 
        struct list_head tasks; // 所有进程的链表 
        struct list_head children;// 子进程链表 
        struct list_head sibling; // 兄弟进程链表 
        struct list_head ptrace_list; // ptrace 跟踪链表 
        // 更多链表节点... };
  • 万物皆文件:所有进程都被管理在虚拟文件系统/proc/(本身不占据磁盘空间)之下,如pid=1234的进程就对应/proc/1234,里面记录着进程所有信息,如文件打开表记录在/proc/1234/fd/之下,里面存储着链接文件,命名就是文件描述符(句柄),如下所示: ps指令就是去读取/proc/中的文件,并提取需要输出的信息,按照格式进行输出

  • 僵尸进程(defunct):进程已经终止,但是父进程没有调用wait()/waitpid()回收资源,PCB仍保留在链表中

  • 孤儿进程:父进程先总是,子进程此时仍在运行,且由init(1号)进程接管,init进程一直都在运行wait(),等待回收孤儿进程 僵尸进程-->孤儿进程:当主进程退出都没有回收僵尸进程的资源,此时僵尸进程会被init接管变为孤儿进程,进而被回收;所以ps看见的僵尸进程(状态为z)都是父进程没有结束而导致的。

基本函数

  • pid_t getpid(void);

  • pid_t getppid(void);

    • 获取父进程号
    • 在终端中使用./启动程序时,此时终端bash就是该进程的父进程
  • int setpgid(pid_t pid, pid_t pgid);

  • pid_t getpgid(pid_t pid);

    • 获取组号,默认为父进程pid
  • pid_t fork(void);

    • 变量(堆)读时共享,写时拷贝
    • 如果共享代码中分配了堆,那子进程、父进程中都需要释放
    • valgrind ./可执行文件,来查看内存泄漏
  • void exit(int status);

    • 参数是返回给父进程的参数,低8位有效
    • 终结该进程,并回收资源(除了pcb),如堆栈代码段、文件描述符等
    • 会刷新io缓冲区,将内存中的内容写入文件中
  • void _exit(int status);

    • 功能一样,但是是直接杀死进程,并清理资源
    • io缓冲区的内容直接清理
  • pid_t wait(int *status);

    • 父进程阻塞等待子进程退出,若没有子进程则立即返回,用于回收子进程的pcb并获取状态
    • 传入的参数用于接受子进程调用exit传出的参数,status需要搭配成套宏函数使用,来判断返回的状态
    • 返回退出的子进程pid
  • pid_t waitpid(pid_t pid,int *status,int options);

    • 和wait差不多,只是wait是阻塞等待,waitpid可配置
    • 参数说明
      • pid>0 等待指定进程
      • pid=0 等待同一组中任何进程
      • pid=-1 等待任一子进程
      • pid<-1 等待指定进程组中任何子进程,进程组为pid绝对值
      • status 和wait中一样
      • options=0 阻塞等待
      • options=WNOHANG:没有任何已经结束的子进程,则立即返回
    • 返回说明:
      *

      0 子进程正常退出

      • =0 非阻塞模式,子进程正在运行
      • <0 子进程异常退出或已经没有子进程
    c 复制代码
     int status; 
     pid_t pid; 
     while ((pid = waitpid(-1, &status, WNOHANG)) == 0) 
     { //轮询等待回收资源
         printf("无子进程退出,父进程继续执行其他任务\n"); 
         sleep(1); 
     }

    status

    • 父进程无法直接读取子进程中的变量,子进程也无法直接写入父进程变量中,子进程只能写入os分配的空间,然后父进程调用wait、waitpid将数据移动到自己的变量中
    • status是按位写入的,整体的指并没有作用
      • 低7位记录信号,比如kill -9 pid,就会记录信号为9
      • 低第8位没有使用
      • 次低8位:进程退出时所对应的退出状态(退出码)
    c 复制代码
    printf("waitpid返回的退出码:%d,信号:%d", status >> 8 & 0xFF, status & 0x7F);
  • 进程替换函数exec族函数

    参考链接

    调用成功后,就使用指定的程序来替换当前进程,新老进程,就代码、数据不同,且会从新程序的main从头执行,老程序的代码全部抛弃,包括没有执行的部分 调用失败,则出错返回-1

    exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe,规律如下:

    • l(list) : 表示参数采用列表,NULL作为参数结束标志;

    • v(vector) : 参数用数组;

    • p(path) : 有p自动搜索环境变量PATH,所以第一个参数只用指定名字就行,而不需要路径; - e(env) : 表示自己维护环境变量;

    c 复制代码
    execl("/usr/bin/ls","ls","-a","-l",NULL);
    execlp("ls","ls","-a","-l",NULL);
    //第一个ls代表路径,需要用此去环境变量中查找路径
    //第二个ls代表在命令行中如何调用此指令(程序)
    char* const argv[] = {
      "ls",
      "-a",
      "-l",
      NULL 
    };
    execv("/usr/bin/ls",argv);
    execvp("ls",argv);
相关推荐
咸鱼睡不醒_1 小时前
SpringBoot项目接入DeepSeek
java·spring boot·后端
jarreyer1 小时前
Vim 编辑器常用快捷键速查表
linux·编辑器·vim
冰激凌zz1 小时前
ubuntu nobel + qt5.15.2 设置qss语法识别正确
linux·qt·ubuntu
编码雪人2 小时前
CentOS算法部署
linux·运维·centos
广药门徒2 小时前
关于多版本CUDA共存的研究,是否能一台机子装两个CUDA 版本并正常切换使用
linux·运维·人工智能
又逢乱世2 小时前
Ubuntu 安装 Docker
linux·ubuntu·docker
yi念zhi间2 小时前
如何把ASP.NET Core WebApi打造成Mcp Server
后端·ai·mcp
声声codeGrandMaster2 小时前
Django之账号登录及权限管理
后端·python·django
码傻啦弟2 小时前
常用设计模式在 Spring Boot 项目中的实战案例
java·spring boot·后端·设计模式
南玖yy2 小时前
C++ 工具链与开发实践:构建安全、高效与创新的开发生态
开发语言·c++·人工智能·后端·安全·架构·交互