当前位置: 首页 > news >正文

Linux字符设备驱动开发的三种方式(分析+对比+示例)

文章目录

  • 一. 字符设备的驱动方法
  • 二. 三种方法的对比
  • 三. 开发环境
  • 四. 代码示例
    • 1. 传统设备驱动模型
    • 2. 总线设备驱动模型
    • 3. 设备树驱动模型
  • 五. 相关链接

一. 字符设备的驱动方法

字符设备驱动 是指在I/O传输过程中以字节流进行读写操作的设备。典型的如LCD、蜂鸣器、SPI、触摸屏等驱动,都属于字符设备驱动的范畴,Linux系统下的大部分的驱动程序都是属于字符设备驱动。

Linux 驱动 = Linux软件框架 + 硬件操作。 Linux驱动在系统中实际起到承上启下的作用,上承应用程序,对下则实现了具体的硬件操作。字符设备有非常多种,但无论何种字符设备,Linux软件框架的核心是不变的,差别就在于如何指定硬件资源。本文主要讲述三种字符设备的驱动框架:传统设备驱动模型、总线设备驱动模型、设备树驱动模型。

1. 传统设备驱动模型: 传统的Linux设备驱动模型将设备的一切操作都放在了一个文件当中,文件既包含了硬件相关的设备信息(例如引脚信息),也包含了对设备的操作函数,这种模型简单直观。但是由于设备信息和驱动代码杂糅在一起,一旦硬件信息发生改变(例如硬件更改引脚,或者需要移植到另外一个平台上),便需要修改驱动源码,然后重新编译,非常不利于扩展。

2. 平台总线设备驱动模型: 为了解决传统设备驱动不易扩展的问题,总线设备驱动使用虚拟总线将设备信息和驱动程序进行分离,其中设备信息指定对应硬件资源(例如引脚信息),驱动程序完成相应的逻辑操作。平台总线会维护两条链表,分别管理设备和驱动,当一个设备被注册到总线上时,总线会根据其名字搜索对应的驱动,如果找到就将设备信息导入驱动程序并执行驱动。当一个驱动被注册到平台总线的时候,总线也会搜索设备。总之,平台总线负责将设备信息和驱动代码进行匹配,这样就可以做到驱动和设备信息的分离。平台总线驱动模型将设备与驱动解耦开来,便于扩展与移植,但是冗余代码太多,且还是需要重新编译。

3. 设备树驱动模型: 设备树(dts文件)是描述计算机的特定硬件设备信息的数据结构,以便于操作系统的内核可以管理和使用这些硬件,包括CPU或CPU,内存,总线和其他一些外设。dtb文件(dtb为dts编译后的二进制文件)会被保存到ROM中,最终通过bootbolader被加载到内核,这样内核就可以通过解析设备树来让驱动去控制实际的硬件。通过设备树对硬件信息的抽象,驱动代码只要负责处理逻辑,而关于设备的具体信息存放到设备树文件中。因此,如果只是硬件接口信息的变化而没有驱动逻辑的变化,开发者只需要修改设备树文件信息,不需要改写驱动代码。设备树驱动模型相比于平台总线设备驱动模型,将所有的设备信息集成在了设备树文件当中,大大减少了冗余代码,且只在硬件信息更改的情况下无需重新编译,只需提供不同的设备树文件。

二. 三种方法的对比

假设某公司需要使用同一款芯片开发两款产品,分别为设备一与设备二。两个设备均需要使用IO引脚控制LED灯,但是引脚位置不同,下面分别使用传统设备驱动模型、平台总线设备驱动模型、 设备树驱动模型来开发,其中区别如下:

设备传统设备驱动模型总线设备驱动模型设备树驱动模型
设备一  driver_gpio.c
1. 分配一个file_operations结构体。
2. 设置 file_operations。
  - open 根据平台设备初始化GPIO引脚。
  - write 根据应用程序控制引脚。
  - close 释放硬件资源。
3. 注册设备()。
4. module_init ( )入口函数。
5. module_exit ( )出口函数。
















device_gpio.c
1. 分配一个 platform_device结构体。
2. 设置 platform_device。
 - resource 指定GPIO引脚
3. 注册 platform_device。
4. module_init ( )入口函数。
5. module_exit ( )出口函数。


driver_gpio.c
1. 分配一个 platform_driver结构体。
2. 设置 platform_driver。
  - probe
   - 链接硬件资源 resource
   - 分配 file_operation
  - remove
