操作系统内核架构解析

Posted 者也乎之

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统内核架构解析相关的知识,希望对你有一定的参考价值。

内核架构

在分析之前我们先明确这个内核架构概念,操作系统的内核结构可以分为三类:

  1. 模块结构,也叫做单内核结构,整个系统是一个大模块,而可以被划分为几个逻辑上的模块。包括处理器管理、存储器管理、文件管理,模块之间的交互通过直接调用其他模块的函数来实现
  2. 层次结构,这种内核架构是把操作系统划分为内核和若干个模块或者进程,这些模块或者进程被划分为多个层次,层次之间只能是单项依赖或者单向调用的关系,不会构成循环调用(可以理解为类似于计算机网络的五个层次结构,从高到低是应用层、传输层、网络层、数据链路层、物理层)
  3. 微内核结构,核心思想是内核只保留少量基本功能,而其他模块分离出来,不与他们耦合。各个模块和微内核之间通过通信机制进行交互,因此运行效率较低。[3]

内核架构名

模块

微内核

优点

灵活性好,效率高

未使用不运行,减少内存消耗,方便第三方扩展

缺点

高耦合

效率低

Window用的何种内核架构?

Windows使用NT内核,而NT内核是一种宏内核,可以理解为是模块结构。

宏内核定义:

宏内核同样管理着用户程序和硬件之间的系统资源,但是和微内核不一样的是,在宏内核架构中,用户服务和内核服务在同一空间中实现。具体一点,就是内核可以代表内核进程运行代码,就是通常的内核进程;当用户进程经过系统调用或者中断进入到内核态时,内核也可以代表它运行代码。这样一来,宏内核需要管理的资源多于微内核,其大小就相对大一些了。

在宏内核架构当中,内核管理着CPU调度,内存管理,文件管理和系统调用等各模块的的工作,由于用户服务和内核服务被实现在同一空间中,这样在执行速度上要比微内核快。然而,宏内核的劣势也是显而易见的,那就是当内核中的某个服务崩溃了,整个内核也会崩溃。另一点,想要在内核中添加新的功能就意味着内核中的各个模块需要做相应的修改,因此其扩展性很弱。

如果一个内核,只在Kernel Mode模式里放置IPC(进程通讯),MM(内存管理),Scheduling(调度)。那么就认为是微内核,反之,如果把其它的东西,比如file system, VFS, device drivers放到Kernel Mode,那么就认为是宏内核。简单来说,判断一个内核就是看它在Kernel Mode中运行了哪些东西。

Windows NT is like a microkernel in the sense that it has a core Kernel (KE) that does very little and uses the Executive layer (Ex) to perform all the higher-level policy. Note that EX is still kernel mode, so it's not a true microkernel.[5]
翻译一下:Windows NT有点像微内核,因为它里面的Core Kernel(简称KE)只是负责很少的工作,其它的事呢都交给执行层(简称Ex)来完成。要强调一下,因为Ex是在Kernel Mode下运行的,所以Windows NT不是一个真正的微内核。

为了不让程序任意存取资源,大部分的CPU架构都支持Kernel mode与User mode两种执行模式。当CPU运行于Kernel mode时,任务可以执行特权级指令,对任何I/O设备有全部的访问权,还能够访问任何虚拟地址和控制虚拟内存硬件;这种模式对应x86的ring0层,操作系统的核心部分,包括设备驱动程序都运行在该模式。当CPU运行于User Mode时,硬件防止特权指令的执行,并对内存和I/O空间的访问操作进行检查,如果运行的代码不能通过操作系统的某种门机制,就不能进入内核模式;这种模式对应于x86的ring3层,操作系统的用户接口部分以及所有的用户应用程序都运行在该级别。

[架构之路-34]:目标系统 - 系统软件 - Linux OS硬件电路的文本描述:设备树的构成属性解析使用

目录

第1章 什么是树

1.1 树形结构

1.2 无处不在的树形结构

1.3 树形结构的好处

第2章 硬件系统设备树

2.1 什么是硬件设备树

2.2 Linux引入设备树的好处

2.3 Linux什么时候引入设备树与设备

2.4 Linux设备树的外观

第3章 Linux设备树的工作原理

3.1 Linux内核驱动的设备树

3.2 硬件电路设备树描述文件

3.3 uboot如何把硬件电路设备树传递给Linux内核

3.4 xxx.dts的组织与结构

第4章 硬件电路设备树描述文件的实例

