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);
相关推荐
幽络源小助理3 分钟前
SpringBoot学生宿舍管理系统的设计与开发
java·spring boot·后端·学生宿舍管理
猿java4 分钟前
源码分析:Spring IOC容器初始化过程
java·后端·spring
li星野1 小时前
Linux应用:进程的回收
linux
20242817李臻1 小时前
课上测试:MIRACL共享库使用测试
linux·数据库
BirdMan982 小时前
app=Flask(__name__)中的__name__的意义
后端·python·flask
电鱼智能的电小鱼2 小时前
SAIL-RK3576核心板应用方案——无人机视觉定位与地面无人设备通信控制方案
linux·嵌入式硬件·无人机·边缘计算
昨天今天明天好多天2 小时前
【Scala】
开发语言·后端·scala
wmze2 小时前
AbstractQueuedSynchronizer源码分析
后端
有龍则灵3 小时前
Dubbo3.2.x 服务发现流程源码解析
后端·dubbo
.YYY3 小时前
Linux--普通文件的管理
linux