Linux操做系统原理

                        Linux操做系统原理程序员

                                          做者:尹正杰算法

版权声明:原创做品,谢绝转载!不然将追究法律责任。编程

 

一.计算机经历的四个时代
1.第一代:
  真空管计算机,输入和输出:穿孔卡片,对计算机操做起来很是不便,作一件事可能须要十几我的去共同去完成,年份大概是:1945-1955。并且耗电量特别大,若是那个时候你家里有台计算机的话,可能你一开计算机你家的电灯泡亮度就会变暗,哈哈~
2.第二代:
  晶体管计算机,批处理(串行模式运行)系统出现。相比第一台省电多了。典型表明是Mainframe。年份大概是:1955-1965。在那个年代:Fortran语言也就诞生啦~一门很是古老的计算机语言。
3.第三代:
  集成电路出现,多道处理程序(并行模式运行)设计,比较典型的表明就是:分时系统(把CPU的运算分红了时间片)。年份大概是:1965-1980年左右。
4.第四代:
  PC机出现,大概是从:1980年左右。相信这个时代典型人物表明:比尔盖茨,乔布斯。
 
二.计算机的工做体系
  虽说计算机通过了四个时代的演变,可是到今天为止,计算机的工做体系仍是比较简单的。一 般而言,咱们的计算机有五大基本部件。
1.MMU(内存控制单元,实现内存分页【memory page】)
   运算机制被独立在CPU(计算控制单元)上,在CPU当中有一个独特的芯片叫MMU。他是用来计算进程的线线地址和物理地址的对应关系的。它还用于访问保护的,即一个进程先要访问到不是它的内存地址,是会被拒绝的!
2.存储器(memory)
3.显示设备(VGA接口,显示器等等)【属于IO设备】
4.输入设备(keyboard,键盘设备)【属于IO设备】
5.硬盘设备(Hard dish control ,硬盘控制器或适配器)【属于IO设备】
 
扩充小知识:
                          
这些硬件设备在一条总线上连接,他们经过这条线进行数据交互,里面的带头大哥就是CPU,拥有最高指挥权。那么它是如何工做的呢?
  A.取指单元(从内存中取得指令);
  B.解码单元(完成解码[讲内存中取到的数据转换成CPU真正能运行的指令]);
  C.执行单元(开始执行指令,根据指令的需求去调用不一样的硬件去干活。);
咱们经过上面知道了MMU是CPU的一部分,可是CPU有还要其余的部件吗?固然是有的啦,好比指令寄存器芯片,指令计数器芯片,堆栈指针。
  指令寄存器芯片:就是CPU用于将内存中的数据取出来存放的地方;
  指令计数器芯片:就是CPU为了记录上一次在内存中取数据的位置,方便下一次取值;
  堆栈指针:CPU每次取完指令后,就会把堆栈指针指向下一个指令在内存中的位置。
  他们的工做周期和CPU是同样快的速度,跟CPU的工做频率是在同一个时钟周期下,所以他的性能是很是好的,在CPU内部总线上完成数据通讯。指令寄存器芯片,指令计数器芯片,堆栈指针。这些设备一般都被叫作CPU的寄存器。
  寄存器其实就是用于保存现场的。尤为是在时间多路复用尤其明显。好比说CPU要被多个程序共享使用的时候,CPU常常会终止或挂起一个进程,操做系统必需要把它当时的运行状态给保存起来(方便CPU一会回来处理它的时候能够继续接着上次的状态干活。)而后继续运行其余进程(这叫计算机的上下文切换)。
 
 
三.计算机的存储体系。
1.对称多处理器SMP
  CPU里面除了有MMU和寄存器(接近cpu的工做周期)等等,还有cpu核心,正是专门处理数据的,一颗CPU有多个核心,能够用于并行跑你的代码。工业上不少公司采用多颗CPU,这种结构咱们称之为对称多处理器。
 
2.程序局部性原理
  空间局部性:
    程序是由指令和数据组成的。空间局部性指的是一个数据被访问到以后,那么离这个数据很近的其余数据随后也可能会被访问到。
  时间局部性:
    通常而言当一个程序执行完毕后,可能很快会被访问到。数据也是一样的原理,一个数据的被访问到,极可能会再次访问到。
  正是由于程序局部性的存在,因此使得不管是在空间局部性或者时间的局部性的角度来考虑,通常而言咱们都须要对数据作缓存。
 
