模版网站建设/已备案域名购买平台
🐱作者:一只大喵咪1201
🐱专栏:《Linux驱动》
🔥格言:你只管努力,剩下的交给时间!
目录
- 🏀最基本的驱动框架
- ⚽驱动程序框架
- ⚽编程
- 🏀LED驱动
- ⚽配置GPIO
- ⚽编程
- 驱动程序
- 映射虚拟地址
- 应用层
- 🏀总结
🏀最基本的驱动框架
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
结构体的部分定义,其中有read
和write
以及open
等函数指针,当应用层使用相应的open/write/read
系统调用接口时,最终会调用内核层中该结构体里对应的函数指针来实现目的。
⚽驱动程序框架
如上图所示,驱动程序的目的就是要在应用层调用open/write/read
等系统调用接口时,在内核层中调用file_operations
里的open/write/read
函数指针,而函数指针指向的drv_open/drv_read/drv_write
等驱动层函数是由我们自己定义的。在驱动程序中,实现硬件的初始化,以及数据读写。
- 定义自己的
file_operations
结构体。
前面本喵说过,file_operations
结构体是由驱动程序提供的,而驱动程序又是我们写的,所以我们首先要做的就是定义自己的file_operations
结构体。
如上图所示,在hello_drv.c
源文件中定义file_operations
结构体变量,并且进行初始化,给函数指针赋值相应的函数。
owner
:是一个指向模块所有者的指针,是必须设置的。
- gcc编译器中增加了使用
.结构体成员 = xxx
来给成员变量赋值的语法。
- 实现对应的
drv_open/drv_read/drv_write
等函数。
如上图所示驱动函数的定义,由于现在讲解的是框架,所以本喵在函数里没有写任何操作,只是使用printk
打印一些调试信息。
- 内核中打印调试信息只能使用
printk
,不能使用printf
。- 使用命令行指令
dmesg
就能看到日志中的调试信息。
- 确定主设备号,也可以让内核自己分配。
每一个字符设备都有一个主设备号,用于标识设备的类型或者设备驱动程序。不同类型的设备或不同的驱动程序会有不同的主设备号。例如,所有的串口设备可能共享一个主设备号,而所有的打印机设备可能又共享另一个不同的主设备号。
- 主设备号就像是一个类,可以用这个类定义出多个实例。
- 主设备号可以由我们自己决定,也可以将其设置为0,让内核自己分配。
建议让内核去分配主设备号,因为我们并不是很清楚有哪些主设备号,自己决定的是否已经被使用。
/* 确定主设备号 */
static int major = 0
定义一个全局变量major
来表示主设备号,暂时先给它赋值为0。
- 把
file_operations
结构体注册到内核。
暂时可以认为在内核中有一个chardevs[]
数组,该数组中存放的是字符设备节点的主设备号,当使用某一类字符设备时,会从该数组中寻找对应设备的file_operations
结构体对象。
所谓注册就是将我们自己的字符设备主设备号注册到这个数组中,使用register_chrdev
函数来实现:
major = register_chrdev(0, "hello", &hello_drv);
- 第一个参数是主设备号,如果传入的是0,则返回内核自动分配的主设备号。
- 第二个参数是字符设备的名称,是一个字符串。
- 第三个参数是我们提供的
file_operations
结构体指针。
调用该函数后,主设备号和file_operations
以及设备名称就绑定在了一起,而且主设备号放入到了chardevs[]
数组中。
- 定义入口函数,安装驱动程序时,就会去调用这个入口函数 。
将file_operations
注册到内核中是由入口函数完成的,入口函数使用宏__init
修饰:
如上图所示,在安装驱动程序的时候,内核会自动去调用这个hello_init
函数,在该函数中完成:
-
注册
file_operations
结构体到内核中,并得到主设备号。 -
创建设备信息类,使用
class_create
实现,该类中包含内核需要的设备节点信息,更方便内核去创建节点。 -
创建设备节点,使用
device_create
实现,此时在内核中会生成一个/dev/hello
路径用来表示节点设备。
- 出口函数,卸载驱动程序时,就会去调用这个出口函数。
有入口函数就有出口函数,出口函数使用宏__exit
来修饰:
如上图所示函数,在卸载时会由内核自动调用,都要卸载了,就要将前面注册到内核中的字符设备移除,使用unregister_chrdev
实现,并且将前面创建的字符设备类和设备节点都销毁,使用class_destroy
和device_destroy
实现。
- 完善设备信息
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
结构体中的read
和write
方法,也就是对应的hello_drv_read
和hello_drv_wite
函数,其他的没有用到。
- 使用
__user
修饰的buf
,表示这是来自用户层的缓冲区。
用户层的缓冲区不能使用strcpy
等应用层函数直接操作,而是必须要使用内核提供的复制函数:
copy_to_user
:从内核缓冲区复制数据到用户缓冲区,第一个参数是目的buf
,第二个参数是源kernel_buffer
,第三个参数是要复制的字节数。copy_from_user
:从用户缓冲区复制数据到内核缓冲区,参数参考上面。
由于定义的缓冲区大小是1024,防止越界,使用宏MIN
将1024和用户层指定的数据大小size
作比较,取较小值作为复制数据的大小。
应用层代码:
如上图所示,使用命令行参数传入-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
,暂时不用这些指令是什么意思,直接用就星。
如上图所示,生成hello_drv.ko
和hello_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
设备节点。
测试:
如上图所示,执行应用层测试程序hello_drv_test
:
- 在执行可执行程序的命令行参数中使用
-w
选项,写入A-Big-MiaoMi
字符串到内核缓冲区中。 - 再使用
-r
选项,从内核缓冲区中读取刚刚写入数据,结果是APP read: A-Big-MiaoMi
。
根据上面测试结果,说明我们的第一个驱动程序就写成功了。
🏀LED驱动
⚽配置GPIO
配置GPIO通用步骤:
如上图所示IMX6ULL
GPIO框图,输出功能的配置和其他芯片一样分为四步:
- 使能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表示输出高电平。
具体单板:
如上图所示本喵的IMX6ULL
开饭上LED2
的电路图:
GPIO5_3
输出低电平,LED灯亮。GPIO5_3
输出低电平,LED灯灭。
按照上面的配置步骤,寻找GPIO5_3
的那几个寄存器和对应的比特位:
CCM_CCGR1
中的CG15
:
如上图所示,CCM_CCGR1
中的CG15
是保留的,在IMX6ULL
中,GPIO5
这组GPIO默认使能,所以不用设置。CCM_CCGR1
的绝对地址是0x020C4000 + 0x6C = 0x020C406C
。
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
:
如上图所示,将 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
寄存器中的MUX_MODE
4位配置为101
,选择GPIO5_IO03
为通用输入输出模式。该寄存器的绝对地址是0x02290000 + 0x14 = 0x02290014
。
GPIO5_GDIR
:
如上图,使用的是GPIO5_3
IO口,所以要配置GPIO5_GDIR
中的bit3
,该位为1,表示输出,该位为0,表示输入。该寄存器的偏移量是0x4
。
如上图所示,GPIO5
的基地址是0x020AC000
,所以GPIO5_GDIR
的绝对地址是0x020AC000 + 0x4 = 0x020AC004
。
GPIO5_DR
:
如上图所示,GPIO5_DR
中的bit3
设置为1,GPIO5_3
就输出高电平,设置为0就输出低电平,该寄存器的地址偏移量是0x0
,所以它的绝对地址就是0x020AC000 + 0x0 = 0x020AC000
。
⚽编程
驱动程序
按照驱动程序框架来编写:
1. 提供file_operations并实现相应驱动函数:
如上图所示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_create
和device_create
为内核创建设备节点提供信息。
- 用来指向寄存器的指针使用
volatile
关键字修饰,保持内存可见性。- 对于寄存器来说,有没有数据写入区别非常大,所以要保证每次操作寄存器都能写入,不被优化。
映射虚拟地址
前面查芯片手册时看到的寄存器地址是实实在在的物理地址,但是在Linux中是不允许直接操作物理地址的。
在led_open
和led_write
中操作寄存器时使用的指针IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
,GPIO5_GDIR
,以及GPIO5_DR
变量,其中的地址都是经过映射以后得到的虚拟地址。
如上图所示,在Linux系统中存在多个进程,假设此时存在两个进程,每个进程都有一个PCB结构体,里面的struct mm_struct* mm
指向各自进程地址空间(也叫虚拟地址空间)。
- 不同进程的进程地址空间是相互独立的,互不影响。
- 每个进程地址空间都包含栈区,共享区,堆区,数据段,代码段等等区域。
在led_open
和led_write
驱动函数中使用的寄存器指针,它们属于全局变量,所以存放在使用该驱动程序进程地址空间的数据段。
如果这两个进程都会调用open
和write
系统调用来操作LED灯:
- 假设进程地址空间的数据段存放的是
GPIO5
相关寄存器的物理地址。
进程1对GPIO5_3
IO口的操作是正常的,符合规范的,但是进程2对GPIO5_3
IO口的操作是违规的,如越界操作,溢出等错误操作。
由于进程1和进程2操作的是物理地址,所以进程2的错误操作会影响到进程1的正常操作,两个进程就相互影响了。
- 进程地址空间的数据段存放的是
GPIO5
相关寄存器的映射后的虚拟地址。
实际上采样的就是这种方式,使用虚拟地址的方式来管理和保护内存。上图中的MMU可以把物理地址和虚拟地址建立映射关系,当操作进程地址空间中的虚拟地址时:
- MMU会先判断该操作是否合法,对物理地址形成保护,防止非法访问。
- 操作合法时,去该虚拟地址所映射的物理地址处进行操作。
此时进程1和进程2就不会互相影响,当进程2对寄存器进程非法操作时,MMU就会直接驳回它的操作请求。
- 操作系统Linux运行在保护模式下,使用虚拟内存来管理和保护内存,同一个物理地址可以被映射到不同进程的不同虚拟地址上。
- 直接访问物理地址会绕过这层保护,可能导致系统不稳定或不安全。
使用ioremap
进行虚拟地址映射时:
- 第一个参数:要进行映射的物理地址。
- 第二个参数:要映射的内存大小(字节)。
由于IMX6ULL
的GPIO5_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
文件稍作修改:
最后在IMX6ULL上挂载的跟文件系统中使用insmod led_drv.ko
按照myled
设备节点,然后执行led_drv_test
测试程序就可以点亮和熄灭LED灯了,这里本喵就不贴板子的效果图了。
🏀总结
通过和硬件无关的hello
驱动程序来引出驱动程序的框架。然后使用该框架实现了IMX6ULL
单板上LED灯的驱动程序。
相关文章:

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

