i.MX6ULL(十六) linux 设备驱动

一 简介

Linux设备驱动是指驱动Linux内核与硬件设备进行通信的软件模块。设备驱动通常分为两类:字符设备驱动和块设备驱动。

设备驱动的主要功能包括:

  1. 设备初始化:在系统启动时,设备驱动需要初始化相应的硬件设备,设置设备的寄存器和接口等参数,以确保设备能够正常工作。
  2. 设备控制:设备驱动需要提供一些接口,用于控制设备的各种操作,如打开设备、读取数据、写入数据、关闭设备等。
  3. 中断处理:设备驱动需要处理硬件设备的中断请求,在中断发生时执行相应的中断处理程序,以便及时响应设备的各种事件和请求。
  4. 数据传输:设备驱动需要实现数据的传输功能,包括从硬件设备读取数据和向硬件设备写入数据等操作。
  5. 错误处理:设备驱动需要处理设备发生的错误和异常情况,例如设备读写错误、中断丢失等。

1.1 设备驱动分类

1.1.1 块设备驱动

块设备驱动是指以块为单位与设备进行通信的驱动程序,例如硬盘、固态硬盘、USB闪存驱动器等存储设备驱动

块设备驱动程序通常采用分层结构,包括以下几个层次:

  1. 设备驱动程序层:这一层直接与硬件设备进行通信,并控制设备的各种操作。
  2. 存储卷管理器层:这一层负责管理存储卷(如硬盘分区、逻辑卷等),并提供对卷的访问接口。
  3. 文件系统层:这一层提供对卷的的文件系统接口,使得用户可以按照文件系统的目录结构访问和管理文件。

1.1.2 字符设备驱动

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节
流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC SPI
LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

I2C/SPI 设备可以是字符设备、块设备或网络设备,具体取决于其功能和应用场景。

然而,对于某些I2C设备来说,它们可能被归类为块设备或网络设备等其他类型的设备。例如,某些I2C存储器(如EEPROM)可以被视为块设备的例子,因为它们以数据块为单位进行数据传输。而其他一些I2C设备,如温度传感器或光传感器等,则可以被视为网络设备,因为它们以数据包为单位进行数据传输。

因此,并不是所有的I2C/SPI设备都是字符设备,而是根据其功能和应用场景可能被归类为不同类型的设备。不同类型的设备需要不同的驱动程序来实现对它们的访问和控制。

Linux 应用程序对驱动程序的调用如图 40.1.1 所示

Linux 中一切皆为文件,驱动加载成功以后会在“ /dev ”目录下生成一个相应的文件,应
用程序通过对这个名为“ /dev/xxx (xxx 是具体的驱动文件名字 ) 的文件进行相应的操作即可实
现对硬件的操作。比如现在有个叫做 /dev/led 的驱动文件,此文件是 led 灯的驱动文件。应用程
序使用 open 函数来打开文件 /dev/led ,使用完成以后使用 close 函数关闭 /dev/led 这个文件。 open 和 close 就是打开和关闭 led 驱动的函数,如果要点亮或关闭 led ,那么就使用 write 函数来操 作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开 led 的控制参数。如果要获取
led 灯的状态,就用 read 函数从驱动中读取相应的状态。
应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。
当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开 /dev/led 这个驱动,因为用
户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空
间“陷入”到内核空间,这样才能实现对底层驱动的操作。

设备驱动原理

2.1 驱动模块的加载和卸载

2.1.1 加载

Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启
动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块 (Linux 下模块扩展名为 .ko) ,在
Linux 内核启动以后使用“ insmod ”命令加载驱动模块。在调试驱动的时候一般都选择将其编译
为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。
而且在调试的时候只需要加载或者卸载驱动模块即可
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

驱动编译完成以后扩展名为.ko,有两种命令可以加载驱动模块:insmodmodprobe

insmod 命令不能解决模块的依赖关系
比如 drv.ko 依赖 first.ko 这个模块,就必须先使用 insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。
modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,
insmod drv.ko
modprobe   drv.ko

2.1.2 卸载

