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

相关文章

springboot加载不到nacos配置中心的配置问题处理

《springboot加载不到nacos配置中心的配置问题处理》:本文主要介绍springboot加载不到nacos配置中心的配置问题处理,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录springboot加载不到nacos配置中心的配置两种可能Spring Boot 版本Nacos

Linux中修改Apache HTTP Server(httpd)默认端口的完整指南

《Linux中修改ApacheHTTPServer(httpd)默认端口的完整指南》ApacheHTTPServer(简称httpd)是Linux系统中最常用的Web服务器之一,本文将详细介绍如何... 目录一、修改 httpd 默认端口的步骤1. 查找 httpd 配置文件路径2. 编辑配置文件3. 保存

Java调用C#动态库的三种方法详解

《Java调用C#动态库的三种方法详解》在这个多语言编程的时代,Java和C#就像两位才华横溢的舞者,各自在不同的舞台上展现着独特的魅力,然而,当它们携手合作时,又会碰撞出怎样绚丽的火花呢?今天,我们... 目录方法1:C++/CLI搭建桥梁——Java ↔ C# 的“翻译官”步骤1:创建C#类库(.NET

Linux使用scp进行远程目录文件复制的详细步骤和示例

《Linux使用scp进行远程目录文件复制的详细步骤和示例》在Linux系统中,scp(安全复制协议)是一个使用SSH(安全外壳协议)进行文件和目录安全传输的命令,它允许在远程主机之间复制文件和目录,... 目录1. 什么是scp?2. 语法3. 示例示例 1: 复制本地目录到远程主机示例 2: 复制远程主

Java Lambda表达式的使用详解

《JavaLambda表达式的使用详解》:本文主要介绍JavaLambda表达式的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言二、Lambda表达式概述1. 什么是Lambda表达式?三、Lambda表达式的语法规则1. 无参数的Lambda表

详解如何使用Python构建从数据到文档的自动化工作流

《详解如何使用Python构建从数据到文档的自动化工作流》这篇文章将通过真实工作场景拆解,为大家展示如何用Python构建自动化工作流,让工具代替人力完成这些数字苦力活,感兴趣的小伙伴可以跟随小编一起... 目录一、Excel处理:从数据搬运工到智能分析师二、PDF处理:文档工厂的智能生产线三、邮件自动化:

Spring @RequestMapping 注解及使用技巧详解

《Spring@RequestMapping注解及使用技巧详解》@RequestMapping是SpringMVC中定义请求映射规则的核心注解,用于将HTTP请求映射到Controller处理方法... 目录一、核心作用二、关键参数说明三、快捷组合注解四、动态路径参数(@PathVariable)五、匹配请

Java进程CPU使用率过高排查步骤详细讲解

《Java进程CPU使用率过高排查步骤详细讲解》:本文主要介绍Java进程CPU使用率过高排查的相关资料,针对Java进程CPU使用率高的问题,我们可以遵循以下步骤进行排查和优化,文中通过代码介绍... 目录前言一、初步定位问题1.1 确认进程状态1.2 确定Java进程ID1.3 快速生成线程堆栈二、分析

git stash命令基本用法详解

《gitstash命令基本用法详解》gitstash是Git中一个非常有用的命令,它可以临时保存当前工作区的修改,让你可以切换到其他分支或者处理其他任务,而不需要提交这些还未完成的修改,这篇文章主要... 目录一、基本用法1. 保存当前修改(包括暂存区和工作区的内容)2. 查看保存了哪些 stash3. 恢

java String.join()方法实例详解

《javaString.join()方法实例详解》String.join()是Java提供的一个实用方法,用于将多个字符串按照指定的分隔符连接成一个字符串,这一方法是Java8中引入的,极大地简化了... 目录bVARxMJava String.join() 方法详解1. 方法定义2. 基本用法2.1 拼接