【Linux】进程学习① (进程的PCB(task_struct)进程的标识符详解进程的创建fork函数)

目录

​编辑

1.进程的概念

1.1进程的描述与组织:进程的PCB

进程:进程是 内核pcb对象+可执行程序/内核数据结构+可执行程序=进程

1.3 task_struct

2.PCB内部属性

3 查看进程

4.获取进程标识符:getpid函数(4-6主要围绕进程的标识符展开)

5.父进程标识符获取getppid函数

另外一种查看进程方式:

cwd

6.创建进程fork

6.1 fork函数的返回值

6.2 父进程的代码和数据

6.3 关于fork函数的返回值:

6.3.1 如果创建进程成功:返回子进程pid给父进程,给子进程返回0,创建失败返回-1.  这是为什么?

6.3.2 函数有两个返回值,至少应该返回两次,所以fork函数为什么能够返回两次呢?

6.3.3 写实拷贝(对父子进程数据的解释)

6.4 一次创建多个进程:

7.结语


1.进程的概念

根据冯诺依曼计算机结构,首先什么叫做程序?

程序本质就是二进制文件,C语言经过编译器编译生成可执行程序后,在磁盘放着。磁盘本质上是外设(输入输出设备),双击运行,首先要将程序加载到内存中,再交给cpu运行(才得以被cpu读写,解释)

其次,什么叫进程?

教材观点:把程序加载到内存叫进程或者正在运行的程序。

我们要理解好什么叫做进程。理解计算机各种工作的第一性原理:先描述再组织。

切入点:在操作系统或者计算机体系中,可以同时运行多个程序

任务栏空白处单击右键打开任务管理器可以查看

这是一个事实:我们可以同时启动多个程序

1.1进程的描述与组织:进程的PCB

每一个程序背后都有一个exe文件, 那么启动多个程序也就意味着将多个.exe文件加载到了内存中,那么操作系统要不要管理多个加载到内存中的多个程序呢?答案是要的,那么操作系统如何管理这些加载到内存中的的程序呢?操作系统肯定是先处于内存中,因为开机就启动了操作系统这个软件,就已经加载到内存中了

下一步:操作系统如何管理:

先描述,再组织

先描述一下这个加载到内存中这个程序的属性可以使用结构体,l因为inux操作系统是用c语言写的

struct  xxx

{

状态;//活着还是死亡的

优先级;//cpu只有一个,决定执行先后

内存指针字段;//要运行这个应用程序,这个应用程序的代码在什么地方,去哪里寻找这个程序的代码和是数据

标识符;//每一个程序的标识标号

::

::

::

要包含进程几乎所有的属性字段

struct  xxx * next;
|}

 那么当每一个可执行程序也可以叫做进程被加载到内存中时,只有可执行程序的代码和数据,操作系统并不认识加载到内存中的每一个程序,就好比我们刚入学,院长根本不认识我们,那么学校就说,为了下次我知道你们是谁,也为了让班主任、学院好管理认识你们,我给你们每个人一个学生卡,上面就有学生的名字、班级、学号、等等信息。那么操作系统将可执行加载到内存的同时,为了更好管理,作系统就会给每一个可执行程序、进程创建对应的描述该进程的属性的结构体变量或者对象,把属性补充好,所以每一个进程都有一个对应的结构体变量。这个结构体变量中保存的是每一个进程的属性,在官方的描述中我们称这个结构体变量为PCB(process ctrl block),中文名称为:进程控制块。于此同时,每一个pcb内部还包含指向下一个pcb的指针,pcb创建的时候也是要占据空间的,所以当程序加载到内存中,占据的空间比程序的大小要大,多出来的空间就是我们的进程控制块。

从此,操作系统通过pcb可以访问各个进程,然后进每个进程的pcb组织起来:

 对进程的管理就变成了对pcb链表的增删查改。如果此时新增加一个程序,对新程序创建pcb结构体,然后填充属性,分配编号,连入pcb链表。,输出释放进程,将代码删除将pcb描述删除,进程就删除了。

