Linux系统之Uboot、Kernel、Busybox思考之四

目录

三 内核的运行

9 设备树:

  1) 设备树产生缘由

  2) 设备树方案的流程

  3) 有了上述概念,为了支撑整个设备树的工程实现,内核实现以下内容

  4) 内核解析设备树

  5) 入口分析

  6) 解析处理。

10 udev devfs sysfs

11 系统中的USB设备 

12 网络子系统

13 文件子系统

四 运行的内核


三 内核的运行

9 设备树:

  1) 设备树产生缘由

  为解决板级设备杂乱的问题,提供一种简洁方便的管理方案

  通过定义一些规则,按规则描述设备信息,然后由内核解析,动态生成设备相关代码,完成设备的挂载

  相对硬件编码而言,规则可以看作是在代码上面又封装了一层,更加灵活

  而且,规则可以定义的更适合人类阅读,这样对提高效率和复用,也很有帮助

  2) 设备树方案的流程

  定义设备数规则,根节点,子节点,节点属性,节点属性值,cell单元,引用等等。

  需要注意,根

  需要注意,可以支持c语言预处理

  需要注意,节点可以被覆盖,重写

  需要注意,一些节点有限制,比如CPU 节点中的size必须为0, memory的类型必须为memory

  需要注意,节点名称带有 根 标记

  属性可以有值,也可以无值

  属性值可以为字符串,字符串数组,以及字节数组等形式

  cell单元,需要注意格式,其地址和size由父节点决定

  中断分ppi spi,分上升沿下降沿等,有专有定义

  引用可以使用label形式,也可以自定义全局唯一值来实现

  兼容属性定义为从一般到特殊,如果一致,需要通过model属性区分

  以上为设备树本身的一些构成规则,编写和查看设备树文件时会用到上述内容。

  从名称可以看出来,设备树的组织结构形式为树。树是一种层次结构,对应到设备上,也代表着根、枝、叶的构成。

  根可以看作是顶层总线,下面挂接了CPU、内存和其他总线,总线下面还可以有总线和设备,这样一级一级展开,构成了系统的硬件体系结构

  对应到设备树中,也就存在着父节点和子节点

 

  3) 有了上述概念,为了支撑整个设备树的工程实现,内核实现以下内容

  首先是设备树规则,不再多说。

  其次是设备树文件,dts格式,source文件,另外还有dtsi格式,属于头文件,设备树支持头文件引用,这有利于代码复用和管理。除此,设备树还支持C语言的宏定义等

  编写设备树文件后,该文件内容是human readable的,但不是计算机容易readable的,所以还需要进行转换

  内核提供dtc工具,将dts文件内容编译成dtb格式。blob代表着二进制的对象。内核读取dtb格式内容,动态生成内存中的设备对象。

  dtc工具在内核源代码的/scripts/dtc/下,编译内核源代码时,会编译出主机端的dtc工具。需要打开内核的CONFIG_DTC选项,配置其为Y

  如果配置了CONFIG_ARM_APPENDED_DTB=y,编译内核源代码时,会自动调用dtc工具编译生成dtb文件,并将其附到内核尾部。

  当然,我们也可以单独执行该命令编译内核dtb文件,比如

  ./scripts/dtc/dtc -I dts -O dtb -o /home/nfsshare/hisi.dtb arch/arm/boot/dts/hi3519av100.dts

  内核在编译过程中,如何确定编译那些dts文件呢,这些在makefile中会指定,比如对于hisi来讲,arch/arm/boot目录下makefile中有 

