linux-驱动开发之设备树详解(RK平台为例)
前言
Linux3.x以后的版本才引入了设备树,设备树用于描述一个硬件平台的板级细节。 在早些的linux内核,这些“硬件平台的板级细节”保存在linux内核目录“/arch”, 以ARM为例“硬件平台的板级细节”保存在“/arch/arm/plat-xxx”和“/arch/arm/mach-xxx”目录下。 随着处理器数量的增多用于描述“硬件平台板级细节”的文件越来越多导致Linux内核非常臃肿, Linux之父发现这个问题之后决定使用设备树解决这个问题。设备树简单、易用、可重用性强, linux3.x之后大多采用设备树编写驱动。
关于设备树的详细请参考:The Devicetree Project
1.1. 设备树简介
设备树(Device Tree)的作用就是描述一个硬件平台的硬件资源,一般描述那些不能动态探测到的设备,可以被动态探测到的设备是不需要描述。设备树可以被bootloader(uboot)传递到内核, 内核可以从设备树中获取硬件信息。
设备树描述硬件资源时有两个特点。
-
第一,以“树状”结构描述硬件资源。例如本地总线为树的“主干”在设备树里面称为“根节点”, 挂载到本地总线的IIC总线、SPI总线、UART总线为树的“枝干”在设备树里称为“根节点的子节点”, IIC 总线下的IIC设备不止一个,这些“枝干”又可以再分,除了根节点没有父节点外,其他节点都只有一个父节点。
-
第二,设备树源文件可以像头文件(.h文件)那样,一个设备树文件引用另外一个设备树文件, 这样可以实现“代码”的重用。例如多个硬件平台都使用rk系列处理器作为主控芯片, 那么我们可以将rk系列芯片的硬件资源写到一个单独的设备树文件里面一般使用“.dtsi”后缀, 其他板级设备树文件直接使用“# include xxx.dtsi”引用即可。
DTS、DTC和DTB它们是文档中常见的几个缩写。
-
DTS 是指.dts格式的文件,是一种ASII 文本格式的设备树描述,也是我们要编写的设备树源码,一般一个.dts文件对应一个硬件平台,对应arm架构,源文件位于Linux源码的“/arch/arm/boot/dts”目录下。
-
DTC 是指编译设备树源码的工具,一般情况下我们需要手动安装这个编译工具。
-
DTB 是设备树源码编译生成的文件,类似于我们C语言中“.C”文件编译生成“.bin”文件。
1.2. 设备树框架
设备树(Device Tree)由一系列被命名的结点(node)和属性(property)组成,以lubancat2为例, 不妨打开本章节的配套代码 linux_driver/device_tree/rk3561-lubancat2.dts或者 内核源码/arch/arm64/boot/dts/rockchip/rk3561-lubancat2.dts先睹为快。
下面的内容将围绕着设备树源码,来讲解设备树框架和基本语法。
设备树 (内核源码/arch/arm64/boot/dts/rockchip/rk3561-lubancat2.dts)
123456719 10 11 12 13 14 15 16 17 11 19 20 21 22 23 24 25 26 27 21 29 30 31 32 33 34 35 36 37 31 39 40 41 | /dts-v1/;#include <dt-bindings/gpio/gpio.h> #include <dt-bindings/pwm/pwm.h> #include <dt-bindings/pinctrl/rockchip.h> #include <dt-bindings/input/rk-input.h> #include <dt-bindings/display/drm_mipi_dsi.h> #include <dt-bindings/sensor-dev.h> #include "rk3561.dtsi"/ {model = "EmbedFire LubanCat2 HDMI";compatible = "embedfire,lubancat2", "rockchip,rk3561";chosen: chosen {bootargs = "earlycon=uart1250,mmio32,0xfe660000 console=ttyFIQ0 root=PARTUUID=614e0000-0000 rw rootwait";};fiq-debugger {compatible = "rockchip,fiq-debugger";rockchip,serial-id = <2>;rockchip,wake-irq = <0>;/* If enable uart uses irq instead of fiq */rockchip,irq-mode-enable = <1>;rockchip,baudrate = <1500000>; /* Only 115200 and 1500000 */interrupts = <GIC_SPI 252 IRQ_TYPE_LEVEL_LOW>;pinctrl-names = "default";pinctrl-0 = <&uart2m0_xfer>;status = "okay";}; /*-------------内容省略--------------*/&saradc {vref-supply = <&vcca_1v1>;status = "okay";};&tsadc {status = "okay";}; /*-------------以下内容省略--------------*/ } |
rk3561.dtsi头文件 (内核源码/arch/arm64/boot/dts/rockchip/rk3561.dtsi)
123456719 10 11 12 13 14 15 16 17 11 19 20 21 22 23 24 25 26 27 21 29 30 | #include <dt-bindings/clock/rk3561-cru.h> #include <dt-bindings/interrupt-controller/arm-gic.h> #include <dt-bindings/interrupt-controller/irq.h> #include <dt-bindings/pinctrl/rockchip.h> #include <dt-bindings/soc/rockchip,boot-mode.h> #include <dt-bindings/phy/phy.h> #include <dt-bindings/power/rk3561-power.h> #include <dt-bindings/soc/rockchip-system-status.h> #include <dt-bindings/suspend/rockchip-rk3561.h> #include <dt-bindings/thermal/thermal.h> #include "rk3561-dram-default-timing.dtsi"/ {compatible = "rockchip,rk3561";interrupt-parent = <&gic>;#address-cells = <2>;#size-cells = <2>;aliases {csi2dphy0 = &csi2_dphy0;csi2dphy1 = &csi2_dphy1;csi2dphy2 = &csi2_dphy2;dsi0 = &dsi0;dsi1 = &dsi1;ethernet0 = &gmac0;/*-------------以下内容省略--------------*/} |
设备树源码分为三部分,介绍如下:
-
第3-9行: 头文件。设备树是可以像C语言那样使用“#include”引用“.h”后缀的头文件,也可以引用设备树“.dtsi”后缀的头文件。 rk3561.dtsi由rockchip官方提供,是一个rk3561.dtsi平台“共用”的设备树文件。
-
第11-30行: 设备树节点。设备树给我们最直观的感受是它由一些嵌套的大括号“{}”组成, 每一个“{}”都是一个“节点”。“/ {…};”表示“根节点”,每一个设备树只有一个根节点。 如果打开“rk3561.dtsi”文件可以发现它也有一个根节点,虽然“rk3561-lubancat2.dts”引用了“rk3561.dtsi”文件, 但这并不代表“rk3561-lubancat2.dts”设备树有两个根节点,因为不同文件的根节点最终会合并为一个。 在根节点内部的“aliases {…}”、“chosen {…}”、“memory {…}”等字符,都是根节点的子节点。
-
第32-39行: 设备树节点追加内容。第三部分的子节点比根节点下的子节点多了一个“&”, 这表示该节点在向已经存在的子节点追加数据。这些“已经存在的节点”可能定义在“rk3561-lubancat2.dts”文件, 也可能定义在“rk3561.dtsi”文件所包含的设备树文件里。 rk3561-lubancat2.dts代码中的“&cpu0 {…}”、“&dmc {…}”、“&i2c0 {…}”等等追加的目标节点,就是定义在“stm32mp157c.dtsi”中。
到目前为止我们知道设备树由一个根节点和众多子节点组成,子节点也可以继续包含其他节点,也就是子节点的子节点。 设备树的组成很简单,下面我们一起来看看节点的基本格式和节点属性。
1.2.1. 节点基本格式
节点的结构参考:
node-name 节点名称
节点格式中的 node-name
用于指定节点的名称。 它的长度为1至31个字符,只能由如下字符组成
表 节点名称
字符 | 描述 |
---|---|
0-9 | 数字 |
a-z | 小写字母 |
A-Z | 大写字母 |
, | 英文逗号 |
. | 英文句号 |
_ | 下划线 |
加号 | |
减号 |
另外,节点名应当使用大写或小写字母开头,并且能够描述设备类别。
注意,根节点没有节点名,它直接使用“/”指代这是一个根节点。
@unit-address
@unit-address
,其中的符号“@”可以理解为是一个分割符,“unit-address”用于指定“单元地址”, 它的值要和节点“reg”属性的第一个地址一致。如果节点没有“reg”属性值,可以直接省略“@unit-address”, 不过要注意这时要求同级别的设备树下(相同级别的子节点)节点名唯一,从这个侧面也可以了解到, 同级别的子节点的节点名可以相同,但是要求“单元地址”不同,node-name@unit-address
的整体要求同级唯一。
1.2.2. 节点标签
在rk3561.dtsi头文件中,节点名“cpu0”前面多了个“cpu@0”,这个“cpu0”就是我们所说的节点标签。 通常节点标签是节点名的简写,所以它的作用是当其它位置需要引用时可以使用节点标签来向该节点中追加内容。
1.2.3. 节点路径
通过指定从根节点到所需节点的完整路径,可以唯一地标识设备树中的节点, 不同层次的设备树节点名字可以相同,同层次的设备树节点要唯一
。 这有点类似于我们Windows上的文件,一个路径唯一标识一个文件或文件夹,不同目录下的文件文件名可以相同。例如前面节点的结构参考图中, 节点node1-name的子节点child-node1,节点路径就是 “/node1-name/child-node1”。
1.2.4. 节点属性
在节点的“{}”中包含的内容是节点属性,通常情况下一个节点包含多个属性信息, 这些属性信息就是要传递到内核的“板级硬件描述信息”,驱动中会通过一些API函数获取这些信息。
例如根节点“/”就有属性compatible = “rockchip,rk3561”。 我们可以通过该属性了解到硬件设备相关的名字叫“rk3561”,设备所使用的的是“rk3561”这颗 SOC。
我们编写设备树最主要的内容是编写节点的节点属性,通常情况下一个节点代表一个设备, 设备有哪些属性、怎么编写这些属性、在驱动中怎么引用这些属性是我们后面讲解的重点, 这一小节只讲解设备节点有哪些可设置属性。有一些节点属性是所有节点共有的,一些作用于特定的节点, 我们这里介绍那些共有的节点属性,其他节点属性使用到时再详细介绍。
节点属性分为标准属性和自定义属性,也就是说我们在设备树中可以根据自己的实际需要定义、添加设备属性。 标准属性的属性名是固定的,自定义属性名可按照要求自行定义。
compatible属性
属性值类型:字符串
compatible属性
123456719 10 11 12 13 14 | model = "EmbedFire LubanCat2 HDMI"; compatible = "embedfire,lubancat2", "rockchip,rk3561"; aliases {csi2dphy0 = &csi2_dphy0;csi2dphy1 = &csi2_dphy1;csi2dphy2 = &csi2_dphy2;dsi0 = &dsi0;dsi1 = &dsi1;ethernet0 = &gmac0;ethernet1 = &gmac1;gpio0 = &gpio0;.....}; |
compatible属性值由一个或多个字符串组成,有多个字符串时使用“,”分隔开。
设备树中的每一个代表了一个设备的节点都要有一个compatible属性。 compatible是系统用来决定绑定到设备的设备驱动的关键。 compatible属性是用来查找节点的方法之一,另外还可以通过节点名或节点路径查找指定节点。
例如系统初始化时会初始化platform总线上的设备时,根据设备节点”compatible”属性和驱动中of_match_table对应的值,匹配了就加载对应的驱动。
model属性
属性值类型:字符串
示例:
model属性
1 | model = "EmbedFire LubanCat2 HDMI+MIPI"; |
model属性用于指定设备的制造商和型号,推荐使用“制造商, 型号”的格式,当然也可以自定义。
status属性
属性值类型:字符串
示例:
status属性
1 2 3 4 | /* External sound card */ sound: sound {status = "disabled"; }; |
状态属性用于指示设备的“操作状态”,通过status可以去禁止设备或者启用设备,可用的操作状态如下表。默认情况下不设置status属性设备是使能的。
节点名称
状态值 | 描述 |
---|---|
okay | 使能设备 |
disabled | 禁用设备 |
fail | 表示设备不可运行,目前驱动不支持,待修复。 |
fail-sss | 表示设备不可运行,目前驱动不支持,待修复。“sss”的值与具体的设备相关。 |
#address-cells 和 #size-cells
属性值类型:u32
示例:
#address-cells和 #size-cells
123456719 10 11 | soc {#address-cells = <1>;#size-cells = <1>;compatible = "simple-bus";interrupt-parent = <&gpc>;ranges;ocrams: sram@900000 {compatible = "fsl,lpm-sram";reg = <0x900000 0x4000>;}; }; |
#address-cells和 #size-cells属性同时存在,在设备树ocrams结构中, 它们用在有子节点的设备节点(节点),用于设置子节点的“reg”属性的“书写格式”。
补充:reg属性值由一串数字组成,如上图中的reg = <0x900000 0x4000>, ret属性的书写格式为reg = < cells cells cells cells cells cells…>,长度根据实际情况而定, 这些数据分为地址数据(地址字段),长度数据(大小字段)。
#address-cells,用于指定子节点reg属性“地址字段”所占的长度(单元格cells的个数)。 #size-cells,用于指定子节点reg属性“大小字段”所占的长度(单元格cells的个数)。
例如#address-cells=2,#address-cells=1,则reg内的数据含义为reg = <address address size address address size>, 因为每个cells是一个32位宽的数字,例如需要表示一个64位宽的地址时,就要使用两个address单元来表示。 而假如#address-cells=1,#address-cells=1,则reg内的数据含义为reg = < address size address size address size>。
总之#size-cells和#address-cells决定了子节点的reg属性中哪些数据是“地址”,哪些数据是“长度”信息。
reg属性
属性值类型:地址、长度数据对
reg属性描述设备资源在其父总线定义的地址空间内的地址。通常情况下用于表示一块寄存器的起始地址(偏移地址)和长度, 在特定情况下也有不同的含义。例如上例中#address-cells = <1>,#address-cells = <1>,reg = <0x9000000 x4000>, 其中0x9000000表示的是地址,0x4000表示的是地址长度,这里的reg属性指定了起始地址为0x9000000,长度为0x4000的一块地址空间。
ranges
属性值类型:任意数量的 <子地址、父地址、地址长度>编码
示例:
ranges属性
123456719 10 11 | soc {#address-cells = <1>;#size-cells = <1>;compatible = "simple-bus";interrupt-parent = <&gpc>;ranges;busfreq {/*-------------以下内容省略--------------*/}; } |
该属性提供了子节点地址空间和父地址空间的映射(转换)方法,常见格式是ranges = <子地址, 父地址, 转换长度>。 如果父地址空间和子地址空间相同则无需转换,如示例中所示,只写了renges,内容为空,我们也可以直接省略renges属性。
比如对于#address-cells和#size-cells都为1的话,以ranges=<0x0 0x10 0x20>为例,表示将子地址的从0x0~(0x0 + 0x20)的地址空间映射到父地址的0x10~(0x10 + 0x20)。
name和device_type
属性值类型:字符串。
示例:
name属性
1 2 3 | example{name = "name" } |
device_type属性
123456719 10 11 12 13 14 15 16 17 | cpus {#address-cells = <2>;#size-cells = <0>;cpu0: cpu@0 {device_type = "cpu";compatible = "arm,cortex-a55";reg = <0x0 0x0>;enable-method = "psci";clocks = <&scmi_clk 0>;operating-points-v2 = <&cpu0_opp_table>;cpu-idle-states = <&CPU_SLEEP>;#cooling-cells = <2>;dynamic-power-coefficient = <117>;};... } |
这两个属性很少用(已经被废弃),不推荐使用。name用于指定节点名,在旧的设备树中它用于确定节点名, 现在我们使用的设备树已经弃用。device_type属性也是一个很少用的属性,只用在CPU和内存的节点上。 如上例中所示,device_type用在了CPU节点。
1.2.5. 追加/修改节点内容
追加/修改节点内容
1 2 3 | &cpu0 {cpu-supply = <&vdd_cpu>; }; |
这些源码并不包含在根节点“/{…}”内,它们不是一个新的节点,而是向原有节点追加内容。 以上方源码为例,“&cpu0”表示向“节点标签”为“cpu0”的节点追加数据, 这个节点可能定义在本文件也可能定义在本文件所包含的设备树文件中, 本例子中源码的“cpu0”定义在“rk3561.dtsi”文件中。
1.2.6. 特殊节点
aliases子节点
aliases子节点的作用就是为其他节点起一个别名,如下所示。
别名子节点
123456719 10 11 12 13 14 | aliases {csi2dphy0 = &csi2_dphy0;csi2dphy1 = &csi2_dphy1;csi2dphy2 = &csi2_dphy2;/*----------- 省略------------*/mmc0 = &sdhci;mmc1 = &sdmmc0;mmc2 = &sdmmc1;mmc3 = &sdmmc2;serial0 = &uart0;serial1 = &uart1;serial2 = &uart2;/*----------- 以下省略------------*/ } |
以“serial0 = &uart0;”为例。“serial0”是一个节点的名字, 设置别名后我们可以使用“serial0”来指代uart0节点,与节点标签类似。 在设备树中更多的是为节点添加标签,没有使用节点别名,别名的作用是“快速找到设备树节点”。 在驱动中如果要查找一个节点,通常情况下我们可以使用“节点路径”一步步找到节点。 也可以使用别名“一步到位”找到节点。
chosen子节点
chosen子节点位于根节点下,如下所示
chosen子节点
1 2 3 | chosen {bootargs = "earlycon=uart1250,mmio32,0xfe660000 console=ttyFIQ0 root=PARTUUID=614e0000-0000 rw rootwait";}; |
chosen子节点不代表实际硬件,它主要用于给内核传递参数。 此外这个节点还用作uboot向linux内核传递配置参数的“通道”, 我们在Uboot中设置的参数就是通过这个节点传递到内核的, 这部分内容是uboot和内核自动完成的,作为初学者我们不必深究。
在中断、时钟部分也有自己的节点标准属性,随着深入的学习我们会详细介绍这些节点标准属性。
1.3. 如何获取设备树节点信息
在设备树中“节点”对应实际硬件中的设备,我们在设备树中添加了一个“led”节点, 正常情况下我们可以从这个节点获取编写led驱动所用到的所有信息,例如led相关控制寄存器地址、 led时钟控制寄存器地址等等。
这一小节我们就开始学习如何从设备树的设备节点获取我们想要的数据。 内核提供了一组函数用于从设备节点获取资源(设备节点中定义的属性)的函数,这些函数以of_开头,称为OF操作函数。 常用的OF函数介绍如下:
1.3.1. 查找节点函数
1.3.1.1. 根据节点路径寻找节点函数
of_find_node_by_path函数 (内核源码/include/linux/of.h)
1 | struct device_node *of_find_node_by_path(const char *path) |
参数:
-
path: 指定节点在设备树中的路径。
返回值:
-
device_node: 结构体指针,如果查找失败则返回NULL,否则返回device_node类型的结构体指针,它保存着设备节点的信息。
device_node结构体如下所示。
device_node结构体
123456719 10 11 12 13 14 15 16 17 11 19 20 21 22 23 | struct device_node {const char *name;const char *type;phandle phandle;const char *full_name;struct fwnode_handle fwnode;struct property *properties;struct property *deadprops; /* removed properties */struct device_node *parent;struct device_node *child;struct device_node *sibling; #if defined(CONFIG_OF_KOBJ)struct kobject kobj; #endifunsigned long _flags;void *data; #if defined(CONFIG_SPARC)const char *path_component_name;unsigned int unique_id;struct of_irq_controller *irq_trans; #endif }; |
-
name: 节点中属性为name的值
-
type: 节点中属性为device_type的值
-
full_name: 节点的名字,在device_node结构体后面放一个字符串,full_name指向它
-
properties: 链表,连接该节点的所有属性
-
parent: 指向父节点
-
child: 指向子节点
-
sibling: 指向兄弟节点
得到device_node结构体之后我们就可以使用其他of 函数获取节点的详细信息。
1.3.1.2. 根据节点名字寻找节点函数
of_find_node_by_name函数 (内核源码/include/linux/of.h)
1 | struct device_node *of_find_node_by_name(struct device_node *from,const char *name); |
参数:
-
from: 指定从哪个节点开始查找,它本身并不在查找行列中,只查找它后面的节点,如果设置为NULL表示从根节点开始查找。
-
name: 要寻找的节点名。
返回值:
-
device_node: 结构体指针,如果查找失败则返回NULL,否则返回device_node类型的结构体指针,它保存着设备节点的信息。
1.3.1.3. 根据节点类型寻找节点函数
of_find_node_by_type函数 (内核源码/include/linux/of.h)
1 | struct device_node *of_find_node_by_type(struct device_node *from,const char *type) |
参数:
-
from: 指定从哪个节点开始查找,它本身并不在查找行列中,只查找它后面的节点,如果设置为NULL表示从根节点开始查找。
-
type: 要查找节点的类型,这个类型就是device_node-> type。
返回值:
-
device_node: device_node类型的结构体指针,保存获取得到的节点。同样,如果失败返回NULL。
1.3.1.4. 根据节点类型和compatible属性寻找节点函数
of_find_compatible_node函数 (内核源码/include/linux/of.h)
1 | struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible) |
相比of_find_node_by_name函数增加了一个compatible属性作为筛选条件。
参数:
-
from: 指定从哪个节点开始查找,它本身并不在查找行列中,只查找它后面的节点,如果设置为NULL表示从根节点开始查找。
-
type: 要查找节点的类型,这个类型就是device_node-> type。
-
compatible: 要查找节点的compatible属性。
返回值:
-
device_node: device_node类型的结构体指针,保存获取得到的节点。同样,如果失败返回NULL。
1.3.1.5. 根据匹配表寻找节点函数
of_find_matching_node_and_match函数 (内核源码/include/linux/of.h)
1 | static inline struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match) |
可以看到,该结构体包含了更多的匹配参数,也就是说相比前三个寻找节点函数,这个函数匹配的参数更多,对节点的筛选更细。参数match,查找得到的结果。
参数:
-
from: 指定从哪个节点开始查找,它本身并不在查找行列中,只查找它后面的节点,如果设置为NULL表示从根节点开始查找。
-
matches: 源匹配表,查找与该匹配表想匹配的设备节点。
-
of_device_id: 结构体如下。
返回值:
-
device_node: device_node类型的结构体指针,保存获取得到的节点。同样,如果失败返回NULL。
of_device_id结构体
123456719 10 | /** Struct used for matching a device*/struct of_device_id {char name[32];char type[32];char compatible[121];const void *data; }; |
-
name: 节点中属性为name的值
-
type: 节点中属性为device_type的值
-
compatible: 节点的名字,在device_node结构体后面放一个字符串,full_name指向它
-
data: 链表,连接该节点的所有属性
1.3.1.6. 寻找父节点函数
of_get_parent函数 (内核源码/include/linux/of.h)
1 | struct device_node *of_get_parent(const struct device_node *node) |
参数:
-
node: 指定谁(节点)要查找父节点。
返回值:
-
device_node: device_node类型的结构体指针,保存获取得到的节点。同样,如果失败返回NULL。
1.3.1.7. 寻找子节点函数
of_get_next_child函数 (内核源码/include/linux/of.h)
1 | struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev) |
参数:
-
node: 指定谁(节点)要查找它的子节点。
-
prev: 前一个子节点,寻找的是prev节点之后的节点。这是一个迭代寻找过程,例如寻找第二个子节点,这里就要填第一个子节点。参数为NULL 表示寻找第一个子节点。
返回值:
-
device_node: device_node类型的结构体指针,保存获取得到的节点。同样,如果失败返回NULL。
这里介绍了7个寻找节点函数,这7个函数有一个共同特点——返回值类型相同。只要找到了节点就会返回节点对应的device_node结构体,在驱动程序中我们就是通过这个device_node获取设备节点的属性信息、顺藤摸瓜查找它的父、子节点等等。第一函数of_find_node_by_path与后面六个不 同,它是通过节点路径寻找节点的,“节点路径”是从设备树源文件(.dts)中的到的。而中间四个函数是根据节点属性在某一个节点之后查找符合要求的设备节点,这个“某一个节点”是设备节点结构体(device_node),也就是说这个节点是已经找到的。最后两个函数与中间四个类似,只不过最后两个没有使用节点属性 而是根据父、子关系查找。
1.3.2. 提取属性值的of函数
上一小节我们讲解了7个查找节点的函数,它们有一个共同特点,找到一个设备节点就会返回这个设备节点对应的结构体指针(device_node*)。这个过程可以理解为把设备树中的设备节点“获取”到驱动中。“获取”成功后我们再通过一组of函数从设备节点结构体(device_node)中获取我们想要的设备节点属 性信息。
1.3.2.1. 查找节点属性函数
of_find_property函数 (内核源码/include/linux/of.h)
1 | struct property *of_find_property(const struct device_node *np,const char *name,int *lenp) |
参数:
-
np: 指定要获取那个设备节点的属性信息。
-
name: 属性名。
-
lenp: 获取得到的属性值的大小,这个指针作为输出参数,这个参数“带回”的值是实际获取得到的属性大小。
返回值:
-
property: 获取得到的属性。property结构体,我们把它称为节点属性结构体,如下所示。失败返回NULL。从这个结构体中我们就可以得到想要的属性值了。
property属性结构体
123456719 10 11 12 13 14 15 | struct property {char *name;int length;void *value;struct property *next; #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)unsigned long _flags; #endif #if defined(CONFIG_OF_PROMTREE)unsigned int unique_id; #endif #if defined(CONFIG_OF_KOBJ)struct bin_attribute attr; #endif }; |
-
name: 属性名
-
length: 属性长度
-
value: 属性值
-
next: 下一个属性
1.3.2.2. 读取整型属性函数
读取属性函数是一组函数,分别为读取1、16、32、64位数据。
of_property_read_uX_array函数组 (内核源码/include/linux/of.h)
123456719 10 11 | //1位整数读取函数 int of_property_read_u1_array(const struct device_node *np, const char *propname, u1 *out_values, size_t sz)//16位整数读取函数 int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)//32位整数读取函数 int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)//64位整数读取函数 int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz) |
参数:
-
np: 指定要读取那个设备节点结构体,也就是说读取那个设备节点的数据。
-
propname: 指定要获取设备节点的哪个属性。
-
out_values: 这是一个输出参数,是函数的“返回值”,保存读取得到的数据。
-
sz: 这是一个输入参数,它用于设置读取的长度。
返回值:
-
返回值,成功返回0,错误返回错误状态码(非零值),-EINVAL(属性不存在),-ENODATA(没有要读取的数据),-EOVERFLOW(属性值列表太小)。
1.3.2.3. 简化后的读取整型属性函数
这里的函数是对读取整型属性函数的简单封装,将读取长度设置为1。用法与读取属性函数完全一致,这里不再赘述。
of_property_read_uX函数组 (内核源码/include/linux/of.h)
123456719 10 11 | //1位整数读取函数 int of_property_read_u1 (const struct device_node *np, const char *propname,u1 *out_values)//16位整数读取函数 int of_property_read_u16 (const struct device_node *np, const char *propname,u16 *out_values)//32位整数读取函数 int of_property_read_u32 (const struct device_node *np, const char *propname,u32 *out_values)//64位整数读取函数 int of_property_read_u64 (const struct device_node *np, const char *propname,u64 *out_values) |
读取字符串属性函数
在设备节点中存在很多字符串属性,例如compatible、status、type等等,这些属性可以使用查找节点属性函数of_find_property来获取,但是这样比较繁琐。内核提供了一组用于读取字符串属性的函数,介绍如下:
of_property_read_string函数 (内核源码/include/linux/of.h)
1 | int of_property_read_string(const struct device_node *np,const char *propname,const char **out_string) |
参数:
-
np: 指定要获取那个设备节点的属性信息。
-
propname: 属性名。
-
out_string: 获取得到字符串指针,这是一个“输出”参数,带回一个字符串指针。也就是字符串属性值的首地址。这个地址是“属性值”在内存中的真实位置,也就是说我们可以通过对地址操作获取整个字符串属性(一个字符串属性可能包含多个字符串,这些字符串在内存中连续存储,使用’0’分隔)。
返回值:
-
返回值:成功返回0,失败返回错误状态码。
这个函数使用相对繁琐,推荐使用下面这个函数。
of_property_read_string_index函数 (内核源码/include/linux/of.h)
1 | int of_property_read_string_index(const struct device_node *np,const char *propname, int index,const char **out_string) |
相比前面的函数增加了参数index,它用于指定读取属性值中第几个字符串,index从零开始计数。 第一个函数只能得到属性值所在地址,也就是第一个字符串的地址,其他字符串需要我们手动修改移动地址,非常麻烦,推荐使用第二个函数。
读取布尔型属性函数
在设备节点中一些属性是BOOL型,当然内核会提供读取BOOL型属性的函数,介绍如下:
of_property_read_string_index函数 (内核源码/include/linux/of.h)
1 | static inline bool of_property_read_bool(const struct device_node *np, const char *propname); |
参数:
-
np: 指定要获取那个设备节点的属性信息。
-
propname: 属性名。
返回值:
这个函数不按套路出牌,它不是读取某个布尔型属性的值,仅仅是读取这个属性存在或者不存在。如果想要或取值,可以使用之前讲解的“全能”函数查找节点属性函数of_find_property。
1.3.3. 内存映射相关of函数
在设备树的设备节点中大多会包含一些内存相关的属性,比如常用的reg属性。通常情况下,得到寄存器地址之后我们还要通过ioremap函数将物理地址转化为虚拟地址。现在内核提供了of函数,自动完成物理地址到虚拟地址的转换。介绍如下:
of_iomap函数 (内核源码/drivers/of/address.c)
1 | void __iomem *of_iomap(struct device_node *np, int index) |
参数:
-
np: 指定要获取那个设备节点的属性信息。
-
index: 通常情况下reg属性包含多段,index 用于指定映射那一段,标号从0开始。
返回值:
-
成功,得到转换得到的地址。失败返回NULL。
内核也提供了常规获取地址的of函数,这些函数得到的值就是我们在设备树中设置的地址值。介绍如下:
of_address_to_resource函数 (内核源码/drivers/of/address.c)
1 | int of_address_to_resource(struct device_node *dev, int index, struct resource *r) |
参数:
-
np: 指定要获取那个设备节点的属性信息。
-
index: 通常情况下reg属性包含多段,index 用于指定映射那一段,标号从0开始。
-
r: 这是一个resource结构体,是“输出参数”用于返回得到的地址信息。
返回值:
-
成功返回0,失败返回错误状态码。
resource结构体如下所示:
resource属性结构体
1 2 3 4 5 6 7 1 | struct resource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;unsigned long desc;struct resource *parent, *sibling, *child; }; |
-
start: 起始地址
-
end: 结束地址
-
name: 属性名字
从这个结构体比较简单,很容从中得到获取得到的具体信息。这里不再赘述。
这里介绍了三类常用的of函数,这些基本满足我们的需求,其他of函数后续如果使用到我们在详细介绍。
1.4. 向设备树中添加设备节点实验
1.4.1. 实验说明
通常情况下我们几乎不会从零开始写一个设备树,因为一个功能完善的设备树通常比较庞大, 例如本教程引用的rockchip官方编写的设备树“rk3561.dtsi”就几千行, 另外官方已经写好了主干的部分,我们只需要引用官方写好的设备树,然后根据自己的实际情况修改即可。
本节实验使用野火Lubancat_RK系列板卡,使用lubancat2为例,板卡系统是ubuntu20.04,设备树文件是rk3561-lubancat2.dts。
实验中如出现 Permission denied
或类似字样,请注意用户权限,大部分操作硬件外设的功能, 几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。
1.4.2. 代码讲解
本章的示例代码目录为:linux_driver/device_tree
在实际应用中我们最常见的操作是向设备节点中增加一个节点、向现有设备节点追加数据、和编写设备树插件。
根据之前讲解,lubancat2默认使用的是”kernel/arch/arm64/boot/dts/rockchip/rk3561-lubancat2.dts”设备树, 我们就在这个设备树里尝试增加一个设备节点,如下所示。
添加子节点led_test(在rk3561-lubancat2.dts)
123456719 10 11 12 13 14 15 16 17 11 19 20 21 22 23 24 25 26 27 21 29 30 31 32 33 34 35 | /* * Copyright (C) 2022 - All Rights Reserved by * EmbedFire LubanCat *//dts-v1/;#include <dt-bindings/gpio/gpio.h> #include <dt-bindings/pwm/pwm.h> #include <dt-bindings/pinctrl/rockchip.h> #include <dt-bindings/input/rk-input.h> #include <dt-bindings/display/drm_mipi_dsi.h> #include <dt-bindings/sensor-dev.h> #include "rk3561.dtsi" / {model = "EmbedFire LubanCat2 HDMI";compatible = "embedfire,lubancat2", "rockchip,rk3561";/*.......................*//*添加led_test节点*/get_dts_info_test: get_dts_info_test{compatible = "get_dts_info_test";#address-cells = <1>;#size-cells = <1>;led@0xfdd60000{ //GPIO0基地址0xfdd60000compatible = "fire,led_test";reg = <0xfdd60000 0x00000100>;status = "okay";};};/*.......................*/ }; |
在我们在rk3561-lubancat2.dts设备树文件中新增了一个节点名为“led_test”的节点, 里面只添加了几个基本属性,我们这里只是学习添加一个设备节点。
在以上代码中,led_test节点的#address-cells = <1>,#size-cells = <1>, 意味着它的子节点的reg属性里的数据是“地址”、“长度”交替的。
第二部分是led 节点的子节点,它定义了三个属性分别为compatible、reg、status, 这三个属性在 “节点属性”章节已经介绍。需要注意的是rgb属性, 在父节点设置了#address-cells = <1>,#size-cells = <1>, 所以这里0xfdd60000表示的是地址(这里填写的是GPIO0控制寄存器的首地址), 0x00000100表示的是地址长度。“led@0xfdd60000”中的单元的地址0xfdd60000要和reg属性的第一个地址一致。
提示
其余系列板卡参考 驱动章节实验环境搭建 章节确定板卡使用的设备树文件,然后进行修改添加。
内核编译设备树:
编译内核时会自动编译设备树,但是编译内核很耗时,所以我们推荐使用如下命令只编译设备树:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat2_defconfig //这里以rk356x系列配置文件为例 make ARCH=arm64 -j4 CROSS_COMPILE=aarch64-linux-gnu- dtbs
编译成功后生成的设备树文件(.dtb)位于源码目录下的arch/arm64/boot/dts/rockchip/,文件名为“rk3561-lubancat2.dtb”
1.4.3. 程序结果
1.4.3.1. 加载设备树
同SCP或NFS将编译的设备树拷贝到开发板上,替换/boot/dtb/rk3561-lubancat2.dtb。
uboot在启动的时候负责该目录的设备文件加载到内存,供内核解析使用,重启开发板。
1.4.3.2. 实验结果
设备树中的设备树节点在文件系统中有与之对应的文件,位于“/proc/device-tree”目录。进入“/proc/device-tree”目录如下所示。
接着进入led 文件夹,可以发现led节点中定义的属性以及它的子节点,如下所示。
在节点属性中多了一个name,我们在led节点中并没有定义name属性,这是自从生成的,保存节点名。
这里的属性是一个文件,而子节点是一个文件夹,我们再次进入“led@0xfdd60000”文件夹。 里面有compatible、name、reg、status四个属性文件。 我们可以使用“cat”命令查看这些属性文件,如下所示。
至此,我们已经成功的在设备树中添加了一个名为“get_dts_info_test”的节点。
1.5. 在驱动中获取节点属性实验
本实验目的是演示如何使用上一小节讲解的of函数,进行本实验之前要先完成“在设备树中添加设备节点实验”, 因为本实验就是从我们添加的节点中获取设备节点属性,驱动是基于平台驱动。
1.5.1. 代码讲解
本章的示例代码目录为:linux_driver/device_tree/get_dts_info.c
程序源码如下所示,这里只列出了get_dts_info_probe函数中的内容,完整内容请参考本章配套源码。
获取节点属性实验
123456719 10 11 12 13 14 15 16 17 11 19 20 21 22 23 24 25 26 27 21 29 30 31 32 33 34 35 36 37 31 39 40 41 42 43 44 45 46 47 41 49 50 51 52 | /*get_dts_info_probe 函数*/ static int get_dts_info_probe(struct platform_device *pdev) {int error_status = -1;pr_info("%s\n",__func__);led_test_device_node = of_find_node_by_path("/get_dts_info_test");if(led_test_device_node == NULL){printk(KERN_ALERT "\n get led_device_node failed ! \n");return -1;}/*根据 led_test_device_node 设备节点结构体输出节点的基本信息*/printk(KERN_ALERT "name: %s",led_test_device_node->name); //输出节点名printk(KERN_ALERT "child name: %s",led_test_device_node->child->name); //输出子节点的节点名/*获取 led_device_node 的子节点*/led_device_node = of_get_next_child(led_test_device_node,NULL);if(led_device_node == NULL){printk(KERN_ALERT "\n get led_device_node failed ! \n");return -1;}printk(KERN_ALERT "name: %s",led_device_node->name); //输出节点名printk(KERN_ALERT "parent name: %s",led_device_node->parent->name); //输出父节点的节点名/*获取 led_device_node 节点 的"compatible" 属性 */led_property = of_find_property(led_device_node,"compatible",&size);if(led_property == NULL){printk(KERN_ALERT "\n get led_property failed ! \n");return -1;}printk(KERN_ALERT "size = : %d",size); //实际读取得到的长度printk(KERN_ALERT "name: %s",led_property->name); //输出属性名printk(KERN_ALERT "length: %d",led_property->length); //输出属性长度printk(KERN_ALERT "value : %s",(char*)led_property->value); //属性值/*获取 reg 地址属性*/error_status = of_property_read_u32_array(led_device_node,"reg",out_values, 2);if(error_status != 0){printk(KERN_ALERT "\n get out_values failed ! \n");return -1;}printk(KERN_ALERT"0x%01X ", out_values[0]);printk(KERN_ALERT"0x%01X ", out_values[1]);return 0; } |
-
第7-12行:使用“of_find_node_by_path”函数寻找“get_dts_info_test”设备节点。参数是“get_dts_info_test”的设备节点路径。
-
第14-15行:获取成功后得到的是一个device_node类型的结构体指针,然后我们就可以从这个结构体中获得我们想要的数据。获取完整的属性信息可能还需要使用其他of函数。
-
第19-24行:获取 led_device_node 的子节点,在第二部分我们得到了“led”节点的“设备节点结构体”这里就可以使用“of_get_next_child”函数获取它的子节点。当然我们也可以从“led”节点的“设备节点结构体”中直接读取得到它的第一个子节点。
-
第30-35行:使用“of_find_property”函数获取“led”节点的“compatible”属性。
-
第42-49行:使用“of_property_read_u32_array”函数获取reg属性。
进入到驱动模块文件夹中,编译驱动模块:
make
该文件夹会产生get_dts_info.ko驱动模块
1.5.2. 程序结果
编译成功后将驱动.ko拷贝到开发板,使用insmod安装驱动模块然后可以看到:
sudo insmod get_dts_info.ko
从上图中可以看到,驱动程序中得到了设备树中设置的属性值。
相关文章:
linux-驱动开发之设备树详解(RK平台为例)
前言 Linux3.x以后的版本才引入了设备树,设备树用于描述一个硬件平台的板级细节。 在早些的linux内核,这些“硬件平台的板级细节”保存在linux内核目录“/arch”, 以ARM为例“硬件平台的板级细节”保存在“/arch/arm/plat-xxx”和“/arch/ar…...
【现代深度学习技术】注意力机制05:多头注意力
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上,结合当代大数据和大算力的发展而发展出来的。深度学习最重…...
RDD的五大特征
1. 由多个分区(Partitions)组成 特性:RDD 是分区的集合,每个分区在集群的不同节点上存储。分区是数据并行处理的基本单位。作用:分区使 RDD 能够在集群中并行计算,提高处理效率。 2. 有一个计算每个分区的…...
键盘RGB矩阵与LED指示灯(理论部分)
键盘RGB矩阵与LED指示灯(理论部分) 一、LED指示灯基础 在键盘世界里,LED指示灯不仅仅是装饰,它们还能提供丰富的状态信息。QMK固件提供了读取HID规范中定义的5种LED状态的方法: Num Lock(数字锁定)Caps Lock(大写锁定)Scroll Lock(滚动锁定)Compose(组合键)Desp…...
HTTP方法和状态码(Status Code)
HTTP方法 HTTP方法(也称HTTP动词)主要用于定义对资源的操作类型。根据HTTP/1.1规范(RFC 7231)以及后续扩展,常用的HTTP方法有以下几种: GET:请求获取指定资源的表示形式。POST:向指…...
【sqlmap需要掌握的参数】
sqlmap需要掌握的参数 目标-u 指定URL 用于get请求-l 用于post请求- r 用于post请求指定数据库/表/字段 -D/-T/-C 脱库获得数据库获取用户获取表获取列获取字段获取字段类型获取值 其他 目标 -u 指定URL 用于get请求 -u URL, --urlURL 目标URL 只使用于get命令中 -l 用于pos…...
用 AltSnap 解锁 Windows 窗口管理的“魔法”
你有没有遇到过这样的场景:电脑屏幕上堆满了窗口,想快速调整它们的大小和位置,却只能拖来拖去,费时又费力?或者你是个多任务狂魔,喜欢一边写代码、一边看文档、一边刷视频,却发现 Windows 自带的…...
高并发内存池(三):TLS无锁访问以及Central Cache结构设计
目录 前言: 一,thread cache线程局部存储的实现 问题引入 概念说明 基本使用 thread cache TLS的实现 二,Central Cache整体的结构框架 大致结构 span结构 span结构的实现 三,Central Cache大致结构的实现 单例模式 thr…...
数据治理域——数据治理体系建设
摘要 本文主要介绍了数据治理系统的建设。数据治理对企业至关重要,其动因包括应对数据爆炸增长、提升内部管理效率、支撑复杂业务需求、加强风险防控与合规管理以及实现数字化转型战略。其核心目的是提升数据质量、统一数据标准、优化数据资产管理、支撑业务发展和…...
数据库实验报告 SQL SERVER 2008的基本操作 1
实验报告(第 1 次) 实验名称 SQL SERVER 2008的基本操作 实验时间 9月14日1-2节 一、实验内容 数据库的基本操作:包括创建、修改、附加、分离和删除数据库等。 二、源程序及主要算法说明 本次实验不涉及程序和算法。 三、测…...
基于STM32、HAL库的ICP-20100气压传感器 驱动程序设计
一、简介: ICP-20100 是 InvenSense(TDK 集团旗下公司)生产的一款高精度数字气压传感器,专为需要精确测量气压和海拔高度的应用场景设计。它具有低功耗、高精度、快速响应等特点,非常适合物联网、可穿戴设备和无人机等应用。 二、硬件接口: ICP-20100 引脚STM32L4XX 引脚…...
提示工程实战指南:Google白皮书关键内容一文讲清
You don’t need to be a data scientist or a machine learning engineer – everyone can writea prompt. 一、概述 Google于2025年2月发布的《Prompt Engineering》白皮书系统阐述了提示工程的核心技术、实践方法及挑战应对策略。该文档由Lee Boonstra主编,多位…...
国产大模型「五强争霸」:决战AGI,谁主沉浮?
引言 中国AI大模型市场正经历一场史无前例的洗牌!曾经“百模混战”的局面已落幕,字节、阿里、阶跃星辰、智谱和DeepSeek五大巨头强势崛起,形成“基模五强”新格局。这场竞争不仅是技术实力的较量,更是资源、人才与生态的全面博弈。…...
Linux进程10-有名管道概述、创建、读写操作、两个管道进程间通信、读写规律(只读、只写、读写区别)、设置阻塞/非阻塞
目录 1.有名管道 1.1概述 1.2与无名管道的差异 2.有名管道的创建 2.1 直接用shell命令创建有名管道 2.2使用mkfifo函数创建有名管道 3.有名管道读写操作 3.1单次读写 3.2多次读写 4.有名管道进程间通信 4.1回合制通信 4.2父子进程通信 5.有名管道读写规律ÿ…...
高吞吐与低延迟的博弈:Kafka与RabbitMQ数据管道实战指南
摘要 本文全面对比Apache Kafka与RabbitMQ在数据管道中的设计哲学、核心差异及协同方案。结合性能指标、应用场景和企业级实战案例,揭示Kafka在高吞吐流式处理中的优势与RabbitMQ在复杂路由和低延迟传输方面的独特特点;介绍了使用Java生态成熟第三方库&…...
C++23 views::slide (P2442R1) 深入解析
文章目录 引言C20 Ranges库回顾什么是Rangesstd::views的作用 views::slide 概述基本概念原型定义辅助概念工作原理代码示例输出结果 views::slide 的应用场景计算移动平均值查找连续的子序列 总结 引言 在C的发展历程中,每一个新版本都会带来一系列令人期待的新特…...
SpringDataRedis的入门案例,以及RedisTemplate序列化实现
目录 SpringDataRedis 简单介绍 入门案例 RedisTemplate序列化方案 方案一: 方案二: SpringDataRedis 简单介绍 提供了对不同Redis客户端的整合(Lettuce和Jedis) 提供了RedisTemplate统一API来操作Redis 支持Redis的发布订阅模型 支持Redis哨兵和Redis集群 支持基于…...
鸿蒙HarmonyOS list优化一: list 结合 lazyforeach用法
list列表是开发中不可获取的,非常常用的组件,使用过程中会需要不断的优化,接下来我会用几篇文章进行list在纯原生的纯血鸿蒙的不断优化。我想进大厂,希望某位大厂的看到后能给次机会。 首先了解一下lazyforeach: Laz…...
【Jenkins简单自动化部署案例:基于Docker和Harbor的自动化部署流程记录】
摘要 本文记录了作者使用Jenkins时搭建的一个简单自动化部署案例,涵盖Jenkins的Docker化安装、Harbor私有仓库配置、Ansible远程部署等核心步骤。通过一个SpringBoot项目 (RuoYi) 的完整流程演示,从代码提交到镜像构建、推送、滚动更新,逐步实…...
【愚公系列】《Manus极简入门》034-跨文化交流顾问:“文化桥梁使者”
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! …...
数字滤波器应用介绍
此示例说明如何设计、分析数字过滤器并将其应用于数据。它将帮助您回答以下问题: 如何补偿滤波器引入的延迟?如何避免使信号失真?如何从信号中删除不需要的内容?如何微分信号?以及积分信号文章目录 补偿筛选引入的延迟补偿恒定滤波器延迟 如FIR引起的消除方法,末尾添零补…...
木马查杀篇—Opcode提取
【前言】 介绍Opcode的提取方法,并探讨多种机器学习算法在Webshell检测中的应用,理解如何在实际项目中应用Opcode进行高效的Webshell检测。 Ⅰ 基本概念 Opcode:计算机指令的一部分,也叫字节码,一个php文件可以抽取出…...
栈和队列复习(C语言版)
目录 一.栈的概念 二.栈的实现 三.队列的概念 四.队列的实现 五.循环队列的实现 一.栈的概念 可以将栈抽象地理解成羽毛球桶,或者理解成坐直升电梯;最后一个进去的,出来时第一个出来,并且只有一个出入口。这边需要注意的是&am…...
SDK does not contain ‘libarclite‘ at the path
Xcode16以上版本更新SDK之后就报错了。是因为缺少libarclite_iphoneos.a文件。所以需要在网上找一下该文件根据路径添加进去,arc文件可能需要新建一下。 clang: error: SDK does not contain ‘libarclite’ at the path ‘/Applications/Xcode.app/Contents/Develo…...
Kotlin跨平台Compose Multiplatform实战指南
Kotlin Multiplatform(KMP)结合 Compose Multiplatform 正在成为跨平台开发的热门选择,它允许开发者用一套代码构建 Android、iOS、桌面(Windows/macOS/Linux)和 Web 应用。以下是一个实战指南,涵盖核心概念…...
Oracle数据库全局性HANG的处理过程
如果Oracle数据库全局性HANG,首先要做的就是收集数据库HANG时的状态,只有收集到了相应状态,抓住故障现场,才可以进一步分析故障产生的可能原因。 出现此故障,一般情况下可以如此处理: 如果数据库是单节点&a…...
MySQL 8.0 OCP(1Z0-908)英文题库(21-30)
目录 第21题题目分析正确答案 第22题题目分析正确答案 第23题题目分析正确答案 第24题题目分析正确答案 第25题题目分析正确答案 第26题题目分析正确答案 第27题题目分析正确答案 第28题题目分析正确答案 第29题题目分析正确答案 第30题题目解析正确答案 第21题 Choose three.…...
beyond compare 免密钥进入使用(删除注册表)
beyond compare 免密钥进入,免费使用(删除注册表) 温馨提醒:建议仅个人使用,公司使用小心律师函警告! 1.winr 输入regedit 打开注册表 2.删除计算机 \HKEY_CURRENT_USER\Software\Scooter Software\Beyo…...
前端项目2-01:个人简介页面
目录 一.代码显示 二.效果图 三.代码分析 1. 文档声明和 HTML 基本结构 2. CSS 样式部分 全局样式 body 样式 页面主要容器 box 样式 左侧区域 l 样式 右侧区域 r 样式 左侧区域中头像容器 to 样式 头像图片样式及悬停效果 左侧区域中个人信息容器 tit 样式 个人…...
.NET 8 API 实现websocket,并在前端angular实现调用
.NET 8 API 实现websocket,并在前端angular实现调用。 后端:.NET 8 WebSocket API 实现 在 .NET 8 中,可以通过 Microsoft.AspNetCore.WebSockets 提供的支持来实现 WebSocket 功能。以下是创建一个简单的 WebSocket 控制器的步骤。 安装必…...
P2P架构
P2P 是 Peer-to-Peer(点对点) 的缩写,是一种 去中心化 的网络架构,其中每个节点(称为 “对等节点”,Peer)既是 “客户端”,也是 “服务器”,可以直接与其他节点通信、共享…...
菊厂0510面试手撕题目解答
题目 输入一个整数数组,返回该数组中最小差出现的次数。 示例1:输入:[1,3,7,5,9,12],输出:4,最小差为2,共出现4次; 示例2:输入:[90,98,90,90,1,1]…...
【25软考网工】第六章(4)VPN虚拟专用网 L2TP、PPTP、PPP认证方式;IPSec、GRE
博客主页:christine-rr-CSDN博客 专栏主页:软考中级网络工程师笔记 大家好,我是christine-rr !目前《软考中级网络工程师》专栏已经更新二十多篇文章了,每篇笔记都包含详细的知识点,希望能帮助到你!…...
C语言:深入理解指针(3)
目录 一、数组名的理解 二、用指针访问数组 三、一维数组传参的本质 四、冒泡排序 五、二级指针 六、指针数组 七、指针数组模拟二维数组 八、结语 一、数组名的理解 数组名其实就是首元素的地址 int arr[3] {1,2,3}; printf("arr :%p\n" ,arr); printf(…...
R语言实战第5章(1)
第一部分:数学、统计和字符处理函数 数学和统计函数:R提供了丰富的数学和统计函数,用于执行各种计算和分析。这些函数可以帮助用户快速完成复杂的数学运算、统计分析等任务,例如计算均值、方差、相关系数、进行假设检验等。字符处…...
Lodash isEqual 方法源码实现分析
Lodash isEqual 方法源码实现分析 Lodash 的 isEqual 方法用于执行两个值的深度比较,以确定它们是否相等。这个方法能够处理各种 JavaScript 数据类型,包括基本类型、对象、数组、正则表达式、日期对象等,并且能够正确处理循环引用。 1. is…...
探索边缘计算:赋能物联网的未来
摘要 随着物联网(IoT)技术的飞速发展,越来越多的设备接入网络,产生了海量的数据。传统的云计算模式在处理这些数据时面临着延迟高、带宽不足等问题,而边缘计算的出现为解决这些问题提供了新的思路。本文将深入探讨边缘…...
Ubuntu中配置【Rust 镜像源】
本篇主要记录Ubuntu中配置Rust编程环境时,所需要做的镜像源相关的配置 无法下载 Rust 工具链 通过环境变量指定 Rust 的国内镜像源(如中科大或清华源)。 方法一:临时设置镜像 export RUSTUP_DIST_SERVERhttps://mirrors.ustc.e…...
netty 客户端发送消息服务端收到消息无法打印,springBoot配合 lombok使用@Slf4j
netty 客户端发送消息服务端收到消息无法打印,springBoot配合 lombok使用Slf4j 服务端代码 Slf4j public class EventLoopServer {public static void main(String[] args) throws InterruptedException {new ServerBootstrap().group(new NioEventLoopGroup()).c…...
学习笔记:黑马程序员JavaWeb开发教程(2025.4.3)
12.1 基础登录功能 EmpService中的login方法,是根据接收到的用户名和密码,查询时emp数据库中的员工信息,会返回一个员工对象。使用了三元运算符来写返回 Login是登录,是一个业务方法,mapper接口是持久层,是…...
Spark SQL 运行架构详解(专业解释+番茄炒蛋例子解读)
1. 整体架构概览 Spark SQL的运行过程可以想象成一个"SQL查询的加工流水线",从原始SQL语句开始,经过多个阶段的处理和优化,最终变成分布式计算任务执行。主要流程如下: SQL Query → 解析 → 逻辑计划 → 优化 → 物理…...
【时时三省】(C语言基础)字符数组的输入输出
山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 字符数组的输入输出可以有两种方法。 ( 1 )逐个字符输入输出。用格式符“% c”输入或输出一个字符. ( 2 )将整个字符串一次输入或输出。用“% s”格式符,意思是对字符串( strin…...
Hive HA配置高可用
Hive的高可用性(HA)通过消除关键组件的单点故障来实现,确保系统在部分故障时仍能正常运行。其基本原理涉及以下核心组件和策略: 1. Hive Metastore 的高可用 多实例部署:部署多个Metastore服务实例,每个实例连接到共享的后端数据库(如MySQL、PostgreSQ…...
Python爬虫第20节-使用 Selenium 爬取小米商城空调商品
目录 前言 一、 本文目标 二、环境准备 2.1 安装依赖 2.2 配置 ChromeDriver 三、小米商城页面结构分析 3.1 商品列表结构 3.2 分页结构 四、Selenium 自动化爬虫实现 4.1 脚本整体结构 4.2 代码实现 五、关键技术详解 5.1 Selenium 启动与配置 5.2 页面等待与异…...
重构金融数智化产业版图:中电金信“链主”之道
近日,《商学院》杂志独家专访了中电金信常务副总经理(主持经营工作)冯明刚,围绕“金融科技”“数字底座”“架构转型”“AI驱动”等议题,展开了一场关于未来架构、技术变革与系统创新的深入对话。 当下,数字…...
笔记本电脑升级实战手册【扩展篇1】:flash id查询硬盘颗粒
文章目录 前言:一、硬盘颗粒介绍1、MLC(Multi-Level Cell)2、TLC(Triple-Level Cell)3、QLC(Quad-Level Cell) 二、硬盘与主控1、主控介绍2、主流主控厂家 三 、硬盘颗粒查询使用flash id工具查…...
文档外发安全:企业数据防护的最后一道防线
在当今数字化时代,数据已成为企业最宝贵的资产之一。随着网络安全威胁日益增多,企业安装专业加密软件已从"可选"变为"必选"。本文将全面分析企业部署华途加密解决方案后获得的各项战略优势。 一、数据安全防护升级 核心数据全面保护…...
springboot集成langchain4j实现票务助手实战
前言 看此篇的前置知识为langchain4j整合springboot,以及springboot集成langchain4j记忆对话。 Function-Calls介绍 langchain4j 中的 Function Calls(函数调用)是一种让大语言模型(LLM)与外部工具(如 A…...
ZYNQ笔记(二十一): VDMA HDMI 彩条显示
版本:Vivado2020.2(Vitis) 任务:实现驱动 HDMI 显示彩条图像,同时支持输出给 HDMI 的图像分辨率可调。 目录 一、介绍 二、硬件设计 (1)DVI_Transmitter (2)Clockin…...
常用的maven插件及其使用指南
目录 1.maven官方插件列表2.两种方式调用maven插件3.常用的maven插件总结参考文献 1.maven官方插件列表 groupId为org.apache.maven.pluginshttp://maven.apache.org/plugins/index.html 2.两种方式调用maven插件 将插件目标与生命周期阶段绑定,例如maven默认将m…...