4.1 imx6dl-hummingboard.dts文件

4.2 imx6dl.dtsi文件

4.3 实例2

第5章 硬件电路设备树描述文件的语法解析

5.1 节点或子节点或分支描述:

5.2 设备节点的地址属性:Reg

5.3 设备节点的地址段的位数:#address-cells

5.4 设备节点地址段的位数:#size-cells 

5.5 设备驱动程序兼容性属性:compatible

5.6 设备所属的软件模块名属性:model

5.7 设备节点的使能状态属性:status

5.8 地址映射属性:ranges

5.9 别名属性

5.10 中断属性:interrupts

5.11 子节点:xxx

第6章 设备节点的中断与SOC的中断控制器


第1章 什么是树

1.1 树形结构

树形结构指的是数据元素之间存在着“一对多”的树形关系的数据结构,是一类重要的非线性数据结构。

树形结构是一层次的嵌套结构。

在树形结构中,树根结点没有前驱结点,其余每个结点有且只有一个前驱结点。

叶子结点没有后续结点,其余每个结点的后续节点数可以是一个也可以是多个。

一个树形结构的外层和内层有相似的结构,所以这种结构多可以递归的表示。

经典数据结构中的各种树状图是一种典型的树形结构:一棵树可以简单的表示为根,左子树,右子树。左子树和右子树又有自己的子树。

1.2 无处不在的树形结构

(1)大自然

(2)公司的的组织架构

(3)Linux的文件系统的组织架构

1.3 树形结构的好处

(1)树型结构是组织各种“资源”、构建一个系统非常有效的一种手段。

(2)树形结构非常容易扩展,可以动态的增加、插入、删除任意节点和分支。

(3)符合大脑的思维方式,思维导图就是一个树形结构。

(4)使用范围广:几乎可以应用在任何领域,包括各种抽象概念。

(5)层次分明:树形结构可以展现鲜明的层次关系。

第2章 硬件系统设备树

2.1 什么是硬件设备树

用树型的结构和文本的方式来描述硬件电路的各种元器件或设备的组成和相互关系,这就是设备树。

2.2 Linux引入设备树的好处

从电路板布局的角度来看,ARM架构与X86的架构最大的区别,就是采用ARM的嵌入式系统硬件电路板千差万别,而X86的PC机的硬件电路板数量和种类相对较少。

Linux支持各种CPU架构的硬件电路,他们是如何区分不同的硬件电路板呢?如何区分不同硬件电路板内部的硬件设备的组成与关系?以及硬件设备的参数,如内存的大小和起始地址?SOC芯片不同控制器设备的初始地址?数量?这些信息,直接影响硬件驱动程序的编写和执行?

在Linux 2.6以及之前的版本, ARM架构的板极硬件细节过多地被硬编码在C语言的源代码中,相应的源代码被安置在arch/arm/plat-xxx和arch/arm/mach-xxx中,大量的#define宏定义,定义了不同板子的不同设备的参数。这就导致,硬件电路的细节、参数发生变化后,都需要重新编译内核程序或内核设备驱动程序,而这些变化,实际上并没有影响内核和设备驱动程序的实际逻辑,只是数据或参数发生了变化而已。这些代码都需要与Linux一起发布,这导致Linux与硬件绑定越来越紧,Linux越来越丧失适应性和硬件无关性,这导致Linux的创始人对ARM很是不满,并且大骂ARM的硬件厂家提供的解决方案。考虑到Linux应用的广泛性和Linux创始人的个人威望,于是引起了ARM硬件厂家的高度重视。强烈需要一种新的硬件电路板的配置与Linux驱动程序分离的机制。设备树机制因运而生,设备树并不是ARM的独创,PowerPC架构的CPU先前已经采用,效果不错。ARM以此为基础,并在此基础上进一步的优化,从而定义了一个事实上的硬件配置与Linux软件分离的标准。

采用设备树后,许多硬件的细节可以直接通过设备树的形式传递给Linux,而不再需要在内核中进行大量的冗余编码,也不需要在源代码中通过#define定义大量的硬件相关的信息。Linux内核驱动程序从传入的设备树数据中获取硬件的相关信息,如地址信息,长度信息等。

2.3 Linux什么时候引入设备树与设备

Linux内核从3.x开始正式引入设备树的概念,用于实现驱动代码与硬件设备信息相分离。

2.4 Linux设备树的外观

“/":表示根节点。

cpus: 多核CPU

第3章 Linux设备树的工作原理

