Linux驱动学习—设备树及设备树下的platform总线

1、什么是设备树?

设备树是一种描述硬件资源的数据结构。他通过bootloader将硬件资源传给内核,使得内核和硬件资源 描述相对独立。

2、设备树的由来

2.1 平台总线的由来

要想了解为什么会有设备树,设备树是怎么来的,我们就要先来回顾以下在没有设备树之前我们是怎么来写一个驱动程序的。以字符设备驱动代码框架为例,我们一起一起来回顾下。任何的设备驱动的编写,Linux已经为我们打好了框架,我们只需要做完形填空一样填写就可以了。

下面是注册字符设备驱动框架图:

具体过程可以参考下面这篇文章:

Linux驱动学习—字符设备驱动注册详解-CSDN博客

下面是注册杂项设备驱动框架图:

有关杂项设备驱动注册流程可以参考下面这篇:

Linux驱动学习—杂项设备驱动注册-CSDN博客

通过这些框架,我们可以很容易编写我们 的驱动代码,但是,当我们用这个框架非常熟练的时候,我们就会发现虽然这个方法很简单,但是非常不容易扩展,当有很多很多类似的设备的时候,如果我们都是按照这个框架来完成,那就要写很多遍这个流程,但是多个相似设备之间真正差异的地方只有初始化硬件部分,其他步骤的代码基本都是一样的。这样就会造成大量的重复代码。但是,我们在编写驱动代码的时候,要尽量做到代码的复用,也就是一套驱动尽量可以兼容很多设备,如果我们还按照这个来编写就不太符合我们的规则了。

为了实现这个目标,我们就要吧通用的代码和有差异的代码分离出来,来增强我们驱动代码的可移植性。所以,设备驱动分离的思想就应运而生了,在Linux中,我们是在写代码的时候进行分离。分离是把一些不相似的东西放到device.c,把相似的东西放到driver.c,如果有很多相似的设备或者平台,我们只要修改device.c就可以了,这样我们重复性的工作就大大的减少了。这就是平台总线的由来。

2.2 平台总线这个方法有什么弊端呢?(设备树由来)

当我们用这个方法用习惯以后就会发现,假如soc不变,我们每换一个平台,都要修改C文件,并且还要重新编译。而且会在arch/arm/plat-xxx和arch/arm/mach-xxx下面留下大量关于板级细节的代码。并不是说这个方法不好,只是从Linux的发展来看,这些代码相对于Linux内核来说就是“垃圾代码”,而且这些“垃圾代码”非常多。

为了改变这个现状,设备树也就被引进到Linux上了,用来剔除相对内核来说的“垃圾代码”,即用设备树文件来描述这些设备信息,也就是代替device.c文件,虽然拿到了内核外面,但是platform匹配基本不变,并且相比于之前的方法,使用设备树不仅可以去掉大量的“垃圾代码”,并且采用文本格式,方便阅读和修改,如果需要修改部分资源,我们也不用在重新编译内核了,只需要把设备树源文件编译成二进制文件,在通过bootloader传递给内核就可以了。内核对其进行解析和展开得到关于硬件的拓扑图。我们通过内核提供的接口剖获取设备树的节点和属性就可以了。即内核对于同一soc的不同主板,只需要换设备树文件dtb即可实现不同主板的无差异支持,而无需更换内核文件。

3、设备树的基本概念

3.1 为啥叫设备树呢?

因为他的语法结构像树一样,所以管它叫设备树

3.2常用名词解释

<1>DT:Device Tree           //设备树
<2>FDT:Flattened Device Tree//展开设备树//开放固件,设备树起源于OF,所以我们在设备树中可以看到很多of字母的函数
<3>device tree source(dts)  //设备树代码
<4>device tree source includeDTB(dtsi) //更通用的设备树代码,也就是相同芯片但不同平台都可以使用的代码
<5>device tree blob(dtb)    //DTS编译后得到的DTB文件
<6>device tree complier(dtc) //设备树编译器

dtsi:一个 SOC 可以作出很多不同的板子,这些不同的板子肯定是有共同的信息, 将这些共同的信息提取出来作为一个通用的文件,其他的.dts 文件直接引用这个通用文件即可,这个通用文件就是.dtsi 文件,类似于 C 语言中的头文件。

DTS,DTSI,DTB,DTC他们之间的关系如下:

4、设备树基本语法

4.1 设备树基本框架

<1>设备从根节点开始,每个设备都是节点。
<2>节点和节点之间可以相互嵌套,形成父子关系。
<3>设备的属性用key-value对(键值对)来描述,每个属性用分号结束