所以

进程:进程是 内核pcb对象+可执行程序/内核数据结构+可执行程序=进程

我们的pcb链表可以理解为数据结构链表,只不过我们当时使用一个data来代表每个节点的数据,这里的单个pcb就可以理解为单个的节点,每个节点的数据域有百多个属性这样。那么我们的数据结构还有队列这样的数据结构,那么我们的cpu是如何运行进程的呢,将进程pcb入队列,所以这就是让进程运行的时候排队,实质上是让进程的pcb进行排队,进程可以动态被调度的实质也就是将进程的pcb放入运行队列里面,等待cpu调度。

结论:所有对进程的控制和操作,都只和进程的pcb有关,和进程的可执行程序没有关系。如果愿意,也可以把pcb这个(广义可以理解为一个节点)放入都任何数据结构中。放到不同的数据结构就相当于放到不同的容器之中,就可以进行各种各样的组织管理模式。各种增删查改,旋转等等,都是对节点的管理,节点里面包含的就是数据,所以数据结构所有的动作模拟的都是对进程的管理工作动作。节点表示是人就是对人数据进行管理,节点表示进程,表示驱动,表示硬件就是对进程、驱动、硬件进行管理。

PCB是操作系统学科的叫法,上述结论对于任何操作系统都是一样的适用,不过PCB只是宏观学科的称谓,到具体的操作系统中,叫法可能不一样。

1.3 task_struct

在linux中,linux是一款具体的操作系统,将这个操作系统的PCB成为:task_struct

strruct就告诉我们这是一个结构体,task有时也将进程描述为任务。

里面包含了经常的所有属性,可以在linux源代码中进行查看

2.PCB内部属性

task_struct 内容属性分类(粗粒度了解一下)

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。现实生活中,很多问题都与优先级有关,我们到银行取钱要排队,排队在前优先级高,但是优先级诞生的原因就是资源过少,窗口少,办理人多,我们电脑多数只有一个cpu,这就注定了进程要排队执行。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。我们写下代码让编译器执行,我们的编译器怎么知道当前执行到哪一行了,下一行要运行到哪里呢?在cpu计算机内部存在一个寄存器,叫做pc指针或者eip寄存器,也叫作指令寄存器,也叫作程序计数器。

   pc指针指向哪一个程序的代码就表示那个程序被调度运行。

  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

pcb如何找到进程,如何找到进程对应的代码和数据,那么能帮助pcb找得到程序的代码和数据的部分就叫做内存指针。

  • 上下文数据: 进程执行时处理器的寄存器中的数据
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。进程拥有那些设备信息,比如printf要打印,屏幕使用权申请先给了这个函数,使用完再还给操作系统
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

  • 其他信息

3 查看进程

ps axj命令

pid --process id   进程的唯一标识符

command 代表启动时对应的可执行程序

写一段代码,运行变成进程

查看:

ps axj | head -1 && ps axj | grep myprocess
 

 grep  是负责过滤我们的myrocess这个关键字,当他要过滤时,关键字就会包含这个关键字,自己也会变成进程,又包含这个关键字,就会被保留。

这也说明每个独立的指令都是一个进程。 

不想要grep -v反向匹配:

ps axj | head -1 && ps axj | grep myprocess | grep -v grep
 

 如果是这样简单的程序:

 

瞬间启动, 变成进程,操作系统立马将代码加载到进程,创建pcb,被cpu调度,不过这个进程执行太快了。所以计算机是很快的。

看到进程退出:

每隔一秒检测进程

 while :; do ps axj | head -1 && ps axj | grep myprocess | grep -v grep; sleep 1; done

现在没有运行程序,没得对应进程,我们运行起来

然后我们结束程序

这就是一个进程从无到有再到无,进程是有生命的。

正确的叫法:将程序运行起来变成进程。

进程还有很多属性

