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

rk3588 驱动开发(三)第五章 新字符设备驱动实验

register_chrdev 和 unregister_chrdev 这两个函数是老版本驱动使用的函数,现在新的字符设备驱动已经不再使用这两个函数,而是使用 Linux 内核推荐的新字符设备驱动 API 函数。本节我们就来学习一下如何编写新字符设备驱动,并且在驱动模块加载的时候自动创建设备节点文件
设备节点文件:
设备节点文件(Device Node 或 Device File),也叫设备文件,是 Linux 系统中用来访问硬件设备的一种特殊文件,通常位于 /dev 目录下。
🧠 一句话解释:
设备节点文件是用户空间访问内核驱动和硬件设备的接口桥梁。

5.1 新字符设备驱动原理

5.1.1分配和释放设备号

使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会
带来两个问题:
①、需要我们事先确定好哪些主设备号没有使用。
②、会将一个主设备号下的所有次设备号都使用掉,比如现在设置 LED 这个主设备号为
200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被 LED 一个设备分走了。这样太
浪费次设备号了!一个 LED 设备肯定只能有一个主设备号,一个次设备号。
解决这两个问题最好的方法就是在使用设备号的时候向 Linux 内核申请,需要几个就申请
几个,由 Linux 内核分配设备可以使用的设备号。
如果没有指定设备号的话就使用如下函数来申请设备号:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:

int register_chrdev_region(dev_t from, unsigned count, const char *name)

参数 from 是要申请的起始设备号,也就是给定的设备号;参数 count 是要申请的数量,一般都是一个;参数 name 是设备名字。
注销字符设备之后要释放掉设备号,不管是通过 alloc_chrdev_region 函数还是
register_chrdev_region 函数申请的设备号,统一使用如下释放函数:

void unregister_chrdev_region(dev_t from, unsigned count)

新字符设备驱动下,设备号分配示例代码如下:

1 int major; /* 主设备号 */
2 int minor; /* 次设备号 */
3 dev_t devid; /* 设备号 */
4 
5 if (major) { /* 定义了主设备号 */
6 devid = MKDEV(major, 0); /* 大部分驱动次设备号都选择 0 */  
7 register_chrdev_region(devid, 1, "test");
8 } else { /* 没有定义设备号 */
9 alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */
10 major = MAJOR(devid); /* 获取分配号的主设备号 */
11 minor = MINOR(devid); /* 获取分配号的次设备号 */
12 }

devid是设备号是由主设备号major 和此设备号minor 合成得到的
这里注意设备名和设备节点文件的区别
概念 | 举例 | 用途 | 关系
设备名 | “rk3588-led” | 设备/驱动内部识别和匹配 | 属于内核驱动的部分
设备节点文件名 | /dev/platled | 用户空间访问设备接口 | 可以根据设备名生成
设备名是内核态区分识别匹配设备/驱动的设备节点文件是用户态和内核态沟通的桥梁

如果要注销设备号的话,使用如下代码即可:

1 unregister_chrdev_region(devid, 1); /* 注销设备号 */

5.1.2 新的字符设备注册方法

1、字符设备结构
在 Linux 中使用 cdev 结构体表示一个字符设备,cdev 结构体在 include/linux/cdev.h 文件中的定义如下

示例代码 5.1-3 cdev 结构体
1 struct cdev {
2 struct kobject kobj;
3 struct module *owner;
4 const struct file_operations *ops;
5 struct list_head list;
6 dev_t dev;
7 unsigned int count;
8 } __randomize_layout;

在 cdev 中有两个重要的成员变量:ops 和 dev,这两个就是字符设备文件操作函数集合file_operations 以及设备号 dev_t。编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个变量就表示一个字符设备,如下所示:

struct cdev test_cdev;

2、cdev_init 函数
定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化,cdev_init 函数原型如下:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合。
使用 cdev_init 函数初始化 cdev 变量的示例代码如下:

示例代码 5.1-4 cdev_init 函数使用示例代码
1 struct cdev testcdev;
2 
3 /* 设备操作函数 */
4 static struct file_operations test_fops = {
5 .owner = THIS_MODULE,
6 /* 其他具体的初始项 */
7 };
8 
9 testcdev.owner = THIS_MODULE;
10 cdev_init(&testcdev, &test_fops); /* 初始化 cdev 结构体变量 */

3、cdev_add 函数
cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),首先使用 cdev_init 函数
完成对 cdev 结构体变量的初始化,然后使用 cdev_add 函数向 Linux 系统添加这个字符设备。
cdev_add 函数原型如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

参数 p 指向要添加的字符设备(cdev 结构体变量),参数 dev 就是设备所使用的设备号,参数 count 是要添加的设备数量。完善上面的示例代码,加入 cdev_add 函数,内容如下所示:

示例代码 5.1-5 cdev_add 函数使用示例
1 struct cdev testcdev;
2 
3 /* 设备操作函数 */
4 static struct file_operations test_fops = {
5 .owner = THIS_MODULE,
6 /* 其他具体的初始项 */
7 };
8 
9 testcdev.owner = THIS_MODULE;
10 cdev_init(&testcdev, &test_fops); /* 初始化 cdev 结构体变量 */
11 cdev_add(&testcdev, devid, 1); /* 添加字符设备 */

