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);
相关推荐
IMPYLH18 分钟前
Lua 的 Coroutine(协程)模块
开发语言·笔记·后端·中间件·游戏引擎·lua
我命由我1234527 分钟前
python-dotenv - python-dotenv 快速上手
服务器·开发语言·数据库·后端·python·学习·学习方法
txzz888828 分钟前
CentOS-Stream-10 系统安装之网络设置
linux·运维·服务器·网络·计算机网络·centos
qq_4017004139 分钟前
嵌入式Linux网口MAC地址修改
linux·运维·macos
LucianaiB1 小时前
震惊!我的公众号被我打造成了一个超级个体
后端
Xの哲學1 小时前
Linux DRM 架构深度解析
linux·服务器·算法·架构·边缘计算
不会写DN1 小时前
fmt 包中的所有 Print 系列函数
开发语言·后端·golang·go
老王熬夜敲代码1 小时前
Linux的权限
linux
我是谁??2 小时前
Linux上检查U盘可读和修复
linux·运维·服务器
南棱笑笑生2 小时前
20251213给飞凌OK3588-C开发板适配Rockchip原厂的Buildroot【linux-6.1】系统时适配CTP触摸屏FT5X06
linux·c语言·开发语言·rockchip