4.获取进程标识符:getpid函数(4-6主要围绕进程的标识符展开)

PCB属性数据属于内核还是操作系统?

 PCB整个结构体变量和内部数据虽然是进程属性,但是是在操作系统内部维护,属于内核数据结构,属于操作系统内部结构。所以一个进程所有数据是在操作系统内部维护的。

那么如果我们想要获取我们进程的标识符,也就是获取进程的Pid,就是用户想要访问内核数据,按照上节内容讲的,那么我们必然要调用系统调用才可以做到。 

这个系统调用接口为函数:getpid,默认认手册二号手册

 

默认认手册二号手册 

 函数功能由系统内核提供

头文件:《sys/types.h》

函数功能;获得一个进程的标识符信息

参数为空

返回值类型:pid_t,这是系统自己定义的一个类型,相当于C语言中的整数类型。

誰调用就获得誰的pid

开始mian函数的时候,已经是cpu在执行指令了。

5.父进程标识符获取getppid函数

在linux中,一般普通进程都有它的父进程

当前进程是被父进程创建出来的。 

ppid -- 就是父进程的标识符

任何人都只有一个亲生父亲,但是一个父亲有多个孩子

getppid 获得父进程的标识符

 但是我们发现每次启动,进程的pid都会发生变化,但是父进程的pid不变:

每次启动进程都是被看做一个新的进程来看待。 父进程没变说明我们的

进程每一次调度都是由一个父进程来调度的,我们通过这个父进程标识符好来看一下这个父进程是誰:

是我们的bash ,所以在命令行中启动的所有的程序转换为进程都是bash的子进程,bash是命令行解释器。

另外一种查看进程方式:

linux把进程相关的内存级数据,以文件系统的形式显示在文件目录中

在根目录proc路径下:

我们可以像查文件一样查进程,而且这个文件是实时的。 

当我们关掉进程绝没有这个文件了

所以linux会将一个进程的进程信息以 进程pid命名的目录中:

我们查看一个进程的信息:

如果在运行的时候我们将可执行程序删除掉:

cwd

这一行闪动爆红

 进程还在跑,原因:

进程对应的可执行程序就是我们删除的那个,我们运行程序的时候,将程序拷贝到内存中,所以不会影响本次进程,但是重启就,没有办法了。程序运行时要记得自己对应的可执行程序是谁。

cwd  是可执行程序所在的路径

create work direct  当前工作目录,

6.创建进程fork

我们平时创建一个进程的时候,只是运行,操作系统会为可执行程序创造一个进程,我们是手动启动一个进程的,那么我们可不可以使用函数创建一个进程呢,是可以的

fork函数创建子进程。

①系统调用

② 作用:创建一个子进程

③参数为空

④头文件《unistd.h》

⑤返回值:pid_t

          编写代码如下:

                                                                                                       

after打印两遍,说明fork之后是有两个执行分支。都执行了printf

根据上图pid的关系就可以知道fork之后一个是父进程一个是子进程,而且fork之后代码共享。 

怎么知道那个是父进程,那个是子进程呢?

6.1 fork函数的返回值

如果创建进程成功:返回子进程pid给父进程,给子进程返回0,创建失败返回-1.

访问同一个变量id,怎么会等于两个值, 先简单理解:如果是父进程来读这个id变量,如果创建成功父进程读到的就是子进程的pidd,如果是子进程来读,如果是创建成功的话,读到的就是0.

那么我们创建进程的目的是要么这件事父进程不做交给子进程去做,或者父子进程做不一样的事。所以才要创建进程,一般都是父子做不同的事。

所以我们fork级别创建子进程的时候,一般我们会这样写:

我们具体实现,可曾见过两个死循环一直跑

 ps指令可以查到两个进程。

每一个进程 = 内核数据结构+可执行程序

