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
五. 相关链接
- 全志 linux-sunxi 官网 :https://linux-sunxi.org/。
- 荔枝派Zero官网:https://wiki.sipeed.com/soft/Lichee/zh/Zero-Doc/Start/board_intro.html
- 本文所写的所有代码示例:https://download.csdn.net/download/weixin_44793491/90178263
相关文章:

Linux字符设备驱动开发的三种方式(分析+对比+示例)
文章目录 一. 字符设备的驱动方法二. 三种方法的对比三. 开发环境四. 代码示例1. 传统设备驱动模型2. 总线设备驱动模型3. 设备树驱动模型 五. 相关链接 一. 字符设备的驱动方法 字符设备驱动 是指在I/O传输过程中以字节流进行读写操作的设备。典型的如LCD、蜂鸣器、SPI、触摸屏…...
C++设计模式之行为型模式概述,它们的目的与特点
行为型设计模式需要解决的问题 行为型设计模式主要关注对象之间的责任分配和交互。它们解决的问题包括: 对象之间的通信:如何让对象之间高效地通信,同时保持松耦合。算法的封装与复用:如何将算法或行为封装起来,使其…...

把Huggingface下载的arrow数据集转化为json格式
Arrow2json 使用默认的Huggingface路径 以allenai/tulu-3-sft-mixture数据集为例。 使用load_dataset即可: from datasets import load_dataset# 加载数据集 dataset load_dataset("allenai/tulu-3-sft-mixture")# 指定保存路径 output_dir "~/…...
复习打卡大数据篇——Hadoop YARN
目录 1.什么是yarn 2.yarn的三大角色 3.任务(MR)提交到YARN运行流程 4. 调度器Scheduler 5.YARN HA 高可用 1.什么是yarn YARN(Yet Another Resource Negotiator)是一个资源管…...

fpga系列 HDL:ModelSim显示模拟波形以及十进制格式数值
FPGA中使用数字滤波器时,可通过观察模拟波形更好地查看滤波效果。可以通过ModelSim中的波形格式设置来实现更直观的波形显示。 右键波形->Format-> Analog 效果 不同的数值格式显示:右键波形->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进制的整形时,可以使用 strconv.Atoi、strconv.Itoa: 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 列不为空的用户数量,你可以使用以下 SQL 查询语句: SELECT COUNT(*) FROM user WHERE we_chat_subscribe IS NOT NULL;解释: …...

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

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注:此时说明已安装过JDK,否则为未安装。如若已安装过JDK可以跳过安装步骤直接使用,或者先卸载已安装的JDK版本重新安装。 安装JDK 官网下载地址:https://www.oracle.com/java/technologies/downloads…...

前端(htmlcss)
前端页面 Web页面 PC端程序页面 移动端APP页面 ... HTML页面 HTML超文本标记页面 超文本:文本,声音,图片,视频,表格,链接 标记:由许多标签组成 HTML页面运行到浏览器上面 vscode便捷插件使用 vs…...
py打包工具
pyinstaller 安装 大佬文档参考 pip install pyinstallerpyinstaller 参数 -i 给应用程序添加图标 -F 只生成一个exe格式的文件 -D 创建一个目录,包含exe文件,但会依赖很多文件(默认选项) -c 有黑窗口 -w 去掉黑窗口pyinstalle…...
华为OD E卷(100分)39-最长子字符串的长度(二)
前言 工作了十几年,从普通的研发工程师一路成长为研发经理、研发总监。临近40岁,本想辞职后换一个相对稳定的工作环境一直干到老, 没想到离职后三个多月了还没找到工作,愁肠百结。为了让自己有点事情做,也算提高一下自己的编程能力…...

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集成指南 引言 在当今的软件开发世界中,数据交换格式的选择对于系统的互操作性和效率至关重要。JSON(JavaScript Object Notation)和XML(eXtensible Markup Language)是两种广泛使用的数据表…...

【视觉惯性SLAM:四、相机成像模型】
相机成像模型介绍 相机成像模型是计算机视觉和图像处理中的核心内容,它描述了真实三维世界如何通过相机映射到二维图像平面。相机成像模型通常包括针孔相机的基本成像原理、数学模型,以及在实际应用中如何处理相机的各种畸变现象。 一、针孔相机成像原…...
网络编程:TCP和UDP通信基础
TCP 简易服务器: #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简单的示例
将自定义的信息内容(如Wi-Fi配置、数字数据)转换为音波是一种音频调制与解调技术,广泛应用于声波配网、数据传输和近场通信中。这项技术的实现涉及将数字信息编码为音频信号,并通过解码还原信息。 实现方法 1. 数字数据编码 将原…...
深度学习任务中的 `ulimit` 设置优化指南
深度学习任务中的 ulimit 设置优化指南 1. 什么是 ulimit?2. 深度学习任务中的关键 ulimit 设置2.1 max locked memory(-l)2.2 open files(-n)2.3 core file size(-c)2.4 stack size(…...
maven编译时跳过test过程
如果代码里有无法在打包环境中测试的部分,则直接运行mvn clean package,因为测试失败,会导致打包失败。目前有两种方式可以跳过测试: 1. mvn clean package -DskipTests,这会跳过执行阶须,但仍会生成测试所…...

C++数据结构 : 哈希表的实现
C数据结构 : 哈希表的实现 目录 C数据结构 : 哈希表的实现引言1. 哈希概念1.1 直接定址法1.2 哈希冲突1.3 负载因子 2. 哈希函数2.1 除法散列法/除留余数法2.2 乘法散列法(了解)2.3 全域散列法(了解) 3. 处…...
记录算法笔记(2025.5.28)只出现一次的数字
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。 示例 1 : 输入࿱…...

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

QNAP MEMOS 域名访问 SSL(Lucky)
注意:下述是通过ssh、docker-compose方式安装docker的,不是直接在container station中安装的哈!!! 一、编辑docker-compose.yml文件 用“#”号标识的,在保存文件的时候建议去掉,不然有时候会出…...

使用摄像头推流+VLC软件拉流
一、作用 使用摄像头创建rtsp链接,并使用VLC软件拉流显示。 二、步骤 1、安装FFmpeg库 下载地址:https://ffmpeg.org/download.htmlFFmpeg库的下载参考之前的博客,下载Win64版本即可:https://blog.csdn.net/beijixingcd/artic…...
android协程异步编程常用方法
在 Android 开发中,Kotlin 协程是处理异步操作的首选方案,它能让异步代码更简洁、更易读。以下是 Android 协程异步编程的常用方法和模式: 一、基础构建块 1. launch 作用:启动一个新协程,不返回结果。适用场景&…...

基于Web的濒危野生动物保护信息管理系统设计(源码+定制+开发)濒危野生动物监测与保护平台开发 面向公众参与的野生动物保护与预警信息系统
博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…...
索引的选择与Change Buffer
1. 索引选择与Change Buffer 问题引出:普通索引 vs 唯一索引 ——如何选择? 在实际业务中,如果一个字段的值天然具有唯一性(如身份证号),并且业务代码已确保无重复写入,那就存在两种选择&…...

rtpinsertsound:语音注入攻击!全参数详细教程!Kali Linux教程!
简介 2006年8月至9月期间,我们创建了一个用于将音频插入指定音频(即RTP)流的工具。该工具名为rtpinsertsound。 该工具已在Linux Red Hat Fedora Core 4平台(奔腾IV,2.5 GHz)上进行了测试,但预…...