Linux的进程详解(进程创建函数fork和vfork的区别,资源回收函数wait,进程的状态(孤儿进程,僵尸进程),加载进程函数popen)

本文主要是介绍Linux的进程详解(进程创建函数fork和vfork的区别,资源回收函数wait,进程的状态(孤儿进程,僵尸进程),加载进程函数popen),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 目录

什么是进程

 Linux下操作进程的相关命令

进程的状态(生老病死)

创建进程系统api介绍:

fork()

父进程和子进程的区别

vfork()

进程的状态补充:

孤儿进程

僵尸进程

回收进程资源api介绍:

wait()

waitpid()

exit()

popen


什么是进程

        一个程序是由源代码在编译后产生的,格式为ELF的,存储于硬盘的文件,在Linux中,程序文件的格式都是ELF,这些文件在被执行的瞬间,就被载入内存,所谓的载入内存,就是将数据段、代码段这些运行时必要的资源拷贝到内存,另外系统会再分配相应的栈、堆等内存空间给这个进程,使之成为一个动态的实体。而这个动态的实体,程序中的代码和数据,被加载到内存中运行的过程,就叫进程,程序是静态的,进程是动态的

 Linux下操作进程的相关命令

ps 查看当前终端运行的进程

ps -e 查看当前系统的所有进程

./ 程序名 运行当前目录下的程序 

top 查看当前Linux系统的进程运行情况

pstree 查看进程树

killall 杀死所有进程 后面加名字即为杀死所有叫这个名字的进程

kill 杀死一个进程 后面跟进程的pid即为杀死指定的pid进程

killall/kill -STOP 进程名/进程PID #暂停进程

killall/kill -CONT 进程名/进程PID #继续运行继承

进程的状态(生老病死)

文字是从某本书摘抄的,我觉得写的非常好(当然图也是)

创建进程系统api介绍:
fork()
 #include <sys/types.h>#include <unistd.h>pid_t fork(void);

创建一个子进程

成功时返回两个返回值

返回子进程pid,返回子进程pid时,执行父进程

返回值0,返回值为0时,执行子进程

创建失败时返回一个值为-1

返回值-1时不创建子进程

pid_t pid;  //pid_t跟int含义相同if(pid == 0)
{//子进程的代码  
}
if(pid > 0)
{//父进程的代码
}
父进程和子进程的区别

父子进程的以下属性在创建之初完全一样:
A) 实际UID和GID,以及有效UID和GID。
B) 所有环境变量。
C) 进程组ID和会话ID。
D) 当前工作路径。
E) 打开的文件。
F) 信号响应函数。
G) 整个内存空间,包括栈、堆、数据段、代码段、标准IO的缓冲区等等。
而以下属性,父子进程是不一样的:
A) 进程号PID。PID是身份证号码,哪怕亲如父子,也要区分开。
B) 记录锁。父进程对某文件加了把锁,子进程不会继承这把锁。
C) 挂起的信号。这是所谓“悬而未决”的信号,等待着进程的响应,子进程不会继承这些信号。


子进程只会执行fork()语句下面的程序段,不会执行fork上的程序段

引用:”的确子进程包含有和父进程一样的代码和数据(虽然一样但的确是自己的一份)。 但别忘了,子进程复制的不仅是父进程的代码和数据,还包括状态,这个状态就包含有PC指针寄存器的值。 也就是说子进程创建完成后,他和父进程一样,PC指针都指向下一条语句, 因此子进程是从自身创建完成后的地方继续运行 ,而父进程运行过得代码将不再运行。“

参考文献:为什么fork创建子进程后,父进程中运行过的代码在子进程中不再运行了_fokk一个子进程后,父进程变量的值在子进程中仍然存在对吗-CSDN博客

分析下面的demo,判断printf打印了几次

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{fork();fork();fork();printf("15678\n"); 
}

printf共打印8次,可以用一个阻塞函数阻塞程序运行,然后在Linux终端输入pstree查看原因


vfork()

创建一个子进程并阻塞父进程

fork()和vfork()的区别

1.资源复制方式:

fork:创建子进程时,子进程会复制父进程的数据段、堆栈段和代码段。虽然现代Linux通过写时复制(Copy-on-Write, COW)技术优化了这一过程,但本质上,父子进程拥有独立的地址空间。

vfork:子进程与父进程共享数据段。vfork创建的子进程基本上是一个轻量级进程,它不会复制父进程的地址空间,而是直接共享。

2.执行顺序:

fork:父子进程的执行顺序是不确定的。操作系统可能会先调度父进程或子进程运行。

vfork:保证子进程先运行。在子进程调用exec或exit之前,父进程不会运行。这确保了在子进程执行完毕或替换为新程序之前,父进程不会继续执行。

1.vfrok 保证子进程先执行,父进程后执行 

2.fork 父子进程的内存独立,vfrok 父子进程共享数据段 (常量,静态数据,全局变量)。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>// 全局变量,存储数据段
int value = 0;int main()
{// 1.创建一个子进程pid_t pid = vfork(); // 共享数据段//pid_t pid = fork(); // 父子进程的内存完全独立if (pid == 0)       // 子进程{while (1){printf("%d 我是子进程pid是%d  我的爸爸是%d\n", value, getpid(), getppid());value++;sleep(1);if (value == 10){printf("子进程结束\n");exit(0); // 子进程结束}}}//sleep(12);printf("父进程运行\n"); // 父进程被阻塞了,直到子进程结束if (pid > 0)            // 父进程{while (1){printf("%d 我是父亲进程pid是%d  我的儿子是%d\n", value, getpid(), pid);value--;sleep(1);}}
}


可以看到在执行过程中父进程中的value的值受子进程的影响

