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

Linux进程概念

Linux进程概念

  • 前言
  • 冯诺依曼体系
  • 操作系统
    • 设计操作系统的目的
    • 如何理解OS是一款搞“管理”的软件?
    • 系统调用和库函数的概念
  • 进程的概念
    • 描述进程
    • 组织进程
    • 查看进程
    • fork()

前言

本篇博客主要介绍一些:冯诺依曼体系、OS的理解、进程的一些概念;

冯诺依曼体系

我们常见的计算机,比如笔记本;以及我们不常见的服务器等等,大部分都遵循冯诺依曼体系;
在这里插入图片描述

截至目前,我们所认识的计算机,都是有一个个的硬件组件组成
输入单元: 包括键盘, 鼠标,扫描仪, 写板等
中央处理器(CPU): 含有运算器和控制器等
输出单元: 显示器,打印机等

关于冯诺依曼,必须强调几点
1、这里的存储器指的是内存
2、不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备);
那么这里为什么CPU只能从内存进行读取呢?
①、除了冯诺依曼体系结构决定;
②、早期计算机是没有内存的,主要使用为早期的CPU读写速度与磁盘的读写速度相差不大,CPU能够及时的从磁盘读取和写入数据;但是随着科技的进步,CPU是越来越快了,快到磁盘的读写速度已经远远更不上CPU了,这就会造成CPU存在大量的空闲时间来等待数据从磁盘被读取进来,然后才开始处理,这是对于CPU性能的极大浪费!!!!为了解决这个问题,科学家们就创建了一个内存,内存的创建就是为了适应CPU与磁盘读写速度不匹配的问题,现在CPU只需要在内存里面取数据就可以了,因为内存的速度能缓解CPU的读写速度(虽然还是没有CPU快),同时在磁盘的数据,也不会再被直接加载到CPU,而是先加载进内存;当然随着科技的进步,内存也渐渐的不能满足CPU的要求了,于是科学家们,又在内存之上加入了:高速缓存、寄存器等存储结构以此来满足CPU的读写速度;
因此我们日常生活中双击的本质就是将存在与磁盘的内容加载进内存;
3、外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
4、一句话,所有设备都只能直接和内存打交道、

下面我们站在硬件角度(不考虑软件、网络)来观察一下数据是怎么在计算机中流动的,以此来更加深刻的理解冯诺依曼体系;(以我们登上qq向好友发送信息为例)
在这里插入图片描述

操作系统

任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:内核(进程管理、内存管理、文件管理、驱动管理)、其他程序(驱动程序、Shell、函数库等等);
目前主流的操作系统有:Windows、Unix、Linux、Mac OS等;

设计操作系统的目的

1、管理所有的软硬件资源;
2、为用户程序提供良好(安全的、高效的、功能丰富的、稳定的)的运行环境;

如何理解OS是一款搞“管理”的软件?

首先什么是管理?
管理的本质是什么?
举个简单例子:

就比如说学校有:校长、辅导员、学生;(不考虑其他角色,只考虑这三种);
在这里插入图片描述
在大学我们一年到头可能都见不到校长几次,但是他却能把我们管的很好;
这是为什么?
就比如学校要举办一个运动会,校长决定好了时间地点,然后就把这项决策下达给了辅导员,辅导员再来转达给我们,我们最后就会去执行校长下达的决策,最后这个运动会就会被漂亮的办起来;
在这里校长处于管理者的位置,管理者所做的工作就是进行决策!
而辅导员是去传达校长的决策的,因为学校很大,不可能让校长单独去对每个学生传达;
你看在这段期间,校长似乎都没有与我们进行任何交流,我们就把运动会办了起来;校长是如何做到的?
主要是因为校长手里掌握着我们学校的面积。比如:操作有多少个、每个操场多大、我们学校总共有多少学生等等;根据这些信息,校长就能做出在哪里举办、什么时候举办运动会的决策;说白了,校长就是在对一些数据做“管理”;
我们在举一个例子来理解管理:
就比如我们学校要选一个学生去参加物理竞赛,作为校长我怎么知道该选那个学生去呢?当然是选物理成绩最高的学生去啊,于是校长在一堆学生信息中筛选出了物理成绩最高的小王,于是他做出了让小王代替我校去参加物理竞赛的决策,然后他把这个决策下达给辅导员,然后辅导员找到小王,让小王去执行校长所作的决策;
我们知道,校长与学生是不见面的(很少见面),那么他是如何拿到学生信息的呢?
答案是通过辅导员,辅导员是直接与学生接触的,校长下发一个学生信息格式,让辅导员们按格式统计这些学生信息、最后在上交给校长;校长不就拿到这些信息了吗,有了这些信息不就可以知道各个学生的情况了吗?该选谁去参加比赛不久一目了然了;
所以,管理的本质就是:管理者对被管理的数据进行处理
在上面的角色中,学生是被管理者、校长是管理者、辅导员是直接与管理者与被管理者接触的角色,可以理解为帮助提高管理者工作效率的;
那么对应过来,与我们操作系统有什么关系呢?
上文我们说了,OS是一款搞管理的软件;
那么OS对应的就是校长;
硬件对应的就是学生;
驱动程序对应的是辅导员;
在这里插入图片描述
管理者与被管理者是不需要直接接触的!!!
上面的例子对比过来就是:驱动程序去统计各个硬件的基本信息、和工作情况等数据,然后上报给OS,OS也自然会接受这些数据,然后将这些数据利用相关的数据结构保存(组织)起来,然后到时候需要哪个硬件进行工作或停止工作,只需要在该数据结构中找到对应硬件的信息,并对这些信息进行处理,就行了,然后将处理过后的结果下达给驱动程序,驱动程序在下达给硬件,硬件就开始执行OS所作出的决策;当然如果那个硬件不能正常工作了,驱动程序也会检测出来然后上报给OS,让OS及时向用户做出反应,这样OS不需要与硬件直接进行接触,也就能把硬件管理好了;

总结:
OS管理硬件:
1、先将各个硬件的基本信息描述起来,利用struct;
2、组织起来,用链表或其他数据结构;
六字真言:先描述,再组织;

系统调用和库函数的概念

1、在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
为什么会存在系统调用?
因为OS不相信我们,但是又要为我们提供良好的服务;这种矛盾关系就好似我们与银行的关系,我们去银行存钱,银行是让我们自己亲自将钱放进银行的金库,然后在登个记吗?当然不是,银行是不会这么做的,因为银行压根不会相信我们,但是银行又要为我们提供良好的服务,为此银行开了一个个小窗口去办理业务,我们只需要将我们的需求通过小窗口告诉银行,我们的业务就会被办理,OS也是这样,为了给我们提供良好的服务,也会提供这样的“小窗口”,专业一点就叫做系统调用,在Linux环境下系统调用本质上就是C语言的函数接口,因为Linux本身就是用C语言写的
系统调用这个设计可谓是一石二鸟,即保证了OS自身的安全,又为我们提供了良好的服务;
但是我们想要使用这个服务是有代价的,就比如一个一个不识字的人、或者对于银行业务系统完全不了解的人去银行办理业务,就算窗口服务再方便,也无法为这些人提供良好的服务,这时为了帮助这些人顺利完成业务,银行一般都会设置一些大堂经理来专门为这些人“一条龙”服务;对于我们普通程序员来书也是一样的,如果直接让我们去操作系统调用,可能需要我们去提前熟悉一些操作系统相关的知识,这对于我们的开发效率来说比较低,为此一些大佬程序员,在基于这些系统调用的基础上又开发了一些类似于:图形化界面、命令行解释器等Shell工具,(这些工具伴随着操作系统的加载就一并被加载进入了内存)这些工具极大的提升了我们的开发效率;
实际上我们在日常的编程语言学习中也是经常的使用系统调用,比如最常见的printf、scanf函数的实现一定是调用了输入输出的系统调用;
2、系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。

在这里插入图片描述

进程的概念

