当前位置: 首页 > news >正文

Linux系统编程---多进程

一、进程的概念

1、什么是程序?什么是进程?

  • 程序:编译后产生的,格式为ELF的,存储于硬盘的文件。
  • 进程:程序中的代码和数据,被加载到内存中运行的过程。其实说白了,进程就是一个正在执行的程序。
  • 程序是静态的概念,进程是动态的概念。

        在Linux中,程序文件的格式都是ELF,这些文件在被执行的瞬间,就被载入内存,所谓的载入内存,如上图所示,就是将数据段、代码段这些运行时必要的资源拷贝到内存,另外系统会再分配相应的栈、堆等内存空间给这个进程,使之成为一个动态的实体。

2、为什么需要多进程?

        多进程(Multiprocessing)是操作系统提供的一种并发执行机制,允许系统同时运行多个进程。它的主要目的是提高计算机资源的利用率、增强程序的并发能力,并改善系统的整体性能

        单进程程序 -> 只能一行行代码去执行。

        多进程程序 -> 同时执行两行及以上的代码 -> 产生一个或多个子进程,帮自己处理另其它的事情。

3、在linux下,如何开启一个新的进程?

        直接在linux下,执行一个程序,就会开启相应的进程。

        例如:在Linux的终端执行 ./hello -> 开启一个名字为hello的进程。

4、当进程开启之后,系统会为进程分配什么资源?

1)会分配进程对应内存空间

        如int x -> 运行程序之后,就会在栈区申请4个字节的内存空间。

2)进程在系统内核中如何进行表示呢

        学生管理系统 ---> 每个学生使用结构体进行表示和管理

        linux系统 ---> 每个进程使用结构体进行表示和管理

        当进程开启之后,会为这个进程分配一个任务结构体,这个任务结构体就是用于描述这个进程的。也就是说,进程在内核中是以结构体struct task_struct{} 进行表示的。这个结构体也被称之为进程控制块。

        结构体:进程ID号、信号、文件、资源....

        /usr/src/linux-headers-5.15.0-136/include/linux ----第717行

(不同版本系统的Linux可能存放的路径不同)

5、关于进程的命令

1)pstree:查看整个Linux系统进程之间关系的命令

        在Linux系统中,除了系统的初始进程之外,其余所有进程都是通过从一个父进程(parent)复刻(fork)而来的,有点像人类社会,每个个体都是由亲代父母繁衍而来。

        因此,在Linux系统中,所有的进程都起源于相同的初始进程,它们之间形成一棵倒置的进程树,就像家族族谱,可以使用命令pstree查看这些进程的关系:

Snail@ubuntu:~/Desktop$ pstree
systemd─┬─ModemManager───2*[{ModemManager}]├─NetworkManager───2*[{NetworkManager}]├─VGAuthService├─accounts-daemon───2*[{accounts-daemon}]├─acpid├─avahi-daemon───avahi-daemon├─blkmapd├─colord───2*[{colord}]├─cron├─cups-browsed───2*[{cups-browsed}]├─cupsd───dbus├─dbus-daemon├─freshclam├─fwupd───4*[{fwupd}]...

2)ps -ef:查看进程ID号(静态)

        如下图所示,从最开始的系统进程叫systemd(init),这个进程的诞生比较特别,其身份信息在系统启动前就已经存在于系统分区之中,在系统启动时直接复制到内存。而其余的进程,从前面提到的pstree命令的执行效果可见,都是系统初始进程的直接或间接的后代进程

3)top:查看进程CPU的占用率(动态)

Snail@ubuntu:~/Desktop$ toptop - 18:19:03 up 31 min,  1 user,  load average: 0.10, 0.07, 0.14
Tasks: 342 total,   1 running, 341 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.7 us,  1.4 sy,  0.0 ni, 97.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7894.6 total,   4216.9 free,   1381.5 used,   2296.2 buff/cache
MiB Swap:   1162.4 total,   1162.4 free,      0.0 used.   6187.0 avail Mem PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND  1935 Snail     20   0 4665680 273728 130240 S   8.6   3.4   0:43.43 gnome-s+ 6117 Snail     20   0  556976  52472  39952 S   3.3   0.6   0:00.97 gnome-t+ 2230 Snail     20   0  498040  32856  24280 S   0.7   0.4   0:02.74 fcitx    2507 Snail     20   0    8484   3472   3200 S   0.7   0.0   0:01.95 dbus-da+ 17 root      20   0       0      0      0 I   0.3   0.0   0:18.27 rcu_pre+ 55 root      20   0       0      0      0 I   0.3   0.0   0:02.04 kworker+ ...    