4.2 设备树语法

4.2.1节点

什么是节点呢?节点就好比一颗大树,从树的主干开始,然后有一节一节的树枝,这个就叫节点。在代码中的节点是什么样子的呢。我们把上面模板的根节点摘出来,如下所示,这个就是根节点。相当于大树的树干。

/{
};//分号

而树枝就相当于设备树的子节点,同样我们把子节点摘出来就是根节点里面的node1和node2,如下所示:

/{  //根节点node1//子节点node1{};node2//子节点node2{};
};//分号

一个树枝是不是也可以继续分成好几个树枝呢,也就是说子节点里面可以包含子子节点。所以child-node1和child-node2是node1和node2的子节点,如下所示:

/{  //根节点node1//子节点node1{child-node1//子子节点{};};node2//子节点node2{child-node2//子子节点{};};
};//分号
4.2.2 节点名称

节点的命名有一个固定的格式。

格式:<名称>[@<设备地址>]

(1)<名称>节点的名称也不是任意起的,一般要体现设备的类型而不是特点的型号,比如网口,应该命名为ethernet,而不是随意起一个,比如111。

(2)<设备地址>就是用来访问该设备的基地址。但并不是说在操作过程中来描述一个地址,其主要用来区分用。

(3)注意事项:A、同一级的节点只要地址不一样,名称是可以不唯一的。

B、设备地址是一个可选选项,可以不写。但为了任意区分和理解,一般是都写的。

4.2.3 节点别名

当我们找一个节点的时候,必须书写完整的节点路径,如果节点名很长,那么我们在引用的时候就十分不方便,所以,设备树允许我们用下面的形式为节点标注引用(起别名)。举例:

uart8:serial@02288000

其中uart8就是这个节点名称的别名,serial@02288000就是节点名称。

4.2.4 节点引用

一般往节点里面添加内容的时候,不会直接把直接添加的内容写到节点里面,而是通过节点的引用来添加。

举例:

&uart8{pinctrl-names = "default";pinctrl-0 = <&pinctrl_uart8>;status = "okay";
};

&uart8表示引用节点别名为uart8的节点。并往节点添加以下内容:

    pinctrl-names = "default";pinctrl-0 = <&pinctrl_uart8>;status = "okay";

注意事项:编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的先相同的属性会被重写,使用引用可以避免移植者四处找节点。如dts和dtsi里面都有根节点,但最终会合并成一个根节点。

4.2.5属性
(1)reg属性

reg属性用来描述一个设备的地址范围。格式:

reg=<add1 lenth1 [add2 length2]...>

举例:

serial@02288000{reg=<101F2000 0x1000>;//101F2000是起始地址,0x1000是长度
};
(2)#address-cells和#size-cells属性

#address-cells用来设置子节点中reg地址的数量

#size-cells用来设置子节点中reg地址长度的数量

举例:

cpu{#address-cells = <1>;//用来设置子节点中reg地址的数量#size-cells = <1>;//用来设置子节点中reg地址长度的数量serial@02288000{reg=<101F2000 0x1000>;//101F2000是起始地址,0x1000是长度};
};

其中#address-cells和#size-cells均为1,也就是说我们子节点里面的reg属性里这个寄存器组的起始地址只有一个,长度也只有一个。所以101F2000是起始地址,0x1000是长度。

(3)compatible属性

compatible是一个自负床列表,可以在代码中进行匹配。

举例:

compatible = “led";
(4)status属性

status属性的值类型是字符串,这里我们只要记住两个常用的即可,一个是okay,表示涉笔可以正常使用,一个disable,表示设备不能正常使用。

5、在设备树中添加自定义节点

5.1 命令查看设备树节点

<1> cd /proc/device-tree/下就可看到
<2> cd /sys/firmware/devicetree/base/下就可看到

这是设置uboot环境变量的:

5.2 安装dtc工具

(1)直接make dtbs出现这种情况,说明环境没有配置对,则需要安装dtc工具

(2)安装dtc工具

apt-get install device-tre-compiler

5.3 实验:添加一个节点

如下图,添加一个节点test,对这个节点取别名为test1,然后节点引用&test1,一般往节点里面添加内容的时候,不会直接把直接添加的内容写到节点里面,而是通过节点的引用来添加。所以最终的compatible和status属性是节点引用里面的内容。

添加完之后编译dts,在内核源码路径下输入以下命令即可编译:

make ARCH=arm CROSS_COMPILE=arm-linux-guneabihf-  dtbs

把编译的dtb烧录到开发板,cd /proc/device-tree/目录下可以看到test节点已经生成。cat /proc/devicetree/test/compatible发现是test1234,cat /proc/devicetree/test/status发现是okay。

6、设备树中常见的of操作函数

设备都是以节点的形式“挂”到设备树上的,因此姚秀昂获取这个设备的其他属性信息,必须先获取到这个设备的节点。linux内核实验device_node结构体来描述一个节点,此结构体的第一在文件include/linux/of.h中,如下:

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;struct  kobject kobj;unsigned long _flags;void    *data;
#if defined(CONFIG_SPARC)const char *path_component_name;unsigned int unique_id;struct of_irq_controller *irq_trans;
#endif
};

节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要,Linux内核中使用结构体property表示属性,此结构体同样定义在include/linux/of.h中,如下:

struct property {char    *name;//属性名字int length;//属性长度void    *value;//属性值struct property *next;//下一个属性unsigned long _flags;unsigned int unique_id;struct bin_attribute attr;
};

6.1获取设备树文件节点里面资源的步骤

<1>步骤一:查找我们要找的节点。

<2>步骤一:查找我们要找的属性值。

6.2 查找节点常用的of函数

<1>of_find_node_by_path函数

作用:函数通过路径来查找指定的节点。

函数原型:

static inline struct device_node *of_find_node_by_path(const char *path)
参数:
path:带有全路径的节点名,可以使用节点的别名,比如"/test"就是test这个节点的全路径。使用节点别名的路径是"/test1".
返回值:成功就返回找到的节点。失败则返回NULL。
<2>of_get_parent函数

作用:用于获取节点的父节点(如果有父节点的话)。

struct device_node *of_get_parent(const struct device_node *node);
node:要查找的父节点的节点
返回值:找到的父节点。
<3>of_get_next_child函数

作用:用于迭代的查找子节点。

static inline struct device_node *of_get_next_child(const struct device_node *node, 
struct device_node *prev)
参数如下:
node:父节点。
prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL表示从第一个子节点开始。
返回值:找到的下一个子节点。

6.3 查找节点属性常用的of函数

<1>of_get_property函数

作用:用于查找指定的属性。

static inline const void *of_get_property(const struct device_node *node,const char *name,int *lenp)
参数如下:   
np:设备节点。
name:属性名字。
lenp:属性值的字节数。
返回值:找到的属性。
<2> of_property_read_u8、of_property_read_u16、of_property_read_u32、of_property_read_u64

有些属性只有一个整型值,者四个函数就是用于读取这种只有一个整型值的属性,分别用于读取u8、u16、u32、u64类型属性值,函数原型如下:

static inline int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value);
static inline int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value);
static inline int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value);
static inline int of_property_read_u64(const struct device_node *np,const char *propname, u64 *out_value);
参数如下:
np:设备节点。
proname:要读取的属性名字。
out_value:读取的值。
返回值:0,读取成功,负值,读取失败。
<3>of_property_read_u8_array、of_property_read_u16_array、of_property_read_u32_array、of_property_read_u64_array

这四个函数分别是读取属性中u8、u16、u32、u64类型的数组数据,比如大多数的reg属性都是数组数据,可以使用这4个函数一次读取reg属性中的所有数据。这四个函数的原型如下:

static inline int of_property_read_u8_array(const struct device_node *np,const char *propname,u8 *out_values, size_t sz);
static inline int of_property_read_u16_array(const struct device_node *np,const char *propname, u16 *out_values, size_t sz);
static inline int of_property_read_u32_array(const struct device_node *np,const char *propname, u32 *out_values, size_t sz);
static inline int of_property_read_u64_array(const struct device_node *np,const char *propname, u64 *out_values, size_t sz);
参数:
np:设备节点。
proname:要读取的属性名字。
out_values:读取的数组值。
返回值:0,读取成功,负值,读取失败。
<4>of_property_read_string

作用:用于读取属性只呢个字符串值

int of_property_read_string(const struct device_node *np,const char *propname,const char **out_string);
参数:
np:设备节点。
proname:要读取的属性名字。
out_values:读取的字符串值。
返回值:0,读取成功,负值,读取失败。     
 

