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

【Linux驱动】字符设备驱动程序框架 | LED驱动

🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
图

目录

  • 🏀Hello驱动程序
    • ⚽驱动程序框架
    • ⚽编程
  • 🏀LED驱动
    • ⚽配置GPIO
    • ⚽编程
      • 驱动程序
        • 映射虚拟地址
      • 应用层
  • 🏀总结

🏀Hello驱动程序

Linux下一切皆文件,使用open系统调用打开文件时会得到一个文件描述符,也被叫做文件句柄

图

如上图所示,在打开该文件进程的PCB中有一个文件描述符表的指针struct file_struct* files,该指针指向属于该进程的文件描述符表,本质上就是一个数组,所谓文件句柄就是该数组的下标,每打开一个文件,就在该数组中放入这个文件的struct file*指针,并且返回数组的下标。

struct file结构体的定义,在使用open时传入的flags、mode等参数都会被记录在这个结构体中,在读写文件时,文件的当前偏移地址也会保存在f_pos成员里。

  • 打开字符设备节点时,内核中也会打开一个对应的struct file结构体。

字符设备节点是一种特殊类型的文件,用于表示字符设备。这些设备通常以字符为单位进行数据的输入和输出,例如键盘或者串口。

  • 字符设备节点文件通常位于 /dev目录下。

图
如上图所示,当应用层使用open打开字符设备节点时,在内核中会创建一个struct file结构体,并且将传入的参数记录到该结构体中,而且会使用file_operations* f_op结构体成员中的open函数指针来打开设备节点。

当应用层使用read/write函数进行读写时,也会使用file_operations* f_op结构体成员中的read/write函数指针来实现读写目的,这个结构体是由字符设备驱动程序提供的。


图
如上如所示file_operations结构体的部分定义,其中有readwrite以及open等函数指针,当应用层使用相应的open/write/read系统调用接口时,最终会调用内核层中该结构体里对应的函数指针来实现目的。

⚽驱动程序框架

图
如上图所示,驱动程序的目的就是要在应用层调用open/write/read等系统调用接口时,在内核层中调用file_operations里的open/write/read函数指针,而函数指针指向的drv_open/drv_read/drv_write等驱动层函数是由我们自己定义的。在驱动程序中,实现硬件的初始化,以及数据读写。

  1. 定义自己的 file_operations 结构体。

前面本喵说过,file_operations结构体是由驱动程序提供的,而驱动程序又是我们写的,所以我们首先要做的就是定义自己的file_operations 结构体。

图
如上图所示,在hello_drv.c源文件中定义file_operations结构体变量,并且进行初始化,给函数指针赋值相应的函数。

  • owner:是一个指向模块所有者的指针,是必须设置的。
  • gcc编译器中增加了使用.结构体成员 = xxx来给成员变量赋值的语法。
  1. 实现对应的drv_open/drv_read/drv_write等函数。

图
如上图所示驱动函数的定义,由于现在讲解的是框架,所以本喵在函数里没有写任何操作,只是使用printk打印一些调试信息。

  • 内核中打印调试信息只能使用printk,不能使用printf
  • 使用命令行指令dmesg就能看到日志中的调试信息。
  1. 确定主设备号,也可以让内核自己分配。

每一个字符设备都有一个主设备号,用于标识设备的类型或者设备驱动程序。不同类型的设备或不同的驱动程序会有不同的主设备号。例如,所有的串口设备可能共享一个主设备号,而所有的打印机设备可能又共享另一个不同的主设备号。

  • 主设备号就像是一个类,可以用这个类定义出多个实例。
  • 主设备号可以由我们自己决定,也可以将其设置为0,让内核自己分配。

建议让内核去分配主设备号,因为我们并不是很清楚有哪些主设备号,自己决定的是否已经被使用。

/* 确定主设备号 */
static int major = 0

定义一个全局变量major来表示主设备号,暂时先给它赋值为0。

  1. file_operations 结构体注册到内核。