include $(srctree)/arch/arm/boot/dts/Makefile,在这个makefile中,又会引入

  dtb-$(CONFIG_ARCH_HISI) += \

        hi3519-demb.dtb

  定义了dtb目标,目录下有hi3519-demb.dts源文件,这样dtc工具就会将该文件编译为目标的dtb文件

  dtc工具不但可以编译dtb文件,还可以将dtb文件反编译为dts文件。我们看到,为了管理和复用代码,dts文件支持头文件引用,节点覆盖等。

  当文件变得庞大时,对于确认和查找节点的最终描述,反而有点不方便,此时可以使用反编译过程,将dtb文件反编译为dts文件。

  反编译后的文件是最终整理过的结果,是一颗比较规整和标准的树结构,节点描述是唯一的,这对于确认节点的最终描述,反倒是比较方便的。反编译命令如下

  ./scripts/dtc/dtc -I dtb -O dts -o /home/nfsshare/hisi.dts arch/arm/boot/dts/hi3519av100-smp-flash.dtb

  可以看到,输入-I和输出-O格式发生了调换,输出文件-o也变为了dts格式,默认输入源倒是dtb

 

  4) 内核解析设备树

  有了dtb文件,内核就可以解析该文件,动态生成设备对象。

  dtb文件的解析在内核启动过程的开始阶段,最终也是解析为一棵树。(待确认)。后续,在内核启动的后期阶段,init线程中,加载设备驱动时,会遍历之前解析的树,

  找到每一个节点,生成节点相关的设备对象及属性信息。(待确认)

  到此,整个链条就完整了。

 

  5) 入口分析

  除了内核,uboot也是可以支持设备树的。相应的,就有以下几种可能:

  A uboot支持设备树,uboot解析设备树,将解析后的结果地址告诉内核(这种应该是不行的,从软件开发感觉来讲,也没必要,反而增加了uboot和内核之间的耦合度)

  B uboot支持设备树,uboot不解析设备数,只是将dtb文件的地址告诉内核,由内核解析从特定内存或者存储地址获取dtb信息,并进行解析。

  实际中是采用这种方式,bootm启动内核时,除了传递内核地址外,还需要在命令行参数中增加dtb的地址。这种方式,二者之间通过dtb达成一致。只要对dtb的解读一致即可。

  其实,也完全可以做到不存在任何耦合,比如dtb文件只在内核这一端生成

  C uboot不支持设备树,这种情况下,uboot仍然按照bootm启动内核,设备树的相关操作完全由内核完成。

  此时,在内核编译中,平台体系结构相关部分,不再是obj-y的目标,而是dtb-y。查看内核源代码可以发现,此时arch/arm/目录下的mach-xxx目录下,体系结构相关代码已经很少

  转而,在arch/arm/boot/dts目录下有大量体系结构相关的dts存在。

  makefile中也是指定dtc工具编译dtb目标,同时定义了zImage-dtb目标,由原始vmlinux生成压缩的zImage后,通过@cat $(obj)/zImage $(DTB_OBJS_FULL) > $@将dtb目标缀在zImage后面生成zImage-dtb

  最终生成uboot引导的uImage镜像也是基于zImage-dtb结果。

  $(obj)/uImage:  $(obj)/zImage-dtb FORCE

 用二进制编辑工具打开此时的uImage,可以在末尾看到可读的dts痕迹,比如P/soc/amba/uart@04540000等,说明内核镜像中包括了dtb内容。

 为了让内核启动时,自动查找dtb并进行解析,需要打开配置选项CONFIG_ARM_APPENDED_DTB。

   当上述选项配置为Y时,在compressed目录下的head.S汇编代码中,可以看到有对设备树的处理

   补充一点,zImage作为压缩镜像,其生成$(obj)/zImage:  $(obj)/compressed/vmlinux FORCE依赖于compressed下的目标

   head.S汇编代码会嵌入到内核启动代码中,判断启动合法条件,引导跳转到C代码,在head.S中会发现如下一段代码,

   

    #ifdef CONFIG_ARM_APPENDED_DTB
    /*
     *   r0  = delta
     *   r2  = BSS start
     *   r3  = BSS end
     *   r4  = final kernel address (possibly with LSB set)
     *   r5  = appended dtb size (still unknown)
     *   r6  = _edata
     *   r7  = architecture ID
     *   r8  = atags/device tree pointer
     *   r9  = size of decompressed image
     *   r10 = end of this image, including  bss/stack/malloc space if non XIP
     *   r11 = GOT start
     *   r12 = GOT end
     *   sp  = stack pointer
     *
     * if there are device trees (dtb) appended to zImage, advance r10 so that the
     * dtb data will get relocated along with the kernel if necessary.
     */
    
    		ldr	lr, [r6, #0]
    #ifndef __ARMEB__
    		ldr	r1, =0xedfe0dd0		@ sig is 0xd00dfeed big endian
    #else
    		ldr	r1, =0xd00dfeed
    #endif
    		cmp	lr, r1
    		bne	dtb_check_done		@ not found

  从上面代码可以看出,r6是内核正常的结尾处,D0 0D FE ED是dtb的魔数,当CONFIG_ARM_APPENDED_DTB选项被选中时

    会将内核正常结尾处也就是附加dtb开头的内容加载到lr中,同时根据大小端,将dtb的魔数设置到寄存器r1中

    接着判断二者是否相等,如果不等,说明未找到dtb,否则说明内核自带了dtb

    如果发现存在dtb append,则在后续解压内核时避免对dtb部分造成影响,最终调整dtb的相关指针,以便内核启动时,能够正确加载和解析dtb

  6) 解析处理。