6.2 父进程的代码和数据

 创建父进程的时候会同时建立父进程的task_struct 来维护父进程的代码和数据,实际上子进程的创建是操作系统创建的,父进程执行代码创建一个进程,创建一个进程的时候,系统中就会多一个进程,同时操作系统层面要为子进程创建独立对应的task_struct,每一个进程创建的时候,也就是说父进程或者历史上的其他进程,除了操作系统创建的task_struct以外还天生的有自己的代码和数据,但是我们由父进程创建的子进程,有自己的task_struct,却没有自己的代码和数据,所以,子进程某认就会指向父进程的代码和数据。儿子说:你给我生出来了,又不给我买房买车,我只能坑老,挤在一起。父进程不愿意,就将自己的代码划分,自己和子进程一个执行一块。

fork之后代码共享,可以使用if else 来进行分流让父子进程执行不同的代码。这是代码层面,数据层面稍后。

cpu在调度的时候,不会管父子进程,只会正常调度,所以父子都会调度,所以两个while死循环才会都执行。同时,操作系统在为子进程创建task_struct的时候,父进程也会将自己的task_struct中的属性大部分的拷贝给子进程。所以两者才能看到相同的代码和数据。所以子进程会继承父进程的大部分属性。这种继承不是百分之百,至少pib ppid 等不同。

6.3 关于fork函数的返回值:

6.3.1 如果创建进程成功:返回子进程pid给父进程,给子进程返回0,创建失败返回-1.  这是为什么?

从结果来看确实两个if都执行了,说明我们的id确实满足两个条件,可是是一个变量 ,应该变量怎么等于0又大于0,首先系统调用是操作系统单独设计的。在现实生活中,每一个人只有一个父亲,但是一个父亲可以有多个儿子。我们的进程也是这样的,如果我们使用子进程去找父进程是比较好找的,因为父进程具有唯一性的,父进程找子进程就必须使用标识符(家里几个兄弟姐妹,叫一声爸爸,就可以找到,但是爸爸找儿子就不会叫称谓而是叫名字)所以,我们会给父进程返回子进程pid,方便我们的父进程对子进程进行唯一标识,后续便于管理,那我们的子进程不需要父进程的pid,关心自己创建成功没有。子进程很容易得到父进程的pid,父进程唯一。

6.3.2 函数有两个返回值,至少应该返回两次,所以fork函数为什么能够返回两次呢?

fork 刀叉,分支的意思,,fork只有一个进程在运行,所以只能是父进程调用fork。那么我们思考。如果一个函数,已经运行到了最后开始执行return 的时候,一般这个函数的核心逻辑是已经做完了(不考虑递归),fork也是函数,只不过是系统调用的接口,fork会以调用进程为模版创建子进程,然后把对应当前进程的pcb放入到运行队列里面,所以子进程也就开始运行了,那么当fork运行到return 使,是不是已经将上述工作已经做完了呢?答案是肯定的,已经做完了,而且父子进程都已经可以被调度了,return 作为一个语句,所以在执行return 语句前已经有了父进程,子进程,所以机会分别执行一次,那么return 就会执行两次。但是fork返回时是用一个变量接收的,这个变量怎么会等于0也大于0呢,(关乎进程地址空间),先简单了解:我们将电脑上所有程序打开,就有很多进程启动运行了,一个程序崩溃,并不会影响其他进程,那么当我们关闭掉父进程或者子进程中的一个会不会影响我们的另外一个进程呢?

向指定进程发信号杀掉进程:

kill -9 pid

杀掉父进程过后,只有子进程在运行。 两个不影响

结论:任意进程之间是具有独立性的,互相不能影响。(bash干掉一般也不影响已经运行起来的进程)父子进程一个退出,即便代码共享也不能互相影响,一个挂了,代码也不能回收,等两个都结束再回收,但是当两个进程对数据进行改变的时候,可能会互相影响。

6.3.3 写实拷贝(对父子进程数据的解释)