暂时可以认为在内核中有一个chardevs[]数组,该数组中存放的是字符设备节点的主设备号,当使用某一类字符设备时,会从该数组中寻找对应设备的file_operations结构体对象。

所谓注册就是将我们自己的字符设备主设备号注册到这个数组中,使用register_chrdev函数来实现:

major = register_chrdev(0, "hello", &hello_drv); 
  • 第一个参数是主设备号,如果传入的是0,则返回内核自动分配的主设备号。
  • 第二个参数是字符设备的名称,是一个字符串。
  • 第三个参数是我们提供的file_operations结构体指针。

调用该函数后,主设备号和file_operations以及设备名称就绑定在了一起,而且主设备号放入到了chardevs[]数组中。

  1. 定义入口函数,安装驱动程序时,就会去调用这个入口函数 。

file_operations注册到内核中是由入口函数完成的,入口函数使用宏__init修饰:

图

如上图所示,在安装驱动程序的时候,内核会自动去调用这个hello_init函数,在该函数中完成:

  • 注册file_operations结构体到内核中,并得到主设备号。

  • 创建设备信息类,使用class_create实现,该类中包含内核需要的设备节点信息,更方便内核去创建节点。

  • 创建设备节点,使用device_create实现,此时在内核中会生成一个/dev/hello路径用来表示节点设备。

  1. 出口函数,卸载驱动程序时,就会去调用这个出口函数。

有入口函数就有出口函数,出口函数使用宏__exit来修饰:

图

如上图所示函数,在卸载时会由内核自动调用,都要卸载了,就要将前面注册到内核中的字符设备移除,使用unregister_chrdev实现,并且将前面创建的字符设备类和设备节点都销毁,使用class_destroydevice_destroy实现。

  1. 完善设备信息
module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");

使用module_init告诉内核hello_init函数是入口函数,使用module_exit告诉内核hello_exit是出口函数。

使用MODULE_LICENSE表明遵循GPL协议,否则是无法使用我们的驱动程序的。


至此已经实现了一个驱动程序框架,在命名上以hello为例,这个可以作为一个模板,在使用的时候只需要将hello改为相应的设备名字即可,然后再在我们自己实现的驱动函数中增加一些具体的代码。

⚽编程

下面用上面的框架来实现一个不涉及硬件操作的hello驱动程序:

  • 命令行输入./hello_drv_test -w abc,将abc字符串写入内核缓冲区中。
  • 命令行输入./hello_drv_test -r,从内核缓冲区中读出刚刚输入的字符串。

驱动层代码:

图
如上图所示代码,只需要实现file_operations结构体中的readwrite方法,也就是对应的hello_drv_readhello_drv_wite函数,其他的没有用到。

  • 使用__user修饰的buf,表示这是来自用户层的缓冲区。

用户层的缓冲区不能使用strcpy等应用层函数直接操作,而是必须要使用内核提供的复制函数:

  • copy_to_user:从内核缓冲区复制数据到用户缓冲区,第一个参数是目的buf,第二个参数是源kernel_buffer,第三个参数是要复制的字节数。
  • copy_from_user:从用户缓冲区复制数据到内核缓冲区,参数参考上面。

由于定义的缓冲区大小是1024,防止越界,使用宏MIN将1024和用户层指定的数据大小size作比较,取较小值作为复制数据的大小。

应用层代码:

tu
如上图所示,使用命令行参数传入-w-r,以及要写入的字符串,在main函数中:

  • 先打开/dev/hello目录下的字符设备节点,在应用层看来,这就是一个普通文件。
  • 根据命令行中的第二个参数判断:
    • -w:使用write将第三个参数的字符串写入到内核缓冲区中。
    • -r:使用read将内核缓冲区的数据读出来。

这里应用层的wite最终会调用驱动层中的hello_drv_write,应用层的read最终会调用驱动层中的hello_drv_read

交叉编译:

图

如上图所示,在命令行中输入上面的三条指令,设置环境变量,从而实现交叉编译环境的配置。