10 udev devfs sysfs

  设备的发现与管理

  内核启动时,初始化相关资源,包括进程、内存、中断、锁、设备模型、文件系统框架、用户空间交互通道等,并完成设备树的解析

  内核创建init进程,在恰当时机执行initcall区域的函数指针,进行driver setup

  各个驱动模块的初始化在执行过程中,会进行总线注册和设备匹配,匹配分多种条件,包括id table,设备名称,设备树兼容性属性等等。一般按照严格到宽松的条件进行匹配。

  匹配成功后,驱动的probe接口将被调用,进行设备的初始化及设备所需中断和内存等资源的申请

  之后,设备一般在中断的驱动下开始正常工作,包括上层触发数据发送,中断触发数据接收,上层触发各种控制操作等。

 

  在Linux中,一切设备均抽象为文件。内核驱动中,可以申请设备号,成功后,在proc/devices中可以看到设备号使用情况

  设备号申请分自动和手动两种,自动方式由内核提供空闲设备号,手动由驱动主动注册设备号,手动方式可能存在设备号冲突情况

  设备驱动申请设备号并注册后,只是在上述目录下可以看到新增加的设备号,而/dev目录下并没有实际产生设备节点

  要在/dev目录下产生设备节点,有多种方式,一种是驱动中调用内核接口device_create,注册时自动生成设备节点;一种是通过sysfs系统,在用户空间通过udev工具生成;还可以调用mknod程序,手动生成。

 

  在内核驱动中自动创建设备节点存在一些缺点,比如节点命名问题,特定功能节点的创建问题。

  所以,一些节点可能在需要时由用户空间程序创建,会提供方便。但是,完全交给用户空间,也存在问题。比如,设备发现问题。在错过设备驱动加载时机后,如何发现设备。

  这里也牵涉一些关于内核职责的问题,比如机制与策略的分离。

  2.6之后的内核提供了sysfs系统,用于管理总线、驱动、设备及它们之间的关系。udev工具就是基于sysfs进行设备管理的用户空间程序。

  借助udev工具,可以很好的解决设备命名问题,也可以在设备发现和移除时,很方便的执行定制化动作。

 

  整个流程为:

  首先,内核在初始化过程中,会加载内置驱动,完成设备的初始化和部分设备的创建。

  init程序加载用户空间第一个程序并执行时,会执行udevd后台例程。

  udevd会使用/etc/udev/udev.conf文件配置自己

  udevd会监听内核设备事件,包括add remove change等

  检测到事件后,会按优先级匹配/etc/udev/rules.d/下面的规则文件(规则文件是udevd启动时预置到其内存数据结构中的?)

  udevd是否会监听/etc/udev/rules.d目录下的规则文件的变化,实时更新规则。应该要这样,否则,需要重启udevd获取系统,这不符合用户体验要求。

  udevd会遍历所有预置的规则,匹配后,就执行相应的动作,包括赋值、执行动作、获取特定动作的结果进行赋值或执行动作

  动作的执行要放到单独的环境中,如果是脚本的话,要明确指定shell类型,udevd会fork子进程处理动作。ps可能会看到多个udevd daemon进程。

  规则文件编写原则可参考已有规则文件,其中有关属性和预定义变量,可以通过执行udevadm info -q all -n /dev/xxx -a的输出查看

  设备的相关操作信息会被记录到一个数据库中,udevadm就是读取数据库信息来展示设备相关信息的。

  -a参数可以输出设备的整个加载链条,比如从最顶层的总线到下一级总线到再下一级总线,直到当前指定设备为止。

  设备的组成结构是一个树形结构,比如执行udevadm info -q all -n /dev/sdd -a输出sdd设备信息如下

  

