我们都知道计算机是由硬件和软件组成的。硬件中的CPU是计算机的核心,它承担计算机的所有任务。 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配、任务的调度。 程序是运行在系统上的具有某种功能的软件,比如说浏览器,音乐播放器等。 每次执行程序的时候,都会完成一定的功能,比如说浏览器帮我们打开网页,为了保证其独立性,就需要一个专门的管理和控制执行程序的数据结构——进程控制块。 进程就是一个程序在一个数据集上的一次动态执行过程。 进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
在早期的操作系统里,计算机只有一个核心,进程执行程序的最小单位,任务调度采用时间片轮转的抢占式方式进行进程调度。每个进程都有各自的一块独立的内存,保证进程彼此间的内存地址空间的隔离。 随着计算机技术的发展,进程出现了很多弊端,一是进程的创建、撤销和切换的开销比较大,二是由于对称多处理机(对称多处理机(SymmetricalMulti-Processing)又叫SMP,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构)的出现,可以满足多个运行单位,而多进程并行开销过大。 这个时候就引入了线程的概念。 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合 和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。 线程没有自己的系统资源,只拥有在运行时必不可少的资源。但线程可以与同属与同一进程的其他线程共享进程所拥有的其他资源。
## 进程与线程之间的关系
线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。
优点: 同时利用多核cpu,能够同时进行多个操作
缺点:耗费资源(每个进程内核都要为其开辟线性地址空间,概念清看下面的)
优点:共享内存,I/O操作可以并发,比如爬虫
缺点:抢占资源,内核会枷锁,容易造成死锁
首先我们知道cpu是执行二进制的指令的,而对应的就有两种编程:解释形语言 和 编译形语言 两种编程, c++就是编译成二进制一起执行的,所以c++是编译形 解释型:JavaScript等。所以cpu是执行指令流的。
进程在发起之前,是一个程序,在磁盘上。比如我们ls 多个用户可以发起多个ls进程。不互相干扰,不会意识到彼此存在。进程是程序的副本。
程序本来在硬件运行。但是指令+数据在内存中。 所以cpu会执行一条拿一条,为什么要用内核呢?我们程序在cpu运行,再完之前,不会退出的, 我想让另一个程序在cpu运行,是不行的。
比如第一个在执行时,可能会产生I/O,过程中比如需要打开一个文件,这个文件在磁盘上很大,而程序必须将这个文件内容加载进内存的这个程序的数据区域之中。硬盘速度很慢。我们cpu这个时间很快,就加载一下就休息一下。cpu就闲置了。
我们为了解决多个问题,就在cpu执行多任务了,同时执行。内存中有多个程序了。那第一个加载数据了,第二个怎么执行呢?抢过cpu?就打架了,而内存也可能打架,这样就需要一个监控工具在上层。所以内核这个资源调度监控工具。
linux 本身就是抢占式多任务。
有内核这个监控调度工具:内核就负责这样几项: 以后这个程序再想执行,是直接向内核注册申请的,内核决定,你可以使用cpu,使用2ms,分配给你,不管2ms以后你有没有结束,都要交给另一个程序。这样轮询交替,让用户看起来是同时运行的。其实在同一时间只能有一个程序来占用一个cpu运行的。
内核必须把有限资源分配个各个贪婪的程序,会把cpu切割成片,时间片,我们cpu是按照时间流逝进行完成的。内存是拿空间用来存储。cpu是时间
内核也是程序,所以有内核空间,用户程序,在用户空间,用户程序的单独空间是可以伸缩的。都直接把程序的空间连续起来是有弊端的,伸缩,会导致很多内存碎片。以后再想找连续空间就没了
MMU:内存控制单元,其实是星型方式才具备的组件,尤其是x86架构。在arm上有没有不知道。主要是以x86架构的cpu说明的
mmu:memerry manage unit
mmu 出现的主要目的是为了实现内存分页
我们应该知道,一个程序被加载到内存,并不是所有指令在运行,比如ls /etc 跟ls -l /etc 这个ls 的执行代码都不一样。所以我们的ls 程序不是所有指令都加载进去的。需要-l再把-l的指令加载进来。 数据量不同。存的页框不同。需要1个我们分1个,需要2个分2个页框,不够了,找其他页框,即便不连续也可以,最好连续。 只要我们内核追踪着这个进程知道这个内存页框就行。
我们内核,都给进程虚拟出了一段假的内存空间,让进程看起来是连续的。
一个进程最多能识别多大内存呢?
32位 4G
64位 4G^4G
一个程序的4G内存空间,它的所有执行操作都是通过调用内核中的系统调用来完成的。
后来编程的时候不是在cpu上使用指令集编程的。而是使用系统调用。
所以进程是知道内核存在的,所以进程要跟内核和平共处。
线性地址空间:
因此32位的这4g虚拟 空间,有1g分给内核,剩下的程序自己用。而实际上我们的物理内存只有512M。但是为了统一管理,为了使得进程能够以统一风格,任程序员编程,不管你是512M物理内存还是1g物理内存,都是虚拟出4g内存,1g分给内核,其他分给程序的。这段空间是假的,是内核虚拟给进程的。这段地址空间被称为线性地址空间,看上去连续的,其实不连续。
我们的3g开始是程序的执行入口的,开始一段是记录文件的格式等内容,前面是为空的
真实内存比如512M,但是会虚拟出4G线性空间给每个进程,每个进程都会以为4G虚拟空间,但是 不会用4G,该用多少用多少。
怎么使用这4G空间呢?\
刚一直在讲,程序是由指令加数据组成的,这个空间内放着程序的指令和数据,除了内核空间外。光有指令和数据 还不够,实际上在物理内存中是可能不连续的页框,线性地址空间的对应指令指向物理内存的不连续的页框。这样单个进程看到的虚拟空间只是 自己单独存在的。各自不互相影响。
这样总有一天内存耗尽了。就会将内存页面页框内的 数据转移到我们硬盘上,叫做swap交换分区。用于临时存放内存中暂时用不上的内存页面。用的什么算法呢?lru算法,lru:最近最少使用的内存页面数据。但是如果人家进程醒了,访问被拿走的数据,内核会阻止它,告诉它没有,把它的拿回来从swap内,再把别人的腾空放到swap内。这就是所谓的缺页异常。 我找的页面不在了。我的内容本来在内存中,但是内存中不在了。这种情况就会产生I/O。为什么产生I/O呢?因为内核要从磁盘上抢回数据,从swap内。与磁盘操作就产生I/O,这就是大的异常。
我们知道,程序在执行时,都是动态链接的方式链接到好多个库上的。如果程序运行要依赖这个库,就会把这个库加载到内存上来的,因为程序的某些功能要依赖这个库的执行来实现的。但是这个库叫so ,共享对象,多个进程可以共同使用的。比如进程1 需要 lib1加载到内存的一个页面上。进程2启动也需要依赖lib1,库文件都是执行的代码,不会改变。
进程2 通知内核加载lib1,由于lib1已经由进程1加载到内存在,不会再次加载,但是进程2没有虚拟内存对应的页面,就会出现缺页异常,这时内核就会说,等一下,内存中有已经加载了lib2,安抚一下,(当然不是其独享的。如果其他进程需要依赖某个库,就会映射到哪个进程的空间) 将加载的页面告诉进程2.
而lib2加载到内存的空间由两个进程共享,都以为是自己的。但是不会 随着一个 结束而释放,只有全部释放才 释放。而这段共享空间是,内存映射,mmap==memery map。 这段内存是共享内存空间,是映射到进程里面去的。
对于linux用户空间中,init是所有进程的老祖宗。除了init之外,其他进程如果需要其他任务,就会创建子进程。子进程都是由其父进程通过向内核fork系统调用复制的,父进程通过自身fork一个一模一样的子进程除了。由子进程完成特定任务,一旦子进程完成任务,就清理子进程。kill子进程,终止子进程
一旦父进程都over了,如果还有子进程。所有子进程都成孤儿了。所有进程的父进程是init。父进程在over之前,要给子进程找一个新爹,即监护进程。如果没有给子进程找新爹,这个子进程就成孤儿了,所有的孤儿进程就会被回收了,无法被回收占用的内存就无法被回收了,无法被分配了。这样分配出去的内存无法回收旧造成内存泄露了。这就找程序员了。程序问题。重启可以,内存重新分配。 因为我们无法追踪孤儿进程,无法kill。
linux用户空间中,init是所有进程的老祖宗。除了init之外,其他进程如果需要其他任务,就会创建子进程。子进程都是由其父进程通过向内核fork系统调用复制的,父进程通过自身fork一个一模一样的子进程除了
就算是父进程kill杀掉子进程,也会像内核去申请终止 才能终止。因此,在内核中为进程创建了信息库,户口簿,
记录了每一个进程的 进程号,进程名 进程父进程 进程使用的cpu时间累积 分配的内存也,父进程编号,占用了哪些cpu 等等。监视无处不在。这些对于linux内核叫 task_struct,任务结构。每一个任务都有一个结构,所以每创建一个子进程,都会向内核注册一个任务,内核创建一个任务结构,来保存任务的各相关属性信息,内核从而追踪他所使用的各相关资源了。
内核这种任务有n个,那内核是如何找到每一个呢?
这些数据结构,是c实现的。python简单的多。单c的效率高。
着每个结构对编程语言来讲是数据结构。描述 数据的属性信息。 进程号 id 父进程等属性信息。这种描述进程格式叫数据 结构。这种数据结构用c描述要复杂的多。
每个进程有一个任务结构,那我们内核怎么知道有多少个呢,如果方便的找到任意一个ne ?
而我们的linux 的内核也是程序,而程序也要 存储到内存当中。内核也要在cpu运行之后,才能存到内存中的。内核要自身追踪这些结构,是通个c语言的双向循环的链表 实现的。每个结构也有结构的编号。
第一个结构尾部记录第二个结构的内存地址编号。而第二个结构首部 存有第一个结构的地址编号。
没有进程查看工具之前,我们要查看系统有多少个进程,就遍历上图的列表计算一次。
内核不能随便访问。所以我们需要伪文件系统。所以每个进程的进程号在proc下。这些个进程号下存着各种属性数据。启动命令,那个cpu运行过,哪些内存。
进程管理工具实现:
我们所谓的进程查看工具,就是获取为文件系统的进程属性的。
内存中,有1g是我们内核空间的,用户空间存放我们要运行程序的指令,cpu执行指令的方式有三种,顺序、循环、选择这三种执行。
比如一个进程申请的4g空间,内核占1g,而程序指令在用户空间,cpu依次读取进行,当执行到一个点无法完成,就会生成一个子进程,由于同一时间1个cpu只能做1件事情,cpu是时间片切片机制,这时父进程就会休眠。而子进程又会申请开辟一个4g的线性地址 空间,cpu又会继续执行子进程的指令,执行完后,结构返回给父进程。
即使有两个cpu 程序也不能即使执行。因为父进程等着子进程返回结构才能继续工作。
程序时由指令和数据组成。程序时位于硬盘上的,是死的,只有当内核创建数据结构 ,分配了数据资源,cpu资源,处于活动状态,才有真正的执行价值,才会被拿来一个个被运行。
程序内的指令能不能并行执行?即进程内的多条指令流。单核是不能了。双核的话,要是第一条运行到一半依靠第二个指令的结果,所以不能同时运行的。这是在一个执行流的情况下。
那我们两个cpu干什么呢?可以执行多个进程啊,虽然不能执行一个进程的多个指令
分成n个执行流,每个流内都有指令和数据,是不互相干扰的。
这样就需要一个进程空间内部,并行执行。一个进程内部有多个执行流来并行操作,但是出现资源争抢线性
线程是一个进程内部的多个执行流
多个线程不能同时打开一个资源文件,而lib尽管可以共享,也是在内核级别存在的。
资源发送争抢的区域叫临界区。
单进程、单线程:一个进程内存空间内有一个执行流。
单进程、多线程:一个进程内的指令集分成多个执行流
不管你的一个进程空间内部有多少个执行流,说白了就是多少个线程,但是你有一个cpu,多个线程没有什么明显的地方,除了麻烦以外。
多个cpu就有好多好处了。
web服务器工作模型及短板。:
web服务器:web服务进程。有一个用户访问时,我不能直接访问,否则有问题,那第二个来访问怎么办,只能等着。
怎么办呢?给每个用户生成一个子进程,每个用户一个子进程,一个用户需要10M内存,1000个用户就是10g。还要涉及到进程切换。但是如果访问主页,主页这个内存空间的话,每个用户这一个子进程就要开辟一块,那我们的服务器资源都要撑坏了。所以为了让众多用户共享一个资源来说,我们就要采用线程模型。web服务比如启动一个进程,里面分配多个线程,这样我们就打开一份数据就行了。cpu多的话就好了。
但是多个I/O争用的。虽然我们cpu多个,里面处理数据很快,但是我们网卡就有一个,还是会堵在网卡队列这里。
所以我们要理解原理,我们就知道系统瓶颈短板在哪了。
对于linux 而言,不是线程操作系统。windows solorias 都是微内核的线程操作系统。linux是单内核的。
线程跟内核本身的关系并不大,是由线程库实现的。迄今为止,linux内核中维持的每一个线程,跟进程毫无区别。
就算你生成了n个线程,但是在 linux看来跟进程是一样的。只不过是占用内存大小变小,称为轻量级进程。LIGHT WEIGHT PROCESS
linux 原生内核不支持线程,好多爱好开发者提供了好多实现方式,linux内核实现一套 红帽 一套 其他一套 三种实现需要线程库的支持。
线程死锁
线程不一定是好事,多个线程不能同时抢占一个文件。内核会给资源枷锁。
线程资源浪费:
死锁:1拿2的资源 2拿1的资源,都在等着释放。
1 等着2拿的资源,cpu过一下看一回,一直cpu看,浪费时间。
liunx内部有自选锁。很复杂
为了避免用户的进程操作系统资源,cpu是有保护进制的。cpu把它能操作运行的指令时分4类。分别放在环0 1 2 3 上。
用户进程只要不是以内核方式开发的,是以自己编程方式开发的基本程序,是不能调用特权指令,一旦尝试调用cpu的特权指令,cpu就会唤醒内核的。而普通程序要想访问硬件,就要访问特定的程序接口访问特权指令,要通过系统调用通知内核。
库调用,系统调用。
库调用是执行库的代码的,而系统调用是执行内核代码的。
程序运行,到内核运行 在回到用户空间这就叫做模式转换。
所以产生系统调用在内核浪费的时间越多,cpu越浪费。所以大部分时间70%应该浪费到用户空间,才会产生生产力
比如我们的ftp服务器,只有在访问硬盘数据资源,网卡封包解包等才会执行内核模式操作。所以浪费在内核模式的程序大部分是系统程序。而用户工作程序,应该大部分时间需要浪费在用户空间。
cpu只有一颗,在同一时刻,cpu运行的进程只有一个,不能让一个进程一直占用cpu,我们一定要让其及时切换交给其他进程。
寄存器:保存当前进程的执行状态码
1级缓存 2级缓存 3级缓存 内存 硬盘
而进程切换,我们要把寄存器当前的执行状态码,保存下来。保存现场。
每个进程,内核都会在内存维护一个task_starck 任务结构,用于保存进程的属性状态信息。一旦切换进程,就会保存当前状态。再把另一个进程,本来保存在内存的任务结构的状态信息,从新交给cpu执行。在这个切换时间内,cpu并没有干活。所以这个切换时间越快越好。
那是越频繁越好呢?? 大量时间cpu都浪费到来回切换了。
当然内核也有内核工作频率的,靠时钟驱动的。比如内核100hz/s 1s 震荡100次。内核工作hz 200 500 1000 s是越快越好吗?
每次HZ翻转时,时钟震荡翻转时,都会产生时钟中断发生1s,linux是抢占式多任务,如果中断就会发生抢占在时钟中断周期走完时。
cpu时间走完了,或者内核分配的时间走完了。
为什么会抢?
进程是有优先级的。
进程调度,是内核的核心功能之一。要公平合理。对于那些需要占用特权资源的。紧急的进程优先级高。
公平:
结果公平
七点公平
进程的调度算法非常重要:
统筹方法
程序=算法指令+数据 结构
优先级高的排前面,低的排后面。排1对合适吗?
我们按照优先级排队,优先级相同的排一队。在算法过滤
Big O :算法优先的判断工具
O(1)
横 队列长度 纵是扫描时间
进程ID (Process ID ,PID )号码被用来标记各个进程
UID 、GID 、和SELinux 语境决定对文件系统的存取和 访问权限,通常 从执行进程的用户来继承存在生命周期
init :第一个进程
父子关系
进程:都由其父进程创建,CoWfork(), clone()
关注获取最新优质文章