图
如上图所示,使用该Makefile文件来编译驱动文件hello_drv.c和应用层测试文件hello_drv_test.c,暂时不用这些指令是什么意思,直接用就星。

tu

如上图所示,生成hello_drv.kohello_drv_test两个文件:

  • .ko后缀:表示这是一个内核模块,用于在运行时向内核动态添加功能,而不需要重新编译整个内核。

挂载根文件系统:
图
如上图,将生成的hello_drv.ko驱动模块文件和hello_drv_test测试可执行程序复制到nfs_rootfs文件下。

图
如上图所示,在IMX6ULL开发板上,通过串口工具执行mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt指令,将刚刚进行编译等操作的Linux服务器里的根文件系统挂载到开发版上。

  • nfs_rootfs是一个通过网络文件系统(NFS)挂载的根文件系统。
  • 网络文件系统(NFS):NFS 是一个分布式文件系统协议,它允许用户在网络上访问存储在远程计算机上的文件,就像访问本地存储的文件一样。
  • 根文件系统(rootfs):根文件系统包含操作系统的核心组件,如可执行文件、库文件、配置文件等。

此时在开发板上就相当于有了一个Linux操作系统,实际上用的是服务器的系统,可以看到,服务器的根文件系统中有什么,挂载之后的/mnt里就有什么。

安装驱动程序:
图
如上图所示,进入开发板挂载的根文件/mnt中,找到我们的hello_drv.ko所在位置,然后执行insmod hello_drv.ko指令安装设备节点的驱动程序,安装完毕后,在/dev设备节点中可以看到hello设备节点。

测试:

tu
如上图所示,执行应用层测试程序hello_drv_test

  • 在执行可执行程序的命令行参数中使用-w选项,写入A-Big-MiaoMi字符串到内核缓冲区中。
  • 再使用-r选项,从内核缓冲区中读取刚刚写入数据,结果是APP read: A-Big-MiaoMi

根据上面测试结果,说明我们的第一个驱动程序就写成功了。

🏀LED驱动

⚽配置GPIO

配置GPIO通用步骤:
tu
如上图所示IMX6ULLGPIO框图,输出功能的配置和其他芯片一样分为四步:

  • 使能GPIO组:设置CCM寄存器组中的CCGRx寄存器中的相应位CGx来使能对应的GPIO组。
  • 选择GOIO为通用输入输出功能:设置IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPERx寄存器中的MUX_MODE位将IO口设为通用输入输出功能。
  • 选择方向:设置GPIOx_GDIR中的相应位,0表示输入,1表示输出。
  • 写数据寄存器:设置GPIOx_DR中的相应位,0表示输出低电平,1表示输出高电平。

具体单板:

tu
如上图所示本喵的IMX6ULL开饭上LED2的电路图:

  • GPIO5_3输出低电平,LED灯亮。
  • GPIO5_3输出低电平,LED灯灭。

按照上面的配置步骤,寻找GPIO5_3的那几个寄存器和对应的比特位:

  1. CCM_CCGR1中的CG15

图
如上图所示,CCM_CCGR1中的CG15是保留的,在IMX6ULL中,GPIO5这组GPIO默认使能,所以不用设置。CCM_CCGR1的绝对地址是0x020C4000 + 0x6C = 0x020C406C

  1. IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3

图
如上图所示,将 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3寄存器中的MUX_MODE4位配置为101,选择GPIO5_IO03为通用输入输出模式。该寄存器的绝对地址是0x02290000 + 0x14 = 0x02290014

  1. GPIO5_GDIR

图
如上图,使用的是GPIO5_3IO口,所以要配置GPIO5_GDIR中的bit3,该位为1,表示输出,该位为0,表示输入。该寄存器的偏移量是0x4

图
如上图所示,GPIO5的基地址是0x020AC000,所以GPIO5_GDIR的绝对地址是0x020AC000 + 0x4 = 0x020AC004

  1. GPIO5_DR