6.4 实验:把5.3添加的一个节点的值和属性读取出来

 
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
​
struct device_node *test_device_node;
struct property *test_node_property;
int size;
u32 out_values[2]={0};
const char *str=NULL;
​
static int hello_init(void)
{int ret = 0;printk("hello_init\n");//查找要查找的节点test_device_node = of_find_node_by_path("/test");if(test_device_node == NULL) {printk("test_device_node find error\n");return -1;}printk("test_device_node name is %s\n",test_device_node->name);//test//获取compatible属性内容test_node_property = of_find_property(test_device_node, "compatible", &size);if(test_node_property == NULL) {printk("test_node_property find error\n");return -1;}printk("test_node_property name is %s\n",test_node_property->name);//compatibleprintk("test_node_property->value is %s\n",test_node_property->value);//test1234//获取reg属性内容ret = of_property_read_u32_array(test_device_node, "reg", out_values, 2);if(ret < 0) {printk("of_property_read_u32_array is error\n");return -1;}printk("out_values[0]  is 0x%08x\n",out_values[0]);//0x020ac000printk("out_values[1]  is 0x%08x\n",out_values[1]);//0x00000004//获取status属性内容ret = of_property_read_string(test_device_node, "status", &str);if(ret < 0) {printk("of_property_read_string is error\n");return -1;}printk("status is %s\n",str);//okayreturn 0;
}
​
static void hello_exit(void) 
{printk("hello_exit\n");
}
​
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

加载驱动,打印如下:

7、设备树下的platform总线

7.1 传统方法下的platform总线

Linux驱动学习—平台总线模型-CSDN博客

之前这篇文章中是使用传统的方法对平台总线进行学习,什么是传统方法呢,就是硬件设备信息部分写在device.c,驱动部分写在driver.c中。而设备树下的platform总线 则是用设备树文件代替device.c。所以使用设备树的方法在配置好设备树文件后,只需编写driver.c。

7.2 of_iomap函数

作用:of_iomap函数用于直接内存映射,以前我们会通过ioremap函数来完成物理地址到虚拟地址的映射。

函数原型:

void __iomem *of_iomap(struct device_node *node, int index);
参数:
np:设备节点
index:reg属性中要完成内存映射的段,如果reg属性只有一段的话inbdex就设置0。
返回值:经过内存映射后的虚拟内存首地址,如果为NULL的话就表示内存映射失败。

7.3 实验代码

Linux驱动学习—平台总线模型-CSDN博客