3.1 Linux内核驱动的设备树

除了由电路板厂家提供的设备树描述文件外,内核的的硬件设备驱动程序,也有相应的数据结构描述硬件电路的组成。只不过,内核数据结构中的参数值,要来自外部的来自设备厂家提供的设备描述文件。

3.2 硬件电路设备树描述文件

Linux为设备树定义了三种文件:

(1)xxx..dts:device tree source,设备树源描述文件

该文件不是c语言代码,而是一种文本描述文件。每个硬件开发板,都有自己的xxx.dts文件,一般放置在内核的"arch/arm/boot/dts/xxx.dts"目录内。

(2)xxx.DTB:device tree binary,设备树二进制文件

.dtb文件是 .dts 被 DTC 编译后的二进制格式的设备树文件,它可以被linux内核解析。

(3)DTC: device tree compile,设备树编译器

DTC是将.dts编译为.dtb的工具,相当于gcc。

3.3 uboot如何把硬件电路设备树传递给Linux内核

(1)编译

有两种方式可以编译xxx..dts:

  • 用工具单独编译DTC文件
  • 在编译uboot的时候,把xxx.DTB打包到uboot中。

(2)load到内存

  • uboot在启动的时候,把xxx.DTB load到自己的内存空间中

(3)传递给kernel

  • 通过boot arg参数,把内存中的xxx.DTB的地址传递给内核

(4)Kernel解析DTB,并映射到内部设备驱动树

内核在获取到设备树二进制文件xxx.DTB的地址后,解析该二进制中的结构化数据,并把相关的信息保存到内核驱动程序的设备树中。

3.4 xxx.dts的组织与结构

由于一个SoC芯片可能对应多一个硬件电路板,即不同的硬件电路板有相同的SOC芯片以及SOC芯片内部的控制器,但拥有不同的SOC外部电路。为了支持在不同的电路板之间共享相同的SOC以及其内部电路描述,Linux设备树描述文件支持类似C语言的Include机制,把一些共同的部分提炼为一个xxx .dtsi 文件。每个SOC芯片内部的电路描述就可以定义成xxx .dtsi 文件,该文件描述了SOC芯片内部的硬件设备的连接关系。

而使用该SOC的电路板的硬件描述文件,就可以直接include该xxx .dtsi,这样就不需要重复定义。

从而,可以通过include的方式,组织一个更大范围的电路板的描述文件。

另外,被#include的文件,还可以进一步的嵌套其他的设备描述文件。

因此,1个dts文件+n个dtsi文件,它们编译而成的dtb文件就是真正的设备树。

soc厂商会把soc公共的特性和多块开发板公用的特性提炼为dtsi,而dts则负责描述某个具体的产品(开发板)的特性。

dts直接或间接的包含多个dtsi(类似于c语言的头文件),就体现了一个完整的产品(开发板)所有的特性。以solidrun公司的hummingboard为例,其组成关系示例如下:

imx6dl-hummingboard.dts
        |_imx6dl.dtsi
        |   |_imx6qdl.dtsi
        |_imx6qdl-microsom.dtsi
        |_imx6qdl-microsom-ar8035.dtsi

第4章 硬件电路设备树描述文件的实例

下面分别是是imx6dl-hummingboard.dts以及imx6dl.dtsi文件,我们以它们为例来分析

不难发现dts文件内容很少,只有一些板级的特征,大部分公共的硬件描述都在dtsi文件中。

4.1 imx6dl-hummingboard.dts文件

/dts-v1/;                                           /* 指定dts的版本号*/
#include "imx6dl.dtsi"                              /* 包含其他公共设备描述*/
#include "imx6qdl-microsom.dtsi"
#include "imx6qdl-microsom-ar8035.dtsi"

/                                                  /* 根节点 */
    model = "SolidRun HummingBoard DL/Solo";
    compatible = "solidrun,hummingboard", "fsl,imx6dl";

    ir_recv: ir-receiver 
        compatible = "gpio-ir-receiver";
        gpios = <&gpio1 2 1>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_hummingboard_gpio1_2>;
    ;

    regulators 
        compatible = "simple-bus";

        reg_3p3v: 3p3v 
            compatible = "regulator-fixed";
            regulator-name = "3P3V";
            regulator-min-microvolt = <3300000>;
            regulator-max-microvolt = <3300000>;
            regulator-always-on;
        ;
    

&i2c1 
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_hummingboard_i2c1>;

    rtc: pcf8523@68 
        compatible = "nxp,pcf8523";
        reg = <0x68>;
    ;