图
如上图所示,GPIO5_DR中的bit3设置为1,GPIO5_3就输出高电平,设置为0就输出低电平,该寄存器的地址偏移量是0x0,所以它的绝对地址就是0x020AC000 + 0x0 = 0x020AC000

⚽编程

驱动程序

按照驱动程序框架来编写:

1. 提供file_operations并实现相应驱动函数:

tu
如上图所示file_operations结构体,只初始化三个成员:

  • owner是必须有的,其值是该模块所属者的指针THIS_MODULE

  • open初始化为led_open
    图
    如上图所示,在led_open函数中,对GPIO5_3进行使能,功能选择以及方向选择,当应用层调用open系统调用时,最终会调用驱动层的led_open函数,对GPIO进行初始化。

  • write初始化为led_write

图
如山图所示,在led_write函数中,使用copy_from_user读取应用层调用write系统调用时写入的参数,并且复制到val中,根据该参数的逻辑值来控制LED灯的状态:

  • 用户层写入非0值:向GPIO5_DR寄存器的bit3写0,LED灯亮。
  • 用户层写入0值:向GPIO5_DR寄存器的bit3写1,LED灯灭。

2. 实现入口函数并注册设备节点:

图
如上图所示,创建相应寄存器的指针变量,然后在入口函数中首先使用resister_chrdev注册设备节点,然后再使用ioremap函数映射虚拟地址。然后再使用class_createdevice_create为内核创建设备节点提供信息。

  • 用来指向寄存器的指针使用volatile关键字修饰,保持内存可见性。
  • 对于寄存器来说,有没有数据写入区别非常大,所以要保证每次操作寄存器都能写入,不被优化。
映射虚拟地址

前面查芯片手册时看到的寄存器地址是实实在在的物理地址,但是在Linux中是不允许直接操作物理地址的。

led_openled_write中操作寄存器时使用的指针IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3GPIO5_GDIR,以及GPIO5_DR变量,其中的地址都是经过映射以后得到的虚拟地址。

TU

如上图所示,在Linux系统中存在多个进程,假设此时存在两个进程,每个进程都有一个PCB结构体,里面的struct mm_struct* mm指向各自进程地址空间(也叫虚拟地址空间)。

  • 不同进程的进程地址空间是相互独立的,互不影响。
  • 每个进程地址空间都包含栈区,共享区,堆区,数据段,代码段等等区域。

led_openled_write驱动函数中使用的寄存器指针,它们属于全局变量,所以存放在使用该驱动程序进程地址空间的数据段

如果这两个进程都会调用openwrite系统调用来操作LED灯:

  • 假设进程地址空间的数据段存放的是GPIO5相关寄存器的物理地址

进程1对GPIO5_3IO口的操作是正常的,符合规范的,但是进程2对GPIO5_3IO口的操作是违规的,如越界操作,溢出等错误操作。

由于进程1和进程2操作的是物理地址,所以进程2的错误操作会影响到进程1的正常操作,两个进程就相互影响了。

  • 进程地址空间的数据段存放的是GPIO5相关寄存器的映射后的虚拟地址

实际上采样的就是这种方式,使用虚拟地址的方式来管理和保护内存。上图中的MMU可以把物理地址和虚拟地址建立映射关系,当操作进程地址空间中的虚拟地址时:

  • MMU会先判断该操作是否合法,对物理地址形成保护,防止非法访问。
  • 操作合法时,去该虚拟地址所映射的物理地址处进行操作。

此时进程1和进程2就不会互相影响,当进程2对寄存器进程非法操作时,MMU就会直接驳回它的操作请求。

  • 操作系统Linux运行在保护模式下,使用虚拟内存来管理和保护内存,同一个物理地址可以被映射到不同进程的不同虚拟地址上。
  • 直接访问物理地址会绕过这层保护,可能导致系统不稳定或不安全。

使用ioremap进行虚拟地址映射时:

  • 第一个参数:要进行映射的物理地址。
  • 第二个参数:要映射的内存大小(字节)。