驱动模块的卸载使用命令“ rmmod ”即可,比如要卸载 drv.ko
rmmod drv.ko
modprobe -r drv.ko //
使用 modprobe 命令可以卸载掉驱动模块所依赖的其他模块,前提是这些依赖模块已经没
有被其他模块所使用,否则就不能使用 modprobe 来卸载驱动模块。所以对于模块的卸载,还是 推荐使用 rmmod 命令

2.2 地址映射 MMU

MMU 全称叫做 Memory Manage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU ,但是现在
Linux 内核已经支持无 MMU 的处理器了。 MMU 主要完成的功能如下:
①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性
对于 32 位 的处理器来说,虚拟地址范围是 2^32=4GB
Linux 内核启动的时候会初始化 MMU ,设置好内存映射,设置好以后 CPU 访问的都是虚
拟地址。
比如I.MX6ULL 的 GPIO1_IO03 引脚的复用寄存器 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068
如果没有开启 MMU 的话 直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。
现在开启 了 MMU ,并且设置了内存映射,因此就不能直接向 0X020E0068 这个地址写入数据了。我们必 须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内 存和虚拟内存之间的转换,需要用到两个函数:ioremap iounmap

2.2.1 ioremap 函数

ioremap 函数用于获取指定物理地址空间对应的虚拟地址空间,定义在 arch/arm/include/asm/io.h 文件中,定义如下:

示例代码 41.1.1.1 ioremap 函数
 #define ioremap(cookie,size) __arm_ioremap((cookie), (size), 
MT_DEVICE)

 void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, 
unsigned int mtype)
 {
 return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));
 }
此函 数有三个参数和一个返回值,这些参数和返回值的含义如下:
phys_addr :要映射的物理起始地址。
size :要映射的内存空间大小。
mtype ioremap 的类型,可以选择 MT_DEVICE MT_DEVICE_NONSHARED
MT_DEVICE_CACHED MT_DEVICE_WC ioremap 函数选择 MT_DEVICE
返回值: __iomem 类型的指针,指向映射后的虚拟空间首地址。

2.2.2 iounmap 函数

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射, iounmap 函数原
型如下
void iounmap (volatile void __iomem *addr)
iounmap 只有一个参数 addr ,此参数就是要取消映射的虚拟地址空间首地址。

2.2.3 I/O 内存访问函数

linux通过操作虚拟地址来间接操作物理地址

1 、读操作函数
 u8 readb(const volatile void __iomem *addr)
 u16 readw(const volatile void __iomem *addr)
 u32 readl(const volatile void __iomem *addr)

写操作函数

 void writeb(u8 value, volatile void __iomem *addr)
 void writew(u16 value, volatile void __iomem *addr)
 void writel(u32 value, volatile void __iomem *addr)

2.3 设备号

Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分 组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备

2.3.1组成

Linux 提供了 一个名为 dev_t 的数据类型表示设备号, dev_t 定义在文件 include/linux/types.h 里面,
dev_t  __u32 类型的其中高 12 位为主设备号,低 20 位为次设备号。因此 Linux
系统中主设备号范围为 0~4095 ,所以大家在选择主设备号的时候一定不要超过这个范围。

2.3.2 设备号分配与注销

静态分配
有些常用的设备号已经被 Linux 内核开发者给分配掉 了,具体分配的内容可以查看文档 Documentation/devices.txt 。并不是说内核开发者已经分配掉 的主设备号我们就不能用了,具体能不能用还得看我们的硬件平台运行过程中有没有使用这个 主设备号,使用“cat /proc/devices ”命令即可查看当前系统中所有已经使用了的设备号。
动态分配
静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用
的。而且静态分配设备号很容易带来冲突问题, Linux 社区推荐使用动态分配设备号,在注册字
符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。
卸载驱动的时候释放掉这个设备号即可,设备号的申请函数如下:
申请函数
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
baseminor 次设备号起始地址, alloc_chrdev_region 可以申请一段连续的多个设备号,这
些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递
增。 一般 baseminor 为 0,也就是说次设备号从 0 开始。
count 要申请的设备号数量。
注册函数
如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
3 注销函数
注销字符设备之后要释放掉设备号,设备号释放函数如下:
void unregister_chrdev_region(dev_t from, unsigned count)
from :要释放的设备号。
count 表示从 from 开始,要释放的设备号数量。