二、进程的状态

1、进程从诞生到死亡会经历哪些状态?

        就绪态 TASK_RUNNING 等待CPU资源 不占用CPU资源,不运行代码。

        运行态 TASK_RUNNING 占用CPU资源,运行代码。

        暂停态 TASK_STOPPED 占用CPU资源,不运行代码。 可以到就绪态。

        睡眠态 占用CPU资源,运行代码。 可以到就绪态。

                TASK_INTERRUPTIBLE 响应信号 ----》浅度睡眠 pause()--->一直等待下一信号

                TASK_UNINTERRUPTIBLE 不响应信号 ----》深度睡眠

        僵尸态 EXIT_ZOMBIE 占用CPU资源,不运行代码。不可以到运行态。进程退出的时候,就一定会变成僵尸态。

        死亡态 EXIT_DEAD 不占用CPU资源,不运行代码。进程退出的时候,如果有人去帮自己回收资源,那么僵尸态就会变为死亡态。

2、什么是僵尸态?

        进程结束时,就从运行态变成僵尸态,所谓僵尸态,就是代表这个进程所占用的CPU资源和自身的任务结构体没有被释放,这个状态的进程就是僵尸态进程。

        总结:

1)进程在暂停态时,收到继续的信号时,是切换到就绪态,而不是运行态。

2)程序的main函数执行return 0就会导致进程的退出,一定会变成僵尸态。

3)进程不可以没有父进程,也不能同时拥有两个父进程。

4)孤儿进程特征就是失去父进程时,会马上寻找继父,而不是等到孤儿进程变成僵尸态再找。

5)祖先进程一定要帮其他的进程回收资源。

三、进程创建

1、fork:在一个正在运行的进程中创建一个子进程

#include <sys/types.h>
#include <unistd.h>函数原型:pid_t fork(void);
函数作用:创建一个新的进程(子进程),该进程是调用进程(父进程)的副本。子进程会复制父进程的:代码段(text segment)数据段(data segment)、堆(heap)、栈(stack)文件描述符表(但共享相同的文件表项)进程环境(环境变量、信号处理方式等)写时复制(Copy-On-Write, COW)优化:现代操作系统不会立即复制所有内存,而是共享父进程的内存页。只有当父进程或子进程尝试修改某块内存时,才会真正复制该内存页,以提高效率。
返回值:成功时(fork() 调用一次,但返回两次):在父进程中:返回子进程的 PID(> 0)在子进程中:返回 0(因为子进程可以通过 getppid() 获取父进程的 PID)
失败时:返回-1,并设置 errno(常见原因:进程数达到系统限制、内存不足等)。

        在进程内部创建一个新的子进程,看看会不会同时做两件事情。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{/*当前是单进程的程序*/    printf("main process!\n");/*产生一个子进程*/fork();/* 一个父进程,一个子进程 *//* 接下来的代码,父进程会执行一遍,子进程也会执行一遍 */printf("after fork\n");return 0;
}

        可能的运行结果分析:

结果1:父进程先运行,子进程后运行。

        注意:只有父进程退出,才会出现命令行,子进程退出是不会出现命令行。

结果2:子进程先运行,父进程后运行。

        想要确保每次都是子进程先运行,做法: 就是让父进程先睡眠。

        子进程不用睡眠,父进程需要睡眠。 父子进程任务不一样。

        思路: 通过返回值判断。

#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{printf("main....\n");pid_t id = fork(); //创建一个子进程if(id == -1) //进程创建失败{perror("fork process error");return -1;}else if(id > 0) //父进程 ,但是返回的ID号 是 子进程的ID号{sleep(1); printf("我是父进程,我的进程ID是:%d\n", getpid());printf("我儿子的进程ID是:%d\n",id);}else if(id == 0)//子进程{printf("我是儿子,我的进程ID是:%d\n", getpid());   }printf("hello\n");return 0;
}

        结论:

1)父子进程执行顺序随机的。

2)fork()之后的代码,两个进程都会执行。

四、查看进程号

1、getpid

        查看当前进程号

#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>函数原型:pid_t getpid(void);函数功能:返回当前进程的进程 ID(PID)。每个进程都有一个唯一的 PID,由内核分配。返回值:成功时返回当前进程的 PID(> 0)。不会失败(没有错误情况)。

2、getppid

        查看父进程号