由于IMX6ULLGPIO5_3涉及到的寄存器都是32位的,也就是四个字节,所以第二个参数都是4,将使用ioremap映射后的3个虚拟地址赋值给那几个寄存器指针全局变量。

  • 虽然映射的大小是4个字节,但是映射时是以 页(4KB) 为单位的,所以真正映射出来的虚拟地址大小是4KB。

3. 实现出口函数和完善驱动信息:

图
如上图所示,在出口函数中,首先要把映射的虚拟地址销毁掉,使用iounmap函数实现,只有一个参数就是映射后得到的虚拟地址。

然后就是按照驱动框架中的操作,将设备类以及设备节点全部销毁,以及销毁设备节点的注册,最后再告诉内核入口函数和出口函数,以及声明一下使用GPL开源协议。

应用层

图
如上图所示应用层的测试代码led_drv_test.c,在执行测试程序时,命令行中输入的指令有两种:

  • led_drv_test /dev/myled on:表示点亮LED灯。
  • led_drv_test /dev/myled off:表示熄灭LED灯。

main函数中,首先判断命令行参数的个数,如果个数不为3,说明使用错误,则提示用法并直接返回-1。

参数正确以后,先打开/dev/myled目录下的设备节点,可以看到,使用的是open系统调用,应用层只把它当作一个普通文件,并不知道这是一个字符设备。

根据命令行参数中的最后一个进行判断:

  • 如果是“on”:则将后面要写入内核中的数据status修改为1。
  • 如果是"off":则不做任何修改,status使用创建时的初始值0。

最后使用write系统调用,将status这一个字节的数据写入到内核,驱动函数根据这个数据的逻辑值来判断是点亮LED灯还是熄灭LED灯。


交叉编译和前面hello驱动程序一样,只是需要对Makefile文件稍作修改:

tu

最后在IMX6ULL上挂载的跟文件系统中使用insmod led_drv.ko按照myled设备节点,然后执行led_drv_test测试程序就可以点亮和熄灭LED灯了,这里本喵就不贴板子的效果图了。

🏀总结

通过和硬件无关的hello驱动程序来引出驱动程序的框架。然后使用该框架实现了IMX6ULL单板上LED灯的驱动程序。

相关文章:

【Linux驱动】字符设备驱动程序框架 | LED驱动

🐱作者:一只大喵咪1201 🐱专栏:《RTOS学习》 🔥格言:你只管努力,剩下的交给时间! 目录 🏀Hello驱动程序⚽驱动程序框架⚽编程 🏀LED驱动⚽配置GPIO⚽编程驱动…...

关于编程网站变成了地方这件事

洛谷: 首页 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) csdn CSDN - 专业开发者社区 力扣 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 常州市力扣紧固件有限公司 常州市 力扣 紧固件 有限公司 博客园 博客园 - 开发…...

stable diffusion工作原理

目录 序言stable diffusion能做什么扩散模型正向扩散逆向扩散 如何训练逆向扩散 Stable Diffusion模型潜在扩散模型变分自动编码器图像分辨率图像放大为什么潜在空间可能存在?在潜在空间中的逆向扩散什么是 VAE 文件? 条件化(conditioning)文本条件化&am…...

华清远见嵌入式学习——ARM——作业2

目录 作业要求: 现象: 代码: 思维导图: 模拟面试题: 作业要求: GPIO实验——3颗LED灯的流水灯实现 现象: 代码: .text .global _start _start: 设置GPIOEF时钟使能 0X50000…...

R语言中使用ggplot2绘制散点图箱线图,附加显著性检验

散点图可以直观反映数据的分布,箱线图可以展示均值等关键统计量,二者结合能够清晰呈现数据蕴含的信息。 本篇笔记主要内容:介绍R语言中绘制箱线图和散点图的方法,以及二者结合展示教程,添加差异比较显著性分析&#xf…...

51单片机的羽毛球计分器系统【含proteus仿真+程序+报告+原理图】