3. 设置 file_operation
  - open 初始化引脚
  - write 根据应用程序控制引脚。
  - close 释放硬件资源          
4. 注册 platform_driver。
5. module_init ( )入口函数。
6. module_exit ( )出口函数。
dts 设备树文件
1. 根据需求在dts设备树文件中指定硬件资源(GPIO)。
2. 内核根据dts生成的dtb文件分配/设置/注册platform_device 。





driver_gpio.c
1. 分配一个 platform_driver结构体。
2. 设置 platform_driver。
  - probe
   - 链接硬件资源 resource
   - 分配 file_operation
  - remove
3. 设置 file_operation
  - open 初始化引脚
  - write 根据应用程序控制引脚。
  - close 释放硬件资源
4. 注册 platform_driver。
5. module_init ( )入口函数。
6. module_exit ( )出口函数。


设备二driver_gpio.c
1. 分配一个file_operations结构体。
2. 设置 file_operations。
  - open 根据平台设备初始化GPIO引脚。
  - write 根据应用程序控制引脚。
  - close 释放硬件资源。
3. 注册设备。
4. module_init ( )入口函数。
4. module_exit ( )出口函数。
device_gpio.c
1. 分配一个 platform_device结构体。
2. 设置 platform_device。
 - resource 指定GPIO引脚
3. module_init ( )入口函数。
4. module_exit ( )出口函数。

device_gpio.c
不变。
dts 设备树文件
1. 根据需求修改dts设备树文件中指定硬件资源(GPIO)。
2. 内核会根据dts生成的dtb文件分配/设置/注册platform_device 。



device_gpio.c
不变。
优缺点 优点:简单直观

缺点:不易扩展
优点:易扩展

缺点:代码冗余
优点:易扩展、无冗余代码

缺点:上手难度高

三. 开发环境

Soc芯片   :全志V3s
开发环境  :Ubuntu 20.04
开发板系统 :Licheepi Linux
驱动程序交叉编译器 :gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf

硬件电路 用的是用的是自己画的开发板,基于全志的V3s芯片,采用金手指的方式设计。芯片的PB4引脚连接了一颗黄色的LED灯,下面我们就以传统设备驱动模型、平台总线设备驱动模型、设备树驱动模型为例,写点简单的示例来驱动一个GPIO,从而点亮一颗灯。

软件系统 使用的荔枝派的主线uboot+主线Linux的方式,源码和编译器都可以再荔枝派的官网上找到(荔枝派官网)。不同的Linux开发板只在硬件的寄存器操作上有所不同,驱动框架是通用的,如果使用的也是全志的Soc,甚至源码可以直接复用。

四. 代码示例


以下示例均是驱动开发板的PB4引脚进行高低电平交替切换,交替频率为1s。

1. 传统设备驱动模型

driver_gpio.c