looking at device '/devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd':
    KERNEL=="sdd"
    SUBSYSTEM=="block"
    DRIVER==""
    ATTR{ro}=="0"
    ATTR{size}=="7814037168"
    ATTR{stat}=="    4597       46    37126    69300    10595     5825  2513672    73590        0    53330   142880"
    ATTR{range}=="16"
    ATTR{discard_alignment}=="0"
    ATTR{events}==""
    ATTR{ext_range}=="256"
    ATTR{events_poll_msecs}=="-1"
    ATTR{alignment_offset}=="0"
    ATTR{inflight}=="       0        0"
    ATTR{removable}=="0"
    ATTR{capability}=="50"
    ATTR{events_async}==""

  looking at parent device '/devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0':
    KERNELS=="3:0:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS=="sd"
    ATTRS{rev}=="CV11"
    ATTRS{type}=="0"
    ATTRS{scsi_level}=="6"
    ATTRS{model}=="ST4000VX005-2LY1"
    ATTRS{state}=="running"
    ATTRS{queue_type}=="simple"
    ATTRS{modalias}=="scsi:t-0x00"
    ATTRS{iodone_cnt}=="0x4826"
    ATTRS{iorequest_cnt}=="0x4844"
    ATTRS{queue_ramp_up_period}=="120000"
    ATTRS{timeout}=="30"
    ATTRS{evt_media_change}=="0"
    ATTRS{ioerr_cnt}=="0x0"
    ATTRS{queue_depth}=="31"
    ATTRS{vendor}=="ATA     "
    ATTRS{device_blocked}=="0"
    ATTRS{iocounterbits}=="32"

  looking at parent device '/devices/platform/ahci.0/ata4/host3/target3:0:0':
    KERNELS=="target3:0:0"
    SUBSYSTEMS=="scsi"
    DRIVERS==""

  looking at parent device '/devices/platform/ahci.0/ata4/host3':
    KERNELS=="host3"
    SUBSYSTEMS=="scsi"
    DRIVERS==""

  looking at parent device '/devices/platform/ahci.0/ata4':
    KERNELS=="ata4"
    SUBSYSTEMS==""
    DRIVERS==""

  looking at parent device '/devices/platform/ahci.0':
    KERNELS=="ahci.0"
    SUBSYSTEMS=="platform"
    DRIVERS=="ahci"
    ATTRS{modalias}=="platform:ahci"

  looking at parent device '/devices/platform':
    KERNELS=="platform"
    SUBSYSTEMS==""
    DRIVERS==""

  可以看到,树根是platform,中间控制器scsi,最后是block

  另外,需要注意到,父节点中名称和属性都带S,本节点中不带S,这一点在书写规则文件时也适用。即规则文件中,如果是匹配父节点的属性,则需要使用ATTRS。

  规则文件中至少需要一个匹配和一个赋值,匹配使用==,赋值使用=,赋值可以是重命名节点相关属性,或者执行定制动作。

  使用udev工具主要就是通过规则匹配来执行相关动作,所以规则文件匹配很重要。但是事件的产生,比如设备插拔等,是需要一定条件的,这对规则文件的验证带来了一些麻烦

  能否模拟整个过程,对规则进行快速验证调试呢?如果可以做到这点,那么对提高效率,就非常有帮助了。

  udevadm提供了多个选项,可以帮助用户快速调试验证规则。

  首先,让目标设备正常加载进系统中

  然后,使用前面的udevadm info选项,查看系统中的事件信息,包括一些本地属性和父节点信息

  利用上述信息,编写初步的规则文件

  使用命令udevadm test /sys/devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd模拟规则的匹配和执行过程

  上述命令后面部分是具体设备信息,该命令选项只是模拟过程,不会真正执行

  命令也会输出规则文件的解析优先级,使用该命令可进行初步调试

 

  下面进行正式的验证

  首先,我们可以启动udevadm monitor,监控实时的事件信息,为调试提供分析依据,如下所示:

udevadm monitor --property
  monitor will print the received events for:
  UDEV - the event which udev sends out after rule processing
  KERNEL - the kernel uevent
  
  KERNEL[1624468946.462053] add      /devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd (block)
  UDEV_LOG=3
  ACTION=add
  DEVPATH=/devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd
  SUBSYSTEM=block
  DEVNAME=sdd
  DEVTYPE=disk
  NPARTS=3
  SEQNUM=1416
  MAJOR=8
  MINOR=48
  
  UDEV  [1624468946.474712] add      /devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd (block)
  UDEV_LOG=3
  ACTION=add
  DEVPATH=/devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd
  SUBSYSTEM=block
  DEVNAME=/dev/sdd
  DEVTYPE=disk
  NPARTS=3
  SEQNUM=1416
  MAJOR=8
  MINOR=48

  monitor会输出内核的uevent事件和udev处理后输出的event

  其次,使用udevadm trigger触发事件,比如对于上述硬盘加载,可以用如下触发命令

  udevadm trigger --action=add --subsystem-match=block --sysname-match=sdd

  udevadm trigger --action=add --subsystem-match=block --sysname-match=sd*

  trigger支持shell的通配符匹配,只触发满足规则的节点的事件,这一点需要注意,比如对于上面的第一条规则,monitor输出

  KERNEL[1624533496.063615] add      /devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd (block)
  KERNEL[1624533496.063753] add      /devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd/sdd1 (block)
  KERNEL[1624533496.063840] add      /devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd/sdd2 (block)
  KERNEL[1624533496.063919] add      /devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd/sdd3 (block)

  对于上述第二条,monitor输出

  KERNEL[1624533496.063995] add      /devices/platform/hiusb-ehci.0/usb1/1-1/1-1.4/1-1.4.1/1-1.4.1:1.0/host4/target4:0:0/4:0:0:0/block/sdb (block)
  KERNEL[1624533496.064080] add      /devices/platform/hiusb-ehci.0/usb1/1-1/1-1.4/1-1.4.2/1-1.4.2:1.0/host5/target5:0:0/5:0:0:0/block/sda (block)
  KERNEL[1624533496.064181] add      /devices/platform/hiusb-ehci.0/usb1/1-1/1-1.4/1-1.4.2/1-1.4.2:1.0/host5/target5:0:0/5:0:0:0/block/sda/sda1 (block)
  KERNEL[1624533496.064269] add      /devices/platform/hiusb-ehci.0/usb1/1-1/1-1.4/1-1.4.3/1-1.4.3:1.0/host6/target6:0:0/6:0:0:0/block/sdc (block)
  UDEV  [1624533496.081699] add      /devices/platform/hiusb-ehci.0/usb1/1-1/1-1.4/1-1.4.1/1-1.4.1:1.0/host4/target4:0:0/4:0:0:0/block/sdb (block)
  UDEV  [1624533496.082981] add      /devices/platform/hiusb-ehci.0/usb1/1-1/1-1.4/1-1.4.3/1-1.4.3:1.0/host6/target6:0:0/6:0:0:0/block/sdc (block)
  UDEV  [1624533496.083941] add      /devices/platform/hiusb-ehci.0/usb1/1-1/1-1.4/1-1.4.2/1-1.4.2:1.0/host5/target5:0:0/5:0:0:0/block/sda (block)
  UDEV  [1624533496.085070] add      /devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd (block)
  UDEV  [1624533496.103061] add      /devices/platform/hiusb-ehci.0/usb1/1-1/1-1.4/1-1.4.2/1-1.4.2:1.0/host5/target5:0:0/5:0:0:0/block/sda/sda1 (block)
  UDEV  [1624533496.107014] add      /devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd/sdd3 (block)
  UDEV  [1624533496.107077] add      /devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd/sdd2 (block)
  UDEV  [1624533496.112003] add      /devices/platform/ahci.0/ata4/host3/target3:0:0/3:0:0:0/block/sdd/sdd1 (block)

  可见,monitor输出为满足trigger条件的节点,并未输出父节点或子节点

 

  另外,trigger可以匹配的选项也是比较多的,可以支持attribute匹配,如下

  udevadm trigger --action=add --subsystem-match=block --attr-match=size=7814037168

  使用上述命令时,需要注意只输出符合条件的节点,另外,发现trigger不触发同时匹配父子节点的条件