扩充小知识:
  因为CPU内部的寄存器存储的空间有限,因而就用了内存来存储数据,可是因为CPU和速度和内存的速度彻底不在一个档次上,所以在处理的数据的时候回到多数都在等(CPU要在内存中取一个数据,cpu转一圈的时间就能够处理完,内存多是须要转20圈)。为了解决使得效率更加提升,就出现了缓存这个概念。
  既然咱们知道了程序的局部性原理,有知道了CPU为了得到更多的空间其实就是用时间去换空间,可是缓存就是能够直接让cpu拿到数据,节省了时间,因此说缓存就是用空间去换时间
 
3.就算进存储体系
                                
  工做时间就的朋友可能见过磁带机,如今基本上都被OUT了,企业不少都用机硬盘来替代磁带机了,因此咱们这里就从咱们最熟悉的家用电脑的结构来讲,存下到上一次存储数据是不同。咱们能够简单举个例子,他们的周存储周期是有很大差距的。尤其明显的是机械硬盘和内存,他们两个存取熟读差距是至关大的。
 
扩充小知识:
  相比本身家用的台式机或是笔记本可能本身拆开过,讲过机械式硬盘,固态硬盘或是内存等等。可是可能你没有见过缓存物理设备,其实他是在CPU上的。所以咱们对它的了解可能会有些盲区。
先说说一级缓存和耳机缓存吧,他们的CPU在这里面取数据的时候时间周期基本上查不了多少,因一级缓存和二级缓存都在CPU核心内部资源。(在其余硬件条件相同的状况下。一级缓存128k可能市场价格会买到300元左右,、一级缓存256k可能会买到600元左右,一级缓存512k可能市场价格就得过四位数这个具体价格能够参考京东啊。这足以说明缓存的造价是很是高的!)这个时候你可能会问那三级缓存呢?其实三级缓存就是就是多颗CPU共享的空间。固然多颗cpu也是共享内存的。
                           

 

4.非一致性内存访问(NUMA)
  咱们知道当多颗cpu共享三级缓存或是内存的时候,他们就会出现了一个问题,即资源征用。咱们知道变量或是字符串在内存中被保存是有内存地址的。他们是如何去领用内存地址呢?咱们能够参考下图:
                       

 

  没错,这些玩硬件的大牛们将三级缓存分割,分别让不一样的CPU占用不一样的内存地址,这样咱们能够理解他们都有本身的三级缓存区域,不会存在资源抢夺的问题,可是要注意的是他们仍是同一块三级缓存。就好像北京市有朝阳区,丰台区,大兴区,海淀区等等,可是他们都是北京的所属地。咱们能够这里理解。这就是NUMA,他的特性就是:非一致性内存访问,都有本身的内存空间。
 
扩展小知识:
  那么问题来了,基于从新负载的结果,若是cpu1运行的进程被挂起,其地址在他本身的它的缓存地址是有记录的,可是当cpu2再次运行这个程序的时候被CPU2拿到的它是如何处理的呢?
  这就无法了,只能从CPU1的三级换粗区域中复制一份地址过来一份或是移动过来一份让CPU2来处理,这个时候是须要必定时间的。因此说从新负载均衡会致使CPU性能下降。这个时候咱们就能够用进程绑定来实现,让再次处理该进程的时候仍是用以前处理的CPU来处理。即进程的CPU的亲缘性。
 
5.缓存中的通写和回写机制。
                   
  CPU在处理数据的地方就是在寄存器中修改,当寄存器没有要找的数据是,就会去一级缓存找,若是一级缓存中没有数据就会去二级缓存中找,依次查找知道从磁盘中找到,而后在加载到寄存器中。当三级缓存从内存中取数据发现三级缓存不足时,就会自动清理三级缓存的空间。
  咱们知道数据最终存放的位置是硬盘,这个存取过程是由操做系统来完成的。而咱们CPU在处理数据是经过两种写入方式将数据写到不一样的地方,那就是通写(写到内存中)和回写(写到一级缓存中)。很显然回写的性能好,可是若是断电的话就尴尬了,数据会丢失,由于他直接写到一级缓存中就完事了,可是一级缓存其余CPU是访问不到的,所以从可靠性的角度上来讲通写方式会更靠谱。具体采用哪一种方式得你本身按需而定啦。
 
四.IO设备
1.IO设备由设备控制器和设备自己组成。
  设备控制器:集成在主板的一块芯片活一组芯片。负责从操做系统接收命令,并完成命令的执行。好比负责从操做系统中读取数据。
  设备自己:其有本身的接口,可是设备自己的接口并不可用,它只是一个物理接口。如IDE接口。
 