os在设计进程的时候就必须保证进程得有各自的独立性,当父子任何一方要对数据进行更改,操作系统会介入进来,操作系统会拷贝对应的变量到子进程中,单独修改,父进程也是如此,我们将这个过程称为:写实拷贝,写实拷贝发生后也就意味着父子进程使用不同的空间。

所以id有两个值就是系统帮我们做的,可以用一个变量名表示不同的内存linux是可以做到的。

返回的本质也是写入数据,虽然是同一个变量,但是发生写实拷贝,虽然是一个名字,但是是对应的父子进程各自使用的不同的空间,也就是不同的值。我们从编译的角度理解,当我们写C语言代码的时候,将值放入变量,但是变量名不过是我们给存储值的空间取的代号,编译器编译的时候,变量名也就不在了,会转化成地址,或者某种寻址方式,所以我们代码层面看到的是id,编译器看到的是不同的两个地址。

6.4 一次创建多个进程:

代码如下:

 主进程会一直创建子进程,子进程慢慢倒数知道进程结束。

7.结语

今天的主要内容是进程、进程的pcB,以及linux下进程pcb-task_struct中众多属性的第一个进程的标识符。扩展了很多内容,后续更新进程的状态,僵尸进程、孤儿进程的讲解,欢迎大家关注。创作不易,如果大家觉得有所收获,欢迎关注,一起交流互进。

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

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

相关文章

音频变速python版

音频变速 如何能在不改变音频其他特点的情况下,只改变语速呢? 有几个python的库可以实现该功能,下面一一介绍。 pydub库 首先,确保安装了pydub和ffmpeg。 下面是一个简单的Python脚本,展示如何改变音频的播放速度&a…

韩顺平Java | C25 JDBC和连接池(上)

概述 JDBC概述:JDBC为访问不同数据库提供统一接口,为使用者屏蔽细节问题。Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。 // 模拟代码 //JdbcInterface.java --Java规定的JDBC接口(方法) p…

redis的设计与实现(五)——独立功能

1. Redis的其他功能 redis 除了简单对对象的增删改查的功能之外,其实还有其他高级功能,了解这些内容有利于我们更灵活的使用 redis 完成我们的业务功能。 2. 发布与订阅 2.1. 基本概念 很多中间件都有发布与订阅功能,但是,作为一…

文献阅读:LESS: Selecting Influential Data for Targeted Instruction Tuning

文献阅读:LESS: Selecting Influential Data for Targeted Instruction Tuning 1. 文章简介2. 方法介绍 1. Overview2. 原理说明 1. SGD上的定义2. Adam上的定义 3. 具体实现 1. Overview1. LoRA使用2. 数据选择3. LESS-T 3. 实验考察 & 结论 1. 实验设计2. 主…

UE5 在骨骼动画模型上绘制贴图

参考:Unreal 5.1 - How to paint damage textures and other effects on skeletal meshes 针对模型,在运行状态下通过射线指定一定范围,添加材质效果。 核心思路 通过射线获取命中点,作为材质参数材质中,命中的世界…

DP练习_P1002 [NOIP2002 普及组] 过河卒_python_蓝桥杯

