当前位置: 首页 > article >正文

【进程篇】理解进程

进程

基本概念与基本操作

在这里插入图片描述

什么是进程?通俗点来理解:

在这里插入图片描述

我们的可执行程序在磁盘里,二进制文件,加载到内存里是代码和数据,这就是进程吗?

但磁盘上可能有成百上千的可执行程序,其中可能有50个100个全都加载到内存了,所以可能在同一时刻,系统内部同时被加载了非常多的可执行程序吗?答案是一定。

在这里插入图片描述

这是都应用程序,都被加载到内存中了。(冯诺依曼规定)

然而在最开始就有一款软件已经被加载到内存了:操作系统

我们在开机等待时,操作系统在启动。

在这里插入图片描述

这些被加载进来的这么多程序在内存的什么位置加载?这些代码和数据是否已被CPU执行完了呢?是否要释放空间、扩容空间等?调度?操作系统不知道这些代码和数据属于哪个可执行程序。操作系统必然要对多个加载到内存中的程序进行管理。

怎么管理呢?先描述再组织!

操作系统要给每个加载进来的代码和数据构建一个struct维护起来。

在这里插入图片描述

这些结点能够指向自己对应的代码和数据,结点里包含所有的属性,同时结点还能指向下个结点。

我们将这个程序列表叫做进程列表

所以并不是把我们的可执行程序的代码和数据加载到内存就叫进程了,

在这里插入图片描述

这样的一个才叫进程。

进程=内核数据结构对象+自己的代码和数据

操作系统里这个结构体就叫做PCB。所有操作系统下描述进程的都叫PCB。

PCB全称:process control block 也就是进程控制块

在这里插入图片描述

具体,在Linux下, 这个结构体名称是task_struct

PCB和tast_struct是抽象和具体的关系,就像shell和bash

在这里插入图片描述

进程的所有属性都可以直接或者间接通过task_struct找到

所以刚才说进程=内核数据结构对象+自己的代码和数据,现在又可以说:

进程=PCB(task_struct)+自己的代码和数据

对进程的管理就是对进程链表的增删查改。

一旦一个可执行程序加载到内存里,这个可执行程序自己是最不重要的。最重要的是操作系统要创建对应的PCB来描述它。CPU管理的也是PCB而不是我们自己的可执行程序。

为什么要有PCB?因为要描述,先描述再组织来管理进程。

task_struct里有什么?

在这里插入图片描述

在Linux的源代码中可以看到task_struct的定义(双链表管理)

可以看到PCB的属性规模非常大


现在我们还是用实操来更好地理解:

在这里插入图片描述

我们用sleep,头文件是<unistd.h>

在这里插入图片描述

结论:我们历史上执行的所有的命令、工具、自己的程序,运行起来全部都是进程!

在这里插入图片描述

getpid

那么怎么看这个进程相关的属性值呢?

人生的第一个系统调用:getpid

在这里插入图片描述

系统里如果一个进程自己启动了,要获得标识符,就用getpid。谁调用的这个函数,就获得谁的进程id

从这个头文件也能看出是与系统有关的。

为什么说这个getpid是个系统调用?查看的是2号手册

man man可以看到:

在这里插入图片描述

3号手册是库调用,2号是系统调用。

pid在哪里?在task_struct这个结构体里有个属性叫做pid。

获取pid的本质是让操作系统把当前进程的PCB里的pid拷贝出来,让用户看到自己的id是什么。

有id就能证明是个进程。

pid的p代表进程,id是identity。

我们看到返回值是pid_t,这是个系统提供的数据类型,不是C语言内置类型。其实也就是个整数

获取成功就是一个大于等于0的值,获取失败就是一个-1

在这里插入图片描述

可以看到,确实是一个进程,因为有进程id。

ps

接着,此时我们可以再打开一个Xshell

我们也有指令能让我们去查当前系统里我们所启动的进程有哪些:

ps axj

这样就展现出了当前系统中所有的进程。也可以用top来查进程,退出就是q

但现在如果我们只想看刚才启动起来的进程呢?

ps ajx | grep myprocess

在这里插入图片描述

我们看不懂这个,可以将列表头和这个进程一起查看

在这里插入图片描述

也可以将;换成&&

看到这个:

在这里插入图片描述

这是什么?

当我们查进程时,这样的grep选项总是会被显示出来,因为整条命令从左向右查的时候,grep也是个命令。grep一旦跑起来自己也是个进程。

我们也可以 查看不包含grep的:

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

S

ctrl+c

