【Linux】最基本的字符设备驱动
前面我们介绍到怎么编译出内核模块.ko
文件,然后还加载了这个驱动模块。但是,那个驱动代码还不完善,驱动写好后怎么在应用层使用也没有介绍。
字符设备抽象
Linux内核中将字符设备抽象成一个具体的数据结构(struct cdev
),我们可以理解为字符设备对象,cdev
记录了字符设备的相关信息(设备号、内核对象),字符设备的打开、读写和关闭等操作接口(file_operations
),在我们想要添加一个字符设备时,就是将这个对象注册到内核中,通过创建一个文件(设备节点
)绑定对象的cdev
,当我们对这个文件进行读写操作时,就可以通过虚拟文件系统,在内核中找到这个对象及其操作接口,从而控制设备。
在Linux中,我们使用设备编号来表示设备,主设备号区分设备类别,次设备号标识具体的设备。cdev
结构体被内核用来记录设备号,而在使用设备时,我们通常会打开设备节点,通过设备节点的inode
结构体和file
结构体最终找到file_operations
结构体,并从file_operations
结构体中得到操作设备的具体方法。
设备号
Linux根目录下有/dev
这个文件夹,专门用来存放设备中的驱动程序,我们可以使用ls -l
以列表的形式列出所有设备。
一般来说,主设备号指向设备的驱动程序,次设备号指向某个具体的设备。如上图,i2c-3
和i2c-4
属于不同设备但是共用一套驱动程序。
typedef u32 __kernel_dev_t;typedef __kernel_dev_t dev_t;
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
在内核中,dev_t用来表示设备编号,dev_t是一个32位的数,其中,高12位表示主设备号,低20位表示次设备号。 也就是理论上主设备号取值范围:0-2^12,次设备号0-2 ^ 20。
在kdev_t中,设备编号通过移位操作最终得到主/次设备号码,同样主/次设备号也可以通过位运算变成dev_t类型的设备编号, 具体实现参看下面代码MAJOR(dev)、MINOR(dev)和MKDEV(ma,mi)。
cdev结构体
内核通过一个散列表来记录设备编号。
struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
} __randomize_layout;
- struct kobject kobj: 内嵌的内核对象,通过它将设备统一加入到“Linux设备驱动模型”中管理(如对象的引用计数、电源管理、热插拔、生命周期、与用户通信等)。
- struct module *owner: 字符设备驱动程序所在的内核模块对象的指针。
- const struct file_operations *ops: 文件操作,是字符设备驱动中非常重要的数据结构,在应用程序通过文件系统(VFS)呼叫到设备设备驱动程序中实现的文件操作类函数过程中,ops起着桥梁纽带作用,VFS与文件系统及设备文件之间的接口是file_operations结构体成员函数,这个结构体包含了对文件进行打开、关闭、读写、控制等一系列成员函数。
- struct list_head list: 用于将系统中的字符设备形成链表(这是个内核链表的一个链接因子,可以再内核很多结构体中看到这种结构的身影)。
- dev_t dev: 字符设备的设备号,有主设备和次设备号构成。
- unsigned int count: 属于同一主设备好的次设备号的个数,用于表示设备驱动程序控制的实际同类设备的数量。
设备节点
设备节点是啥呢,其实就是设备文件。设备节点被创建在/dev
目录下,是连接内核与用户层的枢纽。
加载了设备驱动后,我们可以通过mknod
命令来手动创建设备节点,也可以通过代码实现自动创建设备节点。
应用程序通过一组标准化的调用执行访问设备,这些调用独立于任何特点的驱动程序。而驱动程序负责将这些标准调用映射到实际硬件的特有操作。
数据结构
在驱动开发过程中,不可避免要涉及到三个重要的内核数据结构。
- 文件操作方式:
file_operations
- 文件描述结构体:
struct file
- inode结构体:
struct inode
file_operations结构体
file_operations
就是把系统调用和驱动程序关联起来的关键数据结构。以下代码中只列出本章使用到的部分函数。
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);int (*open) (struct inode *, struct file *)int (*release) (struct inode *, struct file *);
};
- llseek: 用于修改文件的当前读写位置,并返回偏移后的位置。参数file传入了对应的文件指针,我们可以看到以上代码中所有的函数都有该形参,通常用于读取文件的信息,如文件类型、读写权限;参数loff_t指定偏移量的大小;参数int是用于指定新位置指定成从文件的某个位置进行偏移,SEEK_SET表示从文件起始处开始偏移;SEEK_CUR表示从当前位置开始偏移;SEEK_END表示从文件结尾开始偏移。
- read: 用于读取设备中的数据,并返回成功读取的字节数。该函数指针被设置为NULL时,会导致系统调用read函数报错,提示“非法参数”。该函数有三个参数:file类型指针变量,char__user*类型的数据缓冲区,__user用于修饰变量,表明该变量所在的地址空间是用户空间的。内核模块不能直接使用该数据,需要使用copy_to_user函数来进行操作。size_t类型变量指定读取的数据大小。
- write: 用于向设备写入数据,并返回成功写入的字节数,write函数的参数用法与read函数类似,不过在访问__user修饰的数据缓冲区,需要使用copy_from_user函数。
- unlocked_ioctl: 提供设备执行相关控制命令的实现方法,它对应于应用程序的fcntl函数以及ioctl函数。在 kernel 3.0 中已经完全删除了 struct file_operations 中的 ioctl 函数指针。
- open: 设备驱动第一个被执行的函数,一般用于硬件的初始化。如果该成员被设置为NULL,则表示这个设备的打开操作永远成功。
- release: 当file结构体被释放时,将会调用该函数。与open函数相反,该函数可以用于释放。
在使用read和write函数时,需要使用copy_to_user函数以及copy_from_user函数来进行数据访问,写入/读取成 功函数返回0,失败则会返回未被拷贝的字节数。
static inline long copy_from_user(void *to, const void __user * from, unsigned long n)
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
file结构体
内核中用file结构体来表示每个打开的文件,每打开一个文件,内核会创建一个结构体,并将对该文件上的操作函数传递给 该结构体的成员变量f_op,当文件所有实例被关闭后,内核会释放这个结构体。如下代码中,只列出了我们本章需要了解的成员变量。
struct file {
{......}
const struct file_operations *f_op;
/* needed for tty driver, and maybe others */
void *private_data;
{......}
};
- f_op:存放与文件操作相关的一系列函数指针,如open、read、wirte等函数。
- private_data:该指针变量只会用于设备驱动程序中,内核并不会对该成员进行操作。因此,在驱动程序中,通常用于指向描述设备的结构体。
inode结构体
VFS inode 包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。 它是Linux 管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。 内核使用inode结构体在内核内部表示一个文件。
inode结构体包含了一大堆文件相关的信息,但是就针对驱动代码来说,我们只要关心其中的两个域即可:
struct inode {dev_t i_rdev;{......}union {struct pipe_inode_info *i_pipe; /* linux内核管道 */struct block_device *i_bdev; /* 如果这是块设备,则设置并使用 */struct cdev *i_cdev; /* 如果这是字符设备,则设置并使用 */char *i_link;unsigned i_dir_seq;};{......}
};
- dev_t i_rdev: 表示设备文件的结点,这个域实际上包含了设备号。
- struct cdev *i_cdev: struct cdev是内核的一个内部结构,它是用来表示字符设备的,当inode结点指向一个字符设备文件时,此域为一个指向inode结构的指针。
字符设备驱动程序框架
在驱动入口函数中
- 分配设备号
- 注册字符设备
- 创建设备节点
在驱动出口函数中
- 删除字符设备
- 归还设备号
- 删除设备节点
已弃用的注册字符设备函数
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
这一组函数时Linux版本2.4之前的注册方式,每注册一个字符设备,都还会连续注册0~255个次设备,使他们绑定在同一个file_operationis
操作方法结构体上,而我们在大多数情况下,都只会用到极少的次设备号,所以会造成资源的极大浪费。所以现在很少用它。
字符设备号的申请和释放
静态申请
register_chrdev_region
函数用于静态地为一个字符设备申请一个或多个设备编号
int register_chrdev_region(dev_t from, unsigned count, const char *name)
void unregister_chrdev_region(dev_t from, unsigned count)
参数:
- from:dev_t类型的变量,用于指定字符设备的起始设备号,如果要注册的设备号已经被其他的设备注册了,那么就会导致注册失败。
- count:指定要申请的设备号个数,count的值不可以太大,否则会与下一个主设备号重叠。
- name:用于指定该设备的名称。
返回值: 返回0表示申请成功,失败则返回错误码。
静态申请设备号是我们人为确定好了设备号,再去申请设备号,属于静态申请。可以通过命令cat /proc/devices
来查询内核已使用的主设备号。
动态申请
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
void unregister_chrdev_region(dev_t from, unsigned count)
参数:
- dev:指向dev_t类型数据的指针变量,用于存放分配到的设备编号的起始值;
- baseminor:次设备号的起始值,通常情况下,设置为0;
- count、name:同register_chrdev_region类型,用于指定需要分配的设备编号的个数以及设备的名称。
返回值: 返回0表示申请成功,失败则返回错误码
字符设备的注册和注销
前面我们已经提到过了,编写一个字符设备最重要的事情,就是要实现file_operations这个结构体中的函数。 实现之后,如何将该结构体与我们的字符设备结构体相关联呢?内核提供了cdev_init函数,来实现这个过程。
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:
- cdev:struct cdev类型的指针变量,指向需要关联的字符设备结构体;
- fops:file_operations类型的结构体指针变量,一般将实现操作该设备的结构体file_operations结构体作为实参。
返回值: 无
cdev_add
函数用于向内核的cdev_map
散列表添加一个新的字符设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数:
- p:struct cdev类型的指针,用于指定需要添加的字符设备;
- dev:dev_t类型变量,用于指定设备的起始编号;
- count:指定注册多少个设备。
返回值: 错误码
从系统中删除cdev,cdev设备将无法再打开,但任何已经打开的cdev将保持不变, 即使在cdev_del返回后,它们的FOP仍然可以调用。
void cdev_del(struct cdev *p)
参数:
- p:struct cdev类型的指针,用于指定需要删除的字符设备;
返回值: 无
设备节点的创建和销毁
创建类的定义在/linux-4.1.15/include/linux/device.h中。
#define class_create(owner, name)
({ static struct lock_class_key __key; __class_create(owner, name, &__key);
})
删除类:
extern void class_destroy(struct class *cls)
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
参数:
- class:指向这个设备应该注册到的struct类的指针;
- parent:指向此新设备的父结构设备(如果有)的指针;
- devt:要添加的char设备号;
- drvdata:要添加到设备进行回调的数据;
- fmt:输入设备名称。
返回值: 成功时返回 struct device 结构体指针, 错误时返回ERR_PTR().
删除使用device_create函数创建的设备
void device_destroy(struct class *class, dev_t devt)
参数:
- class:指向注册此设备的struct类的指针;
- devt:以前注册的设备号;
返回值: 无
除了使用代码创建设备节点,还可以使用mknod
命令创建设备节点。
用法:mknod
设备名 设备类型 主设备号 次设备号
如:
mknod /dev/haptics c 200 0
这个class
和device
具体是什么,我们后面再介绍,目前我们只管这样用就行。
源码
/** Silicon Integrated Co., Ltd haptic sih688x haptic driver file** Copyright (c) 2021 kugua <daokuan.zhu@si-in.com>** This program is free software; you can redistribute it and/or modify it* under the terms of the GNU General Public License version 2 as published by* the Free Software Foundation*/#include <linux/init.h> //包含宏定义的头文件
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>#define HAPTICS_DEV_MAJOR 200
#define HAPTICS_DEV_MINOR 0
#define HAPTICS_DEV_NAME "haptics"
#define HAPTICS_DEV_CNT 1#define HAPTICS_DEV_CLASS_NAME "haptics"
#define HAPTICS_DEV_NODE_NAME "haptics"#define BUFF_SIZE 128
//数据缓冲区
static char vbuf[BUFF_SIZE]="this is driver";static dev_t dev_id;//设备号
static struct cdev haptics_dev;//设备
struct class *class;
struct device *device; static int major = HAPTICS_DEV_MAJOR;//主设备号
static int minor = HAPTICS_DEV_MINOR;//次设备号//打开设备
static int haptics_open(struct inode* inode,struct file * filp)
{printk("%s\n",__FUNCTION__);return 0;
}//关闭设备
static int haptics_release(struct inode* inode ,struct file* filp)
{printk("%s\n",__FUNCTION__);return 0;
}//读设备
static ssize_t haptics_read(struct file* filp, char __user *buf,size_t count,loff_t* ppos)
{unsigned long p = *ppos;int ret; int tmp = count ;if (p >= BUFF_SIZE)return 0;if (tmp > BUFF_SIZE - p)tmp = BUFF_SIZE - p;ret = copy_to_user(buf, vbuf+p, tmp);*ppos +=tmp;return tmp;
}//写设备
static ssize_t haptics_write(struct file* filp,const char __user *buf,size_t count,loff_t* ppos)
{unsigned long p = *ppos;int ret;int tmp = count ;if (p > BUFF_SIZE)return 0;if (tmp > BUFF_SIZE - p)tmp = BUFF_SIZE - p;memset(vbuf,0,BUFF_SIZE);ret = copy_from_user(vbuf, buf, tmp);*ppos += tmp;return tmp;
}static struct file_operations haptics_fops=
{.owner = THIS_MODULE,.open = haptics_open,.release = haptics_release,.read = haptics_read,.write = haptics_write,
};static int __init haptics_init(void)
{int ret = 0;//内核层只能使用printk,不能使用printfprintk(KERN_EMERG "%s\n",__FUNCTION__); //输出等级为0//申请设备号if(major){dev_id = MKDEV(major,minor);//静态申请ret = register_chrdev_region(dev_id, HAPTICS_DEV_CNT, HAPTICS_DEV_NAME);if(0 == ret){printk("register_chrdev_region ok,major=%d minor=%d\n",MAJOR(dev_id),MINOR(dev_id));}}else{//动态申请ret = alloc_chrdev_region(&dev_id, minor, HAPTICS_DEV_CNT, HAPTICS_DEV_NAME);if(0 == ret){printk("alloc_chrdev_region ok,major=%d minor=%d\n",MAJOR(dev_id),MINOR(dev_id));}}//注册字符设备haptics_dev.owner = THIS_MODULE;cdev_init(&haptics_dev,&haptics_fops);cdev_add(&haptics_dev,dev_id,HAPTICS_DEV_CNT);//自动创建设备节点class = class_create(THIS_MODULE,HAPTICS_DEV_CLASS_NAME);device = device_create(class,NULL,dev_id,NULL,HAPTICS_DEV_NODE_NAME);return 0;
}static void __exit haptics_exit(void)
{printk(KERN_EMERG "%s\n",__FUNCTION__); //输出等级为0//释放设备号unregister_chrdev_region(dev_id,HAPTICS_DEV_CNT);//注销字符设备cdev_del(&haptics_dev);//删除设备节点device_destroy(class,dev_id);class_destroy(class);
}module_init(haptics_init);//驱动入口
module_exit(haptics_exit);//驱动出口MODULE_AUTHOR("<daokuan.zhug@si-in.com>");//声明作者信息
MODULE_DESCRIPTION("Haptic Driver V1.0.0"); //对这个模块作一个简单的描述
MODULE_LICENSE("GPL v2");//声明开源许可证// "GPL" 是指明 这是GNU General Public License的任意版本// “GPL v2” 是指明 这仅声明为GPL的第二版本
还有应用层代码
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
/*
argc:应用程序参数个数,包括应用程序本身
argv[]:具体的参数内容,字符串形式
./main <filename> <r:w> r表示读 w表示写
*/int main(int argc,char * argv[])
{char *wbuf = "Hello World\n";char rbuf[128];char* filename;int fd=0;if(argc!=3){printf("error usage\n");return -1;}filename=argv[1];fd = open(filename,O_RDWR);if(fd<0){printf("can not open file %s\n",filename);return -2;}if(0 == strcmp(argv[2],"r")){read(fd, rbuf, 128);//打印读取的内容printf("The content : %s\n", rbuf);}else if(0 == strcmp(argv[2],"w")){write(fd, wbuf, strlen(wbuf));}else{printf("error usage\n");}close(fd);
}
root@RK3588:/# insmod haptics.ko
[ 53.773224] haptics_init
[ 53.773264] register_chrdev_region ok,major=200 minor=0
root@RK3588:/# ./main /dev/haptics w
[ 56.270812] haptics_open
[ 56.270867] haptics_release
root@RK3588:/# ./main /dev/haptics r
[ 59.249993] haptics_open
The content : Hello World[ 59.250155] haptics_release
root@RK3588:/#
优化源码
对于上述驱动代码,有几点需要优化
class
名称、node
名称一般与驱动名称一致- 我们定义了很多全局变量,其实我们可以抽象出一个设备结构体,这个结构体去包含那些成员变量
- 一套驱动代码,是需要兼容多个设备的,所以应该尽量少用全局变量,需要思考如何兼容多设备
/** Silicon Integrated Co., Ltd haptic sih688x haptic driver file** Copyright (c) 2021 heater <daokuan.zhu@si-in.com>** This program is free software; you can redistribute it and/or modify it* under the terms of the GNU General Public License version 2 as published by* the Free Software Foundation*/#include <linux/init.h> //包含宏定义的头文件
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>#define HAPTICS_CDEV_MAJOR 200
#define HAPTICS_CDEV_MINOR 0
#define HAPTICS_CDEV_NAME "haptics"
#define HAPTICS_CDEV_CNT 1#define BUFF_SIZE 128struct haptics_dev
{struct cdev haptics_cdev;//字符设备dev_t dev_id;//设备号struct class *class; //类struct device *device; //设备int major;//主设备号int minor;//次设备号char vbuf[BUFF_SIZE];//数据缓冲区
};struct haptics_dev mdev;//定义一个设备结构体//打开设备
static int haptics_open(struct inode* inode,struct file * filp)
{printk("%s\n",__FUNCTION__);return 0;
}//关闭设备
static int haptics_release(struct inode* inode ,struct file* filp)
{printk("%s\n",__FUNCTION__);return 0;
}//读设备
static ssize_t haptics_read(struct file* filp, char __user *buf,size_t count,loff_t* ppos)
{unsigned long p = *ppos;int ret; int tmp = count ;if (p >= BUFF_SIZE)return 0;if (tmp > BUFF_SIZE - p)tmp = BUFF_SIZE - p;ret = copy_to_user(buf, mdev.vbuf+p, tmp);*ppos +=tmp;return tmp;
}//写设备
static ssize_t haptics_write(struct file* filp,const char __user *buf,size_t count,loff_t* ppos)
{unsigned long p = *ppos;int ret;int tmp = count ;if (p > BUFF_SIZE)return 0;if (tmp > BUFF_SIZE - p)tmp = BUFF_SIZE - p;memset(mdev.vbuf,0,BUFF_SIZE);ret = copy_from_user(mdev.vbuf, buf, tmp);*ppos += tmp;return tmp;
}static struct file_operations haptics_fops=
{.owner = THIS_MODULE,.open = haptics_open,.release = haptics_release,.read = haptics_read,.write = haptics_write,
};static int __init haptics_init(void)
{int ret = 0;//内核层只能使用printk,不能使用printfprintk(KERN_EMERG "%s\n",__FUNCTION__); //输出等级为0mdev.major = HAPTICS_CDEV_MAJOR;//预定义的主设备号mdev.minor = HAPTICS_CDEV_MINOR;//预定义的次设备号//申请设备号if(mdev.major)//如果预定义的主设备号不为0,则需要静态申请设备号{mdev.dev_id = MKDEV(mdev.major,mdev.minor);//静态申请ret = register_chrdev_region(mdev.dev_id, HAPTICS_CDEV_CNT, HAPTICS_CDEV_NAME);if(0 == ret){printk("register_chrdev_region ok,major=%d minor=%d\n",MAJOR(mdev.dev_id),MINOR(mdev.dev_id));}}else//如果预定义的主设备号为0,则需要动态申请设备号{//动态申请ret = alloc_chrdev_region(&mdev.dev_id, mdev.minor, HAPTICS_CDEV_CNT, HAPTICS_CDEV_NAME);if(0 == ret){mdev.major = MAJOR(mdev.dev_id);mdev.minor = MINOR(mdev.dev_id);printk("alloc_chrdev_region ok,major=%d minor=%d\n",MAJOR(mdev.dev_id),MINOR(mdev.dev_id));}}//注册字符设备mdev.haptics_cdev.owner = THIS_MODULE;cdev_init(&mdev.haptics_cdev,&haptics_fops);cdev_add(&mdev.haptics_cdev,mdev.dev_id,HAPTICS_CDEV_CNT);//自动创建设备节点mdev.class = class_create(THIS_MODULE,HAPTICS_CDEV_NAME);mdev.device = device_create(mdev.class,NULL,mdev.dev_id,NULL,HAPTICS_CDEV_NAME);return 0;
}static void __exit haptics_exit(void)
{//释放设备号unregister_chrdev_region(mdev.dev_id,HAPTICS_CDEV_CNT);//注销字符设备cdev_del(&mdev.haptics_cdev);//删除设备节点device_destroy(mdev.class,mdev.dev_id);class_destroy(mdev.class);printk(KERN_EMERG "%s\n",__FUNCTION__); //输出等级为0
}module_init(haptics_init);//驱动入口
module_exit(haptics_exit);//驱动出口MODULE_AUTHOR("<daokuan.zhug@si-in.com>");//声明作者信息
MODULE_DESCRIPTION("Haptics Driver V1.0.0"); //对这个模块作一个简单的描述
MODULE_LICENSE("GPL v2");//声明开源许可证// "GPL" 是指明 这是GNU General Public License的任意版本// “GPL v2” 是指明 这仅声明为GPL的第二版本
相关文章:

【Linux】最基本的字符设备驱动
前面我们介绍到怎么编译出内核模块.ko文件,然后还加载了这个驱动模块。但是,那个驱动代码还不完善,驱动写好后怎么在应用层使用也没有介绍。 字符设备抽象 Linux内核中将字符设备抽象成一个具体的数据结构(struct cdevÿ…...

利用 Llama 3.1模型 + Dify开源LLM应用开发平台,在你的Windows环境中搭建一套AI工作流
文章目录 1. 什么是Ollama?2. 什么是Dify?3. 下载Ollama4. 安装Ollama5. Ollama Model library模型库6. 本地部署Llama 3.1模型7. 安装Docker Desktop8. 使用Docker-Compose部署Dify9. 注册Dify账号10. 集成本地部署的 Llama 3.1模型11. 集成智谱AI大模型…...

Docker常用命令分享二
docker的用户组管理过程: 1、sudo : 可以让普通用户临时获得root用户的权限,来新建docker用户组 2、普通用户并没有使用sudo的权限 3、先要让root用户把testing用户加入到sudoers的授权文件中 4、sudoers的文件居然是只读的,先解决这个问…...

【一步步开发AI运动小程序】二十、AI运动小程序如何适配相机全屏模式?
引言 受小程序camera组件预览和抽帧图像不一致的特性影响,一直未全功能支持全屏模式,详见本系列文件第四节小程序如何抽帧;随着插件在云上赛事、健身锻炼、AI体测、AR互动场景的深入应用,各开发者迫切的希望能在全屏模式下应用&am…...

[Java基础] 运算符
[Java基础] 基本数据类型 [Java基础] Java HashMap 的数据结构和底层原理 目录 算术运算符 比较运算符 逻辑运算符 位运算符 赋值运算符 其他运算符 常见面试题 Java语言支持哪些类型的运算符? 请解释逻辑运算符&&和&的区别? 请解释条件运…...

[001-02-018].第05节:数据类型及类型转换
我的后端学习大纲 我的Java学习大纲 1、数据类型介绍: 1.0.计算机存储单位: 1.1.基本数据类型介绍: a.整型:byte、short、int、long 1.整型包括:byte、short、int、long,可如下图方式类比记忆࿱…...

Netty基础
Netty基础 一级目录I/O请求基础知识Netty如何实现自己的I/O模型 网络框架的选型 Netty整体架构Netty逻辑处理架构网络通信层事件调度层服务编排层 组件关系梳理Netty源码结构 netty是目前最流行的一款高性能java网络编程框架,广泛使用于中间件、直播、社交、游戏等领…...

602,好友申请二:谁有最多的好友
好友申请二:谁有最多的好友 实现 with tmp as (selectrequester_id idfrom RequestAcceptedunion allselectaccepter_id idfrom RequestAccepted )selectid,count(*) num from tmp group by id order by num desc limit 1;...

【Matlab算法MATLAB实现的音频信号时频分析与可视化(附MATLAB完整代码)
MATLAB实现的音频信号时频分析与可视化 前言正文:时频分析实现原理代码实现代码运行结果图及说明结果图:结果说明:总结前言 音频信号的时频分析是信号处理领域中的一个重要研究方向。它允许我们同时观察信号在时间和频率域的特性,为音频处理、语音识别、音乐分析等应用提供…...

界面耻辱纪念堂--可视元素03
更多的迹象表明,关于在程序里使用新的动态界面元素,微软的态度是不确定的,其中一个是仅仅需要对比一下Office97 里的“Coolbars”和“标准工具条”。Coolbar 按钮直到用户指针通过的时候才成为按钮(否则是平的)。 工具…...

国产龙芯处理器选择迅为2K1000开发板有资料
硬件配置国产龙芯处理器,双核64位系统,板载2G DDR3内存,流畅运行Busybox、Buildroot、Loognix、QT5.12 系统!接口全板载4路USB HOST、2路千兆以太网、2路UART、2路CAN总线、Mini PCIE、SATA固态盘接口、4G接口、GPS接口WIF1、蓝牙、Mini HDMI…...

MySQL 命令(持续更新)
将 MySQL 命令结果输出到文件中 通过 k8s MySQL pod 里的客户端连接到 MySQL 服务器 kubectl exec mysql-pod -- mysql -hx.x.x.x -uroot -proot -e SELECT * FROM db.table; > result.txt通过 k8s MySQL pod 的客户端连接 MySQL 服务器,直接进入到 MySQL 客户端…...

Linux下Docker方式Jenkins安装和配置
一、下载&安装 Jenkins官方Docker仓库地址:https://hub.docker.com/r/jenkins/jenkins 从官网上可以看到,当前最新的稳定版本是 jenkins/jenkins:lts-jdk17。建议下在新的,后面依赖下不来 所以,我们这里,执行doc…...

低代码框架参考
企业管理信息系统作为一类重要的应用软件系统,具有自己的特点,主要有两个方面: 1. 系统规模大,目前市场上常见的ERP系统一般都有几千个页面。 2. 页面逻辑相似性强。经过比较可以发现,大部分页面具有类似的功能&…...

2024 年 9 月区块链游戏研报:行业回暖,Telegram 游戏引发热潮
作者:Stella L (stellafootprint.network) 数据来源:Footprint Analytics Games Research Page 9 月份,区块链游戏代币的市场总值增长了 29.2%,达到 232 亿美元,日活跃用户(DAU)数量上升了 1…...

python爬虫登录校验之滑块验证、图形验证码(OCR)
在爬虫过程中,验证码和滑块验证是常见的反爬措施。针对这些挑战,通常采用OCR识别图形验证码和模拟滑块拖动来处理滑块验证。以下是如何处理这两种类型验证的详细方法。 1. 图形验证码(OCR) a. 使用 tesserocr 和 Pillow 处理图形…...

(一)Python程序结构
1、Python写模块 使用缩进分层来写模块; 2、缩进规范 最好使用同一种缩进方式,统一使用tab或者空格键; 3、换行符:\n; print("窗前明月光\n疑似地上霜") 4、续行符号:\; 注意1…...

二叉树——相同的树
还是应用递归的思想,将这个问题拆分为三个部分,第一:当两棵树均为空树,或者访问到最后的子树时均为空树时,则返回true。第二:当两棵树中其中一棵树为空树,另一棵树不是空树,证明两棵…...

探秘 1688 商品详情接口:高并发批量采集的实战攻略
在进行 1688 商品详情接口批量采集并考虑高并发使用时,你可以从以下几个方面着手: 一、技术选型 选择合适的编程语言和框架:例如 Python 的 Scrapy 框架或者 Java 的 Spring Boot 结合相关爬虫库等,这些工具可以帮助你高效地实现…...

【C语言】sizeof
tmp_buf = (char *)malloc(size)不可以直接使用`sizeof`来查看动态分配内存的大小。在C语言中,`sizeof`操作符是在编译时确定类型或对象的大小,并不能用于动态获取malloc分配的内存大小。 下面解释为什么不能用`sizeof(tmp_buf)`查看动态分配的内存大小: 1. 编译时 vs 运行时…...

2024-10-15 问AI: [AI面试题] 人工智能中使用了哪些不同的搜索算法?
文心一言 人工智能中使用了多种不同的搜索算法来解决问题,这些算法各有特点,适用于不同的场景和问题类型。以下是一些常见的人工智能搜索算法: 深度优先搜索(DFS): 是一种基于树或图的搜索算法。核心思想是…...

2024 年 04 月编程语言排行榜,PHP 排名创新低?
编程语言的流行度总是变化莫测,每个月的排行榜都揭示着新的趋势。2024年4月的编程语言排行榜揭示了一个引人关注的现象:PHP的排名再次下滑,创下了历史新低。这种变化对于PHP开发者和整个技术社区来说,意味着什么呢? P…...

Element中el-table组件设置max-height右侧出现空白列的解决方法
之前就出现过这个情况,没理过,因为不影响啥除了不美观...但今天看着实在是难受,怎么都不顺眼(可能是我自己烦躁--) 试了很多网上的方法,都不得行,后面发现了这篇文章,解决了! 感谢! Element中t…...

unity学习-全局光照(GI)
在全局光照(Lighting)界面有两个选项 Realtime Light(实时光照):在项目中会提前计算好光照以及阴影的程序,当你需要调用实时全局光照的时候会将程序调用出来使用 Mixed Light(烘焙光照&#x…...

记录Centos7 漫漫配置路
记录Centos7 漫漫配置路 一、 配置源二、 clinfo三、 PCL 配置1. 依赖2. eigen3. boost4. flann5. pcl 四、YAML-CPP五、 miniconda 安装 python3.6 和 pytorch六、libbot 配置1. 容易安装的依赖2. 需要源码安装的依赖3. [libbot](https://github.com/libbot2/libbot2) 简单地说…...

论文 | OpenICL: An Open-Source Framework for In-context Learning
主要内容: 2. 提供多种 ICL 方法: 3. 完整的教程: 4. 评估和验证: 背景: 随着大型语言模型 (LLM) 的发展,上下文学习 (ICL) 作为一种新的评估范式越来越受到关注。问题: ICL 的实现复杂…...

尚硅谷rabbitmq 2024 Federation配置 第60节答疑
rabbitmq联邦队列怎么做 要在 RabbitMQ 中设置联邦队列(Federated Queues),你需要遵循以下步骤。联邦队列允许你在不同的 RabbitMQ 实例之间共享队列,从而实现消息的分布式处理和高可用性。 ### 步骤 1:安装 RabbitMQ…...

Ubuntu编译MySQL驱动连接QT
1、安装MySQL 安装MySQL软件以及驱动。 sudo apt-get install mysql-serversudo apt install mysql-clientsudo apt-get install libmysqlclient-dev 2、编译qmysql驱动 2.1、修改mysql.pro 找到Qt源码中的mysql.pro项目文件,一般位于:/opt/Qt/5.15…...

时间序列预测(七)——梯度消失(Vanishing Gradient)与梯度爆炸(Exploding Gradient)
目录 一、定义 二、产生原因 三、解决方法: 梯度消失与梯度爆炸是深度学习中常见的训练问题,它们主要发生在神经网络的反向传播过程中,使得模型难以有效学习。 一、定义 1、梯度消失(Vanishing Gradient)…...

ARM assembly 12: GCD(最大公约数)计算
首先,我们看看GCD(Greatest Common Divisor)的CPP实现 int gcd(int a, int b) {if(b 0) return a;return gcd(b, a%b); }基于下面的gcd.s文件,我们尝试实现gcd函数 //gcd.s .global main .extern fopen, fprintf, fclose, printf, atoi.section .dat…...