zoukankan      html  css  js  c++  java
  • 计算机体系结构一点基础知识

    无论是在CPU外部接总线的设备还是在CPU内部接总线的设备都有各自的地址范围,都可以像访问内存一样访问,很多体系结构(比如ARM)采用这种方式操作设备,称为内存映射I/O(Memory-mappedI/O)。但是x86比较特殊,x86对于设备有独立的端口地址空间,CPU核需要引出额外的地址线来连接片内设备(和访问内存所用的地址线不同),访问设备寄存器时用特殊的in/out指令(汇编),而不是和访问内存用同样的指令,这种方式称为端口I/O(PortI/O)

         在x86平台上,硬盘是挂在IDESATASCSI总线上的设备,保存在硬盘上的程序是不能CPU直接取指令执行的,操作系统在执行程序时会把它从硬盘拷贝到内存,这样CPU才能取指令执行,这个过程称为加载(Load)。程序加载到内存之后,成为操作系统调度执行的一个任务,就称为进程(Process)。进程和程序不是一一对应的。一个程序可以多次加载到内存,成为同时运行的多个进程,例如可以同时开多个终端窗口,每个窗口都运行一个Shell进程,而它们对应的程序都是磁盘上的/bin/bash文件。

        操作系统(OperatingSystem)本身也是一段保存在磁盘上的程序,计算机在启动时执行一段固定的启动代码(称为Bootloader)首先把操作系统从磁盘加载到内存,然后执行操作系统中的代码把用户需要的其它程序加载到内存。操作系统和其它用户程序的不同之处在于:操作系统是常驻内存,而其它用户程序则不一定,用户需要运行哪个程序,操作系统就把它加载到内存,用户不需要哪个程序,操作系统就把它终止掉,释放它所占的内存。操作系统最核心的功能是管理进程调度、管理内存的分配使用和管理各种设备,做这些工作的程序称为内核(Kernel),在某个系统上内核程序是/boot/vmlinuz-2.6.28-13-generic文件,它在计算机启动时加载到内存并常驻内存。广义上操作系统的概念还包括一些必不可少的用户程序,比如Shell是每个Linux系统必不可少的,Office办公套件则是可有可无的,所以前者也属于广义上操作系统的范畴,而后者属于应用软件。

        访问设备还有一点和访问内存不同。内存只是保存数据而不会产生新的数据,如果CPU不去读它,它也不需要主动提供数据给CPU,所以内存总是被动地等待被读或者被写。而设备往往会自己产生数据,并且需要主动通知CPU来读这些数据,例如敲键盘产生一个输入字符,用户希望计算机马上响应自己的输入,这就要求键盘设备主动通知CPU来读这个字符并做相应处理(硬中断),给用户响应。这是由中断(Interrupt)机制实现的,每个设备都有一条中断线,通过中断控制器连接到CPU,当设备需要主动通知CPU时就引发一个中断信号,CPU正在执行的指令将被打断,程序计数器会指向某个固定的地址(这个地址由体系结构定义),于是CPU从这个地址开始取指令(或者说跳转到这个地址),执行中断服务程序(ISR,InterruptService Routine),完成中断处理之后再返回先前被打断的地方执行后续指令。比如某种体系结构规定发生中断时跳转到地址0x00000010执行,那么就要事先把一段ISR程序加载到这个地址,ISR程序是内核代码的一部分,在这段代码中首先判断是哪个设备引发了中断,然后调用该设备的中断处理函数做进一步处理。由于各种设备的操作方法各不相同,每种设备都需要专门的设备驱动程序(DeviceDriver),一个操作系统为了支持广泛的设备就需要有大量的设备驱动程序,事实上Linux内核源代码中绝大部分是设备驱动程序设备驱动程序通常是内核里的一组函数,通过读写设备寄存器实现对设备的初始化、读、写等操作,有些设备还要提供一个中断处理函数供ISR调用。

          MMU(MemoryManagement Unit)VA映射到PA是以页(Page)为单位的,32位处理器的页尺寸通常是4KB。例如,MMU可以通过一个映射项将VA的一页0xb7001000~0xb7001fff映射到PA的一页0x2000~0x2fff,如果CPU执行单元要访问虚拟地址0xb7001008,则实际访问到的物理地址是0x2008。物理内存中的页称为物理页面或者页帧(PageFrame)。虚拟内存的哪个页面映射到物理内存的哪个页帧是通过页表(PageTable)来描述的,页表保存在物理内存中,MMU会查找页表来确定一个VA应该映射到什么PA

    操作系统和MMU是这样配合的:

    1.操作系统在初始化或分配、释放内存时会执行一些指令在物理内存中填写页表,然后用指令

    设置MMU,告诉MMU页表在物理内存中的什么位置。

    2.设置好之后,CPU每次执行访问内存的指令都会自动引发MMU做查表和地址转换操作,地址转换操作由硬件自动完成,不需要用指令控制MMU去做。

    我们在程序中使用的变量和函数都有各自的地址,程序被编译后,这些地址就成了指令中的地址,

    指令中的地址被CPU解释执行,就成了CPU执行单元发出的内存地址,所以在启用MMU的情况下,程序中使用的地址都是虚拟地址,都会引发MMU做查表和地址转换操作。

            MMU除了做地址转换之外,还提供内存保护机制。各种体系结构都有用户模式(UserMode)特权模式(PrivilegedMode)之分,操作系统可以在页表中设置每个内存页面的访问权限,有些页面不允许访问,有些页面只有在CPU处于特权模式时才允许访问,有些页面在用户模式和特权模式都可以访问,访问权限又分为可读、可写和可执行三种。这样设定好之后,CPU要访问一个VA,MMU会检查CPU当前处于用户模式还是特权模式,访问内存的目的是读数据、写数据还是取指令,如果和操作系统设定的页面权限相符,就允许访问,把它转换成PA,否则不允许访问,产生一个异常(Exception)异常的处理过程和中断类似,不同的是中断由外部设备产生而异常由CPU内部产生,中断产生的原因和CPU当前执行的指令无关,而异常的产生就是由于CPU当前执行的指令出了问题,例如访问内存的指令被MMU检查出权限错误,除法指令的除数为0等都会产生异常。

            通常操作系统把虚拟地址空间划分为用户空间内核空间,例如x86平台的Linux系统虚拟地址空间是0x00000000~0xffffffff,3GB(0x00000000~0xbfffffff)是用户空间,1GB(0xc0000000~0xffffffff)是内核空间。用户程序加载到用户空间,在用户模式下执行,不能访问内核中的数据,也不能跳转到内核代码中执行。这样可以保护内核,如果一个进程访问了非法地址,顶多这一个进程崩溃,而不会影响到内核和整个系统的稳定性。CPU在产生中断或异常时不仅会跳转到中断或异常服务程序,还会自动切换模式,从用户模式切换到特权模式,因此从中断或异常服务程序可以跳转到内核代码中执行。事实上,整个内核就是由各种中断和异常处理程序组成的。总结一下:在正常情况下处理器在用户模式执行用户程序,在中断或异常情况下处理器切换到特权模式执行内核程序,处理完中断或异常之后再返回用户模式继续执行用户程序.

    段错误是这样产生的:

    1.用户程序要访问的一个VA,MMU检查无权访问。

    2.MMU产生一个异常,CPU从用户模式切换到特权模式,跳转到内核代码中执行异常服务程序。

    3.内核把这个异常解释为段错误,把引发异常的进程终止掉。

    中断与异常:

    (硬件)外部中断:非拼比NMI,可屏蔽INTR(可扩8259A中断控制器,则最高可达64个中断源(I/O设备))。

    (软件)内部中断:

    1、DIV指令除数为0,或者商太大,产生类型0的中断。

    2、INT指令,INT n

    3、INTO指令,若上一条指令执行结果使溢出标志位OF=1,引起类型为4的中断。

    4、单步执行,若标志位TF=1,每一条指令执行完都会引起类型1的中断。

    中断的优先次序:内部中断(单步执行优先级最低),NMI,INTR。

    异常:

    1、故障Fault:指令执行前报告(段和页异常)

    2、陷阱trap:指令执行后报告(单步和数据断点)

    3、夭折abort:硬件故障或系统表不一致

    异常0~16号

    软件中断int n(0~255) --》陷阱trap


     

    movl$1, %eax

    movl$4, %ebx

    int$0x80

            int指令称为软中断指令,可以用这条指令故意产生一个异常,异常的处理和中断类似,CPU从用户模式切换到特权模式,然后跳转到内核代码中执行异常处理程序。int指令中的立即数0x80(0~255)是一个参数,在异常处理程序中要根据这个参数决定如何处理,Linux内核中int$0x80这种异常称为系统调用(SystemCall)。内核提供了很多系统服务供用户程序使用,但这些系统服务不能像库函数(比如printf)那样调用,因为在执行用户程序时CPU处于用户模式,不能直接调用内核函数,所以需要通过系统调用切换CPU模式,经由异常处理程序进入内核,用户程序只能通过寄存器传几个参数,之后就要按内核设计好的代码路线走,而不能由用户程序随心所欲,想调哪个内核函数就调哪个内核函数,这样可以保证系统服务被安全地调用。在调用结束之后,CPU再切换回用户模式,继续执行int$0x80的下一条指令,在用户程序看来就像函数调用和返回一样。eaxebx的值是传递给系统调用的两个参数。eax的值是系统调用号,Linux的各种系统调用都是由int$0x80指令引发的,内核需要通过eax判断用户要调哪个系统调用,_exit的系统调 用号是1ebx的值是传给_exit的参数,表示退出状态。大多数系统调用完成之后会返回用户空间继续执行后面的指令,_exit系统调用比较特殊,它会终止掉当前进程,而不是返回用户空间继续执行。可以说如read,write这样的系统调用的底层实现都是利用了这3条汇编指令,系统调用只是进入内核程序的一个接口,内核调用内核函数(如中断异常服务程序,实现各种普通文件操作的内核函数,各种设备驱动程序等)进行服务.

  • 相关阅读:
    idea 编译内存溢出
    版本兼容问题 用于数据存储的持久化
    java8 函数接口
    akka 的集群访问方式
    Akka Cluster Sharding
    讨厌的adb占用
    安卓编译 签名包
    linux 系统的 cache 过大,解决方案
    kotlin 简单处理 回调参数 加?
    HTML CSS + DIV实现局部布局
  • 原文地址:https://www.cnblogs.com/davy2013/p/3168502.html
Copyright © 2011-2022 走看看