Linux操作系统基础原理

计算机系统

在这里插入图片描述

1.计算机体系层次

计算机自身是由众多电子元器件构成,硬件本身提供给用户的接口十分底层复杂,使用很不方便。在硬件之上的操作系统将硬件接口抽象封装为比较直观,用户容易调用的接口;用户开发应用程序,通过指令代码调用这些接口完成任务。
狭义的操作系统主要指系统内核,内核有以下作用:进程管理、文件系统、网络管理、内存管理、驱动程序、安全功能。内核可以将用户无法操作的底层硬件接口进行抽象并封装为用户可以操作的接口,称为系统调用(system call),当用户需要操作硬件时需要向内核发起系统调用完成相应操作。
shell解释器是运行在操作系统上的一个特殊的应用程序,它提供了人机交互接口,用户通过shell向操作系统发出各种命令,而shell解释器又可以将用户输入的命令翻译成操作系统能够理解的指令交给CPU执行,并将执行结果显示给用户。
最高层是运行在系统上的各种应用程序,由程序员使用各种编程语言开发,例如C、Java、JavaScript、PHP、C++等。应用程序体现了用户的真实目的,才能完成用户想做的事情。
总之从整个计算机系统看,越高级就越抽象,越接近用户的最终目的;越底层就越具体,越接近硬件的真实面目

2.计算机硬件

计算机5大基本部件:运算器、控制器(运算器和控制器都在CPU内部)、存储器、输入设备、输出设备,设备之间通过总线进行连接。
计算机总线按照其功能主要分为3种:
数据总线:传输指令和数据信息
地址总线:传输地址信息,一般是存放数据的内存地址
控制总线:传输控制信号和时钟信号
通常讲32位或64位机,是指其地址总线位数是32位或64位。32位地址总线,其最大内存寻址空间位2^32,1024✖1024✖1024✖4=4GB。计算机主板上并不是全都有这3种总线,CPU通过控制器可以控制当前总线是什么功能。
在这里插入图片描述
各部件性能:CPU(寄存器)>一级缓存>二级缓存>三级缓存>内存>SSD硬盘>机械硬盘,一般每一颗核心都有自己的一级,二级缓存,各核心共享三级缓存。衡量一个CPU性能,不光看工作频率,缓存也是非常重要的指标。两颗相同频率,一级,二级缓存不同的CPU在造价上是有天壤之别。

2.1 CPU

CPU的执行指令大致分为3步骤:从内存中取指令、解码指令、执行指令,与之对应在CPU上分为3个单元:取指单元、解码单元、执行单元。为了实现高性能,这些单元都不止一个。
CPU内部有保存关键变量和临时数据的存储器,称为寄存器。寄存器性能很高,和CPU工作频率相当,在CPU内部总线上完成数据操作。

2.1.1 CPU与多线程

早期CPU性能提升主要靠提升工作频率或时钟周期,即在一秒内能完成的操作次数,但如今制造工艺出现瓶颈,无法在工作频率上继续突破,所以为了提升CPU的性能,出现了多核心技术,在一颗CPU上植入多颗核心,实现并行执行多个进程。
多核CPU上,如果只有一两个进程很繁忙,其他进程空闲,而一个进程只能调度在一颗核心上运行,这时对于CPU来说使用效率不高,如何实现一个进程如何工作在多个核心?从串行执行变为并行执行,将程序中互不相干的指令划分出来,形成多个不同执行流,每一个执行流就是一个线程,每个线程调度到不同的CPU核心上执行,从而实现了单进程到多线程的性能提升。多线程技术是实现并发编程的重要基础。

2.1.2 CPU缓存

CPU的寄存器的空间很小,而且主要用来存放程序执行中产生的临时数据,所以CPU不得不借助内存来存放程序指令,但是内存的运行速度低于CPU,造成二者之间运行不协调,CPU需要为内存等待多个时钟周期,造成了CPU性能浪费,这种情况下在CPU和内存之间加入中间层来解决。理论依据是程序的局部性原理:1、空间局部性,指一个指令或数据被访问到,它附近的数据也可能会被访问;2、时间局部性,指一个指令或数据被访问执行之后,可能会被再次访问到。正是局部性原理的存在,导致了CPU可以使用缓存来存放这些数据,所以在CPU上出现了一级、二级、三级缓存,这大大提高了程序的执行速度。内存是以时间换空间,增加了存储能力,但消耗了CPU时钟;而缓存是以空间换时间,通过增加存储来换取程序更快的执行时间。

2.1.3 NUMA结构

