建站之星极速版,广告设计师工作内容,python网页编程,比较流行的网站建设技术有哪些Linux进程概念前言冯诺依曼体系操作系统设计操作系统的目的如何理解OS是一款搞“管理”的软件#xff1f;系统调用和库函数的概念进程的概念描述进程组织进程查看进程fork#xff08;#xff09;前言
本篇博客主要介绍一些#xff1a;冯诺依曼体系、OS的理解、进程的一些概…
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对应的就是校长 硬件对应的就是学生 驱动程序对应的是辅导员 管理者与被管理者是不需要直接接触的 上面的例子对比过来就是驱动程序去统计各个硬件的基本信息、和工作情况等数据然后上报给OSOS也自然会接受这些数据然后将这些数据利用相关的数据结构保存组织起来然后到时候需要哪个硬件进行工作或停止工作只需要在该数据结构中找到对应硬件的信息并对这些信息进行处理就行了然后将处理过后的结果下达给驱动程序驱动程序在下达给硬件硬件就开始执行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统计起来其中统计这些被加载进内存的程序的信息的节点被称为PCBprocess control block,Linux环境下PCB具体表现为task_struct结构体; task_struct是Linux内核的一种数据结构它会被装载到RAM(内存)里并且包含着进程的信息 其中结构体里面包含的信息有 标示符: 描述本进程的唯一标示符用来区别其他进程。 状态: 任务状态退出代码退出信号等。 优先级: 相对于其他进程的优先级。 程序计数器: 程序中即将被执行的下一条指令的地址。 内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针 上下文数据: 进程执行时处理器的寄存器中的数据. I O状态信息: 包括显示的I/O请求,分配给进程的I O设备和被进程使用的文件列表。 记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。 其他信息 组织进程 所有运行在系统里的进程都以task_struct链表的形式存在内核里; 查看进程 要想查看一个进程的基本信息我们可以利用命令ps -axj 列出当前系统所用进程信息 现在我们可以先写一小段代码来看看进程 测试代码 #includestdio.h #includeunistd.hint 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的进程的文件 在此文件夹下有很多我们不认识的文件 当我们想要结束掉某个进程时我们可以利用快捷键Ctrlc 或者利用命令kill -9 pid 当这个进程被杀掉过后对应的proc目录下也会同步删除这个进程的信息 下面我们测试一下看看 系统表示没有找到对应文件夹 下面我们来写一个可以自动获取自己pid的程序 为此我们需要使用一个系统调用 getpid()注意这是系统调用是OS提供给我们的不是C语言库函数 用之前我们先来了解一下
测试代码
#includestdio.h
#includeunistd.h
#includesys/types.h
int main()
{while(1){printf(这是一个进程,我的pid是:%d\n,getpid());sleep(1);}return 0;
} 我们利用ps命令验证一下 我们发现利用ps查到的和getpid获取的pid是一致的
fork
接着我们介绍一些新的概念 PPID当前进程的父进程的PID 这个我们也可以利用系统调用接口getppid获取 测试代码 #includestdio.h
#includeunistd.h
#includesys/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而是4321OS给进程分配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 看起来怪怪的我们先用用看看 测试代码
#includestdio.h
#includeunistd.h
#includesys/types.h
int main()
{printf(AAAAAAAAAAAAAAAAAAAAA\n);fork();printf(BBBBBBBBBBBBBBBBBBBBB\n);return 0;
}这似乎与我们之前学的C语言有些出入为什么会打印两次B 那么一定是printf(“BBBBBBBBBBBB\n”);被执行了两次 为此我们可以先简单理解 接下来我们来验证不同的进程下所执行的代码 测试代码
#includestdio.h
#includeunistd.h
#includesys/types.h
int main()
{pid_t ret fork();if(ret0){//这是父进程while(1){printf(我是父进程我的pid是:%d,我的ppid是:%d,ret%d,ret%p\n,getpid(),getppid(),ret,ret);sleep(1);}}else if(ret0){//这是子进程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代码中用到的内存地址也是虚拟内存地址