1、主要功能 该系统由AT89C51单片机LCD1602显示模块按键等模块构成。适用于羽毛球计分、乒乓球计分、篮球计分等相似项目。 可实现基本功能: 1、LCD1602液晶屏实时显示比赛信息 2、按键控制比赛的开始、暂停和结束,以及两位选手分数的加减。 本项目同时包含器件清…...

设计模式之-责任链模式,快速掌握责任链模式,通俗易懂的讲解责任链模式以及它的使用场景

系列文章目录 设计模式之-6大设计原则简单易懂的理解以及它们的适用场景和代码示列 设计模式之-单列设计模式,5种单例设计模式使用场景以及它们的优缺点 设计模式之-3种常见的工厂模式简单工厂模式、工厂方法模式和抽象工厂模式,每一种模式的概念、使用…...

Qt通用属性工具:随心定义,随时可见(一)

一、开胃菜&#xff0c;没图我说个DIAO 先不BB&#xff0c;给大家上个效果图展示下&#xff1a; 上图我们也没干啥&#xff0c;几行代码&#xff1a; #include "widget.h" #include <QApplication> #include <QObject> #include "QtPropertyEdit…...

Python中json模块的使用与pyecharts绘图的基本介绍

文章目录 json模块json与Python数据的相互转化 pyecharts模块pyecharts基本操作基础折线图配置选项全局配置选项 json模块的数据处理折线图示例示例代码 json模块 json实际上是一种数据存储格式&#xff0c;是一种轻量级的数据交互格式&#xff0c;可以把他理解成一个特定格式…...

nodejs+vue+微信小程序+python+PHP医院挂号系统-计算机毕业设计推荐

当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c; 本医院挂号系统也是紧跟科学技术的发展&#xff0c;运用当今一流的软件技术实现软件系统的开发&#xff0c;让家具销…...

数据大模型与低代码开发:赋能技术创新的黄金组合

在当今技术领域&#xff0c;数据大模型和低代码开发已经成为两个重要的趋势。数据大模型借助庞大的数据集和强大的计算能力&#xff0c;助力我们从海量数据中挖掘出有价值的洞见和预测能力。与此同时&#xff0c;低代码开发通过简化开发流程和降低编码需求&#xff0c;使得更多…...

Redis BitMap(位图)

这里是小咸鱼的技术窝&#xff08;CSDN板块&#xff09;&#xff0c;我又开卷了 之前经手的项目运行了10多年&#xff0c;基于重构&#xff0c;里面有要实现一些诸如签到的需求&#xff0c;以及日历图的展示&#xff0c;可以用将签到信息存到传统的关系型数据库&#xff08;MyS…...

使用eclipse创建一个java文件并运行

启动 Eclipse 并创建一个新的 Java 项目: 打开 Eclipse。 选择 “File” > “New” > “Java Project”&#xff08;文件 > 新建 > Java 项目&#xff09;。 在弹出的窗口中&#xff0c;为你的项目命名&#xff0c;比如 MyJavaProject。 点击 “Finish”&#xff…...

C#上位机与欧姆龙PLC的通信05---- HostLink协议

1、介绍 Hostlink协议是欧姆龙PLC与上位机链接的公开协议。上位机通过发送Hostlink命令&#xff0c;可以对PLC进行I/O读写、可以对PLC进行I/O读写、改变操作模式、强制置位/复位等操作。由于是公开协议&#xff0c;即便是非欧姆龙的上位设备&#xff08;软件&#xff09;&…...

Uniapp 开发 BLE

BLE 低功耗蓝牙&#xff08;Bluetooth Low Energy&#xff0c;或称Bluetooth LE、BLE&#xff0c;旧商标Bluetooth Smart&#xff09;&#xff0c;用于医疗保健、运动健身、安防、工业控制、家庭娱乐等领域。在如今的物联网时代下大放异彩&#xff0c;扮演者重要一环&#xff…...

c语言排序算法

C语言代码示例&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09;&#xff1a; void bubbleSort(int arr[], int n) {for (int i 0; i < n-1; i) {for (int j 0; j < n-i-1; j) {if (arr[j] > arr[j1]) {int temp arr[j];arr[j] arr[j1];arr[j1] temp;…...