CPU所有核心共享三级缓存,多个CPU又共享内存;一旦出现共享,必然会引起资源争用问题。用户在解决资源争用问题的同时,产生了额外的花销,所以集成多CPU,向上扩展必然会到达一个临界点,超过这个临界点,性能反而会有所下降。
为了解决资源共享的争用问题,在多颗CPU上引入了NUMA结构(Non Uniform Memory Access,非一致性内存访问),每一个CPU都有自己的专用内存空间,这样就大大减少了争用出现的机率。但与此同时,会带来另一个问题,为了充分利用多CPU的性能,内核会对每个CPU上工作的进程进行负载均衡,当进程在被调度在第一颗CPU上运行,下一次有可能会被调度到另外一颗CPU上,但是NUMA结构下的每个CPU使用自己的内存空间,所以第二个CPU找不到这个进程运行的中间数据,需要从第一个CPU的专用内存中去复制一份,这个过程会消耗CPU的性能。所以,在NUMA结构上对进程进行CPU绑定是性能优化的一个重要方法。

2.1.4 通写与回写

CPU只能操作寄存器的数据,如果寄存器中没有,需要从缓存,或者内存甚至IO设备上获取。数据修改后,写入到内存才认为是修改完成。为了保证尽快写到内存,通常有2中策略:通写(write through)和回写(write back)
通写,将数据依次从一级、二级、三级缓存,最终写到内存。整个过程由CPU控制,CPU不能做别的事情。
回写,数据写入一级缓存,便告诉CPU写入完成,CPU可以去做别的事情。
通写数据可靠性高,回写性能高。按需选择,没有绝对的统一法则。
事实上,硬盘存储也遵循通写与回写策略。硬盘回写,是由内核控制将内存中的数据移动到硬盘缓存,便认为写入完成;后续由硬盘控制器将缓存中的数据移动到硬盘自身。硬盘通写,内核控制将数据写入到硬盘缓存,再写到硬盘,才认为写入完成。

2.1.5 用户态与内核态

用户开发的应用程序都是工作在操作系统之上的,不属于操作系统内部。系统内核之上的空间称为用户空间(用户态),内核自身称为内核空间(内核态)。不论用户程序还是内核指令,执行时加载到CPU然后运行。CPU在生产过程中,研制人员开发了CPU能够执行的众多指令,这些CPU支持的所有指令的集合称为CPU的指令集。CPU的指令主要分为4个层次,即环0、环1、环2、环3,每个环对应了不同层次的指令。以Intel的X86指令架构CPU为例,主要使用环0和环3两个层次,环3上是用户可以执行的指令,相当于用户空间;环0上为特权指令,相当于内核空间用户不能直接操作。例如操作硬件,这是个特权操作,用户不可以执行,必须向内核申请,即发起系统调用,由内核执行,完成后将结果返回给用户程序。对于计算机的所有特权操作都必须由内核承担,内核最重要的一个功能就是管理硬件资源
程序员编写的代码其实就是一条条指令构成,程序代码在执行过程中,如果这条指令用户自身有权限去执行,则可以直接执行。如果需要执行特权指令,只能由内核执行,用户没有权限执行;此时要向内核发起系统调用,内核执行系统调用的代码,完成后将执行结果返回给执行的程序,然后再继续执行后面的指令。内核在运行系统调用代码时,程序是处在等待状态。执行系统调用时,程序工作在内核空间(内核态);运行用户可以执行的指令时,程序工作在用户空间(用户态)。
应用程序在工作过程中,就是不断在用户态和内核态之间做模式切换,需要执行特权命令就发起系统调用,陷入内核态,当内核执行系统调用完毕后再返回用户态。所以内核或者系统调用并不直接发挥生产力,它更多是在底层提供支撑,帮助用户完成其特定功能,只有用户自己的程序才具有生产力,才能真正实现用户自己的目标。 一个有效率的程序,它不应该浪费太多时间在系统调用。一般CPU应该至少有70%的时间在执行用户代码(即工作在用户态),不多于30%的时间用来执行系统调用(工作在内核态)。

2.1.6 保护现场与恢复现场