udevadm trigger --action=add    --attr-match=model=ST4000VX005-2LY1 --attr-match=size=7814037168

  这一点与上面所述的规则也是一致的

 

  trigger触发事件后,udev就会匹配规则文件,然后执行动作,通过观察monitor的输出,检查动作的执行是否符合预期。

  另外,udevadm control可以修改udev后台例程的日志级别

  实际设备上发现,修改udev.conf文件,将日志级别从err改为debug或info后,系统启动不了,加载根文件系统后,不再进一步加载其他程序,这一点待研究确认。

 

  通过上述流程,可以发现,新接入的设备,会由内核产生event,udev可以捕获该event

  如果错过设备接入时机,没有捕获事件,可以通过trigger主动触发,让内核将之前保存的事件再次通知到应用层,同样可以匹配规则执行动作

  所以结合sysfs和udev可以解决设备的重命名和挂载等处理需求

 

  最后补充强调一下,

  这里的trigger本质上是让内核重新发出事件,也就是说,实际上事件已经错过了。

  根文件系统加载后,对于用户空间加载前,即udev本身启动监控前,内核驱动加载过程中已经识别和加载的设备,如何进行配置,就是依靠该命令

  但是udev是在用户空间启动的,所以udev启动后,如果需要处理内核加载过程中的设备事件信息,就需要重新触发。但此时,该如何确定匹配条件呢?

  答案是直接执行udevadm trigger,不加参数,此时内核会将所有产生的事件全部上报,由udev按需处理。

11 系统中的USB设备 

  执行lsusb命令输出如下内容

  Bus 002 Device 001: ID 1d6b:0001 主机控制器 根HUB

  Bus 001 Device 001: ID 1d6b:0002 主机控制器 根HUB
  Bus 001 Device 002: ID 05e3:0610 USB HUB
  Bus 001 Device 003: ID 05e3:0610 USB HUB
  Bus 001 Device 004: ID 05e3:0610 USB HUB
  Bus 001 Device 009: ID 1c9e:9b3c 龙尚模块
  Bus 001 Device 006: ID 0424:2240 USB读卡器
  Bus 001 Device 007: ID 0424:2240 USB读卡器
  Bus 001 Device 008: ID 0424:2240 USB读卡器
  Bus 001 Device 005: ID 0403:6011 USB转串口
  Bus 001 Device 010: ID 1c9e:9b3c 龙尚模块

  Bus 003 Device 001: ID 1d6b:0002 主机控制器 根HUB
  Bus 003 Device 002: ID 0eef:0001 触摸屏

  Bus 004 Device 001: ID 1d6b:0003 主机控制器 根HUB

  从上面可以看出,有四个总线,每个总线上各有1个或多个设备

  从CPU角度来看,整体的USB树形结构如下图所示:

CPU 
    --ohci 根设备,全速设备
          --usb2 [1d6b:0001] 主机USB控制器
    --ehci 根设备,高速设备
          --usb1 [1d6b:0002] 主机USB控制器
                           --1-1 [05e3:0610] USB HUB
                                           --1-1.1 [1c9e:9b3c] 龙尚模块
                                           --1-1.4 [05e3:0610] USB HUB
                                                             --1-1.4.1 [0424:2240] 三个存储卡USB口读卡器
                                                             --1-1.4.2 [0424:2240]
                                                             --1-1.4.3 [0424:2240]
                                           --1-1.2 nop
                                           --1-1.3 nop
                           --1-2 [05e3:0610] USB HUB
                                           --1-2.1 [0403:6011] USB转串口
                                                             --1-2.1:1.0 转换的多个串口
                                                             --1-2.1:1.1
                                                             --1-2.1:1.2
                                                             --1-2.1:1.3
                                          --1-2.2 [1c9e:9b3c] 龙尚模块
    --xhci 根设备,USB3.0
          --usb3 [1d6b:0002] 主机USB控制器
                           --touch [0eef:0001] 触摸屏
          --usb4 [1d6b:0003] 主机USB控制器
          --usb4 [1d6b:0003] 主机USB控制器

  CPU本身带了三个控制器,分别支持全速,2.0以及3.0的USB设备。