【机器学习】模式识别

1 概述 模式识别&#xff0c;简单来讲&#xff0c;就是分类问题。 模式识别应用&#xff1a;医学影像分析、人脸识别、车牌识别、遥感图像 2 模式分类器 分类器的分类&#xff1a;线性分类器、非线性分类器、最近邻分类器 2.1 分类器的训练&#xff08;学习&#xff09;过…...

【Prometheus|报错】Out of bounds

【背景】进入Prometheus地址的9090端口&#xff0c;pushgateway&#xff08;0/1&#xff09;error : out of bounds 【排查分析】 1、out of bounds报错&#xff0c;是由于Prometheus向tsdb存数据出错&#xff0c;与最新存数据的时间序列有问题&#xff0c;有可能当前时间与最…...

【音视频】Mesh、Mcu、SFU三种框架的总结

目录 三种网络场景介绍 【Mesh】 【MCU】(MultiPoint Control Unit) 【SFU】(Selective Forwarding Unit) 三种网络架构的优缺点 Mesh架构 MCU架构(MultiPoint Control Unit) SFU架构(Selective Forwarding Unit) 总结 参考文章 三种网络场景介绍 【Mesh】 Mesh架构…...

高级算法设计与分析(四) -- 贪心算法

系列文章目录 高级算法设计与分析&#xff08;一&#xff09; -- 算法引论 高级算法设计与分析&#xff08;二&#xff09; -- 递归与分治策略 高级算法设计与分析&#xff08;三&#xff09; -- 动态规划 高级算法设计与分析&#xff08;四&#xff09; -- 贪心算法 高级…...

MATLAB - 机器人逆运动学设计器(Inverse Kinematics Designer APP)

系列文章目录 前言 一、简介 通过逆运动学设计器&#xff0c;您可以为 URDF 机器人模型设计逆运动学求解器。您可以调整逆运动学求解器并添加约束条件&#xff0c;以实现所需的行为。使用该程序&#xff0c;您可以 从 URDF 文件或 MATLAB 工作区导入 URDF 机器人模型。调整逆…...

使用OpenCV DNN模块进行人脸检测

内容的一部分来源于贾志刚的《opencv4应用开发、入门、进阶与工程化实践》。这本书我大概看了一下&#xff0c;也就后面几章比较感兴趣&#xff0c;但是内容很少&#xff0c;并没有想像的那种充实。不过学习还是要学习的。 在实际工程项目中&#xff0c;并不是说我们将神经网络…...

C#中使用OpenCV的常用函数

以下是一些C#中使用OpenCV的常用函数例子&#xff1a; 1. 加载图像&#xff1a; using OpenCvSharp;Mat image Cv2.ImRead("path_to_your_image.jpg", ImreadModes.Color); 2. 显示图像&#xff1a; Cv2.NamedWindow("Image Window", WindowFlags.Nor…...

使用Swift Package Manager (SPM)实现xcframework分发

Swift Package Manager (SPM) 是苹果官方提供的用于管理 Swift 项目的依赖关系和构建过程的工具。它是一个集成在 Swift 编程语言中的包管理器&#xff0c;用于解决在开发过程中管理和构建包依赖项的需求。 1、上传xcframework.zip到服务端 压缩xcframeworks成一个zip包&…...

非阻塞 IO(NIO)

文章目录 非阻塞 IO(NIO)模型驱动程序应用程序模块使用 非阻塞 IO(NIO) 上一节中 https://blog.csdn.net/tyustli/article/details/135140523&#xff0c;使用等待队列头实现了阻塞 IO 程序使用时&#xff0c;阻塞 IO 和非阻塞 IO 的区别在于文件打开的时候是否使用了 O_NONB…...

Android应用-flutter使用Positioned将控件定位到底部中间