CPU是通过给不同的程序分配时间片(time slice)来完成多任务运行,给每一个程序分配独立的时间片。在执行过程中,很产生很多中间状态数据,例如执行到第几条指令,接下来CPU该去取第几条指令,这些数据存放在CPU的指令指针寄存器中。如果此时发生了进程切换,寄存器的数据就会被新的进程所覆盖,等到下次再执行相同程序时又得从头再来,这是所不能接受的。所以一旦程序在一个时间片内没有办法执行完毕,CPU需要把程序执行的中间状态的数据保存起来,例如当前运行到哪个指令,在内存中取数据取到哪个地址,这称为保存现场。保存完成后,在下一个时间片执行另一个进程。当重新恢复执行当前进程时,需要把保存的中间数据装载到CPU中,称为恢复现场。保存和恢复现场需要执行内核代码完成(即工作在内核空间,所以也会消耗CPU时间),CPU中断一个进程的执行转而去执行另一个进程又称为上下文切换context switch。保存现场的数据存放在内存中,内核为每一个进程创建一个结构体的数据结构进行存放,称为任务结构体task struct,这里面存放了进程的各种信息,例如PID号、进程状态、父子进程关系、优先级、虚拟内存、程序计数器、CPU使用时间等等,当然也包含进程执行的中间状态数据。

2.2 内存

每一个进程都认为自己在系统可以使用3G内存,物理内存被切割为页框page frame,通常4K,每个page frame作为基本单位分配使用。进程的内存空间,从低到高依次为:代码段、数据段、堆、内核映射的共享库、栈。对于进程的虚拟地址空间,每4K为一个页面page,而物理内存的页框page frame就是存放页面page。页面在虚拟地址中是连续的,但是映射到物理内存不一定是连续,很多都是离散。CPU取数据是在物理地址,而进程中所存放的是虚拟地址,所以CPU必须查找进程的task struct中存放的映射关系表,对于进程叫页表page table,然后完成虚拟地址到物理地址的转换。单级的页表转换,从虚拟地址直接到物理地址,需要很大的内存空间存放转换关系,以32位机为例,虚拟地址4G,物理内存page frame为4K,则每一个进程有1M个地址转换关系,而系统中成百上千个进程,那就需要几百M乃至上G的内存来存放映射关系,我们不可能使用这么多的内存空间来存放映射关系,所以通常使用多级页表,通过计算得出物理地址。CPU中专门用于内存地址转换的部件MMU内存管理单元,进程的地址映射关系都存放在MMU,每取一个地址的数据,都要完成一次地址转换。
进程切换时,MMU中的页表项也同时被切换,故而进程的上下文切换是很消耗CPU时间
有些数据是不能被页面化,不能存放在页面再映射到物理地址的数据只能一直留驻在物理内存中,这种始终存放在物理内存的数据叫常驻内存集RSS,top或ps aux命令都可以查看到每个进程的RSS。
能够存放在页面的数据,有可能会被换到swap空间,当物理内存的页框不够使用时,会将一些没有调度到CPU上运行的进程的页面换出到swap,供给其他正在CPU执行的进程使用。当进程再次被CPU调度执行时,进程依然会按照虚拟地址加载数据,但是某些页面之前被换出到swap,根据原来建立的地址映射关系,进程在物理地址上取不到对应的数据,这时候就会出现页面寻址错误,或叫页面异常。异常有2种:大异常、小异常。大异常表明数据压根就不在物理内存,这时需要把数据从IO设备调入物理内存,存放的地址与之前也不尽相同,而后对这部分数据再建立新的地址映射关系。小异常指进程需要的数据没有存放在页面,而物理内存中存放了该数据,此时进程不需要从IO设备调入数据,只需要对该数据建立地址映射关系即可。例如某个进程的内存中加载了系统的共享库文件,当另一个进程运行时也需要该共享库,便从虚拟地址建立关系映射到物理内存的该共享库,不需要再去硬盘上找库文件
程序有局部性原理,当短时间内频繁访问同一数据,MMU都要一次又一次进行地址转换。为了提供地址转换的效率,给MMU加缓存,即TLB转换后援缓冲器Translation Look-aside Buffer,将热点数据的地址的最终转换结果存放在TLB,当CPU访问时数据时首先查找TLB,如果TLB中有,则直接拿到转换的物理地址从而获取数据,否则再从MMU进行地址转换。TLB存放的大小是按照条目来计算,同样以32位机为例,4G的虚拟地址映射有1M个地址表项,而TLB的存放数量远没有那么多,所以在内存调优中,常用的思想是将物理内存page frame调大,使用大页框,缩小地址转换的总条目数量,从而变相提高TLB的缓冲命中率。
在这里插入图片描述
黄色部分是在硬件级别完成的,当状态正常时完成黄色部分的工作流即可完成虚拟地址转换,如果出现异常就必须由操作系统接收处理,即蓝色部分

在这里插入图片描述
共享库文件通常是系统下lib或lib64中的.so文件,为了节约空间,只在内存中装入一份

2.3 输入输出IO