扩展小知识:
  每一个控制器都有少许的用于通讯的寄存器(几个到几十个不等)。这个寄存器是直接集成到设备控制器内部的。比方说,一个最小化的磁盘控制器,它也会用于指定磁盘地址,扇区计数,读写方向等相关操做请求的寄存器。因此任什么时候候想要激活控制器,设备驱动程序从操做系统中接收操做指令,而后将它转换成对应设备的基本操做,并把操做请求放置在寄存器中才能完成操做的。每一个寄存器表现为一个IO端口。全部的寄存器组合称为设备的I/O地址空间,也叫I/O端口空间,
 
2.驱动程序
  真正的硬件操做是由驱动程序操做完成的。驱动程序一般应该由设备生产上完成,一般驱动程序位于内核中,虽然驱动程序能够在内核外运行,可是不多有人这么玩,由于它过低效率啦!
 
3.实现输入和输出
  设备的I/O端口无法事前分配,由于各个主板的型号不一致,因此咱们须要作到动态指定。电脑在开机的时候,每一个IO设备都要想总线的I/o端口空间注册使用I/O端口。这个动态端口是由全部的寄存器组合成为设备的I/O地址空间,有2^16次方个端口,即65535个端口。
              
  如上图所示,咱们的CPU有要想跟指定设备打交道,就须要把指令传给驱动,而后驱动讲CPU的指令转换成设备能理解的信号放在寄存器中(也能够叫套接字,socket).因此说寄存器(I/O端口)是CPU经过总线和设备打交道的地址(I/O端口)。
 
扩展小知识:
三种方式实现I/O设备的输入和输出:
A..轮询:
  一般指的是用户程序发起一个系统调用,内核将其翻译成一个内核对应驱动的过程调用,而后设备驱动程序启动I/O,并在一个连续循环不断中检查该设备,并看该设备是否完成了工做。这有点相似于忙等待(就是cpu会用固定周期不断经过遍历的方式去查看每个I/O设备去查看是否有数据, 显然这种效率并不理想。),
B..中断:
  中断CPU正在处理的程序,中断CPU正在执行的操做,从而通知内核来获取中断请求。在咱们的主板一般有一个独特的设备,叫作可编程中断控制器。这个中断控制器能够经过某个针脚和CPU直接进行通讯,可以出发CPU发生某个位置偏转,进而让CPU知道某个信号到达。中断控制器上会有一个中断向量(咱们每个I/O设备在启动时,要想中断控制器注册一个中断号,这个号一般是惟一的。一般中断向量的每个针脚都是能够识别多个中断号的),也能够叫中断号。
  所以当这个设备真正发生中断时,这个设备不会把数据直接放到总线上,这个设备会当即向中断控制器发出中断请求,中断控制器经过中断向量识别这个请求是哪一个设备发来的,而后经过某种方式通知给CPU,让CPU知道具体哪一个设备中断求情到达了。这个时候CPU能够根据设备注册使用I/O端口号,从而就能获取到设备的数据了。(注意,CPU是不能直接取数据的哟,由于他只是接收到了中断信号,它只能通知内核,让内核本身运行在CPU上,由内核来获取中断请求。)举个例子,一个网卡接收到外来IP的请求,网卡也有本身的缓存区,CPU讲网卡中的缓存拿到内存中进行去读,先判断是否是本身的IP,若是是就开始拆报文,最后会获取到一个端口号,而后CPIU在本身的中断控制器去找这个端口,并作相应的处理。
  内核中断处理分为两步:中断上半部分(当即处理)和中断下半部分(不必定)。仍是从网卡接收数据为例,当用户请求到达网卡时,CPU会命令讲网卡缓存区的数据直接拿到内存中来,也就是接收到数据后会当即处理(此处的处理就是将网卡的数据读到内存中而已,不作下一步处理,以方便之后处理的。),这个咱们称之为中断的上半部分,然后来真正来处理这个请求的叫作下半部份
C.DMA:
  直接内存访问,你们都知道数据的传输都是在总线上实现的,CPU是控制总线的使用者,在某一时刻究竟是有哪一个I/O设备使用总线是由CPU的控制器来决定的。总线有三个功能分别是:地址总线(完成对设备的寻址功能),控制总线(控制各个设备地址使用总线的功能)以及数据总线(实现数据传输)。