#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>函数原型:pid_t getppid(void);函数功能:返回当前进程的父进程 ID(PPID)。如果父进程终止,子进程的 PPID 会变为1(init 或 systemd),这种情况称为“孤儿进程被 init 收养”。返回值:成功时返回当前进程的 PPID(> 0)。不会失败(没有错误情况)。

3、程序实例

#include <stdio.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid == -1) {perror("fork failed");return 1;} else if (pid == 0) {// 子进程printf("Child: PID = %d, PPID = %d\n", getpid(), getppid());} else {// 父进程printf("Parent: PID = %d, child PID = %d\n", getpid(), pid);}return 0;
}

五、进程回收

1、wait

#include <sys/types.h>
#include <sys/wait.h>函数原型:pid_t wait(int *wstatus);
函数功能:阻塞当前进程;等待其子进程退出并回收其系统资源;把子进程从僵尸状态设置为死亡状态。
函数参数:wstatus  --> 用于存储子进程的状态信息 , 设置为NULL 则表示不需要状态信息
返回值:成功 返回接收到的子进程的ID 失败 返回 -1 ,并设置错误码

        接口说明:

  • 如果当前进程没有子进程,则该函数立即返回。
  • 如果当前进程有不止1个子进程,则该函数会回收第一个变成僵尸态的子进程的系统资源。
  • 子进程的退出状态(包括退出值、终止信号等)将被放入wstatus所指示的内存中,若wstatus指针为NULL,则代表当前进程放弃其子进程的退出状态。
  • 如果父进程如果要获取这些信息,需要用以下宏对status进程解析:

功能

WIFEXITED(status)

判断子进程是否正常退出

WEXITSTATUS(status)

获取正常退出的子进程的退出值

WIFSIGNALED(status)

判断子进程是否被信号杀死

WTERMSIG(status)

获取杀死子进程的信号的值

2、waitpid

#include <sys/types.h>
#include <sys/wait.h>函数原型:
pid_t waitpid(pid_t pid, int *wstatus, int options);函数功能:
挂起(阻塞)当前进程,直到指定的子进程终止或收到信号。
回收子进程资源(防止僵尸进程)。
获取子进程退出状态(通过 wstatus 返回)。函数参数:pid> 0    等待指定pid的子进程(假设为138 那么表示等待138号进程退出)= 0   等待本进程组中的任意一个子进程的退出 (与wait功能一致)= -1  等待任意一个子进程 (与进程组无关)< -1  等待的是该进程组(假设 pid 为 -138 那么表示等待进程组ID为 138的任何一个进程)wstatus --> 存储退出状态options --> options的取值,可以是0,也可以是上表中各个不同的宏的位或运算取值。        
返回值:> 0   成功返回终止的子进程 PID。= 0   仅当options=WNOHANG时,表示没有子进程退出。-1    出错(如无子进程、被信号中断等),错误码存于errno。

        与wait()的区别:

  • 可以通过参数 pid 用来指定想要回收的子进程。
  • 可以通过 options 来指定非阻塞等待。

pid

作用

options

作用

等待组ID等于pid绝对值的进程组中的任意一个子进程

0

阻塞等待子进程的退出

-1

等待任意一个子进程

WNOHANG

若没有僵尸子进程,则函数立即返回(非阻塞)

0

等待本进程所在的进程组中的任意一个子进程

WUNTRACED

当子进程暂停时函数返回(除了退出以外子进程暂停也会结束阻塞)

>0

等待指定pid的子进程

WCONTINUED

当子进程收到信号SIGCONT继续运行时函数返回(当子进程收到继续运行是会结束阻塞)

六、进程退出

1、exit()

        exit() 是 C 标准库(stdlib.h)提供的进程终止函数,用于 正常结束程序 并返回状态码给操作系统。它比 _exit() 更安全,因为它会先执行清理工作(如刷新缓冲区)。

#include <stdlib.h>函数原型:void exit(int status);函数作用:先清洗缓冲区和文件指针,再退出参数:status: 退出状态值0  -> 进程正常退出非0 -> 进程异常退出返回值:无但进程的退出状态会通过系统调用 _exit(status) 传递给父进程(可通过waitpid() 获取)

2、_exit()

        _exit() 是一个系统级进程终止函数,定义在中unistd.h。它直接终止进程,不执行任何清理操作(如缓冲区刷新、atexit() 函数调用等),而是立即将控制权交还给操作系统。

#include <unistd.h>函数原型:
void _exit(int status);函数功能:不会清洗缓冲区、不会关闭文件描述符,直接终止进程函数参数:status: 退出状态值0   -> 进程正常退出非0 -> 进程异常退出返回值:无返回值进程的退出状态status 会传递给父进程(可通过 waitpid() 或 wait() 获取)。