IO设备有2部分:
1.设备控制器,通常是集成在主板的一块或一组芯片,负责控制对应设备,从操作系统接收命令完成执行,将命令转换为对应设备的物理操作。例如SATA,IDE控制器。这种转换操作都是要通过驱动程序,由设备生产商开发。
设备控制器内部都有用于实现通信的寄存器,驱动程序接收系统命令,转换为设备的物理操作指令,放置到寄存器中才能完成对设备的操作。主机上所有不同设备控制器的所有寄存器组合起来称为主机的IO端口空间,每一个寄存器表现为一个IO端口,或IO地址空间。通常16位二进制表示
有的计算机中,IO地址被映射到内存当中,所以CPU可以像使用内存一样和IO设备进行通信。
对于IO端口,不能像http,smtp这种分配专用的端口号,因为主板上的设备控制器有很多种,PCI插槽还可以扩展新的设备,所以没办法固定使用,只能动态分配。系统开机时,每一个IO设备都要向总线的IO端口空间注册申请IO端口。CPU想和指定的设备交互,即通过对应的IO端口。但是对于某些众所周知的设备,例如键盘显示器等,都使用固定的IO端口
2.设备本身a

实现输入输出的3种方式:
1.轮询,CPU每隔固定时间去遍历IO设备,查看是否有数据需要处理。显然,这种方式并不理想
2.中断,中断CPU正在执行的操作,让CPU通知内核获取中断请求。在主板上,有一个可编程中断控制器。IO设备要向中断控制器注册使用一个中断号,所以当IO设备上有数据到达时,向中断控制器发出中断请求,随后中断控制器告诉CPU有中断发生,并且是哪个设备发出中断。CPU激活内核把系统当前执行的进程做中断切换,这个不是上下文切换,因为不是进程之间的切换,而是进程与中断的切换;内核获取中断请求并判断是否接收

中断处理过程:大致为2部分,内核接收中断,应用处理
以http服务为例,当网卡接收到http请求,将请求报文存放在网卡缓冲区,发出中断。然后http请求报文从网卡缓冲读取到内核缓冲(内核缓冲其实就一段内存空间),内核处理请求报文,将MAC层,IP层,传输层头部解封装,通过请求的端口号知道这是一个http请求,最终在应用层由http服务进程处理该请求报文

3.DMA,直接内存访问
假设有一个10M的文件要上传到主机上,10M文件会被切割成很多小的网络报文,每一个报文到达网卡就产生一次中断,如此一来会产生大量中断,CPU会浪费很多时间在处理中断上。在这种情况下就用到了DMA机制,IO设备的数据经总线直接传输到内存。但是总线的控制权是在CPU控制器手中,IO设备并没有总线的控制权,所以DMA访问是这样进行的,IO中断发生;CPU收到中断向IO设备发出DMA请求,将总线控制权交给对应设备;IO设备获得总线控制权将数据写入内存,同时CPU挂起;数据写入完成再次向CPU发出中断,告知CPU数据写入完成,并移交总线控制权;CPU回收总线控制权,继续执行原来的程序
为了完成DMA传输,IO设备上都集成DMA控制芯片。DMA传输无需CPU控制,也不像中断那样要对原有进程保存和恢复现场,所以CPU效率大为提升。

4.IO模型
同步与异步:synchronous, asynchronous,他们关心的是消息的通知机制
同步:调用发出后不会立即返回,但一旦返回则返回的是最终结果。
异步:调用发出后,被调用方立刻返回消息,但并不是最终结果。被调用者通过状态,通知机制来通知调用者,或通过回调函数来处理结果。

阻塞与非阻塞:block nonblock,他们关心的是调用者等待返回结果时的中间状态
阻塞:调用返回结果之前,调用者会被挂起,只有在得到返回结果,才能继续
非阻塞:在结果返回之前,不会被挂起,即调用不会阻塞当前进程或线程

同步和异步是站在被调用者的角度,关心如何将结果返回给调用者 阻塞和非阻塞是站在被调用者的角度,关心在调用结果返回之前,调用者处于什么状态

以一次磁盘read操作为例,当用户进程发起IO请求后,分为2个过程。内核收到请求,将数据从磁盘拷贝到内核内存当中;进程不可能访问内核内存,所以第二步是数据从内核内存拷贝到进程内存。真正被称为IO的步骤是数据从内核内存到进程内存。