ctrl+c 在命令行中如果输入错误了可以这样来终止

它其实是杀掉进程

但,还有其他方法来杀掉进程

同一个程序,每一次启动,pid的值不一样。

kill

kill -9 pid -9是信号,这里的pid根据情况替换成我们要杀掉的进程的pid

在这里插入图片描述

补充两个小知识:

在操作系统中药完成任何任务都是进程。

所以用户是通过进程的方式来访问操作系统的。

如果说用户是老师。操作系统是学生,老师是给学生布置任务的。所以进程也可以叫做任务,所以PCB

在linux具体的类型名是task_struct。其实就是任务。

ls /proc

也就是我们还可以通过文件的方式来查看进程

在这里插入图片描述

操作系统不仅可以把磁盘上的文件像这样用ls查到,还把内存相关数据也以文件方式呈现,让我们能够动态地看到内存相关的数据。

像proc就是一种内存级的文件系统,和磁盘没有关系,所有数据都是内存里的数据。

我们在系统中查到的这些都是数据,无论来源是磁盘还是内存,最终都以文件的形式呈现,让我们动态查看。

这也符合linux下一切皆文件的观点

甚至可以把每个进程转化成若干个文件

根目录下存在一个proc目录,白色字体显示的这些我们不懂但蓝色字体的肯定是目录:

ls /proc -l

在这里插入图片描述

但这些目录都是编号,其实就是特定进程的pid。

ls /proc/21150 -dl

我们可以查看pid为21150的进程的目录

这个目录里面就是描述这个进程的很多信息。

总结:

proc目录里记录的是当前系统里所有进程的信息,而当前目录下以数字命名的文件就是特定进程的pid,目录里面包含的是进程在运行时的动态属性,进程一旦退出,该目录会被系统自动移除。

ls /proc/21381
在这里插入图片描述

也看不懂

ls /proc/21381 -l

在这里插入图片描述

这些众多属性里我们可以先看两个

exe

这个进程对应的可执行文件

进程知道自己是从哪来的,启动哪个指令才有了这个进程

如果我们把这个文件删掉,这个进程还会在跑。

这是因为我们的代码和数据已经从磁盘拷贝到内存了,删除我们删除的是磁盘的。没有影响(后面可能会影响)。

当我们再查一次属性,就开始标红和闪了:

在这里插入图片描述

cwd

current work dir

在这里插入图片描述

就是当前可执行程序所在的路径

在这里插入图片描述

曾经在学文件相关内容时说过

fopen("a/b/c/d.txt","w");或者fopen("d.txt","w");不带路径,就会在当前路径直接以这个文件名新建文件。

为什么不带路径会在当前路径下新建?

是因为进程会记录下自己的当前路径。fopen也是进程下的代码,所以我们只传了文件名而fopen内部会想办法获取当前的工作路径。会将文件名跟到当前路径后面,拼接在一起。所以新建的文件就在当前路径下。

真的是这样吗?

在这里插入图片描述

可以看到,就在当前路径下新建了这个文件。

更改一个进程所处的当前路径:
chdir

在这里插入图片描述

这样,进程在启动时先把当前路径改了,改完再创建这个文件

在这里插入图片描述

再次启动

在这里插入图片描述

看到cwd确实被改掉了

再查看当前路径,也不存在刚才的新建文件了:
在这里插入图片描述

所以我们的cd命令是怎么做路径切换的?所以bash是不是个进程?shell命令行解释器自己是不是个进程?所以当我们执行cd命令时shell怎么会根据绝对路径、相对路径切换路径呢?底层也就是chdir

回到主线,我们讲的是怎么查进程,一个是ps,一个是proc,还有top也可以。

补充讲了pid,exe,cwd

getppid

那么,我们刚才还看到了getppid,这是什么?

get parent id 获取父进程ID

linux中所有的进程都是被它的父进程创建的,linux系统是一个单亲繁殖的系统,只有父进程没有母进程。

一个父进程可能创建多个子进程。

linux中进程的结构也是多叉树,每个结点都是个进程。

每次重新启动进程pid都变化了,但是父进程id不变

在这里插入图片描述

bash

那么,父进程是谁呢?

我们可以根据pid查:

ps ajx | head -1 && ps ajx | grep 19811 | grep -v grep

我们在这里把原本的grep myprocess改成了grep 19811也就是可执行程序的名字换成pid,来查父进程。

然后会发现我们查到的进程是一个bash ——命令行解释器

在这里插入图片描述

1.命令行解释器:本质是一个进程!

2.bash和命令的关系