进程的状态补充:
孤儿进程

如果一个子进程的父进程比子进程先死亡,那么这个子进程就会变成孤儿进程,由系统进程(init进程)进行管理

要防止孤儿进程的产生,父进程必须比子进程后死亡

僵尸进程

子进程比父进程先死亡,并且父进程未回收子进程的资源。那么子进程就会变成僵尸进程。
创建僵尸进程

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{for (int i = 0; i < 10; i++){int pid = fork(); // fork 后父子进程谁先执行是不确定的,由操作系统调度决定if (pid == 0){printf("我是子进程:%d ,我的父亲是:%d ,子进程死亡\n", getpid(), getppid());return 0; // 结束子进程}}printf("父亲继续运行,等待任意键结束\n");getchar(); // 挂起态printf("父进程结束\n");return 0;
}

使用ps -e查看僵尸进程

回收进程资源api介绍:
wait()

回收进程资源

#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *wstatus);

成功时返回回收资源的进程的pid号

失败时返回-1

wait一次回收一次任一资源(回收的资源是随机的)

可以通过wait获取进程的退出状态

有在wait(int *status)里放一个整形指针,如果 status 不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内,如果不关心终止状态,则可将该参数指定为空指针,如wait(NULL)

死亡原因由以下宏定义获取


因为wait执行一次只回收一次资源,所以想把所有子进程资源都回收需要不断循环
 

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{// 1.创建一个子进程pid_t pid = fork();if (pid == 0) // 子进程{int i = 0;while (1){printf("子进程执行中 %d\n", i++);if (i == 20){printf("子进程结束\n");// 退出子进程return 5;}sleep(1);}}while (1) {ret = wait(NULL);if (ret == -1) {if (errno == EINTR) {    // 返回值为-1的时候有两种情况,一种是没有子进程了,还有一种是被中断了continue;                  //如果是被中断了就continue继续执行}break;}}printf("当前子进程资源已全部回收"\n);printf("父进程为 %d\n", getpid());
}


通过wait获取退出状态

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{// 1.创建一个子进程pid_t pid = fork();if (pid == 0) // 子进程{int i = 0;while (1){printf("子进程执行中 %d\n", i++);if (i == 20){printf("子进程结束\n");// 退出子进程,设置子进程退出状态return 5;}sleep(1);}}if (pid > 0) // 父进程{int status;printf("等待子进程结束\n");wait(&status);// 获取子进程的退出状态 (死亡原因)if (WIFEXITED(status)) // 判断子进程是否正常退出{printf("子进程正常退出\n");printf("退出状态码: %d\n", WEXITSTATUS(status)); // 获取子进程的return 值}// 获取是否被信号杀死if (WIFSIGNALED(status)){printf("子进程被信号杀死\n");printf("杀死子进程的信号: %d\n", WTERMSIG(status)); // 获取杀死子进程的信号}}
}

waitpid()

pid_t waitpid(pid_t pid, int *wstatus, int options); 等待指定的子进程结束

pid:等待的进程pid  
wstatus:进程的退出状态 
options:等待属性设置阻塞与非阻塞  ,默认为 0 阻塞 

exit()

void _exit(int status);
void exit(int status);

status 子进程的退出值
没有返回值

如果子进程正常退出,则 status 一般为 0。
如果子进程异常退出,则 status 一般为非 0

exit()退出时,会自动冲洗(fush)标准IO总残留的数据到内核,如果进程注册了“退出处理函数”还会自动执行这些函数。
而_exit()会直接退出。

popen

加载一个进程,并创建一个通信管道 (文件流指针)

FILE *popen(const char *command, const char *type);
command:需要加载的程序   
type:加载的权限   
"r"  :读取  
 "w" :写入 
 "e" :可执行
 返回值: 成功  文件流指针    
              失败   NULL                     
                             
int pclose(FILE *stream);  //关闭文件流指针

通过popen输入Linux命令行命令

#include <stdio.h>
int main()
{// 1.加载 ls -l 命令(程序)FILE *pf = popen("ls -l", "r");if (pf == NULL){printf("popen error\n");return -1;}else{printf("加载成功\n");}// 读取 ls -l  程序加载后的输出结果while (1){char buf[1024] = {0};char *ret = fgets(buf, 1024, pf);if (ret == NULL){break;}printf("%s\n", buf);}// 关闭程序pclose(pf);
}

这篇关于Linux的进程详解(进程创建函数fork和vfork的区别,资源回收函数wait,进程的状态(孤儿进程,僵尸进程),加载进程函数popen)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1100597

相关文章

Debezium 与 Apache Kafka 的集成方式步骤详解

《Debezium与ApacheKafka的集成方式步骤详解》本文详细介绍了如何将Debezium与ApacheKafka集成,包括集成概述、步骤、注意事项等,通过KafkaConnect,D... 目录一、集成概述二、集成步骤1. 准备 Kafka 环境2. 配置 Kafka Connect3. 安装 D

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Spring Cloud LoadBalancer 负载均衡详解

《SpringCloudLoadBalancer负载均衡详解》本文介绍了如何在SpringCloud中使用SpringCloudLoadBalancer实现客户端负载均衡,并详细讲解了轮询策略和... 目录1. 在 idea 上运行多个服务2. 问题引入3. 负载均衡4. Spring Cloud Load

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

在 Spring Boot 中使用 @Autowired和 @Bean注解的示例详解

《在SpringBoot中使用@Autowired和@Bean注解的示例详解》本文通过一个示例演示了如何在SpringBoot中使用@Autowired和@Bean注解进行依赖注入和Bean... 目录在 Spring Boot 中使用 @Autowired 和 @Bean 注解示例背景1. 定义 Stud

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为