常用IO模型:
(1)阻塞IO
IO请求的进程或线程会被挂起,转入不可中断睡眠状态,在结果返回之前什么事也做不了
(2)非阻塞IO
在IO处理的第一个阶段,请求是非阻塞的,但有可能要去轮询查看IO处理结果,有时候效率未必高于阻塞IO
在IO处理的第二个阶段,进程要接收从内核内存传输到进程内存的数据,所以在这个阶段是阻塞的
以上2种模式都是传统的单进程模型,串行处理
(3)复用IO
或者叫多路IO,内核中提供的调用,select(BSD研发,默认支持1024个并发请求)和poll()。httpd的prefork模型基于select,所以prefork默认最多1024个并发。此模式下,select或者poll类似于内核的IO请求代理人,进程对IO的请求不再发送到内核的IO调用,而发给select,select再转发给内核IO调用。
此模式最大的好处在于在IO处理的第一阶段,进程不再被阻塞在IO调用,而阻塞在select,进程还可以向select发起其他的IO请求。在IO处理第二阶段,进程接收数据时则阻塞在IO调用。IO复用对IO性能提升不大
(4)事件驱动IO
此模式下,IO处理第一个阶段被简化,进程发起IO请求后,内核接收并告知进程请求已收到,完成了通知你。所以进程可以去干别的事情,在第一个阶段是非阻塞的。当数据从磁盘拷贝的内核内存时,内核就告知进程数据已经准备完成,在第二阶段,进程还是要接收从内核传输过来的数据,所以第二阶段依然被阻塞。
事件驱动IO模式下,由于第一个阶段非阻塞,所以支持单进程接收多个请求。由于第一个阶段完成后,内核要通知进程接收数据,所以通知的方式也分为2种:水平触发和边缘触发
水平触发可以多次通知,直到进程前来接收数据;边缘触发只通知一次,如果进程由于忙碌没有在通知的时候接收数据,之后的处理可以通过回调函数的方式
(5)异步IO
由于事件驱动IO的第二阶段被阻塞,所以异步IO对此阶段进行改进,数据存放到进程内存的过程也由内核来完成,直到数据存放到进程内存即IO调用完成后再通知进程。2个阶段全部都是非阻塞,以web服务为例,进程收到通知后直接拿着数据构建响应报文返回给用户,如果下一个用户也请求相同数据,则不用再向内核发起IO请求,所以异步IO大大提升了性能。

nginx设计之初就采用基于事件驱动IO,边缘触发,同时支持异步IO,所以性能很高。httpd在2.4版本后也开始支持event事件驱动IO模型

3. 操作系统的抽象

CPU抽象为时间片time slice
内存抽象为虚拟地址空间
IO抽象为文件

程序抽象为进程,进程包含了系统分配给进程资源的集合
CPU资源:CPU时间
内存资源:地址空间,虚拟地址空间,又称线性地址空间,在这个层面,每个进程都有自己独立的地址空间。32位地址空间4G,低地址1G给系统,3G给用户进程

I/O:进程需要使用文件,所以在IO层面,进程的资源就是打开的多个文件;在进程内部使用一个编号表示打开的文件,即文件描述符file descriptor。进程有3个编号是固定的:标准输入0,标准输出1,错误输出2
3类文件:普通文件、设备文件(块设备、字符设备)、管道文件

  • 7
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux Wine(即"Wine Is Not an Emulator")是一种允许在Linux操作系统上运行Windows应用程序的兼容层。它的原理是通过重新实现Windows API(应用程序接口)来模拟Windows环境,从而使得Windows程序能够在Linux上正常运行。 首先,Wine使用了一种名为"Wine API"的工具库来替代Windows API。这些库中包含了许多与Windows API相同或相似的函数,允许Windows应用程序在Linux环境中执行相同的任务。Wine还提供了一套以二进制形式存储的Windows系统文件,如.dll文件和.exe文件。这些文件可以被应用程序加载和运行,使得它们能够在Linux中模拟Windows环境。 其次,Wine还提供了一个称为"Winelib"的开发工具集。Winelib允许开发者将他们的Windows应用程序源代码编译为Linux下可执行文件。这使得开发者能够在Linux上编译Windows应用程序,而不需要对代码进行大幅修改。Winelib还提供了一些转换工具,使得开发者能够将原本只能在Windows上编译的代码转换为可在Linux上正常编译的代码。 另外,Wine还提供了一些其他的支持,如对图形和音频的处理、对Windows注册表的模拟、对.NET框架的支持等。这些功能进一步提升了在Linux上运行Windows应用程序的成功率和性能。此外,Wine还允许用户通过配置文件来调整和优化其性能和行为。 总之,Linux Wine通过重新实现Windows API、提供相关的工具库和文件,以及提供其他支持功能,使得Windows应用程序能够在Linux上运行。它为Linux用户提供了一种便捷的方式来使用和享受Windows应用程序的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值