一般是I/O设备自带的一个具备智能型的控制芯片(咱们称之为直接内存访问控制器),当须要处理中断上半部分时,CPU会告知DMA设备,接下来总线归DMA设备使用,而且告知其可使用的内存空间,用于将I/O设备的数据读取到内存空间中去。当DMA的I/O设备将数据读取完成后,会发送消息告诉CPU以及完成了读取操做,这个时候CPU再回通知内核数据已经加载完毕,具体中断下半部分的处理就来交个内核处理了。如今大多数设备都是用DMA控制器的,好比:网卡,硬盘等等。
 
五.操做系统概念
  经过上面的学习,咱们知道了的计算机有五大基本部件。操做系统主要就是把这五个部件给它抽象为比较直观的接口,由上层程序员或者用户直接使用的。那事实上在操做系统中被抽象出来的东西又该是什么呢?
 
1.CPU(time slice)
  在操做系统中,CPU被抽象成了时间片,然后将程序抽象成进程,经过分配时间片让程序运行起来。CPU有寻址单元用于来识别变量在内存的中所保存的集体内存地址。
                 

 

  而咱们主机内部的总线是取决于CPU的位宽(也叫字长),好比32bit的地址总线,它能表示2的32次方个内存地址,转换成10进制就是4G内存空间,这个时候你应该就明白为何32位的操做系统中只能识别4G内存了吧?即便你的物理内存是16G,可是可用的仍是4G,因此,你若是发现你的操做系统能识别4G以上的内存地址,那么你的操做系统必定就不是32位的啦!
 
2.内存(memory)
  在操做系统中,内存的实现是经过虚拟地址空间来实现的。
 
3.I/O设备
  在操做系统中,最核心的I/O设备就是磁盘,你们都知道磁盘是提供存储空间的,在内核中把它抽象成了文件。
 
4.进程
  说白了,计算机存在的主要目的不就是运行程序吗?程序跑起来,咱们统一叫进程(咱们暂时不用理会线程)。那若是多个进程同时运行就意味着把这些有限的抽象资源(cpu,memory等等)分配给多个进程。咱们把这些抽象资源统称为资源集。
  资源集包括:
        1>.cpu时间;
        2>.内存地址:抽象成虚拟地址空间(如32位操做系统,支持4G空间,内核占用1G空间,进程也会默认本身有3G可用,事实上未必有3G空间,由于你的电脑可能会是小于4G的内存。)
        3>.I/O:一切皆文件打开的多个文件,经过fd(文件描述符,file descriptor)打开指定的文件。咱们把文件分为三类:正常文件、设备文件、管道文件。
  每个进行都有本身做业地址结构,即:task struct。其就是内核为每一个进程维护的一个数据结构(一个数据结构就是用来保存数据的,说白了就是内存空间,记录着该进程所拥有的资源集,固然还有它的父进程,保存现场【用于进程切换】,内存映射等待)。task struct模拟出来了线性地址,让进程去使用这些线性地址,可是它会记录着线性地址和物理内存地址的映射关系的。
 
5.内存映射-页框
  只要不是内核使用的物理内存空间咱们称之为用户空间。内核会吧用户空间的物理内存切割成固定大小的页框(即page frame),欢聚话说,就是且更成一个固定大小的存储单位,比默认的单个存储单元(默认是一个字节,即8bit)要大.一般每4k一个存储单位。每个页框做为一个独立的单元向外进行分配,且每个页框也都其编号。【举个例子:假设有4G空间可用,每个页框是4K,一共有1M个页框。】这些页框要分配给不一样的进程使用。
  咱们假设你有4G内存,操做系统占用了1个G,剩余的3G物理内存分配给用户空间使用。每一进程启动以后,都会认为本身有3G空间可用,可是实际上它压根就用不完3G。进程进行写入内存是被离散存储的。哪有空余内存就往哪存取。具体的存取算法不要问我,我也没有研究过。
  进程空间结构:
        1>.预留空间
        2>.栈(变量存放处)
        3>.共享库
        4>.堆(打开一个文件,文件中的数据流存放处)
        5>.数据段(全局的静态变量存放处)
        6>.代码段
  进程和内存的存储关系以下:
            
  每一个进程空间都有预留空间,当某个进程发现本身打开的数据已经不够用,它须要打开一个新文件(打开一个新文件就须要在进程的地址空间存放数据),很显然咱们上图的进程地址空间是线性的并非真正意义上的。当一个进程真正去申请使用一个内存时,须要向内核发起系统调用,由内核在物理内存上找一个物理空间,并告诉该进程可使用的内存地址。比方说进程要在堆上打开一个文件,它须要向操做系统(内核)申请使用内存空间,且在物理内存容许的范围内(即请求的内存须要小于空闲物理内存),内核会分配给该进程内存地址。
  每一进程都有本身想线性地址,这个地址是操做系统虚拟出来的,并不真实存在,它须要把这个虚拟地址和真正的物理内存作一个映射关系,如图“进程和内存的存储关系”,最终的进程数据的存放处位置仍是映射到内存中了。这就意味着,当一个进行跑到CPU上执行时,它告诉CPU的是本身的线性地址,这时候CPU不会直接去找这个线性地址(由于线性地址是虚拟出来的,不真实存在,真正存放地址进程的是物理内存地址。),它会先去找这歌进程的“task struct”,并装载页表(page table)[记录着线性地址到物理内存的映射关系,每个对应关系叫作一个页表项。],以读取到进程的所拥有的线性地址所对应的真正的物理内存地址。
 