2.4 新的字符设备注册方法

2.4.1、字符设备结构

Linux 中使用 cdev 结构体表示一个字符设备, cdev 结构体在 include/linux/cdev.h 文件中
的定义如下:
 struct cdev {
 struct kobject kobj;
 struct module *owner;
 const struct file_operations *ops;
 struct list_head list;
 dev_t dev;
 unsigned int count;
 };
cdev 中有两个重要的成员变量: ops dev ,这两个就是字符设备文件操作函数集合
file_operations 以及设备号 dev_t

2.4.2cdev_init 函数

定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化, cdev_init 函数原型如下:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合。
使用 cdev_init 函数初始化 cdev 变量的示例代码如下:

/* newchrled设备结构体 */
struct newchrled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

	
/* 设备操作函数 */
static struct file_operations newchrled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};


/* 2、初始化cdev */
	newchrled.cdev.owner = THIS_MODULE;
	cdev_init(&newchrled.cdev, &newchrled_fops);

2.4.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 是要添加的设备数量。

2.4.4、cdev_del 函数

卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备, cdev_del
函数原型如下:
void cdev_del(struct cdev *p)
参数 p 就是要删除的字符设备。

2.5 自动创建设备节点

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

2.5.1 mdev 机制

udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除, udev 可以检
测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用
modprobe 命令成功加载驱动模块以后就自动在 /dev 目录下创建对应的设备节点文件 , 使用
rmmod 命令卸载驱动模块以后就删除掉 /dev 目录下的设备节点文件。 使用 busybox 构建根文件
系统的时候,busybox 会创建一个 udev 的简化版本—mdev 所以在嵌入式 Linux 中我们使用
mdev 来实现设备节点文件的自动创建与删除,Linux 系统中的热插拔事件也由 mdev 管理,在
/etc/init.d/rcS 文件中如下语句:
echo /sbin/mdev > /proc/sys/kernel/hotplug
如何通过 mdev 来实现设备文件节点的自动创建与删除?
2.5.1.1 创建和删除类
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添
加自动创建设备节点相关代码。首先要创建一个 class 类, class 是个结构体,定义在文件
include/linux/device.h 里面。 class_create 是类创建函数, class_create 是个宏定义,
struct class *class_create (struct module *owner, const char *name)
卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy ,函数原型如下:
void class_destroy(struct class *cls);
2.5.1.2 创建设备
使用 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 *class, dev_t devt)

2.6 设置文件私有数据

每个硬件设备都有一些属性,比如主设备号 (dev_t) ,类 (class) 、设备 (device) 、开关状态 (state)
等等,在编写驱动的时候你可以将这些属性全部写成变量的形式,编写驱动 open 函数的时候将设备结构体作为私有数据添加到设备文件中,如下
/* newchrled设备结构体 */
struct newchrled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

/*
 * @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;
}

2.7 通用设备驱动创建模版  参考示例

#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 <linux/cdev.h>
#include <linux/device.h>

#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>




/* newchrled设备结构体 */
struct newchrled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

struct newchrled_dev newchrled;	/* led设备 */



