【Linux系统编程】—进程学习笔记(fork进程创建、退出、僵死进程与孤儿进程、如何避免僵死进程)

news2024/7/6 20:09:59

目录

一、进程关键概念

二、进程创建实战

1、fork函数

2、fork创建一个子进程的一般目的:

 3、fork函数实例:

4、fork的写时拷贝技术(COW)

三、进程退出

1、正常退出

2、异常退出

3、总结

四、僵死进程与孤儿进程

1、什么是僵死进程

2、什么是孤儿进程

3、僵尸进程与孤儿进程的区别

4、僵尸进程的危害

5、避免僵死进程的方法(三种方法)


一、进程关键概念

Q1:什么是程序,什么是进程,有什么区别?

A1:程序是静态的概念,gcc xxx.c -o pro执行后在磁盘中生成pro文件,叫做程序。进程是程序的一次运行活动,通俗点意思是程序跑起来了,系统中就多了一个进程。

Q2:如何查看系统中有哪些进程?

A2:(1)使用ps -aux指令查看:实际操作中,配合grep来查找程序中是否存在某一个进程。

Q3:什么事进程标识符?

A3:每个进程都有一个非负整数表示的唯一ID,叫做pid。编程调用getpid()函数获取自身的进程标识符,getppid()获取父进程的进程标识符。 

pid=0:称为交换进程(swapper),处理进程调度

pid=1:init进程,系统初始化和回收孤儿进程

Q4:什么是父进程,什么是子进程?

A4:进程A创建了进程B,那么A叫做父进程,B叫做子进程,父子进程是相对的概念。

Q5:C程序的存储空间是如何分配的?

A5:

(1)正文段。这是由CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器、C编译器和shell等)在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其自身的指令。

(2)初始化数据段。通常将此段称为数据段,它包含了程序中需明确地赋初值的变量。使变量带有其初值存放在初始化数据段中。

(3)非初始化数据段。通常将此段称为bss段,这一名称来源于一个早期的汇编运算符,意思是“block started by symbol”(由符号开始的块),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。出现在任何函数外的C声明,使变量带有其初值存放在非初始化数据段中。