;
  

4.2 imx6dl.dtsi文件

/ 
    aliases 

    /*省略无关代码*/
    
    soc 
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus";
        interrupt-parent = <&intc>;
        ranges;

        /*省略无关代码*/

        timer@00a00600 
            compatible = "arm,cortex-a9-twd-timer";
            reg = <0x00a00600 0x20>;
            interrupts = <1 13 0xf01>;
            clocks = <&clks IMX6QDL_CLK_TWD>;
        ;

        aips-bus@02000000  /* AIPS1 */
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02000000 0x100000>;
            ranges;

            /*省略无关代码*/      

            gpio1: gpio@0209c000 
                compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
                reg = <0x0209c000 0x4000>;
                interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,
                         <0 67 IRQ_TYPE_LEVEL_HIGH>;
                gpio-controller;
                #gpio-cells = <2>;
                interrupt-controller;
                #interrupt-cells = <2>;
            ;

            /*省略无关代码*/  

            i2c1: i2c@021a0000 
                #address-cells = <1>;
                #size-cells = <0>;
                compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
                reg = <0x021a0000 0x4000>;
                interrupts = <0 36 IRQ_TYPE_LEVEL_HIGH>;
                clocks = <&clks IMX6QDL_CLK_I2C1>;
                status = "disabled";
            ;

        ;
            /*省略无关代码*/  
    ;  
;

4.3 实例2

    /   
        compatible = "acme,coyotes-revenge";  
        #address-cells = <1>;  
        #size-cells = <1>;  
        interrupt-parent = <&intc>;  
      
        cpus   
            #address-cells = <1>;  
            #size-cells = <0>;  
            cpu@0   
                compatible = "arm,cortex-a9";  
                reg = <0>;  
            ;  
            cpu@1   
                compatible = "arm,cortex-a9";  
                reg = <1>;  
            ;  
        ;  
      
        serial@101f0000   
            compatible = "arm,pl011";  
            reg = <0x101f0000 0x1000 >;  
            interrupts = < 1 0 >;  
        ;  
      
        serial@101f2000   
            compatible = "arm,pl011";  
            reg = <0x101f2000 0x1000 >;  
            interrupts = < 2 0 >;  
        ;  
      
        gpio@101f3000   
            compatible = "arm,pl061";  
            reg = <0x101f3000 0x1000  
                   0x101f4000 0x0010>;  
            interrupts = < 3 0 >;  
        ;  
      
        intc: interrupt-controller@10140000   
            compatible = "arm,pl190";  
            reg = <0x10140000 0x1000 >;  
            interrupt-controller;  
            #interrupt-cells = <2>;  
        ;  
      
        spi@10115000   
            compatible = "arm,pl022";  
            reg = <0x10115000 0x1000 >;  
            interrupts = < 4 0 >;  
        ;  
      
        external-bus   
            #address-cells = <2>  
            #size-cells = <1>;  
            ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet  
                      1 0  0x10160000   0x10000     // Chipselect 2, i2c controller  
                      2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash  
      
            ethernet@0,0   
                compatible = "smc,smc91c111";  
                reg = <0 0 0x1000>;  
                interrupts = < 5 2 >;  
            ;  
      
            i2c@1,0   
                compatible = "acme,a1234-i2c-bus";  
                #address-cells = <1>;  
                #size-cells = <0>;  
                reg = <1 0 0x1000>;  
                interrupts = < 6 2 >;  
                rtc@58   
                    compatible = "maxim,ds1338";  
                    reg = <58>;  
                    interrupts = < 7 3 >;  
                ;  
            ;  
      
            flash@2,0   
                compatible = "samsung,k8f1315ebm", "cfi-flash";  
                reg = <2 0 0x4000000>;  
            ;  
        ;  
    ; 

第5章 硬件电路设备树描述文件的语法解析

5.1 节点或子节点或分支描述:

包围起来的结构称之为一个节点或一个分支.

(1)根节点, 节点名为"/"

dts中最开头的 /

                           

(2)其他节点, 节点名为“nodeName 

nodeName 

       

nodeName 为ASCII字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是UART1外设;

(3)指定内存寄存器地址的节点, 节点名为“nodeName@unit-address”

nodeName@unit-address

       

其中,unit-address表示节点的内存地址或编号

如果某个节点没有地址或者寄存器的话 “unit-address” 可以不要。