示例:

dev_t devid;
alloc_chrdev_region(&devid, 0, 2, "mydev");

这时候 devid 表示的是:主设备号 + 起始次设备号为 0。

cdev_add(&mycdev, devid, 2);

你这里添加了 2 个次设备号,也就是:

/dev/mydev0(主设备号 + 次设备号 0)

/dev/mydev1(主设备号 + 次设备号 1)

3、cdev_del 函数
卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备,cdev_del
函数原型如下:

void cdev_del(struct cdev *p)

5.2 自动创建设备节点

在前面的 Linux 驱动实验中,当我们使用 modprobe 加载驱动程序以后还需要使用命令“mknod”手动创建设备节点。本节就来讲解一下如何实现自动创建设备节点,在驱动中实现自动创建设备节点的功能以后,使用 modprobe 加载驱动模块成功的话就会自动在/dev 目录下创建对应的设备文件。

5.2.1mdev 机制

udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除,udev 可以检
测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用
modprobe 命令成功加载驱动模块以后就自动在/dev 目录下创建对应的设备节点文件,使用
rmmod 命令卸载驱动模块以后就删除掉/dev 目录下的设备节点文件。
开发板启动的时候会启动 udev,如下图所示
在这里插入图片描述

5.2.2 创建和删除类

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。首先要创建一个 class 类,class 是个结构体,定义在文件include/linux/class.h 里面。class_create 是类创建函数,class_create 是个宏定义,内容如下:

示例代码 5.2-1 class_create 函数
546 extern struct class * __must_check __class_create(struct module 
547 *owner,const char *name,
548 struct lock_class_key *key);
549 extern void class_destroy(struct class *cls);
550
551 /* This is a #define to keep the compiler from merging different
552 * instances of the __key variable */
553 #define class_create(owner, name) \
554 ({ \
555 static struct lock_class_key __key; \
556 __class_create(owner, name, &__key); \
557 })

根据上述代码,将宏 class_create 展开以后内容如下:

struct class *class_create (struct module *owner, const char *name)

class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。
返回值是个指向结构体 class 的指针,也就是创建的类。
卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:
void class_destroy(struct class *cls);
参数 cls 就是要删除的类

5.2.3 创建设备

上一小节创建好类以后还不能实现自动创建设备节点,我们还需要在这个类下创建一个设
备。使用 device_create 函数在类下面创建设备,device_create 函数原型如下:

struct device *device_create(struct class *class, 
struct device *parent, 
dev_t devt, 
void *drvdata, 
const char *fmt, ...)

device_create 是个可变参数函数,参数 class 就是设备要到创建哪个类下面;参数 parent
是父设备,一般为 NULL,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能
会使用的一些数据,一般为 NULL;参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成
/dev/xxx 这个设备文件。返回值就是创建好的设备。
同样的,卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy,函数
原型如下:

void device_destroy(struct class *cls, dev_t devt)

参数 classs 是要删除的设备所处的类,参数 devt 是要删除的设备号。

5.2.4参考示例

在驱动入口函数里面创建类和设备,在驱动出口函数里面删除类和设备,参考示例如下:
示例代码 5.2-2 创建/删除类/设备参考代码
1 struct class *class; /* 类 */ 
2 struct device *device; /* 设备 */
3 dev_t devid; /* 设备号 */ 
4 
5 /* 驱动入口函数 */
6 static int __init xxx_init(void)
7 {
8 /* 创建类 */
9 class = class_create(THIS_MODULE, "xxx");
10 /* 创建设备 */
11 device = device_create(class, NULL, devid, NULL, "xxx");
12 return 0;
13 }
14
15 /* 驱动出口函数 */
16 static void __exit led_exit(void)
17 {
18 /* 删除设备 */
19 device_destroy(newchrled.class, newchrled.devid);
20 /* 删除类 */
21 class_destroy(newchrled.class);
22 }
23
24 module_init(led_init);
25 module_exit(led_exit);

5.3 设置文件私有数据

每个硬件设备都有一些属性,比如主设备号(dev_t),类(class)、设备(device)、开关状态
(state)等等,在编写驱动的时候你可以将这些属性全部写成变量的形式,如下所示

dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */

这样写肯定没有问题,但是这样写不专业!对于一个设备的所有属性信息我们最好将其做
成一个结构体。编写驱动 open 函数的时候将设备结构体作为私有数据添加到设备文件中,如
下所示:

示例代码 5.3-2 设备结构体作为私有数据
/* 设备结构体 */
1 struct test_dev{
2 dev_t devid; /* 设备号 */
3 struct cdev cdev; /* cdev */
4 struct class *class; /* 类 */
5 struct device *device; /* 设备 */
6 int major; /* 主设备号 */
7 int minor; /* 次设备号 */
8 };
9 
10 struct test_dev testdev;
11 
12 /* open 函数 */
13 static int test_open(struct inode *inode, struct file *filp)
14 {
15 filp->private_data = &testdev; /* 设置私有数据 */
16 return 0;
17 }

在 open 函数里面设置好私有数据以后,在 write、read、close 等函数中直接读取private_data 即可得到设备结构体。

5.5 实验程序编写

5.5.1 LED 灯驱动程序编写