(4)栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次调用函数时,其返回地址以及调用者的环境信息(例如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,可以递归调用C函数。递归函数每次调用自身时,就使用一个新的栈帧,因此一个函数调用实例中的变量集不会影响另一个函数调用实例中的变量。

(5)堆。通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于非初始化数据段和栈之间。

二、进程创建实战

1、fork函数

终端输入man fork查看fork函数介绍、包含头文件和原形。

NAME
        fork - create a child process

SYNOPSIS
        #include <unistd.h>

        pid_t fork(void);

2、fork创建一个子进程的一般目的:

(1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

(2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

 3、fork函数实例:

模仿服务器与客户端,客户端连接到来时子进程处理,父进程等待下个客户端到来。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
	pid_t pid;
	int data = 10;

	while(1){

		printf("please input a data\n");
		scanf("%d",&data);	
		if(data == 1){
						
				pid = fork();
					
				if(pid > 0)
				{
					
				}
				else if(pid == 0){
	
					while(1){
						printf("do net request,pid=%d\n",getpid());
						sleep(3);
					}
				}
		}
		else{
			printf("wait ,do nothing\n");
		}
	}
	return 0;
}

fork函数调用成功,返回两次:一次返回到父进程,一次返回到子进程:

(1)返回值为0,代表当前进程是子进程。

(2)返回值为非负数,代表当前进程为父进程,返回值是子进程的id号

(3)调用失败,返回-1。

(4)父子进程谁先执行取决于系统调度。

4、fork的写时拷贝技术(COW)

(1)子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意,这是子进程所拥有的副本。父、子进程并不共享这些存储空间部分。父、子进程共享正文段(代码段)。

(2)由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全复制。作为替代,使用了写时拷贝(Copy-On-Write, COW)技术。这些区域由父、子进程共享,而且内核将它们的访问权限改变为只读的。如果父、子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页”。

三、进程退出

1、正常退出

(1)main函数调用return

(2)进程调用exit(),标准c库

(3)进程调用_exit()或者_Exit(),属于系统调用

(4)进程最后一个线程返回

(5)最后一个线程调用pthread_exit

2、异常退出

(1)调用abort

(2)当进程收到某些信号是,如ctrl+c

(3)最后一个线程取消(cancellation)请求作出响应

3、总结

不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。

对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit._exit和_Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。

四、僵死进程与孤儿进程

1、什么是僵死进程

当子进程退出状态不被收集,就会变成僵死进程(僵尸进程)。一般情况下,程序调用exit(包括_exit和_Exit,它们的区别这里不做解释),它的绝大多数内存和相关的资源已经被内核释放掉,但是在进程表中这个进程项(entry)还保留着(进程ID,退出状态,占用的资源等等),你可能会问,为什么这么麻烦,直接释放完资源不就行了吗?这是因为有时它的父进程想了解它的退出状态。在子进程退出但还未被其父进程“收尸”之前,该子进程就是僵死进程,或者僵尸进程。如果父进程先于子进程去世,那么子进程将被init进程收养,这个时候init就是这个子进程的父进程。

所以一旦出现父进程长期运行,而又没有显示调用wait或者waitpid,同时也没有处理SIGCHLD信号,这个时候init进程就没有办法来替子进程收尸,这个时候,子进程就真的成了“僵尸”了。

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	pid_t pid;

	int cnt = 0;
	
	pid = fork();
		
	if(pid > 0)
	{
		while(1){
			printf("cnt=%d\n",cnt);
			printf("this is father print, pid = %d\n",getpid());
			sleep(1);
		}	
	}
	
	else if(pid == 0){
		
		while(1){
			printf("this is chilid print, pid = %d\n",getpid());
			sleep(1);
			cnt++;
			if(cnt == 3){
				exit(0);
			}
		}	
	}

	return 0;
}

在终端查看进程状态ps -aux|grep a.out,子进程退出,父进程没有接受子进程的退出状态,子进程编程僵尸进程。

2、什么是孤儿进程

父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。

3、僵尸进程与孤儿进程的区别

回答这个问题很简单,就是爸爸(父进程)和儿子(子进程)谁先死的问题!如果当儿子还在世的时候,爸爸去世了,那么儿子就成孤儿了,这个时候儿子就会被init收养,换句话说,init进程充当了儿子的爸爸,所以等到儿子去世的时候,就由init进程来为其收尸。如果当爸爸还活着的时候,儿子死了,这个时候如果爸爸不给儿子收尸,那么儿子就会变成僵尸进程。

4、僵尸进程的危害

(1)僵死进程的PID还占据着,意味着海量的子进程会占据满进程表项,会使后来的进程无法fork。

(2)僵死进程的内核栈无法被释放掉(1K 或者 2K大小),为啥会留着它的内核栈,因为在栈的最低端,有着thread_info结构,它包含着 struct_task 结构,这里面包含着一些退出信息。

5、避免僵死进程的方法(三种方法)

(1)程序中显示的调用signal(SIGCHLD, SIG_IGN)来忽略SIGCHLD信号,这样子进程结束后,由内核来wai和释放资源。

(2)fork两次,第一次fork的子进程在fork完成后直接退出,这样第二次fork得到的子进程就没有爸爸了,它会自动被老祖宗init收养,init会负责释放它的资源,这样就不会有“僵尸”产生了。

(3)对子进程进行wait,释放它们的资源,但是父进程一般没工夫在那里守着,等着子进程的退出,所以,一般使用信号的方式来处理,在收到SIGCHLD信号的时候,在信号处理函数中调用wait操作来释放他们的资源。

例:调用wait函数,父进程等待子进程退出并收集子进程的退出状态

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>


int main()
{
	pid_t pid;

	int cnt = 0;
	int status = 10;

	
	pid = fork();
		
	if(pid > 0)
	{
		wait(&status);
		printf("child quit, child status = %d\n",WEXITSTATUS(status));
		while(1){
			printf("cnt=%d\n",cnt);
			printf("this is father print, pid = %d\n",getpid());
			sleep(1);
		}	
	}
	
	else if(pid == 0){
		
		while(1){
			printf("this is chilid print, pid = %d\n",getpid());
			sleep(1);
			cnt++;
			if(cnt == 5){
				exit(3);
			}
		}	
	}

	return 0;
}

· wait(NULL); 不关心退出状态
· 如果子进程退出状态不被收集,则编程僵尸进程(不用wait函数)
· wait() 使调用者阻塞,等待子进程结束后,再开始父进程
· 参数非空就可以接收子进程结束返回参数(exit(3))
· WEXITSTATUS()这个宏,可以解析status,解析结果为3,如果不用宏解析它,status则是一个不确定整数

其余方法参考:https://blog.csdn.net/astrotycoon/article/details/39717143

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/705385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

融合学习:跨文化交流的学习平台

在全球化的时代&#xff0c;跨文化交流已经成为了一个不可避免的现象。在这种情况下&#xff0c;融合学习平台成为了一个非常重要的工具&#xff0c;可以帮助人们更好地了解和学习不同文化之间的差异和相似之处。本文将探讨融合学习平台的重要性&#xff0c;以及如何选择最佳的…

构建交互式数据框架:使用Gradio的Dataframe模块

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

查找算法-线性搜索

线性搜索 简介 线性搜索是一种简单的搜索算法&#xff0c;也被称为顺序搜索。它从数据集的起始位置开始&#xff0c;逐个比较每个元素&#xff0c;直到找到目标元素或遍历完整个数据集为止。如果目标元素存在于数据集中&#xff0c;线性搜索会返回该元素的位置或索引&#xf…

照片如何转存到手机笔记中?具体方法教程在这里

传统的笔记形式一般是以文字的形式记录&#xff0c;通常是在一个笔记本中写下自己的所感所想、工作、生活等方面的内容。有时也有人会选择贴上照片&#xff0c;成为个人回忆的重要资料。 而随着手机笔记的出现&#xff0c;很多人选择使用它来记录&#xff0c;因为我们可以随时…

小米 红米 Redmi note11 4G 5G 手机解锁BL 秒BL解锁 教程 跳过168小时 selenes evergo线刷机包下载

红米&#xff08;Redmi&#xff09;Note 11 5G 手机BL解锁 红米note11 4G/5G 秒解锁BL锁 方法 教程 跳过168小时 新版本 selenes evergo 红米Note11系列版本非常多&#xff0c;从4G开始一直到后面出现的11R&#xff0c;我们常见的机型有红米 Note11/11Pro/11Pro/11SE/11R等等&…

管理类联考——数学——知识篇——公式——最难记

立方和与立方差公式 a 3 b 3 ( a b ) ( a 2 ∓ a b b 2 ) a^3b^3(ab)(a^2∓abb^2) a3b3(ab)(a2∓abb2) 一元二次方程求根公式 x − b b 2 − 4 a c 2 a &#xff0c; b 2 − 4 a c ≥ 0 x\frac{-b\sqrt{b^2-4ac}}{2a}&#xff0c;\sqrt{b^2-4ac}≥0 x2a−bb2−4ac ​​&…

蓝牙模块(HC-05/HC-06)详解

这里写目录标题 0. 蓝牙概述蓝牙技术的特点 1. 常见的蓝牙模块2. HC-05/HC-062.1 概念2.2 区别 3. STM32使用HC-05通信3.1 方法3.2 示例代码 0. 蓝牙概述 蓝牙&#xff08;Bluetooth&#xff09;是一种用于无线通信的技术标准&#xff0c;允许设备在短距离内进行数据交换和通信…

初学mybatis(七)缓存

学习回顾&#xff1a;初学mybatis&#xff08;六&#xff09; 一、简介 1、什么是缓存 [ Cache ]&#xff1f; 存在内存中的临时数据。将用户经常查询的数据放在缓存&#xff08;内存&#xff09;中&#xff0c;用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询&#x…

6应用层-6.2【实验】【计算机网络】

6应用层-6.2【实验】【计算机网络】 前言推荐6应用层知识一、DNS常用记录类型&#xff1a;二、nslookup命令的用法 6.1 Web服务与FTP服务配置6.2 DNS域名系统配置实验目的实验内容及实验环境实验原理实验过程1.搭建如图所示的网络拓扑2.测试网络连通性3.在服务器上启用DNS服务&…

腾讯云部署tomcat问题--使用localhost访问没问题但是使用公网访问就有问题

1、使用localhost访问没问题。 2、使用公网访问就有问题 3、解决方案 3.1设置了一个规则&#xff0c; 3.2查看页面&#xff0c;访问OK

如何在 Shadow 插件化框架中动态加载和调用插件中的方法

Shadow是一种Android插件化框架&#xff0c;它允许将应用功能模块以插件的方式集成到宿主应用中。插件化是指将应用的不同模块打包成独立的插件&#xff0c;可以在运行时动态地加载和卸载这些插件&#xff0c;从而实现动态扩展和灵活组合功能。 Shadow插件化框架采用了类加载器…

Jenkins全栈体系(一)

Jenkins Jenkins&#xff0c;原名 Hudson&#xff0c;2011年改为现在的名字。它是一个开源的实现持续集成的软件工具。 第一章 GitLab安装使用 官方网站&#xff1a;https://about.gitlab.com/ 安装所需最小配置 内存至少4G https://docs.gitlab.cn/jh/install/requireme…

上海声通团队在WeNet中开源Branchformer

上海声通信息科技股份有限公司作为交互式人工智能市场的领导者&#xff0c;具有极强的技术优势和突出的产品特点。公司基于自研的融合通信及人工智能两项核心技术&#xff0c;打造了丰富的、高度标准化的产品模块&#xff0c;为客户提供高效、稳定的产品体验。公司主要的业务场…

Arduino Proteus仿真空气净化器温湿度PM2.5空气质量MQ135-0049

Arduino Proteus仿真空气净化器温湿度PM2.5空气质量MQ135-0049 Proteus仿真小实验&#xff1a; Arduino Proteus仿真空气净化器温湿度PM2.5空气质量MQ135-0049 功能&#xff1a; 硬件组成&#xff1a;ARDUINO -UNO-R3开发板、 LCD1602 、DHT11温湿度传感器、电位器模拟PM2.…

垃圾收集策略与算法

垃圾收集策略与算法 程序计数器、虚拟机栈、本地方法栈随线程而生&#xff0c;也随线程而灭&#xff1b;栈帧随着方法的开始而入栈&#xff0c;随着方法的结束而出栈。这几个区域的内存分配和回收都具有确定性&#xff0c;在这几个区域内不需要过多考虑回收的问题&#xff0c;因…

详解C++类型转换特性(代码+详解)

C类型转换 引言1. C语言中的类型转换2. 为什么C需要四种类型转换 C强制类型转换1.static_cast补充 2.dynamic_cast3.const_cast4.reinterpret_cast RTTI 引言 1. C语言中的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配…

虚拟机Centos7环境下如何安装wget

一、wget简介 wget 是一个从网络上自动下载文件的自由工具&#xff0c;支持通过 HTTP、HTTPS、FTP 三个最常见的 TCP/IP协议 下载&#xff0c;并可以使用 HTTP 代理。“wget” 这个名称来源于 “World Wide Web” 与 “get” 的结合。所谓自动下载&#xff0c;是指 wget 可以在…

JVM oop内存模型

一、oop模型 1、非数组对象 InstaceOopDesc 2、数组对象 arrayOopDesc 2.1 基本数据类型数组 typeArrayOopDesc 2.2 引用类型数组 objArrayOopDesc 3、MarkOopDesc 存放锁信息、分代年龄等 二、对象的内存结构 对象内存结构分成三大部分 对象头 &#xff08;64位操作系统&a…

软考02原码反码和补码

文章目录 前言一、原码二、反码三、补码总结 前言 机器是通过二进制来存储数据的&#xff0c;最好是在学习了软考01进制转换基础上开始学习原码反码和补码。 一、原码 原码通常以固定位数表示,不足补0&#xff0c;由于需要区分正负数所以&#xff0c;最高位为符号位(0为正&…

Electron中启动node服务

记一次遇到的问题&#xff0c;我们知道Electron 中主进程是在node环境中&#xff0c;所以打算在node环境中再启动一个node服务。但是直接使用exec命令启动就会卡主。对应的代码如下 // 启动Node server const startServer async () > {try {console.log(开始启动node serv…