扩展小知识:
  CPU访问进程的地址时,首先获取到的是进程的线性地址,它将这个线性地址交给本身的芯片MMU进行计算,获得真正的物理内存地址,从而达到访问进程内存地址的目的。换句话说,只要他想要访问一个进程的内存地址,就必须通过MMU运算,这样致使效率很低,所以他们有引进了一个缓存,用于存放频繁访问的数据,这样就能够提升效率,不用MMU进行计算,直接拿到数据去处理就OK了,这个缓存器咱们称之为:TLB:转换后援缓冲器(缓存页表的查询结果)
  注意:在32bit的操做系统是线线地址到物理内存的映射。而在64bit操做系统是偏偏相反的!
 
6.用户态和内核态
  操做系统运行时为了呢可以实现协调多任务,操做系统被分割成了2段,其中接近于硬件一段具备特权权限的叫作内核空间,而进程运行在用户空间当中。因此说,应用程序须要使用特权指令或是要访问硬件资源时须要系统调用。
  只要是被开发成应用程序的,不是做为操做系统自己的一部分而存在的,咱们称之为用户空间的程序。他们运行状态称之为用户态。
  须要在内核(咱们能够认为是操做系统)空间运行的程序,咱们称之他们运行在内核空间,他们运行的状态为用户态,也叫核心态。注意:内核不负责完成具体工做。在内核空间可用执行任何特权操做。
  每个程序要想真正运行起来,它最终是向内核发起系统调用来完成的,或者有一部分的程序不须要内核的参与,有咱们的应用程序就能完成。咱们打个比方,你要计算2的32次方的结果,是否须要运行在内核态呢?答案是否认的,咱们知道内核是不负责完成具体工做的,咱们只是想要计算一个运算结果,也不须要调用任何的特权模式,所以,若是你写了一些关于计算数值的代码,只须要把这个代码交给CPU运行就能够了。
  若是一个应用程序须要调用内核的功能而不是用户程序的功能的话,应用程序会发现本身须要作一个特权操做,而应用程序自身没有这个能力,应用程序会向内核发申请,让内核帮忙完成特权操做。内核发现应用程序是有权限使用特权指令的,内核会运行这些特权指令并把执行结果返回给应用程序,而后这个应用程序拿到特权指令的执行结果后,继续后续的代码。这就是模式转换。
  所以一个程序员想要让你的程序具备生产力,就应该尽可能让你的代码运行在用户空间,若是你的代码大多数都运行在内核空间的话,估计你的应用程序并不会给你打来太大的生产力哟。由于咱们知道内核空间不负责产生生产力。
 
扩充小知识:
  咱们知道计算机的运行就是运行指定的。指令还分特权指令级别和非特权指令级别。了解过计算机的朋友可能知道X86的CPU架构大概分红了四个层次,由内以外共有四个环,被称为环0,环1,环2,环3。咱们知道环0的都是特权指令,环3的都是用户指令。通常来说,特权指令级别是指操做硬件,控制总线等等。
  一个程序的执行,须要在内核的协调下,有可能在用户态和内核态互相切换,因此说一个程序的执行,必定是内核调度它到CPU上去执行的 。有些应用程序是操做系统运行过程中,为了完成基本功能而运行的,咱们就让他在后台自动运行,这叫守护进程。可是有的程序是用户须要的时候才运行的,那如何通知内核讲咱们须要的应用程序运行起来呢?这个时候你就须要一个解释器,它能和操做系统打交道,可以发起指令的执行。说白了就是可以把用户须要的运行请求提交给内核,进而内核给它开放其运行所须要的有赖于的基本条件。从而程序就执行起来了。
相关文章
相关标签/搜索