#include <linux/init.h>             /* module_init()等宏定义头文件   */
#include <linux/module.h>           /* MODULE_LICENSE等宏定义头文件  */          
#include <linux/gpio.h>             /* gpio 相关头文件              */
#include <linux/fs.h>               /* file_operations 的相关头文件 */
#include <linux/uaccess.h>          /* copy_from_user()的头文件    */
#include <linux/device.h>           /* class_destroy()等函数头文件  */#define SUNXI_PA_BASE	0
#define SUNXI_PB_BASE	32
#define SUNXI_PC_BASE	64
#define SUNXI_PD_BASE	96
#define SUNXI_PE_BASE	128
#define SUNXI_PF_BASE	160
#define SUNXI_PG_BASE	192
#define SUNXI_PH_BASE	224/* sunxi gpio name space */
#define GPIOA(n)	(SUNXI_PA_BASE + (n))
#define GPIOB(n)	(SUNXI_PB_BASE + (n))
#define GPIOC(n)	(SUNXI_PC_BASE + (n))
#define GPIOD(n)	(SUNXI_PD_BASE + (n))
#define GPIOE(n)	(SUNXI_PE_BASE + (n))
#define GPIOF(n)	(SUNXI_PF_BASE + (n))
#define GPIOG(n)	(SUNXI_PG_BASE + (n))
#define GPIOH(n)	(SUNXI_PH_BASE + (n))static int major = 0;
static struct class *gpio_class;static int gpio_open(struct inode *node,struct file *filp)
{/*申请GPIO*/if(gpio_request(GPIOB(4),"PB4")!=0){printk(KERN_ERR "GPIOB(4) init err!\n");//打印错误信息return -1;}/*设置为输出功能,输出0*/gpio_direction_output(GPIOB(4), 0);return 0;
}static int gpio_release(struct inode *node,struct file *filp)
{/*灭灯*/gpio_set_value(GPIOB(4), 0);/*释放GPIO硬件资源*/gpio_free(GPIOB(4));return 0;
}static ssize_t gpio_read(struct file *filp,char __user *buf,size_t size,loff_t *off)
{return 0;
}static ssize_t gpio_write(struct file *filp,const char __user *buf,size_t size,loff_t *off)
{unsigned char val_led;copy_from_user(&val_led,buf,1);if(val_led){/*GPIO输出高电平,开灯*/gpio_set_value(GPIOB(4), 1);}else{/*GPIO输出低电平,关灯*/gpio_set_value(GPIOB(4), 0);}return 1;
}static struct file_operations gpio_oprs = {.owner = THIS_MODULE,.open  = gpio_open,.read  = gpio_read,.write = gpio_write,.release = gpio_release,
};//入口:insmod
static int __init gpio_init(void)
{major = register_chrdev(0,"mygpio",&gpio_oprs);gpio_class = class_create(THIS_MODULE,"mygpio");/*  /dev/gpio  */device_create(gpio_class,NULL,MKDEV(major,0),NULL,"mygpio");printk("LED init ...\n");//打印提示信息return 0;
}//出口:rmmod
static void __exit gpio_exit(void)
{unregister_chrdev(major,"mygpio");device_destroy(gpio_class,MKDEV(major,0));class_destroy(gpio_class);printk(KERN_INFO "LED exit...\n");//打印提示信息
}/*三要素*/
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("LDL-1027221389.qq.com");//模块作者的声明
MODULE_DESCRIPTION("LED Driver");//当前模块的功能描述

驱动文件的Makefile文件如下:

ERNELDIR := /home/ldl13927222972/Linux_V3s/linux
CURRENT_PATH := $(shell pwd)obj-m := driver_gpio.oexport ARCH=arm
export CROSS_COMPILE=/opt/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabi-build: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译生成driver_gpio.ko文件,在开发板的终端使用insmod xx.ko加载驱动模块、使用rmmod xx卸载驱动模块、使用lsmod查看驱动模块。

app_gpio.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>int main(int argc,char *argv[])
{int fd;char *path = "/dev/mygpio";fd = open(path,O_RDWR);if(fd == -1){printf("can not open file %s\n",path);return -1;}while(1){   unsigned char gpio_status;gpio_status = 0;write(fd,&gpio_status,sizeof(gpio_status));sleep(1);gpio_status = 1;write(fd,&gpio_status,sizeof(gpio_status));sleep(1);}
}

应用程序的Makefile文件如下:

app_gpio:app_gpio.c/opt/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc -o app_gpio app_gpio.c
clean:rm -f app_gpio

在开发板终端加载好driver_gpio.ko模块后,直接执行编译好的app_gpio文件,就可以实现LED的闪烁效果。

2. 总线设备驱动模型

device_gpio.c

#include <linux/kernel.h>           /* ARRAY_SIZE()相关头文件 */
#include <linux/platform_device.h>  /* platform_device_register()相关头文件 */
#include <linux/module.h>           /* MODULE_LICENSE等宏定义头文件  */      /* 全志平台端口映射 */
#define SUNXI_PA_BASE	0
#define SUNXI_PB_BASE	32
#define SUNXI_PC_BASE	64
#define SUNXI_PD_BASE	96
#define SUNXI_PE_BASE	128
#define SUNXI_PF_BASE	160
#define SUNXI_PG_BASE	192
#define SUNXI_PH_BASE	224/* 全志平台端口映射 */
#define GPIOA(n)	(SUNXI_PA_BASE + (n))
#define GPIOB(n)	(SUNXI_PB_BASE + (n))
#define GPIOC(n)	(SUNXI_PC_BASE + (n))
#define GPIOD(n)	(SUNXI_PD_BASE + (n))
#define GPIOE(n)	(SUNXI_PE_BASE + (n))
#define GPIOF(n)	(SUNXI_PF_BASE + (n))
#define GPIOG(n)	(SUNXI_PG_BASE + (n))
#define GPIOH(n)	(SUNXI_PH_BASE + (n))/* 设置硬件资源 */
static struct resource gpio_resource[] = {[0] = {/* 引脚PB4的硬件描述*/.start = GPIOB(4),.end   = GPIOB(4),.flags = IORESOURCE_MEM,},
};static void gpio_release(struct device *dev)
{printk(KERN_INFO "Releasing GPIO device resources\n");
}static struct platform_device gpio_dev = {.name    = "mygpio",.id      = -1,.num_resources = ARRAY_SIZE(gpio_resource),.resource      = gpio_resource,.dev           = {.release = gpio_release,},
};/* 入口函数 */
static int __init gpio_dev_init(void)
{/* 注册平台设备 */platform_device_register(&gpio_dev);return 0;
}/* 出口函数 */
static void __exit gpio_dev_exit(void)
{/* 删除平台设备 */platform_device_unregister(&gpio_dev);
}module_init(gpio_dev_init);
module_exit(gpio_dev_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("LDL");
MODULE_DESCRIPTION("Platform device model");

driver_gpio.c

#include <linux/init.h>             /* module_init()等宏定义头文件   */
#include <linux/module.h>           /* MODULE_LICENSE等宏定义头文件  */          
#include <linux/gpio.h>             /* gpio 相关头文件              */
#include <linux/fs.h>               /* file_operations 的相关头文件 */
#include <linux/uaccess.h>          /* copy_from_user()的头文件    */
#include <linux/device.h>           /* class_destroy()等函数头文件  */
#include <linux/platform_device.h>  /* platform_device_register()等函数头文件 */static int major = 0;
static int gpio_1;static struct class *gpio_class;static int gpio_open(struct inode *node,struct file *filp)
{/*申请GPIO*/if(gpio_request(gpio_1,"PB4")!=0){printk(KERN_ERR "GPIOB(4) init err!\n");//打印错误信息return -1;}/*设置为输出功能,输出0*/gpio_direction_output(gpio_1, 0);return 0;
}static int gpio_release(struct inode *node,struct file *filp)
{/*灭灯*/gpio_set_value(gpio_1, 0);/*释放GPIO硬件资源*/gpio_free(gpio_1);return 0;
}static ssize_t gpio_read(struct file *filp,char __user *buf,size_t size,loff_t *off)
{return 0;
}static ssize_t gpio_write(struct file *filp,const char __user *buf,size_t size,loff_t *off)
{unsigned char val_led;copy_from_user(&val_led,buf,1);if(val_led){/*GPIO输出高电平,开灯*/gpio_set_value(gpio_1, 1);}else{/*GPIO输出低电平,关灯*/gpio_set_value(gpio_1, 0);}return 1;
}static struct file_operations gpio_oprs = {.owner = THIS_MODULE,.open  = gpio_open,.read  = gpio_read,.write = gpio_write,.release = gpio_release,
};static int gpio_probe(struct platform_device *pdev)
{struct resource *pin_1;/* 根据platform_device链接硬件资源 */pin_1 = platform_get_resource(pdev,IORESOURCE_MEM,0);gpio_1 = pin_1->start;major = register_chrdev(0,"mygpio",&gpio_oprs);gpio_class = class_create(THIS_MODULE,"mygpio");/*  /dev/mygpio  */device_create(gpio_class,NULL,MKDEV(major,0),NULL,"mygpio");return 0;
}int static gpio_remove(struct platform_device *pdev)
{unregister_chrdev(major,"mygpio");device_destroy(gpio_class,MKDEV(major,0));class_destroy(gpio_class);return 0;}struct platform_driver gpio_drv = {.probe   = gpio_probe,.remove  = gpio_remove,.driver  = {.name = "mygpio",}
};//入口:insmod
static int __init gpio_init(void)
{	/* 注册平台驱动 */platform_driver_register(&gpio_drv);printk("LED init ...\n");//打印提示信息return 0;
}//出口:rmmod
static void __exit gpio_exit(void)
{/* 删除平台驱动 */platform_driver_unregister(&gpio_drv);printk(KERN_INFO "LED exit...\n");//打印提示信息
}/*三要素*/
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("LDL-1027221389.qq.com");//模块作者的声明
MODULE_DESCRIPTION("LED Driver");//当前模块的功能描述

两个文件的Makefile文件如下:

KERNELDIR := /home/ldl13927222972/Linux_V3s/linux
CURRENT_PATH := $(shell pwd)obj-m += driver_gpio.o
obj-m += device_gpio.oexport ARCH=arm
export CROSS_COMPILE=/opt/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabi-build: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译生成driver_gpio.ko、device_gpio.ko文件,在开发板的终端使用insmod xx.ko分别加载两个模块。再执行应用程序,便可以使得LED灯闪烁。

app_gpio.c

与传统设备驱动模型中的应用程序相同,都是通过open与write函数操作dev/mygpio文件。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>int main(int argc,char *argv[])
{int fd;char *path = "/dev/mygpio";fd = open(path,O_RDWR);if(fd == -1){printf("can not open file %s\n",path);return -1;}while(1){   unsigned char gpio_status;gpio_status = 0;write(fd,&gpio_status,sizeof(gpio_status));sleep(1);gpio_status = 1;write(fd,&gpio_status,sizeof(gpio_status));sleep(1);}
}

应用程序的Makefile文件如下:

app_gpio:app_gpio.c/opt/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc -o app_gpio app_gpio.c
clean:rm -f app_gpio

3. 设备树驱动模型

dts设备树修改

设备树加入以下内容:

mygpio {compatible = "my-gpio";my-gpios = <&pio 1 4 GPIO_ACTIVE_LOW>; /* PB4 */};

需检查设备树的其他内容,避免出现PB4引脚被其他所占用的情况。

driver_gpio.c

#include <linux/init.h>             /* module_init()等宏定义头文件   */
#include <linux/module.h>           /* MODULE_LICENSE等宏定义头文件  */          
#include <linux/gpio.h>             /* gpio 相关头文件              */
#include <linux/fs.h>               /* file_operations 的相关头文件 */
#include <linux/uaccess.h>          /* copy_from_user()的头文件    */
#include <linux/device.h>           /* class_destroy()等函数头文件  */
#include <linux/platform_device.h>  /* platform_device_register()等函数头文件 */#include <linux/gpio/consumer.h>
#include <linux/gpio.h>         // 用于 GPIO 操作
#include <linux/of_gpio.h>      // 用于设备树 GPIO 操作
#include <linux/of.h>           // 用于设备树操作static int major = 0;
static int gpio_num;  // 使用 gpio_num 来存储 GPIO 引脚编号static struct class *gpio_class;static int gpio_probe(struct platform_device *pdev)
{gpio_num = of_get_named_gpio(pdev->dev.of_node, "my-gpios", 0);if (gpio_num < 0) {pr_err("Failed to get GPIO from device tree\n");return gpio_num;}/* 初始化 GPIO */if (gpio_request(gpio_num, "PB4") != 0) {pr_err("Failed to request GPIO %d\n", gpio_num);return -1;}gpio_direction_output(gpio_num, 0);  // 设置为输出,初始为 0// 注册字符设备major = register_chrdev(0, "mygpio", &gpio_oprs);if (major < 0) {printk(KERN_ERR "Failed to register char device\n");gpio_free(gpio_num);  // 释放 GPIO 资源return major;}// 创建 class 和 devicegpio_class = class_create(THIS_MODULE, "mygpio");if (IS_ERR(gpio_class)) {unregister_chrdev(major, "mygpio");gpio_free(gpio_num);return PTR_ERR(gpio_class);}device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "mygpio");return 0;
}static int gpio_remove(struct platform_device *pdev)
{unregister_chrdev(major, "mygpio");device_destroy(gpio_class, MKDEV(major, 0));class_destroy(gpio_class);return 0;
}struct platform_driver gpio_drv = {.probe   = gpio_probe,.remove  = gpio_remove,.driver  = {.name = "mygpio",}
};static int gpio_open(struct inode *node, struct file *filp)
{/* 设置为输出功能,输出0 */gpio_direction_output(gpio_num, 0);return 0;
}static int gpio_release(struct inode *node, struct file *filp)
{/* 灭灯 */gpio_set_value(gpio_num, 0);/* 释放 GPIO 硬件资源 */gpio_free(gpio_num);return 0;
}static ssize_t gpio_read(struct file *filp, char __user *buf, size_t size, loff_t *off)
{return 0;
}static ssize_t gpio_write(struct file *filp, const char __user *buf, size_t size, loff_t *off)
{unsigned char val_led;copy_from_user(&val_led, buf, 1);if (val_led) {/* GPIO 输出高电平,开灯 */gpio_set_value(gpio_num, 1);} else {/* GPIO 输出低电平,关灯 */gpio_set_value(gpio_num, 0);}return 1;
}static struct file_operations gpio_oprs = {.owner = THIS_MODULE,.open  = gpio_open,.read  = gpio_read,.write = gpio_write,.release = gpio_release,
};// 入口:insmod
static int __init gpio_init(void)
{/* 注册平台驱动 */platform_driver_register(&gpio_drv);printk("LED init ...\n");  // 打印提示信息return 0;
}// 出口:rmmod
static void __exit gpio_exit(void)
{/* 删除平台驱动 */platform_driver_unregister(&gpio_drv);printk(KERN_INFO "LED exit...\n");  // 打印提示信息
}/* 三要素 */
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("LDL-1027221389.qq.com");  // 模块作者的声明
MODULE_DESCRIPTION("LED Driver");  // 当前模块的功能描述

app_gpio.c

与传统设备驱动模型以及总线设备驱动模型程序相同,都是通过open与write函数操作dev/mygpio文件。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>int main(int argc,char *argv[])
{int fd;char *path = "/dev/mygpio";fd = open(path,O_RDWR);if(fd == -1){printf("can not open file %s\n",path);return -1;}while(1){   unsigned char gpio_status;gpio_status = 0;write(fd,&gpio_status,sizeof(gpio_status));sleep(1);gpio_status = 1;write(fd,&gpio_status,sizeof(gpio_status));sleep(1);}
}

应用程序的Makefile文件如下:

app_gpio:app_gpio.c/opt/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc -o app_gpio app_gpio.c
clean:rm -f app_gpio

五. 相关链接

  1. 全志 linux-sunxi 官网 :https://linux-sunxi.org/。
  2. 荔枝派Zero官网:https://wiki.sipeed.com/soft/Lichee/zh/Zero-Doc/Start/board_intro.html
  3. 本文所写的所有代码示例:https://download.csdn.net/download/weixin_44793491/90178263

相关文章:

Linux字符设备驱动开发的三种方式(分析+对比+示例)

文章目录 一. 字符设备的驱动方法二. 三种方法的对比三. 开发环境四. 代码示例1. 传统设备驱动模型2. 总线设备驱动模型3. 设备树驱动模型 五. 相关链接 一. 字符设备的驱动方法 字符设备驱动 是指在I/O传输过程中以字节流进行读写操作的设备。典型的如LCD、蜂鸣器、SPI、触摸屏…...

C++设计模式之行为型模式概述,它们的目的与特点

行为型设计模式需要解决的问题 行为型设计模式主要关注对象之间的责任分配和交互。它们解决的问题包括&#xff1a; 对象之间的通信&#xff1a;如何让对象之间高效地通信&#xff0c;同时保持松耦合。算法的封装与复用&#xff1a;如何将算法或行为封装起来&#xff0c;使其…...

把Huggingface下载的arrow数据集转化为json格式

Arrow2json 使用默认的Huggingface路径 以allenai/tulu-3-sft-mixture数据集为例。 使用load_dataset即可&#xff1a; from datasets import load_dataset# 加载数据集 dataset load_dataset("allenai/tulu-3-sft-mixture")# 指定保存路径 output_dir "~/…...

复习打卡大数据篇——Hadoop YARN

目录 &#xff11;.什么是yarn &#xff12;.yarn的三大角色 &#xff13;.任务&#xff08;MR&#xff09;提交到YARN运行流程 4. 调度器Scheduler 5.YARN HA 高可用 &#xff11;.什么是yarn YARN&#xff08;Yet Another Resource Negotiator&#xff09;是一个资源管…...

fpga系列 HDL:ModelSim显示模拟波形以及十进制格式数值

FPGA中使用数字滤波器时&#xff0c;可通过观察模拟波形更好地查看滤波效果。可以通过ModelSim中的波形格式设置来实现更直观的波形显示。 右键波形->Format-> Analog 效果 不同的数值格式显示&#xff1a;右键波形->Radix-> Decimal 效果 示例代码 ver…...

Linux 基本指令

目录 1.常见指令 1.1 ls指令 1.2 pwd指令 1.3 cd指令 1.4 touch指令 1.5 mkdir指令 1.6 rm和rmdir指令 1.7 man指令 1.8 cp指令 1.9 mv指令 ​编辑 1.10 cat指令 1.11 more指令 1.12 less指令 1.13 head指令 1.14.tail指令 1.15 时间相关的指令 1.16 cal…...

GO语言基础面试题

一、字符串和整型怎么相互转换 1、使用 strconv 包中的函数 FormatInt 、ParseInt 等进行转换 2、转换10进制的整形时&#xff0c;可以使用 strconv.Atoi、strconv.Itoa&#xff1a; Atoi是ParseInt(s, 10, 0) 的简写 Itoa是FormatInt(i, 10) 的简写 3、整形转为字符型时&#…...

要查询 `user` 表中 `we_chat_subscribe` 和 `we_chat_union_id` 列不为空的用户数量

文章目录 1、we_chat_subscribe2、we_chat_union_id 1、we_chat_subscribe 要查询 user 表中 we_chat_subscribe 列不为空的用户数量&#xff0c;你可以使用以下 SQL 查询语句&#xff1a; SELECT COUNT(*) FROM user WHERE we_chat_subscribe IS NOT NULL;解释&#xff1a; …...

小程序基础 —— 10 如何调试小程序代码

如何调试小程序代码 在进行项目开发的时候&#xff0c;不可避免需要进行调试&#xff0c;那么如何调试小程序呢&#xff1f; 打开微信开发者工具后&#xff0c;有一个模拟器&#xff0c;通过模拟器能够实时预览自己写的页面&#xff0c;如下&#xff1a; 在上部工具栏中有一个…...

Vue项目如何设置多个静态文件;如何自定义静态文件目录

Vite实现方案 安装插件 npm i vite-plugin-static-copy在vite.config.ts引入 import { viteStaticCopy } from vite-plugin-static-copy配置 plugins: [viteStaticCopy({targets: [{src: "要设置的静态文件目录的相对路径 相对于vite.config.ts的", dest: ./, // …...

CentOS Stream 9 安装 JDK

安装前检查 java --version注&#xff1a;此时说明已安装过JDK&#xff0c;否则为未安装。如若已安装过JDK可以跳过安装步骤直接使用&#xff0c;或者先卸载已安装的JDK版本重新安装。 安装JDK 官网下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads…...

前端(htmlcss)

前端页面 Web页面 PC端程序页面 移动端APP页面 ... HTML页面 HTML超文本标记页面 超文本&#xff1a;文本&#xff0c;声音&#xff0c;图片&#xff0c;视频&#xff0c;表格&#xff0c;链接 标记&#xff1a;由许多标签组成 HTML页面运行到浏览器上面 vscode便捷插件使用 vs…...

py打包工具

pyinstaller 安装 大佬文档参考 pip install pyinstallerpyinstaller 参数 -i 给应用程序添加图标 -F 只生成一个exe格式的文件 -D 创建一个目录&#xff0c;包含exe文件&#xff0c;但会依赖很多文件&#xff08;默认选项&#xff09; -c 有黑窗口 -w 去掉黑窗口pyinstalle…...

华为OD E卷(100分)39-最长子字符串的长度(二)

前言 工作了十几年&#xff0c;从普通的研发工程师一路成长为研发经理、研发总监。临近40岁&#xff0c;本想辞职后换一个相对稳定的工作环境一直干到老, 没想到离职后三个多月了还没找到工作&#xff0c;愁肠百结。为了让自己有点事情做&#xff0c;也算提高一下自己的编程能力…...

Selenium+Java(21):Jenkins发送邮件报错Not sent to the following valid addresses解决方案

问题现象 小月妹妹近期在做RobotFrameWork自动化测试,并且使用Jenkins发送测试邮件的时候,发现报错Not sent to the following valid addresses,明明各个配置项看起来都没有问题,但是一到邮件发送环节,就是发送不出去,而且还不提示太多有用的信息,急的妹妹脸都红了,于…...

JSON结构快捷转XML结构API集成指南

JSON结构快捷转XML结构API集成指南 引言 在当今的软件开发世界中&#xff0c;数据交换格式的选择对于系统的互操作性和效率至关重要。JSON&#xff08;JavaScript Object Notation&#xff09;和XML&#xff08;eXtensible Markup Language&#xff09;是两种广泛使用的数据表…...

【视觉惯性SLAM:四、相机成像模型】

相机成像模型介绍 相机成像模型是计算机视觉和图像处理中的核心内容&#xff0c;它描述了真实三维世界如何通过相机映射到二维图像平面。相机成像模型通常包括针孔相机的基本成像原理、数学模型&#xff0c;以及在实际应用中如何处理相机的各种畸变现象。 一、针孔相机成像原…...

网络编程:TCP和UDP通信基础

TCP 简易服务器&#xff1a; #include<myhead.h>int main(int argc, const char *argv[]) {int oldfd socket(AF_INET,SOCK_STREAM,0);if(oldfd -1){perror("socket");return -1;}//绑定要绑定的结构体struct sockaddr_in server {.sin_family AF_INET,.…...

声波配网原理及使用python简单的示例

将自定义的信息内容&#xff08;如Wi-Fi配置、数字数据&#xff09;转换为音波是一种音频调制与解调技术&#xff0c;广泛应用于声波配网、数据传输和近场通信中。这项技术的实现涉及将数字信息编码为音频信号&#xff0c;并通过解码还原信息。 实现方法 1. 数字数据编码 将原…...

深度学习任务中的 `ulimit` 设置优化指南

深度学习任务中的 ulimit 设置优化指南 1. 什么是 ulimit&#xff1f;2. 深度学习任务中的关键 ulimit 设置2.1 max locked memory&#xff08;-l&#xff09;2.2 open files&#xff08;-n&#xff09;2.3 core file size&#xff08;-c&#xff09;2.4 stack size&#xff08…...

maven编译时跳过test过程

如果代码里有无法在打包环境中测试的部分&#xff0c;则直接运行mvn clean package&#xff0c;因为测试失败&#xff0c;会导致打包失败。目前有两种方式可以跳过测试&#xff1a; 1. mvn clean package -DskipTests&#xff0c;这会跳过执行阶须&#xff0c;但仍会生成测试所…...

C++数据结构 : 哈希表的实现

C数据结构 &#xff1a; 哈希表的实现 目录 C数据结构 &#xff1a; 哈希表的实现引言1. 哈希概念1.1 直接定址法1.2 哈希冲突1.3 负载因子 2. 哈希函数2.1 除法散列法/除留余数法2.2 乘法散列法&#xff08;了解&#xff09;2.3 全域散列法&#xff08;了解&#xff09; 3. 处…...

记录算法笔记(2025.5.28)只出现一次的数字

给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 示例 1 &#xff1a; 输入&#xff1…...

【C语言】讲解 程序分配的区域(新手)

目录 代码区 数据区 堆区 栈区 常量区 重点比较一下堆区与 栈区 总结&#xff1a; 前言&#xff1a; C语言程序的内存分配区域是理解其运行机制的重要部分。根据提供的多条证据&#xff0c;我们可以总结出C语言程序在运行时主要涉及以下五个关键内存区域&#xff1a; 代…...

QNAP MEMOS 域名访问 SSL(Lucky)

注意&#xff1a;下述是通过ssh、docker-compose方式安装docker的&#xff0c;不是直接在container station中安装的哈&#xff01;&#xff01;&#xff01; 一、编辑docker-compose.yml文件 用“#”号标识的&#xff0c;在保存文件的时候建议去掉&#xff0c;不然有时候会出…...

使用摄像头推流+VLC软件拉流

一、作用 使用摄像头创建rtsp链接&#xff0c;并使用VLC软件拉流显示。 二、步骤 1、安装FFmpeg库 下载地址&#xff1a;https://ffmpeg.org/download.htmlFFmpeg库的下载参考之前的博客&#xff0c;下载Win64版本即可&#xff1a;https://blog.csdn.net/beijixingcd/artic…...

android协程异步编程常用方法

在 Android 开发中&#xff0c;Kotlin 协程是处理异步操作的首选方案&#xff0c;它能让异步代码更简洁、更易读。以下是 Android 协程异步编程的常用方法和模式&#xff1a; 一、基础构建块 1. launch 作用&#xff1a;启动一个新协程&#xff0c;不返回结果。适用场景&…...

基于Web的濒危野生动物保护信息管理系统设计(源码+定制+开发)濒危野生动物监测与保护平台开发 面向公众参与的野生动物保护与预警信息系统

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…...

索引的选择与Change Buffer

1. 索引选择与Change Buffer 问题引出&#xff1a;普通索引 vs 唯一索引 ——如何选择&#xff1f; 在实际业务中&#xff0c;如果一个字段的值天然具有唯一性&#xff08;如身份证号&#xff09;&#xff0c;并且业务代码已确保无重复写入&#xff0c;那就存在两种选择&…...

rtpinsertsound:语音注入攻击!全参数详细教程!Kali Linux教程!

简介 2006年8月至9月期间&#xff0c;我们创建了一个用于将音频插入指定音频&#xff08;即RTP&#xff09;流的工具。该工具名为rtpinsertsound。 该工具已在Linux Red Hat Fedora Core 4平台&#xff08;奔腾IV&#xff0c;2.5 GHz&#xff09;上进行了测试&#xff0c;但预…...