其实每次登录,操作系统会给登录用户分配一个bash

如果我们

while :; do ps ajx | head -1 && ps pjx | grep 'bash' | grep -v grep ; sleep 1; done

就可以看到,我们现在打开了几个xshell就有几个-bash,关掉一个就少掉一个:

在这里插入图片描述

bash是一个进程

那么命令行是什么?

在这里插入图片描述

这是bash打出的一个字符串,bash打出来后就在这里等,bash也是C语言写的,相当于一个printf打出字符串再一个scanf在这等待

所以我们的命令都是以字符串交给bash进行分析

其实我们所有的命令启动时,父进程都是bash

ls pwd top touch等命令都是进程,父进程全都是bash

代码创建子进程的方式

怎么做到创建一个子进程的呢?

fork

在这里插入图片描述

fork是一个系统调用,作用是创建子进程。

我们将代码写成这样:

在这里插入图片描述

fork之后有了两个执行流且都会执行后面的代码

果然:

在这里插入图片描述

其实一般是父进程的PCB拷贝一份给子进程,所以它们的PCB里有很多属性是一样的,但也有部分值不同。

在这里插入图片描述

子进程默认也会指向父进程的数据和代码

所以子进程在被调度的时候就会执行父进程之后的代码

子进程没有自己独立的代码和数据,因为目前没有程序新加载

子进程不会再执行fork之前的代码

fork的返回值

在这里插入图片描述

fork有两个返回值!把子进程pid返回给父进程,0返回给子进程

父子未来执行不同的代码逻辑,不要干一样的事,怎么改写?

在这里插入图片描述

在这里插入图片描述

第3点以后再说,先学前两个。

1.因为父进程:子进程=1:n

任何一个父进程可以有0、1个或多个孩子,任何一个子进程则只有一个父亲。

因为一个父进程有多个子进程,所以要把子进程的pid返回给父进程,父进程要用不同的pid来区分子进程。而子进程不需要获得父进程pid因为getppid就能获得。所以子进程只要看自己是否成功建立就行了。

2.为什么一个函数会返回两次?

一个函数如果已经到return了,核心功能已经做完了

fork本质是个系统调用

在这里插入图片描述

在走到return语句之前,子进程已经被创建甚至调度了,所以return是一条语句那么父子进程都会执行。所以就有两个返回值

3.这一点先说一半

我们说子进程会继承父进程的代码,那么数据也是共享的吗?

在现实中,我们一个进程挂掉了并不会影响另一个进程。结论:进程具有独立性。

所以就算父进程挂了,子进程也会活得好好的。

这是因为它们的内核数据结构是独立的。

代码是只读的父子不能修改,所以也不会互相影响。

父子在数据上默认是共享的,但是如果父子一方要修改数据,OS把被修改的数据在底层拷贝一份,让目标进程修改这个拷贝。

这叫写时拷贝


http://www.kler.cn/a/444822.html

相关文章:

  • 谷歌建筑下载
  • go-zero(十四)实践:缓存一致性保证、缓存击穿、缓存穿透与缓存雪崩解决方案
  • Python国内10个镜像源-地址汇总以及测评
  • 【异常】GL-SFT1200路由器中继模式,TL-CPE1300D无法搜寻5G网问题分析
  • MFC 应用程序语言切换
  • 高防IP能够为游戏行业提供哪些防护?
  • ARM嵌入式学习--第八天(PWM)
  • maven-resources-production:ratel-fast: java.lang.IndexOutOfBoundsException
  • 企业车辆管理系统平台:功能与用途全解析
  • 基于Stable Diffusion技术的视频
  • web3跨链桥协议-Nomad
  • lvs介绍和DR模式
  • Python智能优化算法库
  • 如何实现工厂模式?
  • 服务器数据恢复—V7000存储中多块磁盘出现故障导致业务中断的数据恢复案例
  • 【python】OpenCV—Image Moments
  • 【DevOps工具篇】Jenkins的Pipeline(流水线)和Shared Library(共通库)
  • 基于Spring Boot的水果蔬菜商城系统
  • 写定制程序容易遇见的问题(FLASH不够时)
  • ArcGIS Pro 3.4新功能2:Spatial Analyst新特性,密度、距离、水文、太阳能、表面、区域分析
  • 功能篇:JAVA8实现数据去重
  • API网关基础知识
  • 怎麼在模擬器中實現換IP
  • pyQt5基本需求v1.0
  • C语言基础(五)【控制语句与循环综合应用篇猜数字游戏】
  • gcd 生成4d