12 网络子系统

   文件抽象特性在网络子系统表达能力不强。

   不过,仍然可以通过iNode将网络子系统中的资源和对象跟文件关联起来,这对特定情况下的问题查找很有帮助。

   区分路由策略和路由表

   待补充。

13 文件子系统

   关键几个数据结构

   超级块  inode  目录  文件   等。

   文件子系统有两个主要问题需要处理,一个是高效的组织结构;一个是一致性。

   这里一致性是更加棘手的一个问题。文件系统主要通过两个手段来保证一致性,一个是日志,一个是写时复制。

   除此,还有其他一些原则,包括读写、节点、速度、大小、压缩、缓存、备份、索引、均衡、随机等原则。

   具体参见博客:

   7.5 文件系统_龙赤子的博客-CSDN博客

  

   关于缓存,多说一点。

   页缓存与缓冲区缓存。文件子系统构建后,优化的一大目标就是缓冲。我们知道,机械硬盘的吞吐量跟CPU和内存的处理速度差异太大。

   特别是近二三十年,摩尔定律使得CPU处理性能不断翻翻,但是机械硬盘的速度,并没有太大的改进。为了匹配速度上的差异,就需要缓存来解决。

   在嵌入式领域,很多时候使用flash作为数据断电保存的介质。相对机械动作,电子芯片的处理速度有很大的提升,但是跟CPU和内存相比,仍然有很大差距,所以缓存仍然是需要的。

   最开始,块设备缓冲(更多针对直接操作物理设备块)是单独的,跟页缓冲(更多是针对文件映射)没有关系,这样一来,同一份数据,在内核中就可能存在多份。

   后来,将二者整合到一起,在底层都是基于页缓冲。按物理页操作,也是内核擅长的。

  

   实际中可能出现文件内容改变、损坏、丢失的情况。解决思路是

   A 文件定期备份

   B 分类管理,比如对于不变文件,保证只读属性,减少出错可能

   C 监测文件IO状态,获取相关信息,辅助问题分析

   D 异常断电、关机的特殊处理

   E 电磁隔离保护

   F 专用模块负责文件读写管理。

四 运行的内核

  1 可以把内核想象为一个大进程,把自己想想成CPU,CPU就是一些寄存器的集合加逻辑处理过程。

  2 内存视图看内核,Linux 的内存视图

  参见博客:

  学内核之十六:linux内存管理结构大蓝图_龙赤子的博客-CSDN博客

 

  包括内存实体 :

  任务结构 链条 地址空间

  3 内核与应用层的交互途径:

  proc

  sys

  dev

  ioctl

  netlink

  unix socket

  4 关于堆栈

  堆栈分内核堆栈和应用堆栈

  对于ARM来讲,在内核配置unwind选项的情况下,堆栈是通过解析unwind段来回溯的。

  unwind段中记录了各个函数的入栈指令,然后根据发生异常时的PC地址,定位到具体的函数,之后反推堆栈操作,得到上一级的lr和fp地址

  lr为调用函数的返回地址,通过该寄存器内容可以知道调用者是那个函数

  fp为堆栈指针,通过该寄存器可以定位堆栈边界,这样一级一级回溯,即可获取调用栈信息。

  关于内核调用栈的回溯,在内核unwind.c代码中

  对CPU和堆栈的关系理解,可参见博主的文章:

  学内核之四:关于内核与硬件的衔接_龙赤子的博客-CSDN博客

 

  5 关于内核调试分析

  perf 性能跟踪 大框架

  ftrace 函数调用跟踪

  ebpf 事件跟踪

猜你喜欢

转载自blog.csdn.net/wwwyue1985/article/details/129150927
今日推荐