3、_Exit()

        _Exit() 是C 标准库(stdlib.h)提供的立即终止进程的函数,与 _exit() 类似,但属于 C 标准(而非 POSIX)。它 不执行任何清理操作,直接终止进程。

#include <stdlib.h>函数原型:
void _Exit(int status);函数功能:立即终止当前进程,不执行任何清理操作直接返回状态码 status 给操作系统(父进程可通过 waitpid() 获取)函数参数:status: 退出状态值0   -> 进程正常退出非0 -> 进程异常退出返回值:无返回值进程的退出状态status 会传递给父进程(通过 waitpid() 获取)。

4、 缓冲区问题

#include<stdio.h>
#include<stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>int main()
{/*单进程的程序*/printf("main ");//先清洗缓冲区,再退出当前进程//exit(0);//return  0;//直接退出当前进程,不会帮助你清洗缓冲区_exit(0);printf("11112222\n");
}

5、 退出状态

        父进程监听子进程的退出状态,如果子进程正常退出的,则输出一个ok,如果子进程异常退出,则输出一个error。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char *argv[]) //./show_bmp
{pid_t x;int state; //买一个碗int a = 11;x = fork();if(x > 0){wait(&state); //将碗的地址给你if(state == 0){printf("ok!\n");}else{printf("error!\n");}}if(x == 0){sleep(5);if(a == 10){exit(0); //正常退出}else{exit(-1); //异常退出}}
}

6、 exit与return

        例1:

#include <stdio.h>
#include <stdlib.h>int main(int argc,char *argv[])
{printf("helloworld!\n");return 0;
}
//输出helloworld

        例2:

#include <stdio.h>
#include <stdlib.h>int main(int argc,char *argv[])
{printf("helloworld!\n");exit(0);
}
//输出helloworld

        例3:

#include <stdio.h>
#include <stdlib.h>int fun(void)
{return 0;
}int main(int argc,char *argv[])
{fun();printf("helloworld!\n");return 0;
}
//输出helloworld

        例4:

#include <stdio.h>
#include <stdlib.h>int fun(void)
{exit(0);  -> 整个进程马上结束
}int main(int argc,char *argv[])
{fun();printf("helloworld!\n");return 0;
}
//不会输出helloworld

        结论:

        1)在main函数中,exit()与return语句作用是一样。 -> 因为main函数的返回就等价于进程的退出。

        2)不在main函数中,exit()代表进程的退出,return只是代表函数的返回。

http://www.xdnf.cn/news/17353.html

相关文章:

  • Python3.14都有什么重要新特性
  • 聚合直播-Simple Live-v1.7.7-全网直播平台能在一个软件上看完
  • java+postgresql+swagger-多表关联insert操作(九)
  • C++ 常用的智能指针
  • 使用Docker搭建开源Email服务器
  • 高防IP如何针对DDoS攻击特点起防护作用
  • 小刚说C语言刷题——1033 判断奇偶数
  • 《GPT-4.1深度解析:AI进化新标杆,如何重塑行业未来?》
  • Spring数据访问全解析:ORM整合与JDBC高效实践
  • 【Mysql】mysql数据库占用空间查询
  • 基础编程题目集 6-2 多项式求值
  • VUE简介
  • 蓝桥杯12. 日期问题
  • 全面解析IPv6:从理论到实践(以H3C配置为例)
  • 搜索插入位置--LeetCode
  • Linux中find和grep的区别
  • 常见但是有挑战的效果组件鸿蒙版
  • 视频分析设备平台EasyCVR化解高速服务区管理难题,打造全方位智能安防监控方案
  • 第 5 期(进阶版):训练第一个 DDPM 模型(使用 CIFAR-10 数据集)
  • 服务器上有conda环境 退出conda环境 再安装uv包管理器这样子就不会有冲突吗
  • MQ基础篇
  • LoRA(Low - Rank Adaptation,低秩自适应 )微调技术改进创新点
  • 并发设计模式实战系列(1):半同步/半异步模式
  • day45——非递减数列(LeetCode-665)
  • QT项目打包
  • Multi Agents Collaboration OS:文档合规性及质量检测助手设计及实践
  • 【KWDB 创作者计划】_算法篇---Stockwell变换
  • OpenAI重返巅峰:o3与o4-mini引领AI推理新时代
  • 面试经验杂谈
  • onlyoffice关闭JWT后依然报错如何解决?