前端---表单提交
1. 表单属性设置 <form>标签 表示表单标签,定义整体的表单区域 action属性 设置表单数据提交地址method属性 设置表单提交的方式,一般有“GET”方式和“POST”方式, 不区分大小写 2. 表单元素属性设置 name属性 设置表单元素的名称,…...

[C#]Parallel使用
一、 Parallel的使用 1、Parallel.Invoke2、Parallel.For3、Parallel.Foreach二、 Parallel中途退出循环和异常处理 1、当我们使用到Parallel,必然是处理一些比较耗时的操作,当然也很耗CPU和内存,如果我们中途向停止,怎么办呢&…...

docker container 指定gpu设备
1, 在yaml中 Turn on GPU access with Docker Compose | Docker Docs Example of a Compose file for running a service with access to 1 GPU device: services:test:image: nvidia/cuda:12.3.1-base-ubuntu20.04command: nvidia-smideploy:resources:reserva…...

时间Date
你有没有思考过时间问题: 前端为什么可以直接看见时间格式的数据 后端怎么接受的数据,怎么处理的 一般来说:前端传输来数据都是时间格式的字符串,那么后端需要能够解析时间格式的字符串,归功于JSONFormat ,可以解析…...

前端---css 选择器
1. css 选择器的定义 css 选择器是用来选择标签的,选出来以后给标签加样式。 2. css 选择器的种类 标签选择器类选择器层级选择器(后代选择器)id选择器组选择器伪类选择器 3. 标签选择器 根据标签来选择标签,以标签开头,此种选择器影响范…...

【MybatisPlus快速入门】(2)SpringBoot整合MybatisPlus 之 标准数据层开发 代码示例
目录 1 标准CRUD使用2 新增3 删除4 修改5 根据ID查询6 查询所有7 MyBatis-Plus CRUD总结 之前我们已学习MyBatisPlus在代码示例与MyBatisPlus的简介,在这一节中我们重点学习的是数据层标准的CRUD(增删改查)的实现与分页功能。代码比较多,我们一个个来学习…...

如何将自建的ElasticSearch注册成一个服务
ES 服务管理 注册ES服务 创建一个 Elasticsearch 服务配置文件。 sudo vim /etc/systemd/system/elasticsearch.service 将以下内容复制到 elasticsearch.service 文件中: [Unit] Descriptionelasticsearch Afternetwork.target[Service] Typeforking Useresa…...

360勒索病毒:了解最新变种.360,以及如何保护您的数据
导言: 随着科技的飞速发展,网络安全威胁也在不断演变,.360 勒索病毒成为近期备受关注的一种恶意软件。本文91数据恢复将介绍如何恢复被.360 勒索病毒加密的数据文件,并提供一些建议,帮助你预防这种威胁。 如果您在面对…...

vue使用ElementUI搭建精美页面入门
ElementUI简直是css学得不好的同学的福音 ElementUI官网: Element - The worlds most popular Vue UI framework 安装 在vue文件下,用这个命令去安装Element UI。 npm i element-ui -S step1\先切换到vue的目录下去,注意这里面的WARN不是…...

【C->Cpp】深度解析#由C迈向Cpp(2)
目录 (一)缺省参数 全缺省参数 半缺省参数 缺省参数只能在函数的声明中出现: 小结: (二)函数重载 函数重载的定义 三种重载 在上一篇中,我们从第一个Cpp程序为切入,讲解了Cpp的…...

WPS中如何根据身份证号生成出生日期并排序
1. wps中如何根据身份证号导出出生日期并排序 1.1 wps中建一张表 1.2 使用转日期格式导出出生日期 DATE(VALUE(MID(C2,7,4)),VALUE(MID(C2,11,2)),VALUE(MID(C2,13,2)))MID(C2, 7, 4):这部分从单元格 C2 中提取文本字符串,从第7个字符开始提取长度为4的…...

20231222给NanoPC-T4(RK3399)开发板的适配Android11的挖掘机方案并跑通AP6398SV
20231222给NanoPC-T4(RK3399)开发板的适配Android11的挖掘机方案并跑通AP6398SV 2023/12/22 7:54 简略步骤:rootrootrootroot-X99-Turbo:~/3TB$ cat Android11.0.tar.bz2.a* > Android11.0.tar.bz2 rootrootrootroot-X99-Turbo:~/3TB$ tar jxvf Android11.0.tar.…...

iClient for JavaScript如何以mvt矢量瓦片的形式加载数据服务
刘大 这里写目录标题 前言1.iServer中的预览页面2.iClient for JavaScript加载2.1 构建Style2.2 iCient加载2.2.1Leaflet & MapboxGL2.2.2 OpenLayers 前言 在提到查看iServer REST数据服务的概况的时候,大家总会想到说,通过发布对应的地图服务或者…...

全方位掌握卷积神经网络:理解原理 优化实践应用
计算机视觉CV的发展 检测任务 分类与检索 超分辨率重构 医学任务 无人驾驶 整体网络架构 卷积层和激活函数(ReLU)的组合是网络的核心组成部分 激活函数(ReLU) 引入非线性,增强网络的表达能力。 卷积层 负责特征提取 池化层…...

视频批量处理:随机分割方法,创新剪辑方式
随着数字媒体技术的飞速发展,视频处理已是日常生活和工作中不可或缺的一部分。在处理大量视频时,要一种高效、自动化的方法来满足需求。现在一起来看云炫AI智剪如何批量随机分割视频的批量处理方法,给视频剪辑工作带来创新。 视频随机分割4段…...

Gaussian-Splatting 训练并导入Unity中
这个周末玩点啥~🐞 🍥环境安装💡安装C编译工具💡安装Python💡安装CUDA💡添加ffmpeg到环境变量Path添加COLMAP-3.8-windows-cuda文件路径到环境变量Path💡pytorch安装💡tqdm 安装&…...

账号和权限管理
目录 一、用户账号和的概述 (一)用户类别 (二)组账号 编辑(三)UID号 编辑(四)GID号 (五)配置文件 二、用户账号管理 (一)…...

前端---表单标签
1. 表单的介绍 表单用于搜集不同类型的用户输入(用户输入的数据),然后可以把用户数据提交到web服务器 。 2. 表单相关标签的使用 <form>标签 表示表单标签,定义整体的表单区域 <label>标签 表示表单元素的文字标注标签,定义文字…...

Matplotlib 绘制基本的图表
# 导入包 import pandas as pd import numpy as np import matplotlib.pyplot as plt plt.rcParams[font.sans-serif][SimHei] # 用来显示中文 plt.rcParams[axes.unicode_minus] False # 显示负坐标轴# 读取源数据,后续大部分数据基于词文件的数据,需…...

【JavaScript】异步解决方案的发展历程
✨ 专栏介绍 在现代Web开发中,JavaScript已经成为了不可或缺的一部分。它不仅可以为网页增加交互性和动态性,还可以在后端开发中使用Node.js构建高效的服务器端应用程序。作为一种灵活且易学的脚本语言,JavaScript具有广泛的应用场景&#x…...

前端性能优化三十四:花裤衩模板引入打包分析工具
测量各个插件和loader所花费的时间 (1). install: yarn add speed-measure-webpack-plugin -D(2). Vue-cli 3.x设置: const SpeedMeasurePlugin require(speed-measure-webpack-plugin) const smp new SpeedMeasurePlugin({outputFormat: human }) // 包裹configureWebpac…...

求职小程序列表基础配置-移动端通用列表模块配置教程(1)
求职小程序列表基础配置-移动端通用列表模块配置教程(1) 移动端通用列表页开发指南 准备工作 注册多八多AIIDE账号: 访问多八多AIIDE官网并注册新账号。完成邮箱和手机号的验证。 创建移动应用: 登录后,在工作台新建一个移动应用。填写应用名称,选择“…...

牛客设计模式
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么?二、使用步骤 1.引入库2.读入数据总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:…...

从零构建tomcat环境
一、官网构建 1.1 下载 一般来说对于开源软件都有自己的官方网站,并且会附上使用文档以及一些特性和二次构建的方法,那么我们首先的话需要从官网或者tomcat上下载到我们需要的源码包。下载地址:官网、Github。 这里需要声明一下ÿ…...

MySQL递归公用表表达式
😇作者介绍:一个有梦想、有理想、有目标的,且渴望能够学有所成的追梦人。 🎆学习格言:不读书的人,思想就会停止。——狄德罗 ⛪️个人主页:进入博主主页 🗼专栏系列:MySQL知识 &…...

深入 K8s 网络原理(一)- Flannel VXLAN 模式分析
1. 概述 这周集中聊下 K8s 的集群网络原理,我初步考虑分成3个方向: Pod-to-Pod 通信(同节点 or 跨节点),以 Flannel VXLAN 模式为例; Pod/External-to-Service 通信,以 iptables 实现为例&…...

fpga 8段4位数码管verilator模拟
8段4位数码管verilator模拟 seg.v module seg(input wire clk,input wire rst_n,output wire[7:0] SEG,output wire[3:0] SEL );reg[7:0] digit[0:15] {8h3f, 8h06, 8h5b, 8h4f, 8h66, 8h6d, 8h7d,8h07,8h7f,8h6f, 8h77, 8h7c, 8h39, 8h5e, 8h79, 8h71};reg[31:0] cnt 32…...

HttpURLConnection发送各种内容格式
通过java.net.HttpURLConnection类实现http post发送Content-Type为multipart/form-data的请求。 json处理使用com.fasterxml.jackson 图片压缩使用net.coobird.thumbnailator log使用org.slf4j 一些静态变量 private static final Charset charset StandardCharsets.UTF_8;…...

摇杆控制人物移动
摇杆控制人物移动 一、UI搭建二、3d模型搭建三、脚本JoyStickBar.csPlayerController.cs 工程在我资源里名字叫Joystickbar.unitypackage [连接](https://download.csdn.net/download/qq_42194657/12043019?spm1001.2014.3001.5503) 一、UI搭建 JoyStickBar是图片背景 JoySt…...