新建名为“03_newchrled”文件夹,创建好以后新建 newchrled.c 文件,在 newchrled.c 里
面输入如下内容:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define NEWCHRLED_CNT 1 /* 设备号个数 */
#define NEWCHRLED_NAME "newchrled"
/* 主设备号 设备名称 */
#define LED_MAJOR 200 /* 主设备号 */
#define LED_NAME "led" /* 设备名字 */#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* PMU_GRF_BASE 外设的基地址,为 0xFD5F8000 */
#define PMU_GRF_BASE (0xFD5F8000)
#define BUS_IOC_BASE (0xFD5F8000)
/* IO功能复用寄存器 */
#define BUS_IOC_GPIO1A_IOMUX_SEL_L (BUS_IOC_BASE + 0x0020)
/* 驱动能力设置寄存器基地址 */
#define VCCIO1_4_BASE (0xFD5F9000)
/* GPIO1_A3 的驱动能力设置寄存器地址 */
#define VCCIO1_4_IOC_GPIO1A_DS_L (VCCIO1_4_BASE + 0x0020)
/* GPIO1组的基地址 */
#define GPIO1_BASE (0xFEC20000)
/* 输出输入模式寄存器 */
#define GPIO_SWPORT_DR_L (GPIO1_BASE + 0X0000)
/* 设置输出高低电平寄存器*/
#define GPIO_SWPORT_DDR_L (GPIO1_BASE + 0X0008)static void __iomem *BUS_IOC_GPIO1A_IOMUX_SEL_L_PI;
static void __iomem *VCCIO1_4_IOC_GPIO1A_DS_L_PI;
static void __iomem *GPIO_SWPORT_DR_L_PI;
/* 
static表示这个指针的作用域限制在当前文件中(即文件内全局变量)。void *指针类型为 void,可以转换为任意数据类型,表示一个未定具体类型的地址。__iomem是一个内核宏,表示指针指向 I/O 内存(而非普通的 RAM)。目的是让 sparse(Linux 的静态分析工具)在编译阶段检测可能的非法操作。*/
static void __iomem *GPIO_SWPORT_DDR_L_PI;
struct newchrled_dev    /* 定义led设备结构体 */
{/* 在 Linux 内核中,dev_t 是一个用于表示设备号的类型,通常用于存储主设备号和次设备号的组合。
定义dev_t 是一个内核定义的类型,通常是一个 32 位无符号整数,定义在内核头文件中 */dev_t devid; /* 设备号 */struct cdev cdev; /* cdev 结构体 */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */int minor; /* 次设备号 *//* data */
};
struct newchrled_dev newchrled; /* led设备 */
/* @description : LED 灯控制函数@param - sta : LEDON(0) -- 打开 LED 灯LEDOFF(1) -- 关闭 LED 灯@return : 无*/
void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIO_SWPORT_DR_L_PI);val &= ~(0X8 << 0); /* bit3 清零*/val |= ((0X8 << 16) | (0X8 << 0)); /* bit19 置 1,允许写 bit3 bit3,高电平*/writel(val, GPIO_SWPORT_DR_L_PI);    }else if(sta == LEDOFF) {writel(val, GPIO_SWPORT_DR_L_PI);val = readl(GPIO_SWPORT_DR_L_PI);val &= ~(0X8 << 0); /* bit3 清零*/val |= (0X8 << 16); /* bit19 置 1,允许写 bit3,bit3,低电平*/writel(val, GPIO_SWPORT_DR_L_PI);}
}
/*
* @description : 物理地址映射  将物理地址映射到虚拟地址上
* @return : 无
*/
void led_remap(void)
{/* GPIO的复用功能寄存器 */BUS_IOC_GPIO1A_IOMUX_SEL_L_PI = ioremap(BUS_IOC_GPIO1A_IOMUX_SEL_L,4);/* GPIO输出能力设置寄存器 */VCCIO1_4_IOC_GPIO1A_DS_L_PI = ioremap( VCCIO1_4_IOC_GPIO1A_DS_L ,4);/* 输出输入模式寄存器 */  GPIO_SWPORT_DR_L_PI = ioremap(GPIO_SWPORT_DR_L, 4);/* 输出电平寄存器 */  GPIO_SWPORT_DDR_L_PI = ioremap(GPIO_SWPORT_DDR_L, 4);
}/* @description : 取消映射@param : 无@return : 无*/
void led_unmap(void)
{/* 取消映射 */iounmap(BUS_IOC_GPIO1A_IOMUX_SEL_L_PI);iounmap(VCCIO1_4_IOC_GPIO1A_DS_L_PI);iounmap(GPIO_SWPORT_DR_L_PI);iounmap(GPIO_SWPORT_DDR_L_PI);
}
/** @description : 打开设备* @param - inode : 传递给驱动的 inode* @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变
量* 一般在 open 的时候将 private_data 指向设备结构体。* @return : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &newchrled; /* 设置私有数据 */return 0;
}/** @description : 从设备读取数据* @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{return 0;
}
/** @description : 向设备写数据* @param - filp : 设备文件,表示打开的文件描述符* @param - buf : 要写给设备写入的数据* @param - cnt : 要写入的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 写入的字节数,如果为负值,表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;//返回错误   }ledstat = databuf[0]; /* 获取状态值 */if(ledstat == LEDON) {led_switch(LEDON); /* 打开 LED 灯 */}else if(ledstat == LEDOFF) {led_switch(LEDOFF);}return 0;
}/** @description : 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/static int led_release(struct inode *inode, struct file *filp){return 0;}
static struct file_operations newchrled_fops = {.owner = THIS_MODULE,.open = led_open,.read  = led_read,.write  = led_write,.release  = led_release,};
/* 入口函数实现 */
static int __init led_init(void)
{int retvalue = 0;u32 val = 0;/* 初始化LED *//* 1、映射寄存器地址 */led_remap();/* 2、设置GPIO1_A3的功能 */val = readl(BUS_IOC_GPIO1A_IOMUX_SEL_L_PI);val &= ~(0XF000 << 0); /* bit15:12,清零 */val |= ((0XF000 << 16) | (0X0 << 0)); /* bit31:28 置 1,bit15:12:0,用作 GPIO1_A3 */writel(val, BUS_IOC_GPIO1A_IOMUX_SEL_L_PI);/* 3、设置 GPIO0_C0 驱动能力为 40ohm */val = readl(VCCIO1_4_IOC_GPIO1A_DS_L_PI);val &= ~(0X7000 << 0); /* bit14:12 清零*/val |= ((0X7000 << 16) | (0X6000 << 0));/* bit30:28 置 1,允许写 bit14:12,bit14:12:110, 用作 GPIO1_A3 */writel(val, VCCIO1_4_IOC_GPIO1A_DS_L_PI);/* 4、设置 GPIO1_A3 为输出 */val = readl(GPIO_SWPORT_DDR_L_PI);val &= ~(0X8 << 0); /* bit3 清零*/val |= ((0X8 << 16) | (0X8 << 0)); /* bit19 置 1,允许写 bit3,bit3,高电平 */writel(val, GPIO_SWPORT_DDR_L_PI);/* 5、设置 GPIO1_A3 为低电平,关闭 LED 灯。*/val = readl(GPIO_SWPORT_DR_L_PI);val &= ~(0X8 << 0); /* bit3 清零*/val |= (0X8 << 16); /* bit19 置 1,允许写 bit3,bit3,低电平 */writel(val, GPIO_SWPORT_DR_L_PI);/* 注册字符设备驱动 *//* 1、创建设备号 */if(newchrled.major) {/* #define MKDEV(major, minor) (((major) << 20) | (minor))MKDEV是 Linux 内核提供的一个宏,用于将主设备号和次设备号组合成一个设备号。*/newchrled.devid = MKDEV(newchrled.major, 0);//将主次设备号转换为dev_t类型/* int register_chrdev_region(dev_t from, unsigned count, const char *name); *//* 向内核注册一个字符设备 devt 1连续注册的设备数量 LED_NAME字符设备名*/retvalue = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);//静态注册字符设备  if(retvalue < 0) {pr_err("cannot register %s char driver [ret=%d]\n",NEWCHRLED_NAME, NEWCHRLED_CNT);goto fail_map;}} else {/* int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); *//* dev_t *dev:指向一个 dev_t 类型的变量,用于存储分配到的设备号(主设备号和次设备号的组合)。 0:次设备号开始分配1:分配设备号的数量LED_NAME:设备名*/retvalue = alloc_chrdev_region(&newchrled.devid, 0, 1, LED_NAME);//动态注册字符设备if(retvalue < 0) {pr_err("cannot register %s char driver [ret=%d]\n",NEWCHRLED_NAME, NEWCHRLED_CNT);goto fail_map;}newchrled.major = MAJOR(newchrled.devid);//获取主设备号newchrled.minor = MINOR(newchrled.devid);//获取次设备号}printk("newchrled major=%d,minor=%d\r\n",newchrled.major,newchrled.minor);/* 2、初始化 cdev 结构 */newchrled.cdev.owner = THIS_MODULE;/* cdev_init 是一个内核提供的函数,用于初始化 struct cdev 结构体。它会将字符设备与文件操作函数(struct file_operations)绑定*/cdev_init(&newchrled.cdev, &newchrled_fops);/* 3、向 Linux 内核添加 cdev 结构 */retvalue = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);if(retvalue < 0) {goto del_unregister;}/* 4、自动创建设备节点 -- 创建类 *//* 设备类 是 Linux 内核中的一个抽象概念,用于将具有相似功能或用途的设备分组管理。设备类的主要作用是为设备提供一个逻辑上的分类,并在 class 文件系统中创建一个对应的目录,方便用户空间查看和管理设备。设备类的作用逻辑分组:将功能相似的设备归为一类。例如:所有 LED 设备可以归为 led 类。所有串口设备可以归为 serial 类。设备信息的组织:在 class 文件系统中为设备创建一个类目录,便于用户查看设备信息。例如:/sys/class/led:存放所有 LED 设备的信息。/sys/class/serial:存放所有串口设备的信息。设备节点的自动创建:配合 device_create 函数,设备类可以在 dev 目录下自动创建设备文件(设备节点),方便用户空间访问设备*/newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);if(IS_ERR(newchrled.class)) {goto del_cdev;}/* 5、创建设备 */newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);if(IS_ERR(newchrled.device)) {goto destory_class;}return 0;destory_class:class_destroy(newchrled.class); //删除类del_cdev:cdev_del(&newchrled.cdev);//删除cdevdel_unregister:unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);   //注销设备号fail_map:led_unmap();return -EIO;}/* 出口函数实现 */
static void __exit led_exit(void)
{/* 取消映射 */led_unmap();/* 注销字符设备驱动 */cdev_del(&newchrled.cdev);unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);device_destroy(newchrled.class, newchrled.devid);class_destroy(newchrled.class);
}/* 声明入口函数 */
module_init(led_init);
/* 声明出口函数 */
module_exit(led_exit);
/* 开源协议 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LK");
/* 骗过内核该模块属于设备树 */
MODULE_INFO(intree, "Y");

在这里插入图片描述
APP程序沿用上一章的内容

modprobe newchrled //加载驱动
驱动加载成功以后会自动在
/dev 目录下创建设备节点文件/dev/newchrdev

相关文章:

rk3588 驱动开发(三)第五章 新字符设备驱动实验

register_chrdev 和 unregister_chrdev 这两个函数是老版本驱动使用的函数&#xff0c;现在新的字符设备驱动已经不再使用这两个函数&#xff0c;而是使用 Linux 内核推荐的新字符设备驱动 API 函数。本节我们就来学习一下如何编写新字符设备驱动&#xff0c;并且在驱动模块加载…...

文件上传--WAF绕过干货

本文主要内容 绕过WAF上传文件 -- 安全狗 -- 宝塔 Burp抓包解析 #上传参数名解析&#xff1a;明确哪些东西能修改? Content-Disposition&#xff1a;—般可更改 name&#xff1a;表单参数值&#xff0c;不能更改 filename&#xff1a;文件名&#xff…...

BERT BERT

BERT ***** 2020年3月11日更新&#xff1a;更小的BERT模型 ***** 这是在《深阅读的学生学得更好&#xff1a;预训练紧凑模型的重要性》&#xff08;arXiv:1908.08962&#xff09;中提到的24种较小规模的英文未分词BERT模型的发布。 我们已经证明&#xff0c;标准的BERT架构和…...

Kotlin Multiplatform--02:项目结构进阶

Kotlin Multiplatform--02&#xff1a;项目结构进阶 引言正文 引言 在上一章中&#xff0c;我们对 Kotlin Multiplatform 项目有了基本的了解&#xff0c;已经可以进行开发了。但我们只是使用了系统默认的项目结构。本章介绍了如何进行更复杂的项目结构管理。 正文 在上一章中&…...

【ES实战】Elasticsearch中模糊匹配类的查询

Elasticsearch中模糊匹配类的查询 文章目录 Elasticsearch中模糊匹配类的查询通配符查询前缀匹配查询正则匹配查询标准的正则操作特殊运算符操作 模糊化查询Fuzziness text类型同时配置keyword类型 Elasticsearch中模糊类查询主要有以下 Wildcard Query&#xff1a;通配符查询P…...

纯真社区IP库离线版发布更新

纯真社区IP库离线版发布更新 发布者&#xff1a;技术分享 2005年&#xff0c;随着中国互联网的蓬勃发展&#xff0c;纯真IP库诞生了。作为全球网络空间地理测绘技术的领先者&#xff0c;纯真开源项目为中国互联网行业提供了高质量的网络空间IP库数据。纯真IP库目前已经覆盖超…...

直接偏好优化(Direct Preference Optimization,DPO):论文与源码解析

简介 虽然大规模无监督语言模型&#xff08;LMs&#xff09;学习了广泛的世界知识和一些推理技能&#xff0c;但由于它们是基于完全无监督训练&#xff0c;仍很难控制其行为。 微调无监督LM使其对齐偏好&#xff0c;尽管大规模无监督的语言模型&#xff08;LMs&#xff09;能…...

uniapp-商城-34-shop 购物车 选好了 进行订单确认

在shop页面选中商品添加到购物车&#xff0c;可选好后&#xff0c;进行确认和支付。具体呈现在shop页面。 1 购物车栏 shop页面代码&#xff1a; 购物车代码&#xff1a; 代码&#xff1a; <template><view><view class"carlayout"><!-- 车里…...

Kafka命令行的使用/Spark-Streaming核心编程(二)

Kafka命令行的使用 创建topic kafka-topics.sh --create --zookeeper node01:2181,node02:2181,node03:2181 --topic test1 --partitions 3 --replication-factor 3 分区数量&#xff0c;副本数量&#xff0c;都是必须的。 数据的形式&#xff1a; 主题名称-分区编号。 在…...

虚拟机详解

虚拟机详解 1. 虚拟机&#xff08;Virtual Machine&#xff09;的定义 系统虚拟机&#xff1a;通过软件模拟完整计算机系统&#xff08;CPU、内存、外设等&#xff09;&#xff0c;如 VMware、VirtualBox。进程级虚拟机&#xff1a;为单个应用提供虚拟执行环境&#xff0c;如 …...

NOIP2013 提高组.转圈游戏

目录 题目算法标签: 数论, 模运算思路代码 题目 504. 转圈游戏 算法标签: 数论, 模运算 思路 看题意不难看出, 计算的是 ( x 1 0 k m ) m o d n (x 10 ^ k \times m) \mod n (x10km)modn, 如果直接计算一定会超时, 因此可以使用快速幂进行优化 代码 #include <iost…...

【金仓数据库征文】加速数字化转型:金仓数据库在金融与能源领域强势崛起

目录 一、引言 二、金仓数据库&#xff08;KingbaseES&#xff09;概述 1. 发展历程与市场地位 2. 核心技术架构 3. 金仓数据库的特点 三、金仓数据库在金融行业的应用 1. 金融行业的挑战与需求 2. 金仓数据库在金融行业的优势 3. 金仓数据库在金融行业的实际应用案例 …...

济南国网数字化培训班学习笔记-第二组-5节-输电线路设计

输电线路设计 工程设计阶段划分 35kv及以上输变电工程勘测设计全过程 可行性研究&#xff08;包括规划、工程选站&#xff09;&#xff08;包括电力系统一次二次&#xff0c;站址选择及工程设想&#xff0c;线路工程选择及工程设想&#xff0c;节能降耗分析&#xff0c;环境…...

【前端】【业务场景】【面试】在前端开发中,如何实现一个可拖动和可缩放的元素,并且处理好边界限制和性能优化?

问题&#xff1a;在前端开发中&#xff0c;如何实现一个可拖动和可缩放的元素&#xff0c;并且处理好边界限制和性能优化&#xff1f; 一、实现可拖动和可缩放元素 HTML 和 CSS 基础设置&#xff1a; 创建一个 HTML 元素&#xff0c;并为其设置基本样式&#xff0c;使其在页面…...

BOM与DOM(解疑document window关系)

BOM&#xff08;浏览器对象模型&#xff09; 定义与作用 BOM&#xff08;Browser Object Model&#xff09;提供与浏览器窗口交互的接口&#xff0c;用于控制导航、窗口尺寸、历史记录等浏览器行为 window&#xff1a;浏览器窗口的顶层对象&#xff0c;包含全局属性和方法&am…...

504 nginx解决方案

当遇到 504 Gateway Time-out 错误时&#xff0c;通常是因为 Nginx 作为反向代理等待后端服务&#xff08;如 PHP-FPM、Java 应用等&#xff09;响应的时间超过了预设的超时阈值。以下是详细的解决方案&#xff0c;结合知识库中的信息整理而成&#xff1a; 一、核心原因分析 后…...

【LLM+Code】Windsurf Agent 模式PromptTools详细解读

一、前言 https://windsurf.com/ https://windsurf.com/blog/why-we-built-windsurf https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/tree/main/Windsurf 二、System Prompt 相比于cursor和claude code&#xff0c; windsurf的system prompt非常长&am…...

第三章:Transport Mechanisms

Chapter 3: Transport Mechanisms &#x1f31f; 从上一章到本章 在第二章&#xff1a;MCP服务器分类中&#xff0c;我们学会了如何根据需求选择不同类别的服务器&#xff08;如文件系统、数据库等&#xff09;。现在想象这样一个场景&#xff1a;你有一个本地文件服务器和一个…...

shell练习题(1)

练习: 1.建立脚本service.sh,当执行的时候要求输入(1、2、3、4、5)时安装对应的httpd、vim、wget、更换aliyum等功能&#xff0c;当输入错误 时会提示你&#xff0c;应该输入正确的值 [rootbogon yy]# cat service.sh #!/bin/bash cat <<-EOF ----------------------…...

【解决】Android Gradle Sync 报错 Could not read workspace metadata

异常信息 Caused by: java.io.UncheckedIOException:Could not read workspace metadata from C:\Users\xxx\.gradle\caches\transforms-4\69955912123c68eecd096b71c66ee211\metadata.bin 异常原因 看字面意思是不能读取metadata文件&#xff0c;原因可能是因为缓存目录异常…...

Python中的 for 与 迭代器

文章目录 一、for 循环的底层机制示例&#xff1a;手动模拟 for 循环 二、可迭代对象 vs 迭代器关键区别&#xff1a; 三、for 循环的典型应用场景1. 遍历序列类型2. 遍历字典3. 结合 range() 生成数字序列4. 遍历文件内容 四、迭代器的自定义实现示例&#xff1a;生成斐波那契…...

上篇:深入剖析 BLE 底层物理层与链路层(约5000字)

引言 在无线通信领域,Bluetooth Low Energy(BLE)以其超低功耗、灵活的连接模式和良好的生态支持,成为 IoT 与可穿戴设备的首选技术。要想在实际项目中优化性能、控制功耗、保证可靠通信,必须对 BLE 协议栈的底层细节有深入了解。本篇将重点围绕物理层(PHY)与链路层(Li…...

ArcGIS Pro跨图层复制粘贴

在map视图中&#xff0c;点击selection中的Select按钮&#xff0c;保持选择状态。 点击需要复制的要素&#xff0c;保持选中状态。右击点击copy&#xff0c;或CtrlC进行复制。 在Clipboard下拉框中点击Paste Special&#xff0c;选择需要粘贴的图层后点击OK。...

今日CSS学习浮动->定位

------------------------------------------------------------------------------------------------------- CSS的浮动 float 属性用于创建浮动框&#xff0c;将其移动到一边&#xff0c;直到左边缘或右边缘触及包含块或另一个浮动框的边缘。 float 属性定义元素在哪个方向浮…...

性行为同意协议系统网站源码

性行为同意协议系统网站源码 一个用于创建、签署和管理性行为同意协议的 Web 应用程序。该应用允许用户在线创建详细的性行为同意协议&#xff0c;并通过数字签名方式进行签署&#xff0c;同时支持导出为 PDF 格式保存。 功能特性 创建自定义性同意协议 多步骤表单引导用户完…...

项目自动化测试

一.设计测试用例(细致全面) 二.先引入所需要的pom.xml依赖 1.selenium依赖 2.webdrivermanager依赖 3.commons-io依赖 编写测试用例–按照页面对用例进行划分,每个页面是Java文件,页面下的所有用例统一管理 三.common包(放入公用包) 类1utils 可以调用driver对象,访问url …...

可变形卷积(可以观察到变形图片的卷积)【DCNv1、DCNv2、DCNv3】

一、DCNv1——可以观察到扭曲的图片 1.传统卷积的问题 在普通的卷积操作中&#xff0c;比如 33 卷积&#xff0c;采样的位置总是固定的&#xff1a;就是中间一个点&#xff0c;四周八个点&#xff0c;整齐地排成一个小网格。 但现实中的图像并不整齐——比如猫的身体弯着、车…...

vue3,element ui框架中为el-table表格实现自动滚动,并实现表头汇总数据

基础用法不太明白的请参考官网文档 &#xff1b;element ui Plus官网&#xff1a;Table 表格 | Element PlusA Vue 3 based component library for designers and developershttps://element-plus.org/zh-CN/component/table.html 1、添加一个基础表格 <template><e…...

Selenium 怎么加入代理IP,以及怎么检测爬虫运行的时候,是否用了代理IP?

使用selenium爬虫的时候&#xff0c;如果不加入代理IP&#xff0c;很容易会被网站识别&#xff0c;容易封号&#xff1b; 最近去了解了一下买代理ip&#xff0c;但是还是有一些不太懂的东西。 例如有了代理ip以后&#xff0c;怎么用在爬虫上&#xff0c;requests 和selenium的…...

【Python爬虫实战篇】--Selenium爬取Mysteel数据

任务&#xff1a;爬取我的钢铁网的钢材价格指数数据&#xff0c;需要输入时间和钢材类型 网站&#xff1a;钢铁价格指数_今日钢铁价格指数实时行情走势_我的钢铁指数 目录 1.环境搭建 2.打开网站 3.点击右侧按钮展开 4.点击需要的钢材数据 5.点击“按日查询” 6.输入日查…...

LLM学习笔记4——本地部署Docker、vLLM和Qwen2.5-32B-Instruct实现OpenManus的使用

系列文章目录 参考博客 参考博客 参考博客 参考博客 文章目录 系列文章目录前言一、OpenManus介绍二、环境搭建1.DockervLLM2.搭建OpenManus1&#xff09;安装anaconda虚拟环境2&#xff09;安装OpenManus3&#xff09;下载并配置Qwen2.5-32B-Instruct模型4&#xff09;配置与…...

aarcpy 列表函数的使用(1)

arcpy.ListFeatureClasses() 该函数用于列出指定工作空间中的所有要素类。可以通过通配符和过滤条件进一步筛选结果。 语法&#xff1a; python arcpy.ListFeatureClasses(wild_cardNone, feature_typeNone)• wild_card&#xff1a;用于筛选要素类名称的通配符&#xff0c;…...

maven工程中引入外部jar

1、引入模块下的jar 1.负责打包的模块&#xff0c;pom中加上这个插件&#xff0c;这个可以把外部jar包打入工程中。 <!-- 打包 --> <build><finalName>xxx-send-admin</finalName><resources><resource><directory>${project.base…...

C++智能指针上

一、裸指针 “裸指针”是最基础的&#xff0c;直接存储内存地址的指针类型。特点&#xff1a;①它本身没有自动的内存管理机制&#xff1a;如它不会自动释放内存&#xff0c;也不会检查是否指向有效的内存区域&#xff1b;②直接操作内存地址&#xff0c;不进行任何的边界检查&…...

flutter 中各种日志

日志方法对比 输出方式调试模式控制台输出发布模式控制台输出DevTools Logging 视图print()✅ 显示✅ 显示❌ 不显示debugPrint()✅ 显示✅ 显示❌ 不显示stderr.writeln()✅ 显示✅ 显示✅ 显示dart:developer.log()✅ 显示❌ 不显示✅ 显示 详细说明&#xff1a; print()&a…...

Java面试:从Spring Boot到微服务的全面考核

Java面试&#xff1a;从Spring Boot到微服务的全面考核 场景设定&#xff1a; 在一家互联网大厂的面试室内&#xff0c;严肃的面试官正准备开始对前来面试的赵大宝进行技术考核。赵大宝是一位自称在Java开发方面经验丰富的求职者&#xff0c;不过却是个搞笑的水货程序员。 第…...

安卓adb shell串口基础指令

目录 前言一、列出串口设备节点二、修改串口设备权限三、串口参数配置&#xff08;stty命令&#xff09;3.1 基本配置3.2 其他常用参数3.3 查看当前配置 四、数据收发操作4.1 发送数据4.2 接受数据 参考链接: 前言 在 Android 设备上&#xff0c;ADB提供了一系列命令用于与设备…...

大模型技术全景解析:从基础架构到Prompt工程

大模型技术全景解析&#xff1a;从基础架构到Prompt工程 引言 近年来&#xff0c;大型语言模型(LLMs)如GPT、BERT等取得了突破性进展&#xff0c;彻底改变了自然语言处理领域。本文将全面剖析大模型的核心技术要素&#xff0c;包括三要素构成、系统架构、机器学习范式演进、P…...

404页面精选(一)翻滚盒子

内容很详细&#xff0c;直接上代码 效果演示 源码 <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"><title>翻滚盒子</title><style>body {background: #000;h…...

LJF-Framework 第15章 想想搞点啥-若依管理系统兼容一下

LJF-Framework 第15章 想想搞点啥-若依管理系统兼容一下 一、下载后端源码 我们学习一下他的前后端分离的项目吧RuoYi-Vue,我看他有单独的Vue3版本的项目,我们就整这新的吧,向新新势力低头。 1、下载地址 git clone https://gitcode.com/yangzongzhuan/RuoYi-Vue.git2、…...

Hadoop基础知识

Hadoop 是由 Apache 基金会开发的开源分布式计算框架&#xff0c;主要用于处理海量数据的存储和计算问题。其核心设计基于 Google 的 MapReduce 编程模型和 GFS&#xff08;Google File System&#xff09;&#xff0c;旨在通过集群化的廉价硬件实现高可靠性、高扩展性的大数据…...

第1讲:Transformers 的崛起:从RNN到Self-Attention

序列建模的演进之路 一、RNN&#xff08; Recurrent Neural Networks&#xff09;&#xff1a;序列处理的开拓者 循环神经网络(RNN)是最早处理序列数据的深度学习结构。RNN的核心思想是在处理序列的每个时间步时保持一个"记忆"状态。 h_t tanh(W_x * x_t W_h * …...

经验分享 | 如何高效使用 `git commit --amend` 修改提交记录

背景 在「地面智能观测项目」这种多模块协作的物联网系统中&#xff0c;版本迭代频率高达每周3次。每个部署包&#xff08;如v0.3.19&#xff09;都包含硬件控制脚本、数据处理模块和部署工具&#xff0c;任何提交遗漏都可能导致部署失败。传统的新建提交方式会造成冗余记录&a…...

生物创新药研发为何要上电子实验记录本?

前言&#xff1a;数据驱动的生物创新药研发新范式 在精准医疗时代&#xff0c;生物创新药以其靶向性强、疗效确切的优势&#xff0c;成为肿瘤、自身免疫性疾病等复杂病症的核心治疗方案。国家"十四五" 规划明确将生物制药列为战略性新兴产业&#xff0c;各地政府纷纷…...

PH热榜 | 2025-04-24

1. Peek 标语&#xff1a;AI个人财务教练&#xff0c;帮你做出明智的财务决策。 介绍&#xff1a;Peek的人工智能助手能够主动进行财务检查&#xff0c;分析你的消费模式&#xff0c;并以一种细腻而积极的方式帮助你改善习惯。完全没有评判&#xff0c;也没有负罪感。就像为你…...

民锋视角下的节奏判断与资金行为建模

民锋视角下的节奏判断与资金行为建模 在市场节奏的研判中&#xff0c;行为模型始终是构建逻辑核心。以民锋为代表的一类研究视角&#xff0c;更关注的是微观结构中的资金行为痕迹&#xff0c;而非单一技术形态。 节奏并非由K线决定&#xff0c;而是由成交密度与换手效率共同塑…...

Debian服务器上JSP页面无法加载如何解决?

如果你在 Debian 服务器上部署 JSP 页面无法加载&#xff0c;可以按以下步骤排查和解决问题&#xff1a; 1. 确认安装了 Java 环境 JSP 需要 Java 支持&#xff0c;先确认 Java 是否安装并配置好&#xff1a; java -version如果未安装&#xff0c;使用如下命令安装 OpenJDK&…...

第三篇:Django创建表关系及生命周期流程图

第三篇&#xff1a;Django创建表关系及生命周期流程图 文章目录 第三篇&#xff1a;Django创建表关系及生命周期流程图一、Django中orm创建表关系一、数据库中的表关系二、创建表 二、Django请求生命周期流程图 一、Django中orm创建表关系 一、数据库中的表关系 我们可以通过…...

【玩泰山派】7、玩linux桌面环境xfce - (2)音视频,yt-dlp下载工具、parole播放器

文章目录 前言yt-dlpyt-dlp概述发展背景特点应用场景使用方式局限性 安装yt-dlpyt-dlp常用命令直接下载默认格式指定格式 查看视频所有分辨率下载指定分辨率参考 parole播放器使用Parole概述源码地址使用 前言 前面安装了ubuntu Xfce桌面环境(xubuntu-desktop)&#xff0c;现在…...

【文献速递】NMR代谢组寻找预测DR发展的候选标志物

2024年7月5日&#xff0c;中山大学中山眼科中心王伟教授团队在Ophthalmology&#xff08;IF&#xff1a;13.2&#xff09;上发表了题为“Plasma Metabolomics Identifies Key Metabolites and Improves Prediction of Diabetic Retinopathy&#xff1a;Development and Validat…...