在学习进程之前呢,我们可以先尝试者根据上面的六字真言解释一下OS是如何管理进程的;
很简单,先把进程这个东西所拥有的属性给抽象出来(这里我们可以把进程类比于一个学生,现在我们要将学生的基本信息抽象出来);然后呢利用合理的数据结构将这些属性组织起来;到时候OS对于进程的管理,就变成了对于进程数据信息的管理;

一般的进程的概念被理解为:进程是指在系统中正在运行的一个应用程序。
这样的专业术语对于我们初学者来说并不能很好的理解什么是进程;
下面我们来介绍一个比较好理解的进程的定义:
首先我们如果想要执行我们的程序的话,首先要把我们的数据从外设加载进内存,然后被CPU执行;
可是我们不能随意乱加载进内存啊,我们得让操作系统知道加载进内存的程序的基本信息吧,这样的话才好让OS开展工作(发挥管理的作业);
为此对于加载进内存的每一个程序,OS都会对其进行统计和编号,并把这些信息统计起来存入对应的节点中;只有完成这一步才能将这个程序叫做进程!!
为此,我们总结一下进程的概念进程=内核关于当前程序的信息+当前程序的代码和数据;

描述进程

上面我们说了,每个被加载进内存的程序的信息,都会被OS统计起来,其中统计这些被加载进内存的程序的信息的节点,被称为PCB(process control block),Linux环境下PCB具体表现为:task_struct结构体;
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息;
其中结构体里面包含的信息有:

标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据.
I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息

组织进程

所有运行在系统里的进程都以task_struct链表的形式存在内核里;

查看进程

要想查看一个进程的基本信息,我们可以利用命令ps -axj 列出当前系统所用进程信息;
现在我们可以先写一小段代码来看看进程:

测试代码:

  #include<stdio.h>  #include<unistd.h>int main(){while(1){printf("这是一个进程\n");sleep(1);}return 0;} 

在这里插入图片描述
利用命令ps -axj | head -1 && ps -axj | grep "process",获取表头和带有process的关键字的进程:
在这里插入图片描述
在这里插入图片描述
这其中PID就相当于这个进程的学号,每个进程都是唯一的,我们可以看到我们的进程的的PID是22475;
当然我们也可以去系统文件夹下(/proc)寻找当前进程的文件夹:
proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
ll /proc | grep "22475"筛选出pid为22475的进程的文件:
在这里插入图片描述
在此文件夹下有很多我们不认识的文件;
在这里插入图片描述
当我们想要结束掉某个进程时,我们可以利用快捷键:Ctrl+c;
或者利用命令:kill -9 pid
当这个进程被杀掉过后对应的proc目录下也会同步删除这个进程的信息:
下面我们测试一下看看:
在这里插入图片描述
系统表示没有找到对应文件夹;
下面我们来写一个可以自动获取自己pid的程序:
为此我们需要使用一个系统调用
getpid(),注意这是系统调用,是OS提供给我们的,不是C语言库函数!
用之前我们先来了解一下:
在这里插入图片描述

测试代码:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{while(1){printf("这是一个进程,我的pid是:%d\n",getpid());sleep(1);}return 0;
}

在这里插入图片描述

我们利用ps命令验证一下:
在这里插入图片描述
我们发现利用ps查到的和getpid获取的pid是一致的;

fork()

接着我们介绍一些新的概念;

PPID:当前进程的父进程的PID;
这个我们也可以利用系统调用接口getppid获取;
测试代码:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{while(1){printf("这是一个进程,我的pid是:%d,我的父进程的pid是(ppid):%d\n",getpid(),getppid());sleep(1);}return 0;
}