直接在上面文章的4.3小节platform driver.c上修改,主要实现的功能就是映射GPIO5的数据寄存器的内存地址,实现对数据寄存器的操作,从而实现对蜂鸣器引脚控制。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h> 
#include <linux/of.h>
#include <linux/of_address.h>
​
struct device_node *test_device_node;
struct property *test_node_property;
int size;
u32 out_values[2]={0};
const char *str=NULL;
unsigned int *vir_gpio_dr;
​
static const of_device_id of_match_table_test[] = {//匹配表{.compatible = "test1234"},
};
​
static const platform_device_id beep_id_table ={.name = "beep_test",
};
​
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{int ret = 0;printk("beep_probe\n");/*//查找要查找的节点 pdev是匹配成功后传入的设备树节点,所以不需要用之前的方法进行查找了test_device_node = of_find_node_by_path("/test");if(test_device_node == NULL) {printk("test_device_node find error\n");return -1;}printk("test_device_node name is %s\n",test_device_node->name);//test//获取compatible属性内容test_node_property = of_find_property(test_device_node, "compatible", &size);if(test_node_property == NULL) {printk("test_node_property find error\n");return -1;}printk("test_node_property name is %s\n",test_node_property->name);//compatibleprintk("test_node_property->value is %s\n",test_node_property->value);//test1234*///获取reg属性内容ret = of_property_read_u32_array(pdev->dev.of_node, "reg", out_values, 2);if(ret < 0) {printk("of_property_read_u32_array is error\n");return -1;}printk("out_values[0]  is 0x%08x\n",out_values[0]);//0x020ac000printk("out_values[1]  is 0x%08x\n",out_values[1]);//0x00000004vir_gpio_dr = of_iomap(pdev->dev.of_node, 0);if(vir_gpio_dr == NULL) {printk("of_iomap  error\n");return -1;}return 0;
}
​
int beep_remove(struct platform_device *pdev)
{pritnk("beep_remove \n");return 0;
}
​
strcut platform_driver beep_device = {.probe = beep_probe,.remove = beep_remove,.driver = {.owner = THIS_MODULE,.name  = "123",.of_match_table = of_match_table_test,//匹配表 },.id_table = &beep_id_table,
};
​
static int beep_driver_init(void)
{int ret = -1;ret = platform_driver_register(&beep_device);if(ret < 0) {printk("platform_driver_register error \n");}printk("platform_driver_register ok\n");return 0;
}
​
static void  beep_driver_exit(void)
{platform_driver_unregister(&beep_device);printk("beep_driver_exit \n");
}
​
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");

编译加载驱动,这样寄存器地址就获取成功了,我们可以注册一个杂项设备对数据引脚进行操作高低电平,从而实现对蜂鸣器的操作。

*vir_gpio_dr |= (1<<1);
*vir_gpio_dr &= ~(1<<1);

7.3.1 匹配优先级

先是platform_driver.driver.of_match_table.compatible,然后是platform_driver.driver.id_table,最后是platform_driver.driver.name

注意:设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入probe函数,其函数参数pdev是匹配成功后传入的设备树节点

7.3.2 reg有多组参数时,of_iomap如何传参

当然,如果reg有多组参数的话,这一个是不一样的,举个例子:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/592482.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机毕业论文内容参考|基于Apriori算法的门诊药物推荐系统的设计与实现

文章目录 摘要:前言相关技术与方法介绍系统分析系统设计系统实现系统测试与优化总结与展望摘要: 本文详细介绍了一种基于Apriori算法的门诊药物推荐系统的设计与实现。该系统利用Apriori算法挖掘患者就诊记录中的药物关联规则,为医生提供药物推荐,从而优化治疗方案。文章首…

2023春季李宏毅机器学习笔记 01 :正确认识 ChatGPT

资料 课程主页&#xff1a;https://speech.ee.ntu.edu.tw/~hylee/ml/2023-spring.phpGithub&#xff1a;https://github.com/Fafa-DL/Lhy_Machine_LearningB站课程&#xff1a;https://space.bilibili.com/253734135/channel/collectiondetail?sid2014800 一、对Chatgpt的误解…

CISSP 第5章 保护资产的安全

1、资产识别和分类 1.1 敏感数据 1.1.1 定义 敏感数据是任何非公开或非机密的信息&#xff0c;包括机密的、专有的、受保护的或因其对组织的价值或按照现有的法律和法规而需要组织保护的任何其他类型的数据。 1.1.2 个人身份信息PII 个人身份信息&#xff08;PII&#xff09…

python旅游大数据分析可视化大屏 游客分析+商家分析+舆情分析 计算机毕业设计(附源码)Flask框架✅

毕业设计&#xff1a;2023-2024年计算机专业毕业设计选题汇总&#xff08;建议收藏&#xff09; 毕业设计&#xff1a;2023-2024年最新最全计算机专业毕设选题推荐汇总 &#x1f345;感兴趣的可以先收藏起来&#xff0c;点赞、关注不迷路&#xff0c;大家在毕设选题&#xff…

[GKCTF 2020]ez三剑客-eztypecho

[GKCTF 2020]ez三剑客-eztypecho 考点&#xff1a;Typecho反序列化漏洞 打开题目&#xff0c;发现是typecho的CMS 尝试跟着创建数据库发现不行&#xff0c;那么就搜搜此版本的相关信息发现存在反序列化漏洞 参考文章 跟着该文章分析来&#xff0c;首先找到install.php&#xf…

Ubuntu20.04安装suiteCRM

两篇有用的文章 在ubuntu16.04上安装suitecrm_suitecrm ubuntu-CSDN博客 SuiteCRM搭建安装&#xff08;apachemsyqlphp&#xff09;_suitecrm 宝塔安装-CSDN博客 对照着一步步操作就可以了

基于多反应堆的高并发服务器【C/C++/Reactor】(中)处理任务队列中的任务 添加 删除 修改

&#xff08;1&#xff09;EventLoop启动 EventLoop初始化和启动 // 启动反应堆模型 int eventLoopRun(struct EventLoop* evLoop) {assert(evLoop ! NULL);// 取出事件分发和检测模型struct Dispatcher* dispatcher evLoop->dispatcher;// 比较线程ID是否正常if(evLoop-&…

grep -A -B -C 输出匹配行及相邻行

grep -A -B -C 输出匹配行及相邻行 grep --help 摘抄&#x1f447; 文件控制&#xff1a; -B, --before-context数值 打印前面 <数值> 行上下文-A, --after-context数值 打印后面 <数值> 行上下文-C, --context数值 打印前后 <数值> 行上下文 文件控制&#…

python小工具之弱密码检测工具

一、引用的python模块 Crypto&#xff1a; Python中一个强大的加密模块&#xff0c;提供了许多常见的加密算法和工具。它建立在pyc.ypodome或pyc.ypto等底层加密库之上&#xff0c;为Python程序员提供了简单易用的API&#xff0c;使其可以轻松地实现各种加密功能。 commands…

STM32MP157D-DK1 Qt程序交叉编译与运行测试

上篇文章介绍了STM32MP157D-DK1开发板Qt镜像的构建&#xff0c;通过在Ubuntu中重新编译带有Qt功能的系统来实现。 本篇在上篇的基础上&#xff0c;继续搭建Qt的交叉编译环境&#xff0c;实现Qt程序在Ubuntu中编译&#xff0c;在STM32MP157板子中运行。 1 编译安装SDK 在上篇…

阿里云和腾讯云服务器系统盘40G或50G空间够用吗?

云服务器系统盘40G或50G空间够用吗&#xff1f;够用&#xff0c;操作系统一般占用几个GB的存储空间&#xff0c;尤其是Linux操作系统占用空间容量更小&#xff0c;阿里云和腾讯云服务器系统盘默认提供的40GB高效云盘或50G通用型SSD云硬盘&#xff0c;阿腾云atengyun.com分享是否…

写你的第一个Vue程序

Vue.js渐进式JavaScript框架&#xff0c;Vue是一款用于构建用户界面的JavaScript框架。它基于标准HTML、CSS和JavaScript构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助开发者高效地开发用户界面。 写你的第一个Vue程序 <!DOCTYPE html> <…

【机器学习前置知识】多项式分布

多项式分布是二项式分布的推广。 在二项分布这篇文章中我们曾以抛硬币举例&#xff1a;在一次抛硬币实验中结果只有两种情况&#xff0c;正面或反面向上&#xff1b;在 n n n 次抛硬币实验中&#xff0c;正面向上出现 k k k 次的有 C n k n ! k ! ( n − k ) ! C_{n}^k{n!…

计算机组成原理-总线的性能指标

文章目录 总览总线周期 总线时钟周期 总线工作频率 总线时钟频率总线宽度 总线带宽例题串行总线和并行总线的速度&#xff08;带宽&#xff09;比较总线复用 信号线数总结 总览 总线周期 总线时钟周期 总线工作频率 总线时钟频率 一个总线周期就是指利用总线传输一组数据需要的…

详解进制之间的转换

目录 一、十进制转换 1、十进制转换为二进制 2、十进制转换为八进制 3、十进制转换为十六进制 二、二进制转换 1、二进制转换为八进制 2、二进制转换成十进制 3、二进制转换为十六进制 三、八进制转换 1、八进制转换成二进制 2、八进制转换成十进制 3、八进制转换成…

宝塔面板安装mysql出现最低内存和最低CPU限制的解决方案

当我们服务器配置不高时&#xff0c;在宝塔面板中安装mysql可能会出现&#xff1a;“至少需要2个CPU核心才能安装”或者“至少需要XXX内存才能安装”。这是宝塔面板为了保证服务器的运行&#xff0c;宝塔面板对于低内存和低CPU的服务器&#xff0c;安装mysql时有最低内存和CPU核…

Linux mail自动推送邮件脚本+一键发送邮件脚本

文章目录 说明配置mail安装mail配置mail自动推送邮件脚本定时任务mail_push.shmessage.info效果预览一键发送邮件脚本mail_sent.sh效果预览说明 自动推送邮件脚本:每日定时推送提前定义好的文本数据,以作提醒 mail_push.shmessage.info一键发送邮件脚本:交互式输入邮件主题和…

代价函数详解

代价函数详解 大家好&#xff0c;我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;在计算机科学和机器学习领域中&#xff0c;代价函数&#xff08;Cost Function&#xff09;是一个至关…

Spring Boot中WebMvcConfig配置详解及示例

引言&#xff1a; 在Spring Boot项目中&#xff0c;我们经常需要对Web MVC进行配置&#xff0c;以满足项目的特定需求。例如&#xff0c;设置静态资源映射、自定义消息转换器或生成Swagger接口文档等。今天&#xff0c;我们将详细探讨如何在Spring Boot中通过WebMvcConfig类进行…

C#线程基础(线程启动和停止)

目录 一、关于线程 二、示例 三、生成效果 一、关于线程 在使用多线程前要先引用命名空间System.Threading&#xff0c;引用命名空间后就可以在需要的地方方便地创建并使用线程。 创建线程对象的构造方法中使用了ThreadStart()委托&#xff0c;当线程开始执行时&#xff0c…