在上图中:cpu节点和 ethernet节点依靠不同的unit-address 分辨不同的CPU;

可见,node-name相同的情况下,可以通过不同的unit-address定义不同的设备节点。

5.2 设备节点的地址属性:Reg

reg = <address域  length域>

虽然,可以直接在节点名称后面,直接通过@指明节点设备在地址空间中的位置。

但大多数时候,可以通过reg属性名,来指明该节点设备在地址空间中起始位置和地址长度。

案例:

reg = <0x021a0000 0x4000>;

节点设备的内存地址为0x021a0000,长度为0x4000

<address 和length> 对的个数是可变的,由该节点点的属性#address-cells和#size-cells 决定。

5.3 设备节点的地址段的位数:#address-cells

该域指明:用多少个32bit来表示内存空间的地址。32总线时,该值为1;64位总线时,该值为2。

5.4 设备节点地址段的位数:#size-cells 

该域指明:用多少个32bit来表示内存空间的长度。32总线时,该值为1;64位总线时,该值为2。

5.5 设备驱动程序兼容性属性:compatible

compatible 属性值为字符串列表,⽤于将设备和兼容的驱动程序绑定起来。

字符串列表⽤于选择设备所要使用的驱动程序名称。

"manufacturer,model"     //anufacturer :厂商  model:模块对应的驱动名

一般设备驱动程序文件中都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。

5.6 设备所属的软件模块名属性:model

model 属性:描述设备模块信息,比如名字什么的,如:model = “wm8960-audio”。

5.7 设备节点的使能状态属性:status

status 属性:描述设备状态,如:okay - 设备可操作,disabled - 设备不可操作。

5.8 地址映射属性:ranges

ranges它是一个地址映射/转换表。

如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换。

5.9 别名属性

用 aliases 节点给多个同类型的控制器分配唯一编号(别名),便于Linux内核区分。

在Linux启动时会解析aliases节点。

5.10 中断属性:interrupts

该属性定义了设备节点挂接到哪个全局中断号上,需要为该设备挂接中断服务程序。

(1)带两个参数的情形

    interrupt-parent = <&gpio2>;
    interrupts = <5 1>;

表示中断控制器是GPIO2, 使用GPIO2的第5号中断,

5:该设备使用GPIO作为其中断,中断号为5

1:代表中断的触发方式,这里是指中断触发的方式为上升沿触发,

  • 1:上升沿触发
  • 2:下降沿触发
  • 3:高电平触发
  • 4:低电平触发

详见内核代码中的头文件include/dt-bindings/interrupt-controller/irq.h

(2)带三个参数的情形

interrupts = <GIC_SPI 66 1>;

中断的类型:  

IPI:CPU内部的中断号,inter-processer interrupt   中断号0~15
PPI:CPU core之间的终端号,per processor interrupts    中断号16~31
SPI:所有CPU共享的中断号,shared processor interrupts  中断号  32 ~32 + 224
SGI:软中断,software generated interrupts (SGI).

所以上述示例表示中断类型为共享处理器中断(SPI),

中断号为SPI中断类型中的第66号SPI中断号(共享中断的offset id号)

计算出来的实际中断号即为32 + 66 = 98号中断,  1表示上升沿触发中断。

5.11 子节点:xxx

            i2c@1,0   
                compatible = "acme,a1234-i2c-bus";  
                #address-cells = <1>;  
                #size-cells = <0>;  
                reg = <1 0 0x1000>;  
                interrupts = < 6 2 >;  
                rtc@58   
                    compatible = "maxim,ds1338";  
                    reg = <58>;  
                    interrupts = < 7 3 >;  
                ;  

在上图中,rtc@58是i2c@1,0的子节点。

第6章 设备节点的中断与SOC的中断控制器

以上是关于操作系统内核架构解析的主要内容,如果未能解决你的问题,请参考以下文章

Linux之内核架构

Linux 内核 内存管理内存管理架构 ① ( 内存管理架构组成 | 用户空间 | 内核空间 | MMU 硬件 | Linux 内核架构层次 | Linux 系统调用接口 )

架构Linux的架构(architecture)

linux系统知识 - 系统架构

[架构之路-45]:目标系统 - 系统软件 - Linux OS硬件设备驱动-网络驱动程序模型网络数据包的收发流程

Linux 内核Linux 内核体系架构 ( 硬件层面 | 内核空间 | 用户空间 | 内核态与用户态切换 | 系统调用 | 体系结构抽象层 )