/*
 * @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)
{
	
	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,
};

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init led_init(void)
{
	...

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (newchrled.major) {		/*  定义了设备号 */
		newchrled.devid = MKDEV(newchrled.major, 0);
		register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);	/* 申请设备号 */
		newchrled.major = MAJOR(newchrled.devid);	/* 获取分配号的主设备号 */
		newchrled.minor = MINOR(newchrled.devid);	/* 获取分配号的次设备号 */
	}
	printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);	
	
	/* 2、初始化cdev */
	newchrled.cdev.owner = THIS_MODULE;
	cdev_init(&newchrled.cdev, &newchrled_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);

	/* 4、创建类 */
	newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.class)) {
		return PTR_ERR(newchrled.class);
	}

	/* 5、创建设备 */
	newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.device)) {
		return PTR_ERR(newchrled.device);
	}
	
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{

    ....
	/* 注销字符设备驱动 */
	cdev_del(&newchrled.cdev);/*  删除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("zuozhongkai");
// MODULE_VERSION("4.1.15-g3dc0a4b SMP preempt mod_unload modversions ARMv7 p2v8")

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

8、链路层以太网协议,ARP协议32

网络层IP协议描述了通信中的起点到终点&#xff0c;但是数据不是飞过去的&#xff0c;是经过了大量的中间节点转发完成的。 一、以太网协议 1、MAC地址 物理硬件地址&#xff0c;是每一块网卡在出厂时设定的地址&#xff0c;固定且不可修改&#xff08;早期&#xff0c;现在可…

密码学学习笔记(十五):ECDSA - 椭圆曲线数字签名算法

椭圆曲线数字签名算法是DSA的一种椭圆曲线变体&#xff0c;它发明的初衷只是避免使用Schnorr签名的专利。椭圆曲线数字签名算法依赖于验证器中的私钥和主机用于验证验证器的公钥。它的缺点和DSA一样&#xff0c;它也没有提供安全性证明。 椭圆曲线算法 DSS&#xff08;数字签…

【Vue面试题系列】四

VNode有哪些属性&#xff1f; Vue内部定义的Vnode对象包含了以下属性&#xff1a; __v_isVNode: true&#xff0c;内部属性&#xff0c;有该属性表示为Vnode __v_skip: true&#xff0c;内部属性&#xff0c;表示跳过响应式转换&#xff0c;reactive转换时会根据此属性进行判断…

Django实现接口自动化平台(十四)测试用例模块Testcases序列化器及视图【持续更新中】

相关文章&#xff1a; Django实现接口自动化平台&#xff08;十三&#xff09;接口模块Interfaces序列化器及视图【持续更新中】_做测试的喵酱的博客-CSDN博客 本章是项目的一个分解&#xff0c;查看本章内容时&#xff0c;要结合整体项目代码来看&#xff1a; python django…

一条命令解决端口占用,开启mysql

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录 端口占用开启mysql 端口占用 如果发现 8080 端口被占用可以使用命令 sudo lsof -t -i:8080 | sudo xargs kill -9 查找并杀死相应的进程。 开启mysql 打开命令提示符或终端。如果您已经安装了MySQL&…

Cesium Terrain Builder (CTB) 简单使用_地形切片

Cesium Terrain Builder (CTB) 简单使用_地形切片 目录 Cesium Terrain Builder (CTB) 简单使用_地形切片 官网地址&#xff1a; winr&#xff08;cmd&#xff09;打开命令提示符工具运行&#xff1a; Create a GDAL Virtual Dataset (optional) Create Cesium Terrain fi…

EasyCVR视频融合平台能正常播放其他协议流,但无法播放HLS流的原因排查

EasyCVR基于云边端一体化架构&#xff0c;支持海量视频汇聚管理&#xff0c;平台支持多协议与多类型设备接入&#xff0c;具体包括国标GB28181、RTMP、RTSP/Onvif、海康Ehome、海康SDK、大华SDK、宇视SDK等&#xff0c;能对外分发RTMP、RTSP、HTTP-FLV、WS-FLV、HLS、WebRTC等。…

部署langchain+chatglm

先参考&#xff1a;window零基础部署langchain-ChatGLM_飞奔的屎壳郎的博客-CSDN博客 安装一部分&#xff0c; 1.GCC安装 gcc64位下载 一定要装64位的gcc&#xff0c;因为我的电脑是w10 64位的&#xff0c;装32位运行langchain报错并配置环境变量 可直接用压缩包中的文件&am…

八股文(消息队列)

文章目录 1. RabbitMQ特点2. 如何保证消息的可靠性3. RabbitMQ消息的顺序性4. 实现RabbitMQ的高可用性5. 如何解决消息队列的延时以及过期失效问题&#xff1f;6. RabbitMQ死信队列7. RabbitMQ延迟队列8.RabbitMQ的工作模式9. RabbitMQ消息如何传输10. 核心概念10.1 生产者和消…

python接口自动化(三十六)-封装与调用--流程类接口关联续集(详解)

简介 上一篇已经给大家都介绍过了流程类接口关联&#xff0c;但是由于博客的登录机制改变&#xff0c;所以没有办法给小伙伴们实战演练一下&#xff0c;那么这篇就按照上一篇计划的用jenkins来给小伙伴们演示一下流程类接口的封装和调用&#xff0c;其实很简单&#xff0c;就是…

python_day11_pymysql

SQL基础语法回忆 show DATABASES;use world;-- SELECT DATABASES();show TABLES;CREATE TABLE Student(id int,name VARCHAR(10),age int,gender VARCHAR(5&#xff09; );删除表 # 删除表 DROP TABLE Student;插入操作 insert into student(id) VALUES(1),(2),(3);insert i…

HTML+CSS+JavaScript:渲染柱形统计图

一、需求 用户输入四个季度的数据&#xff0c;根据数据生成柱形统计图&#xff0c;浏览器预览效果如下 二、完整代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content&q…

vue-sticky简单使用(实现吸顶效果)

参考链接 vue-sticky&#xff1a;在页面滚动时将指定元素固定在窗口上的某个位置 生效条件如下&#xff1a; 1、父元素不能设置 overflow:hidden 或者 overflow:auto 属性 2、至少指定 top 、bottom 、left 、right 4 个值中的一个&#xff0c;否则只会处于相对定位 3、父元素…

云曦期末复现

serialize 代码审计&#xff0c;给1传参&#xff0c;满足password的值为yunxi&#xff0c;那么反序列化前就会执行__wakeup函数&#xff0c;从而得到flag.php&#xff0c;但是password的值被定死为1&#xff0c;利用PHP反序列化的字符逃逸: <?php error_reporting(0); hig…

android studio 离线打包配置push模块

1.依赖引入 SDK\libs aps-release.aar, aps-unipush-release.aar, gtc.aar, gtsdk-3.2.11.0.aar, 从android studio的sdk中找到对应的包放到HBuilder-Integrate-AS\simpleDemo\libs下面 2.打开build.gradle&#xff0c;在defaultConfig添加manifestPlaceholders节点&#xff0c…

【数据结构导论】第 7 章:排序

目录 一、概述 &#xff08;1&#xff09;基本概念 &#xff08;2&#xff09;排序分类 &#xff08;3&#xff09;排序文件的物理表示 —— 数组表示 二、插入排序&#xff08;通过比较插入实现排序&#xff09; &#xff08;1&#xff09;直接插入排序 ① 过程 ② 算…

SqlServer数据库【基础-更删改查】

一、创建语句 &#xff08;1&#xff09;创建数据库 1.检查系统中是否存在这个数据库&#xff0c;存在则删除 格式&#xff1a; if exists(select * from sysdatabases where name数据库名) drop database 数据库名 go例子&#xff1a; if exists(select * from sysdataba…

C 知识积累 替换gets函数 Linux C 语法分析 switch和if else的比较

目录 替换gets函数gets()用处gets()的危险之处gets()的几种替代方法一、用%c循环输入直到遇到换行结束二、用getchar()循环输入直到遇到换行结束三、scanf的另一种用法四、c中的getline()方法五、解决方案使用fgets代替 回车与换行一.知其然二.知其所以然 关键字&#xff0c;操…

BI-SQL丨XML

XML SQL Server中&#xff0c;存在一种特殊类型的数据&#xff0c;就是XML数据类型。 可能看到这里&#xff0c;小伙伴都会产生疑惑&#xff0c;XML不是Web语言么&#xff1f;为什么在SQL Server里面也会有XML数据类型&#xff1f; 这个就要从SQL Server的应用开始说起了&am…

JavaSwing+MySQL的酒店管理系统

点击以下链接获取源码&#xff1a; https://download.csdn.net/download/qq_64505944/88063706?spm1001.2014.3001.5503 JDK1.8、MySQL5.7 功能&#xff1a;散客开单&#xff1a;完成散客的开单&#xff0c;可一次最多开5间相同类型的房间。 2、团体开单&#xff1a;完成团体…