在这里插入图片描述
我们多次重新运行该进程:
在这里插入图片描述
我们可以发现,在多次重复运行同一个进程,该进程每次对应的pid是变化的;
这很好理解嘛,每个进程的pid都是OS分配的,不可能每次进来都给你分配相同的pid,就好比,你第一次考上了A大学,学校给你分配了个学号1234,但是你决定去复读,但是第二年又考上了A大学,A大学同样给你分配学号,只不过这次就不是1234,而是4321;OS给进程分配PID也是如此;
但是我们却发现,尽管每次进程的pid不一样,但是该进程的ppid是一样的,也就是说该进程每次进来都是同一个进程的“儿子”;
接下来我们来通过ps命令查一下,pid为21186是谁?
在这里插入图片描述
是bash,就是命令行解释器或者说Shell!!!
我们在讲解Shell运行原理的时候讲解过Shell的一个作用就是:执行命令的时候可以创建子进程来执行
同时Shell本身也是一个程序,只是在加载操作系统的时候一起被加载进内存了;
也就是说当我们输入命令./process shell获取到该命令,然后对该命令向OS进行解释:shell告诉OS,用户想要运行这个程序,你赶紧登记一下这个程序的信息,并且给这块程序开一个进程;
名义上是shell向OS申请的进程来运行加载进来的程序,因此我们把Shell称为一切从命令行进来的程序的父进程!
这里我们需要注意Shell是没有能力创建进程的,创建进程的实际操作是又OS完成的!Shell只是向OS申请;
好,既然Shell也是一个进程,那我们能不能把shell也杀掉?
当然可以,只不过杀掉过后,我们的所输入命令OS就不认识了,OS自然也就无法做出任何反应;
下面我们来实际操作一下:
在这里插入图片描述
我们可以看到,我们直接被踢出了Linux服务器!
如果要恢复Shell的话,重新登陆xShell就行了;

总结一下上面:
1、Shell本质上也是一个进程;
2、从Shell启动的程序都将变成进程,而该进程对应的父进程就是Shell
3、Shell可以向OS申请创建子进程

接着我们继续下一个话题,既然Shell可以创建子进程,那么我们能不能在自己的程序里面也创建子进程?就是不是利用从Shell进入的方式创建子进程;
当然可以,OS为我们提供了一个接口fork:
fork简介:
在这里插入图片描述
对于其返回值的介绍就是:如果进程创建成功,则给父进程返回其创建的子进程的pid,给其子进程创建的子进程返回0;进程创建失败返回-1;
看起来怪怪的,我们先用用看看;
测试代码:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{printf("AAAAAAAAAAAAAAAAAAAAA\n");fork();printf("BBBBBBBBBBBBBBBBBBBBB\n");return 0;
}