文章目录 场景描述示例解释 场景描述 要将Positioned定位到屏幕底部中间的位置&#xff0c;你可以使用MediaQuery来获取屏幕的高度&#xff0c;然后设置Positioned的bottom属性和left或right属性&#xff0c;一般我们left和right都会设置一个值让控制置于合适的位置&#xff0…...

Django 简单图书管理系统

一、图书需求 1. 书籍book_index.html中有超链接&#xff1a;查看所有的书籍列表book_list.html页面 2. 书籍book_list.html中显示所有的书名&#xff0c;有超链接&#xff1a;查看本书籍详情book_detail.html(通过书籍ID)页面 3. 书籍book_detail.html中书的作者和出版社&…...

C++内存管理和模板初阶

C/C内存分布 请看代码&#xff1a; int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] { 1, 2, 3, 4 };char char2[] "abcd";const char* pChar3 "abcd";int* ptr1 (int*)mallo…...

QtRO(Qt Remote Objects)分布式对象远程通信

一、什么是QtRO Qt Remote Objects&#xff08;QRO&#xff09;是Qt提供的一种用于实现远程对象通信的机制。 QtRO支持两种类型的通信&#xff1a;RPC&#xff08;远程过程调用&#xff09;和LPC&#xff08;本地进程通信&#xff09;。 RPC&#xff08;远程过程调用&#xf…...

【K8s】1# 使用kuboard-spray安装K8s集群

文章目录 搭建k8s集群1.推荐配置1.1.服务器配置1.2.软件版本 2.使用Kuboard-Spray安装k8s集群2.1.配置要求2.2.操作系统兼容性2.3.安装 Kuboard-Spray2.4.加载离线资源包2.5.规划并安装集群2.6.安装成功2.7.访问集群 3.涉及的命令3.1.linux 4.问题汇总Q1&#xff1a;启动离线集…...

自己的网站怎么做隐藏内容/常德seo快速排名

程序集生成失败 -- 引用的程序集“Interop.MSScriptControl”没有强名称 为没有源码的DLL文件添加强名称如果项目中引用了其他没有源码的dll文件&#xff0c;并且此dll文件是没有强名称的程序集&#xff0c;则编译时会出现类似 "Assembly generation failed -- 引用的程序…...

北海教网站建设/登封网站建设公司

文章目录BT A2DP、AVRCPA2DPAVRCPBT A2DP、AVRCP A2DP A2DP 全名是 Advenced Audio Distribution Profile 蓝牙音频传输模型协议。 A2DP 规定了使用蓝牙非同步传输信道方式&#xff0c;传输高质量音乐文件数据的协议堆栈软件和使用方法&#xff0c;基于该协议就能通过以蓝牙方…...

如何开一家公司创业/seopeixun com cn

用户配置文件和密码配置文件查看前三行/etc/passwd文件(用户配置文件)&#xff0c;每创建一个用户都会在这个文件的最末尾增加一行[rootapenglinux-001 ~]# head -3 /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:…...

4线城市搞网站开发/百度商城官网

一、前言&#xff1a;mysql8之后想比起之前常用的版本改动还是挺大的&#xff0c;因为刚从安装接触&#xff0c;就先从基本的说起。现在的mysql8安装只能采用解压配置版&#xff0c;像以前老版本的傻瓜式安装将不复存在。其实mysql8了解如何安装之后也不是特别麻烦&#xff0c;…...

本科毕业 做网站编辑/今日头条热搜

pycharm设置注释颜色的方法操作方法如下所示&#xff1a;File-->Settings-->Editor-->Color&Fonts-->LanguageDefaults-->Linecomment-->设置颜色即可以上这篇pycharm设置注释颜色的方法就是小编分享给大家的全部内容了&#xff0c;希望能给大家一个参考…...

越秀区网站建设/手机百度网盘网页版登录入口

运算符含义运算符含义&按位与~取反|按位或<<左移^按位异或>>右移运算量只能是整型或字符型的数据&#xff0c;不能为实型数据。 & 如果参加&运算的是负数&#xff0c;则以补码的形式表示为二进制数&#xff0c;然后按位进行“与”运算。 用途&#x…...