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

相关文章

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

Window Server2016 AD域的创建的方法步骤

《WindowServer2016AD域的创建的方法步骤》本文主要介绍了WindowServer2016AD域的创建的方法步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、准备条件二、在ServerA服务器中常见AD域管理器:三、创建AD域,域地址为“test.ly”

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

2.1/5.1和7.1声道系统有什么区别? 音频声道的专业知识科普

《2.1/5.1和7.1声道系统有什么区别?音频声道的专业知识科普》当设置环绕声系统时,会遇到2.1、5.1、7.1、7.1.2、9.1等数字,当一遍又一遍地看到它们时,可能想知道它们是什... 想要把智能电视自带的音响升级成专业级的家庭影院系统吗?那么你将面临一个重要的选择——使用 2.1、5.1 还是

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

mac中资源库在哪? macOS资源库文件夹详解

《mac中资源库在哪?macOS资源库文件夹详解》经常使用Mac电脑的用户会发现,找不到Mac电脑的资源库,我们怎么打开资源库并使用呢?下面我们就来看看macOS资源库文件夹详解... 在 MACOS 系统中,「资源库」文件夹是用来存放操作系统和 App 设置的核心位置。虽然平时我们很少直接跟它打交道,但了

关于Maven中pom.xml文件配置详解

《关于Maven中pom.xml文件配置详解》pom.xml是Maven项目的核心配置文件,它描述了项目的结构、依赖关系、构建配置等信息,通过合理配置pom.xml,可以提高项目的可维护性和构建效率... 目录1. POM文件的基本结构1.1 项目基本信息2. 项目属性2.1 引用属性3. 项目依赖4. 构

Rust 数据类型详解

《Rust数据类型详解》本文介绍了Rust编程语言中的标量类型和复合类型,标量类型包括整数、浮点数、布尔和字符,而复合类型则包括元组和数组,标量类型用于表示单个值,具有不同的表示和范围,本文介绍的非... 目录一、标量类型(Scalar Types)1. 整数类型(Integer Types)1.1 整数字

Python中@classmethod和@staticmethod的区别

《Python中@classmethod和@staticmethod的区别》本文主要介绍了Python中@classmethod和@staticmethod的区别,文中通过示例代码介绍的非常详细,对大... 目录1.@classmethod2.@staticmethod3.例子1.@classmethod

Java操作ElasticSearch的实例详解

《Java操作ElasticSearch的实例详解》Elasticsearch是一个分布式的搜索和分析引擎,广泛用于全文搜索、日志分析等场景,本文将介绍如何在Java应用中使用Elastics... 目录简介环境准备1. 安装 Elasticsearch2. 添加依赖连接 Elasticsearch1. 创