在这里插入图片描述
这似乎与我们之前学的C语言有些出入,为什么会打印两次B?
那么一定是printf(“BBBBBBBBBBBB\n”);被执行了两次;
为此我们可以先简单理解:
在这里插入图片描述
接下来我们来验证不同的进程下,所执行的代码:
测试代码:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{pid_t ret= fork();if(ret>0){//这是父进程while(1){printf("我是父进程,我的pid是:%d,我的ppid是:%d,ret=%d,&ret=%p\n",getpid(),getppid(),ret,&ret);sleep(1);}}else if(ret==0){//这是子进程while(1){printf("我是子进程,我的pid是:%d,我的ppid是:%d,ret=%d,&ret=%p\n",getpid(),getppid(),ret,&ret);sleep(1);}}else{printf("创建失败\n");}return 0;
}

在这里插入图片描述
通过父进程的pid和子进程的ppid,我们可以确定父子连个进程执行了不同的代码;
其中父进程的ret接收到的的确是子进程的pid,子进程ret接受到的也的确是0;
但是为我们可能会疑惑为什么ret能取出来两个不同的值,明明父子进程中ret的地址都是一样的
难不成fork能返回两个值?
当然不是;
我们可以先从一下几个问题入手,简单了解一下fork原理:

1、fork函数做了什么?
这不废话吗,当然是进行分流,创建子进程啊,我们首先知道当再一个函数中我们执行到return语句的时候,是不是就代表着该函数的主题功能已经完成了?也就是说我们当前的子进程已经创建好了,我们返回return只是想向调用该函数的主体报告完成结果;也就是说,从fork中return语句开始执行流就已经开始进行分流了;那么对于父进程来说return自然返回的是子进程的pid,对于子进程来说return自然返回的是0;
2、fork如何看待–代码和数据?
在这里插入图片描述

首先我们得有个常识性意识:我们再关闭微信这个进程的时候是不影响我画图这个进程的,同理我关闭Eage浏览器这个进程也是不会影响我CCtalk这个进程的,换而言之兄弟进程之间都是互相独立,互不影响的,父子进程之间也是如此!!!
为此我们可以测试:
在这里插入图片描述
我们可以看到当前进程既有父进程,又有子进程;
现在我杀掉子进程:
在这里插入图片描述

我们可以看到当前只有父进程并且还运行的好好的;
我们杀掉父进程也是可以的,只不过此时子进程因为没了父亲,被叫做 “孤儿进程”;我们后面会讲解这个进程;
fork是在PCB链表后面在增加一个pcb节点,并以继承父进程的方式来填充该新pcb节点,当然也不是全部继承,子进程的pcb也有自己的隐私,比如pid;但是在代码和数据方面刚开始时与父进程共享同一块;不是我们想象在内存空间中再拷贝一份一摸一样的数据:
在这里插入图片描述
既然是共享同一块数据,那么父子进程是如何实现独立互不影响?

代码:代码被编译完毕一直都是只读的,父子进程各读各的代码片段,互不影响可以实现独立,这个好理解;
数据:既然要实现独立,就不能从原始空间读取数据并进行修改,必需是两个独立的空间,为此当某一个进程里的执行流尝试修改数据数据的时候,OS会自动给我们当前的数据进行写时拷贝;
什么时写时拷贝?
就是当OS检测到子进程有写的操作的时候,OS才会给子进程分配相应的物理空间;
我们回到开始的问题:为什么ret能取出来两个不同的值,明明父子进程中ret的地址都是一样的?
ret在接受fork的值时候就已经创建好子进程了,执行流就已经开始分流了,然后再将fork的返回值写入ret中,触发了“写时保护”,实际fork是对两个互不相干的内存空间中的ret进行写入,自然从不同的进程中取出ret来时值是不一样的,但是地址是一样的结果!因为我们使用的虚拟地址!
我们平常写的C/C++代码中用到的内存地址也是虚拟内存地址!!

http://www.lryc.cn/news/4354.html

相关文章:

  • 算法设计与分析
  • C++ 基础
  • [golang gin框架] 2.Gin HTML模板渲染以及模板语法,自定义模板函数,静态文件服务
  • 数据仓库层Repository(CrudRepository、PagingAndSortingRepository、JpaRepository)
  • 大数据技术架构(组件)33——Spark:Spark SQL--Join Type
  • Linux: bash起后台进程引发的僵尸进程
  • 网络安全攻防中,Rock-ON自动化的多功能网络侦查工具,Burpsuite被动扫描流量转发
  • 电子技术——共模抑制
  • 对KMP简单的理解
  • Hibernate不是过时了么?SpringDataJpa又是什么?和Mybatis有什么区别?
  • 数学建模拓展内容:卡方检验和Fisher精确性检验(附有SPSS使用步骤)
  • 【Python学习笔记之七大数据类型】
  • Android系统之onFirstRef自动调用原理
  • ipv6上网配置
  • python实现聚类技术—复杂网络社团检测 附完整代码
  • 如何判断两架飞机在汇聚飞行?(如何计算两架飞机的航向夹角?)内含程序源码
  • Scipy稀疏矩阵bsr_array
  • LeetCode笔记:Weekly Contest 332
  • autox.js在vscode(win7)与雷神模拟器上的开发环境配置
  • 创建阿里云物联网平台
  • 【链式二叉树】数据结构链式二叉树的(万字详解)
  • Koa2篇-简单介绍及使用
  • Linux ALSA 之十一:ALSA ASOC Path 完整路径追踪
  • 【Spring Cloud总结】1、服务提供者与服务消费者快速上手
  • 若依项目学习之登录生成验证码
  • 计算机网络5:数据在两台计算机之间是怎样传输的?
  • 就现在!为元宇宙和Web3对互联网的改造做准备!
  • 【mysql数据库】
  • 【测试开发】web 自动化测试 --- selenium4
  • Elasticsearch7.8.0版本进阶——路由计算