P1002 [NOIP2002 普及组] 过河卒 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 1.DFS做超时40分 n, m, x, y map(int,input().split())flag [[0]*(n10) for _ in range(m10)] maps [[0]*(n10) for _ in range(m10)] d [[2,1],[2,-1],[-2,1],[-2,-1],[1,2],[1,-2],[-1,2]…

matlab 安装 mingw64(6.3.0),OPENEXR

matlab安装openexr 1. matlab版本与对应的mingw版本选择2. mingw(6.3.0)下载地址:3. matlab2020a配置mingw(6.3.0)流程“4. matlab 安装openexr方法一:更新matlab版本方法二:其他博文方法方法三…

【算法刷题 | 二叉树 06】4.10( 路径总和、路径总和 || )

文章目录 13.路径总和13.1问题13.2解法一:递归13.2.1递归思路(1)确定递归函数参数以及返回值(2)确定终止条件(3)确定递归逻辑 13.2.2代码实现 14.路径总和 ||14.1问题14.2解法一:递归…

HarmonyOS鸿蒙端云一体化开发--适合小白体制

端云一体化 什么是“端”,什么是“云”? 答:“端“:手机APP端 “云”:后端服务端 什么是端云一体化? 端云一体化开发支持开发者在 DevEco Studio 内使用一种语言同时完成 HarmonyOS 应用的端侧与云侧开发。 …

探索NDVI:了解植被指数的意义与应用

随着科技的进步和遥感技术的发展,我们能够更深入地了解地球上的植被覆盖情况,而其中一项重要的工具就是NDVI(Normalized Difference Vegetation Index,归一化植被指数)。NDVI不仅仅是一个数值,更是一扇窥探…

Keil开启代码提示功能

本文介绍Keil5开启代码提示功能。 进入这个 如此设置: 有的电脑的左边是空白栏,没有设置选项。应该如何解决呢? 找到MDK525安装包,其他版本的 Keil5 应该也可以。 用你的解压软件把它打开: 解压后会多出这些文…

python之字符串操作

1、切片操作 跟列表的切片很相似 代码示例 str1 chengxianzi996 print(str1[0:2]) print(str1[:10]) 代码解释:第一行:创建了一个字符串对象(其中单引号和双引号都可以创建字符串) 第二行提取前两个字符并输出 第三行输出s…

Linux LVM磁盘扩容

1、查看磁盘情况 df -h df -h2、查看逻辑卷 lvdisplay lvdisplay3、查看逻辑组 vgdisplay vgdisplay4、查看物理卷 pvdisplay pvdisplay5、查看磁盘 fdisk -l fdisk -l6、磁盘分区fdisk /dev/磁盘名 # 上一步查看到的新硬盘路径 fdisk /dev/vdb7、格式化磁盘mkfs -t ext4…

梯度提升树(Gradient Boosting Trees)

通过5个条件判定一件事情是否会发生,5个条件对这件事情是否发生的影响力不同,计算每个条件对这件事情发生的影响力多大,写一个梯度提升树(Gradient Boosting Trees)模型程序,最后打印5个条件分别的影响力。 示例一 梯…

字节对编码 (BPE):提升语言处理的效率和有效性

原文地址:byte-pair-encoding-bpe-bridging-efficiency-and-effectiveness-in-language-processing 2024 年 4 月 12 日 介绍 在快速发展的自然语言处理 (NLP) 领域,对人类语言高效解析和理解的追求带来了重大创新。字节对编码(BPE&#x…

C++ UML 类图介绍与设计

1 类图概述 UML(Unified Modeling Language),即统一建模语言,是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。UML从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图…

深拷贝总结

JSON.parse(JSON.stringify(obj)) 这行代码的运行过程,就是利用 JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象;序列化的作用是存储和传输。&#xff08…

亚马逊云科技官方重磅发布GenAI应用开发学习路线(全免费)

今天小李哥给大家分享的是亚马逊云科技(AWS)最近官方发布的GenAI应用开发最佳学习路线,不仅内容非常全面更主要的是全部免费!大家动动小手就能成为GenAI开发大🐮! 1️⃣这个GenAI开发学习路线包括什么&…

遥感卫星:探索地球的科技之旅

遥感卫星是人类探索地球、理解地球、保护地球的重要工具,其发展历程承载了人类对地球的探索与认知的历程。从最初的概念到如今的高科技应用,遥感卫星技术的发展见证了人类科技的不断进步与创新。 初心萌芽: 遥感卫星的发展始于20世纪中叶&…

C语言高质量编程之assert()和const

目录 编程中常见的错误 assert() const 编程中常见的错误 在编程中我们通常会遇到三种错误形式,分别是:编译型错误,链接型错误,运行时错误。 编译型错误: 在编译阶段发生的错误,绝大多数情况是由语法错误…