Linux设备驱动程序(二)——建立和运行模块
文章目录
- 前言
- 一、设置测试系统
- 二、Hello World 模块
- 1、代码详解
- 2、执行效果
- 三、内核模块相比于应用程序
- 1、用户空间和内核空间
- 2、内核的并发
- 3、当前进程
- 4、几个别的细节
- 四、编译和加载
- 1、编译模块
- 2、加载和卸载模块
- 3、版本依赖
- 五、内核符号表
- 六、预备知识
- 七、初始化和关停
- 1、清理函数
- 2、初始化中的错误处理
- 3、模块加载竞争
- 八、模块参数
- 1、模块支持的模块参数:
- 2、访问许可值:
- 3、例程
- 九、在用户空间做
- 十、快速参考
前言
本章介绍所有的关于模块和内核编程的关键概念,通过一个 hello world 模块来认识驱动加载的流程及相关细节。
一、设置测试系统
我是在虚拟机上进行的开发,查看当前 Linux 系统的内核版本:
uname -r
二、Hello World 模块
1、代码详解
hello.c
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");static int hello_init(void)
{printk(KERN_ALERT "Hello, world\n");return 0;
}static void hello_exit(void)
{printk(KERN_ALERT "Goodbye, cruel world\n");
}module_init(hello_init);
module_exit(hello_exit);
- 这个模块定义了两个函数,一个在模块加载到内核时被调用(hello_init)以及一个在模块被去除时被调用(hello_exit)。moudle_init 和 module_exit 这几行使用了特别的内核宏来指出这两个函数的角色。另一个特别的宏(MODULE_LICENSE)是用来告知内核,该模块带有一个自由的许可证;没有这样的说明,在模块加载时内核会抱怨。
- printk 函数在 Linux 内核中定义并且对模块可用;它与标准 C 库函数 printf 的行为相似。内核需要它自己的打印函数,因为它靠自己运行,没有 C 库的帮助,模块能够调用 printk 是因为在 insmod 加载了它之后,模块被连接到内核并且可存取内核的公用符号。 字串 KERN_ALERT 是消息的优先级。
- 可以用 insmod 和 rmmod 工具来测试这个模块,注意只有超级用户可以加载和卸载模块。
Makefile
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
- obj-m := hello.o
- 表明有一个模块要从目标文件 hello.o 建立,在从目标文件建立后结果模块命名为 hello.ko;
-
如果你有一个模块名为 module.ko,是来自 2 个源文件( 姑且称之为,file1.c 和 file2.c ),正确的书写应当是:
obj-m := module.o
module-objs := file1.o file2.o
- KERNELDIR ?= /lib/modules/$(shell uname -r)/build
- 如果这个 KERNELDIR 为空说明你没有指定内核库文件的路径,那么它就会给 KERNELDIR 赋值,因为顶层 Makefile 通过这个环境变量知道内核库文件在哪里。
- PWD := $(shell pwd)
- 获取当前所执行命令的目录
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- 这个命令开始是改变它的目录到用 -C 选项提供的目录下( 就是说,你的内核源码目录 )。它在那里会发现内核的顶层 makefile,这个 M= 选项使 makefile 在试图建立模块目标前,回到你的模块源码目录,这个目标,依次地,是指在 obj-m 变量中发现的模块列表,在我们的例子里设成了 hello.o。
这个 makefile 在一次典型的建立中要被读 2 次,当从命令行中调用这个 makefile,它注意到 KERNELRELEASE 变量没有设置,它利用这样一个事实来定位内核源码目录,即已安装模块目录中的符号连接指回内核建立树,如果你实际上没有运行你在为其而建立的内核,你可以在命令行提供一个 KERNELDIR= 选项,设置 KERNELDIR 环境变量,或者重写 makefile 中设置 KERNELDIR 的那一行。一旦发现内核源码树,makefile 调用 default: 目标,来运行第 2 个 make 命令( 在 makefile 里参数化成 $(MAKE)) 象前面描述过的一样来调用内核建立系统,在第 2
次读,makefile 设置 obj-m,并且内核的 makefile 文件完成实际的建立模块工作。
2、执行效果
①、准备好 hello.c 和 Makefile
②、make 编译
make
查看当前目录下编译产物,其中 hello.ko 是我们需要用到的驱动模块
③、加载 hello.ko 模块
sudo insmod hello.ko
④、lsmod 显示已经加载到内核中的模块的状态信息
lsmod
⑤、查看加载时的打印信息
sudo dmesg -c
⑥、卸载 hello.ko 模块
⑦、查看卸载时的打印信息
sudo dmesg -c
三、内核模块相比于应用程序
- 不同于大部分的小的和中型的应用程序从头至尾处理一个单个任务,每个内核模块只注册自己以便来服务将来的请求,并且它的初始化函数立刻终止。
- 模块初始化函数的任务是为以后调用模块的函数做准备;模块的退出函数就在模块被卸载时调用。这种编程的方法类似于事件驱动的编程,但是虽然不是所有的应用程序都是事件驱动的,每个内核模块都是。
- 另外一个主要的不同,在事件驱动的应用程序和内核代码之间,是退出函数:一个终止的应用程序可以在释放资源方面懒惰,或者完全不做清理工作,但是模块的退出函数必须小心恢复每个由初始化函数建立的东西,否则会保留一些东西直到系统重启。
- 一个应用程序可以调用它没有定义的函数:连接阶段使用合适的函数库解决了外部引用。 printf 是一个这种可调用的函数并且在 libc 里面定义。一个模块,在另一方面,只连接到内核,它能够调用的唯一的函数是内核输出的那些; 没有库来连接。
- 内核编程和应用程序编程之间的重要不同是每一个环境是如何处理错误:在应用程序开发中段错误是无害的,一个调试器常常用来追踪错误到源码中的问题,而一个内核错误至少会杀掉当前进程,如果不终止整个系统。
1、用户空间和内核空间
- 一个模块在内核空间运行,而应用程序在用户空间运行,这个概念是操作系统理论的基础。
- cpu 在被设计时,有保护系统软件不被应用程序破坏的功能。且这种保护功能分为不同级别,当 cpu 中存在多个级别时,unix 通常使用最高级和最低级,即:超级用户级和用户级,也即内核空间和用户空间。
- 在 Unix 下,内核在最高级运行(也称之为超级模式 ),这里任何事情都允许,而应用程序在最低级运行(所谓的用户模式),这里处理器控制了对硬件的直接存取以及对内存的非法存取。
- 模块的角色是扩展内核的功能:模块化的代码在内核空间运行,经常地一个驱动进行之前提到的两种任务:模块中一些的函数作为系统调用的一部分执行,一些负责中断处理。
2、内核的并发
常见引起并发原因:
- linux 系统中通常正在运行多个并发进程,并且可能有多个进程同时使用我们的驱动程序。
- 大多数设备能够中断处理器,而中断处理程序异步运行,而且可能在驱动程序正试图处理其他任务时被调用。
- linux 可以运行在多处理器上,因此可能同时有多个处理器在使用该进程。
3、当前进程
- Current 在<asm.current.h>中定义,是一个指向 struct task_struct 的指针,而 task_struct 结构在 <linux/sched.h> 中定义。
- Current 指针指向当前正在运行的进程;
- 在 open,read 等系统调用的执行过程中,当前进程指的是调用这些系统调用的进程。
struct task_struct *current;
current->id :当前进程的id
current->comm. :当前进程的命令名
4、几个别的细节
- 如果我们需要大的结构,应该调用动态分配该结构,而不是声明大的自动变量。
- 常见函数前加有 __ 两个下划线,这种函数通常是接口的底层组件,实际上,双下划线是告诉程序员:谨慎使用,后果自负。
- 内核代码不支持浮点数运算。
四、编译和加载
1、编译模块
上面已讲解,这里不再讲述。
2、加载和卸载模块
- 模块建立之后,下一步是加载到内核,insmod 完成这个工作。这个程序加载模块的代码段和数据段到内核,接着,执行一个类似 ld 的函数,它连接模块中任何未解决的符号连接到内核的符号表上。
- modprobe 工具值得快速提及一下。modprobe 和 insmod 类似,加载一个模块到内核。它的不同在于它会查看要加载的模块,看是否它引用了当前内核没有定义的符号。如果发现有,modprobe 在定义相关符号的当前模块搜索路径中寻找其他模块。当 modprobe 找到这些模块(要加载模块需要的),它也把它们加载到内核。如果你在这种情况下代替以使用 insmod,命令会失败,在系统日志文件中留下一条 “unresolved symbols” 消息。
- 模块可以用 rmmod 工具从内核去除。注意,如果内核认为模块还在用(就是说,一个程序仍然有一个打开文件对应模块输出的设备),或者内核被配置成不允许模块去除,模块去除会失败,可以配置内核允许“强行”去除模块, 甚至在它们看来是忙的。如果你到了需要这选项的地步,但是,事情可能已经错的太严重以至于最好的动作就是重启了。
- 只有系统调用函数的名字前边带有 sys_ 前缀。
- lsmod 列出当前装载到内核中的所有模块。lsmod 通过读取 /proc/modules 虚拟文件工作。当前加载的模块的信息也可在位于 /sys/module 的 sysfs 虚拟文件系统找到。
3、版本依赖
如果你编写一个模块想用来在多个内核版本上工作(特别地是如果它必须跨大的发行版本)你可能只能使用宏定义和 #ifdef 来使你的代码正确建立,利用 linux/version.h 中发现的定义。这个头文件,自动包含在 linux/module.h,定义了下面的宏定义:
- UTS_RELEASE
- 这个宏定义扩展成字符串,描述了这个内核树的版本,例如, “2.6.10”。
- LINUX_VERSION_CODE
- 这个宏定义扩展成内核版本的二进制形式,版本号发行号的每个部分用一个字节表示。例如 2.6.10 的编码是 132618 ( 就是0x02060a )。有了这个信息, 你可以(几乎是)容易地决定你在处理的内核版本。
- KERNEL_VERSION(major,minor,release)
- 这个宏定义用来建立一个整型版本编码,从组成一个版本号的单个数字。例如 KERNEL_VERSION(2.6.10) 扩展成 132618,这个宏定义非常有用,当你需要比较当前版本和一个已知的检查点。
五、内核符号表
- 通常情况下,一个模块完成它自己的功能不需要输出如何符号。但是,你需要输出符号,在任何别的模块能得益于使用它们的时候。
- linux 内核头文件提供了方便来管理你的符号的可见性,因此减少了命名空间的污染(将与在内核别处已定义的符号冲突的名子填入命名空间),并促使了正确的信息隐藏。如果你的模块需要输出符号给其他模块使用,应当使用下面的宏定义:
- EXPORT_SYMBOL(name);
- EXPORT_SYMBOL_GPL(name);
- 上面宏定义的任一个使得给定的符号在模块外可用。_GPL 版本的宏定义只能使符号对 GPL 许可的模块可用。符号必须在模块文件的全局部分输出,在任何函数之外,因为宏定义扩展成一个特殊用途的并被期望是全局存取的变量的声明,这个变量存储于模块的一个特殊的可执行部分(一个 “ELF 段” ),内核用这个部分在加载时找到模块输出的变量。
六、预备知识
- 有几个文件对模块是特殊的,必须出现在每一个可加载模块中。因此,几乎所有模块代码都有下面内容:
- #include <linux/module.h>
- #include <linux/init.h>
- moudle.h 包含了大量加载模块需要的函数和符号的定义,你需要 init.h 来指定你的初始化和清理函数。
- 不是严格要求的,但是你的模块确实应当指定它的代码使用哪个许可。做到这一点只需包含一行 MODULE_LICENSE:
- MODULE_LICENSE(“GPL”);
- 内核认识的特定许可有"GPL"(适用 GNU 通用公共许可的任何版本),“GPL v2”(只适用 GPL 版本 2),“GPL and additional rights”,“Dual BSD/GPL”,"Dual MPL/GPL"和 “Proprietary”;除非你的模块明确标识是在内核认识的一个自由许可下,否则就假定它是私有的,内核在模块加载时被"弄污浊"了。
- 可以在模块中包含的其他描述性定义有 MODULE_AUTHOR(声明谁编写了模块)。MODULE_DESCRIPION(一个人可读的关于模块做什么的声明), MODULE_VERSION(一个代码修订版本号; 看 <linux/module.h> 的注释以便知道创建版本字串使用的惯例),MODULE_ALIAS (模块为人所知的另一个名子),以及 MODULE_DEVICE_TABLE ( 来告知用户空间,模块支持那些设备 )。
七、初始化和关停
模块初始化函数注册模块提供的任何功能,实际的初始化函数定义常常如:
static int __init initialization_function(void)
{
/* Initialization code here */
}
module_init(initialization_function);
- 初始化函数应当声明成静态的,因为它们不会在特定文件之外可见;
- 声明中的 __init 标志可能看起来有点怪,它是一个给内核的暗示,给定的函数只是在初始化使用,模块加载者在模块加载后会丢掉这个初始化函数,使它的内存可做其他用途。一个类似的标签(__initdata)给只在初始化时用的数据。使用 __init 和 __initdata 是可选的,但是它带来的麻烦是值得的;
- 使用 moudle_init 是强制的,这个宏定义增加了特别的段到模块目标代码中,表明在哪里找到模块的初始化函数。 没有这个定义,你的初始化函数不会被调用;
- 大部分注册函数以 register_ 做前缀,因此找到它们的另外一个方法是在内核源码里查找 register_;
1、清理函数
每个非试验性的模块也要求有一个清理函数,它注销接口,在模块被去除之前返回所有资源给系统。这个函数定义为:
static void __exit cleanup_function(void)
{
/* Cleanup code here */
}
module_exit(cleanup_function);
清理函数没有返回值, 因此它被声明为 void,__exit 修饰符标识这个代码是只用于模块卸载(通过使编译器把它放在特殊的 ELF 段),如果你的模块直接建立在内核里,或者如果你的内核配置成不允许模块卸载,标识为 __exit 的函数被简单地丢弃。因为这个原因,一个标识 __exit 的函数只在模块卸载或者系统停止时调用;任何别的使用是错的。再一次,moudle_exit 声明对于使得内核能够找到你的清理函数是必要的。
2、初始化中的错误处理
你必须记住一件事,在注册内核设施时,注册可能失败。即便最简单的动作常常需要内存分配,分配的内存可能不可用。因此模块代码必须一直检查返回值,并且确认要求的操作实际上已经成功。
int __init my_init_function(void)
{int err;err = register_this(ptr1, "skull"); /* registration takes a pointer and a name */if (err)goto fail_this;err = register_that(ptr2, "skull");if (err)goto fail_that;err = register_those(ptr3, "skull");if (err)goto fail_those;return 0; /* success */fail_those:unregister_that(ptr2, "skull");fail_that:unregister_this(ptr1, "skull");fail_this:return err; /* propagate the error */
}
模块清理函数必须撤销任何由初始化函数进行的注册,并且惯例(但常常不是要求的)是按照注册时相反的顺序注销设施。
void __exit my_cleanup_function(void)
{unregister_those(ptr3, "skull");unregister_that(ptr2, "skull");unregister_this(ptr1, "skull");return;
}
如果你的初始化和清理比处理几项复杂,goto 方法可能变得难于管理,因为所有的清理代码必须在初始化函数里重复,有时包括几个混合的标号,因此,一种不同的代码排布证明更成功。
使代码重复最小和所有东西流线化,你应当做的是无论何时发生错误都从初始化里调用清理函数。清理函数接着必须在撤销它的注册前检查每一项的状态,以最简单的形式,代码看起来象这样:
struct something *item1;
struct somethingelse *item2;
int stuff_ok;
void my_cleanup(void)
{if (item1)release_thing(item1);if (item2)release_thing2(item2);if (stuff_ok)unregister_stuff();return;
}
int __init my_init(void)
{int err = -ENOMEM;item1 = allocate_thing(arguments);item2 = allocate_thing2(arguments2);if (!item2 || !item2)goto fail;err = register_stuff(item1, item2);if (!err)stuff_ok = 1;elsegoto fail;return 0; /* success */fail:my_cleanup();return err;
}
清理函数当由非退出代码调用时不能标志为 __exit。
3、模块加载竞争
内核的某些别的部分会在注册完成之后马上使用任何你注册的设施,这是完全可能的,换句话说,内核将调用进你的模块,在你的初始化函数仍然在运行时,所以你的代码必须准备好被调用,一旦它完成了它的第一个注册。不要注册任何设施,直到所有的需要支持那个设施的你的内部初始化已经完成。
八、模块参数
模块参数可以在运行 insmod 或 modprobe 命令装载模块时赋值,modprobe 可以从配置文件(/etc/modprobe.conf)中读取参数值。
在 insmod 改变模块参数之前,模块必须让参数对 insmod 命令可见。参数使用 module_param(变量名,类型,访问许可值)宏来声明,它定义在 moduleparam.h。
所有的模块参数都应该在源文件中给定一个默认值。
1、模块支持的模块参数:
- bool、invbool(取反,true 变为 false,false 变为 true)
- charp(字符指针)
- int、long、short、unit、ulong、ushort
- 数组参数:module_param_array(数组名,类型,值的个数,访问许可值);
模块中的钩子可让我们自定义类型
2、访问许可值:
使用 <linux/stat.h> 中定义的值
- 设置为0不会有对应的 sysfs 入口项,否则模块参数会在 /sys/module/(如下所示)
- S_IRUGO 任何人都可以读取,但不能修改
- S_IRUGO | S_IWUSR 允许 root 用户修改
大多数情况下不应该让模块参数是可写的
3、例程
hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
MODULE_LICENSE("Dual BSD/GPL");static char *hello_str = "hello";
static int hello_cnt = 2;
module_param(hello_str, charp, S_IRUGO);
module_param(hello_cnt, int, S_IRUGO);static int hello_init(void)
{printk(KERN_ALERT "Hello, world\n");printk("%s, %d\n", hello_str, hello_cnt);return 0;
}static void hello_exit(void)
{printk(KERN_ALERT "Goodbye, cruel world\n");
}module_init(hello_init);
module_exit(hello_exit);
加载 hello 模块驱动,并查看打印信息
sudo insmod hello.ko
sudo dmesg
模块加载后可以在 /sys/module/模块名/parameters 目录下查看参数
cd /sys/module/hello/parameters/
ls
cat hello_cnt
cat hello_str
九、在用户空间做
用户空间驱动的好处在于:
- 完整的 C 库可以连接,驱动可以进行许多奇怪的任务,不用依靠外面的程序(实现使用策略的工具程序,常常随着驱动自身发布);
- 程序员可以在驱动代码上运行常用的调试器,而不必走调试一个运行中的内核的弯路。
- 如果一个用户空间驱动挂起了,你可简单地杀掉它,驱动的问题不可能挂起整个系统,除非被控制的硬件真的疯掉了。
- 用户内存是可交换的,不象内核内存,一个不常使用的却有很大一个驱动的设备不会占据别的程序可以用到的 RAM,除了在它实际在用时。
- 一个精心设计的驱动程序仍然可以,如同内核空间驱动,允许对设备的并行存取。
- 如果你必须编写一个封闭源码的驱动,用户空间的选项使你容易避免不明朗的许可的情况和改变的内核接口带来的问题。
用户空间的设备驱动的方法有几个缺点,最重要的是:
- 中断在用户空间无法用,在某些平台上有对这个限制的解决方法,例如在 IA32 体系上的 vm86 系统调用。
- 只可能通过内存映射 /dev/mem 来使用 DMA,而且只有特权用户可以这样做。
- 存取 I/O 端口只能在调用 ioperm 或者 iopl 之后,此外,不是所有的平台支持这些系统调用,而存取/dev/port 可能太慢而无效率,这些系统调用和设备文件都要求特权用户。
- 响应时间慢,因为需要上下文切换在客户和硬件之间传递信息或动作。
- 更不好的是,如果驱动已被交换到硬盘,响应时间会长到不可接受,使用 mlock 系统调用可能会有帮助,但是常常的你将需要锁住许多内存页,因为一个用户空间程序依赖大量的库代码,mlock 也限制在授权用户上。
- 最重要的设备不能在用户空间处理,包括但不限于网络接口和块设备。
十、快速参考
insmod
modprobe
rmmod
用户空间工具,加载模块到运行中的内核以及去除它们。
#include <linux/init.h>
module_init(init_function);
module_exit(cleanup_function);
指定模块的初始化和清理函数的宏定义。
__init
__initdata
__exit
__exitdata
函数(__init 和 __exit)和数据(__initdata 和 __exitdata)的标记,只用在模块初始化或者清理时间。
#include <linux/sched.h>
最重要的头文件中的一个,这个文件包含很多驱动使用的内核 API 的定义,包括睡眠函数和许多变量声明。
struct task_struct *current;
当前进程。
current->pid
current->comm
进程 ID 和 当前进程的命令名。
obj-m
一个 makefile 符号,内核建立系统用来决定当前目录下的哪个模块应当被建立。
/sys/module
/proc/modules
/sys/module 是一个 sysfs 目录层次,包含当前加载模块的信息。/proc/moudles 是旧式的,那种信息的单个文件版本。其中的条目包含了模块名,每个模块占用的内存数量,以及使用计数,另外的字串追加到每行的末尾来指定标志,对这个模块当前是活动的。
vermagic.o
来自内核源码目录的目标文件,描述一个模块为之建立的环境。
#include <linux/module.h>
必需的头文件,它必须在一个模块源码中包含。
#include <linux/version.h>
头文件,包含在建立的内核版本信息。
LINUX_VERSION_CODE
整型宏定义,对 #ifdef 版本依赖有用。
EXPORT_SYMBOL (symbol);
EXPORT_SYMBOL_GPL (symbol);
宏定义,用来输出一个符号给内核。第 2 种形式输出没有版本信息,第 3 种限制输出给 GPL 许可的模块。
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name);
放置文档在目标文件的模块中。
module_init(init_function);
module_exit(exit_function);
宏定义,声明一个模块的初始化和清理函数。
#include <linux/moduleparam.h>
module_param(variable, type, perm);
宏定义,创建模块参数,可以被用户在模块加载时调整(或者在启动时间,对于内嵌代码)。类型可以是 bool,charp,int,invbool,short,ushort,uint,ulong 或者 intarray。
#include <linux/kernel.h>
int printk(const char * fmt, …);
内核代码的 printf 类似物。
我的qq:2442391036,欢迎交流!
相关文章:

Linux设备驱动程序(二)——建立和运行模块
文章目录 前言一、设置测试系统二、Hello World 模块1、代码详解2、执行效果 三、内核模块相比于应用程序1、用户空间和内核空间2、内核的并发3、当前进程4、几个别的细节 四、编译和加载1、编译模块2、加载和卸载模块3、版本依赖 五、内核符号表六、预备知识七、初始化和关停1…...

【算法】单调栈问题
文章目录 题目思路分析代码实现 题目 给定一个不含有重复值的数组arr,找到每一个i位置左边和右边离i位置最近且值比arr[i]小的位置,返回所有位置相应的消息。 比如arr{3,4,1,5,6,2,…...

Hack The Box - 关卡Dancing
SMB(全称是Server Message Block)是一个协议名,可用于在计算机间共享文件、打印机、串口等,电脑上的网上邻居就是靠它实现的。 SMB 是一种客户机/服务器、请求/响应协议。通过 SMB 协议,客户端应用程序可以在各种网络环境下读、写服务器上的…...

【软件设计与体系结构】 软件体系结构风格
软件体系结构(Software Architecture) 软件体系结构(Software Architecture)包括构成系统的设计元素的描述、 设计元素 之间的交互、 设计元素的组合模式以及在这些模式中的约束。 定义 软件体系结构表示系统的框架结构…...

detectron2 使用教程
本范例演示使用非常有名的目标检测框架detectron2 🤗🤗 在自己的数据集(balloon数据)上训练实例分割模型MaskRCNN的方法。 detectron2框架的设计有以下一些优点: 1,强大:提供了包括目标检测、实例分割、全景分割等非常广泛的视觉任务模型库。 2,灵活:可以通过注册机…...

哈希表常用数据结构
哈希表常用数据结构 查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。 哈希法也是空间换时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。 集合底层实现…...

Java字节流
4 字节流 字节流抽象基类 InputStream:这个抽象类是表示字节输入流的所有类的超类OutputStream:这个抽象类是表示字节输出流的所有类的超类子类名特点:子类名称都是以其父类名作为子类名的后缀4.1 IO流概述和分类 IO流概述: IO: 输入/输出(Input/Output)流:是一种抽象概念…...

arm3399主板-使用ubuntu20.04搭建LVS-DR(netplan)
目录 一、规划 1、网络拓扑 2、检查 二、配置设备 1、配置LVS 1.配置IP转发 2.清除防火墙 3.安装ipvsadm工具 4.配置VIP 5.netplan与NetworkManager介绍 6.添加LVS规则 1.清除防火墙 2.添加伪装IP 3.安装web服务 4. 修改内核参数,防止IP冲突 3、配置w…...

Go中同/异步与锁的应用~~sync包
Go中锁的实现~~sync包 go中sync包中提供了互斥锁; 在前面Go中channel文章中我们使用了time.Sleep()函数使得main函数的Goroutine阻塞至所有协程Goroutine结束,但这并不是一个很好的办法,因为我们实际应用中并不能准确知道协程什么时候结束(这里面要考虑服务器的性能,网络波动以…...

Flask知识点2
1、flash() get_flashed_messages() : 用来消耗flash方法中存储的消息 使用flash存储消息时,需要设置SECRET_KEY flash 内部消息存储依赖了session 2、CSRF(Cross Site Request Forgery) 跨站请求伪造,指攻击者盗用你的身份发送恶意请求 CSRFProt…...

R语言生物群落(生态)数据统计分析与绘图(从数据整理到分析结果展示)
R 语言作的开源、自由、免费等特点使其广泛应用于生物群落数据统计分析。生物群落数据多样而复杂,涉及众多统计分析方法。以生物群落数据分析中的最常用的统计方法回归和混合效应模型、多元统计分析技术及结构方程等数量分析方法为主线,通过多个来自经典…...

代码随想录训练营Day58| 739. 每日温度 496.下一个更大元素 I
目录 学习目标 学习内容 739. 每日温度 496.下一个更大元素 I 学习目标 739. 每日温度 496.下一个更大元素 I 学习内容 739. 每日温度 739. 每日温度 - 力扣(LeetCode)https://leetcode.cn/problems/daily-temperatures/ class Solution:def da…...

设计模式-命令模式
命令模式 问题背景命令模式基本介绍UML类图 解决方案UML类图代码示例 问题背景 1)随着现在科技越来越先进,我们在家庭中对物品的开关都不需要亲自走过去来进行了。我们只需要通过手机APP中的按键来远程执行这个命令。 2)其实这就是命令模式&…...

软考——下午题部分,例题一,二,三,六
例题一 11年上半年 病人,护理人员,医生 D 生命体征范围文件 日志文件 病历文件 治疗意见文件 14年上 E1 巴士司机,2 机械师,3 会计,4 主管,5 库存管理系统 D 巴士列表文件 维修记录文件 部件清单 人事档案 14年下 1 客户 2 供应商 D 销售订单表 库存…...

关于render: h => h(App)的解释
当我们第一次安装完脚手架,打开 的时候,我相信,一定有小伙伴和我一样,看到main.js里面的render: h > h(App),感觉懵懵的。 因为,在刚开始接触vue的时候,我们这里是这样写的: 而使用了脚手…...

flask实现简易图书管理系统
项目结构 技术选型 flask 做后端, 提供数据和渲染html 暂时没有提供mysql, 后续会更新操作mysql和样式美化的版本 起一个flask服务 flask是python的一个web框架, 下面演示如何提供http接口, 并返回json数据 main.py # flask创建http接口 from flask import Flask, request, jso…...

2021 年全国大学生物联网设计竞赛(华为杯)全国总决赛获奖名单
由全国高等学校计算机教育研究会主办,上海交通大学承办,华为技术有限 公司协办,中国电信天翼物联、中国移动中移物联网、霍尼韦尔 Tridium、CSA 联盟、新大陆、德州仪器 (TI)、百度、机械工业出版社华章公司联合支持的 2021 全国大学生物联网…...

操作系统复习2.3.5-管程
引入管程 PV操作困难,容易书写出错,引入管程,作为一种高级同步机制 组成 局限于管程的共享数据结构说明对该数据结构进行操作的一组过程对局部于管程的共享数据结构设置初始值的语句管程有一个名字 基本特征 局限于管程的数据只能被局限…...

List Set Map Queue Deque 之间的区别是什么?
List Set Map Queue Deque 之间的区别是什么? 1. Java 集合框架有那些接口?2. List Set Map Queue Deque 之间的区别是什么? 1. Java 集合框架有那些接口? List、Set、Map、Queue、Deque 2. List Set Map Queue Deque 之间的区别…...

unity行为决策树实战详解
一、行为决策树的概念 行为决策树是一种用于游戏AI的决策模型,它将游戏AI的行为分解为一系列的决策节点,并通过节点之间的连接关系来描述游戏AI的行为逻辑。在行为决策树中,每个节点都代表一个行为或决策,例如移动、攻击、逃跑等…...

Spring学习记录
目录 bean的单例与多例 设置 工厂模式的三种形态 简单工厂模式 代码: 运行结果: 总结: 工厂模式 代码: 运行结果: 总结: 抽象工厂模式 代码: 运行结果: 总结: …...

模板方法-
定义:又叫模板模式,是指定义一个算法骨架,并允许子类为其中的一个或多个步骤提供实现。 适用场景: 1、一次性实现一个算法不变的部分,并将可变的行为留给子类来实现 2、各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复 优点…...

[Kubernetes] - RabbitMQ学习
1.消息队列 消息: 在应用间传送的数据队列,先进先出 1.2. 作用 好处:解耦, 容错,削峰坏处:降低系统可用性,系统复杂度提高,一致性问题; RabbitMQ组成部分:…...

swagger页面 doc.html出不来,swagger-ui/index.html能出来
swagger页面 doc.html出不来,swagger-ui/index.html能出来。前前后后折腾了很久,jar包冲突,jar包版本,添加路径啥的都弄了,就是出不来。 后来全局搜索“doc.html”页面发现能出来的项目能搜到这个页面: 定…...

IEEE802.3和IEEE802.11的分类(仅为分类)
IEEE802.3标准 IEEE802.3:10兆以太网 ●10Base-5 使用粗同轴电缆,最大网段长度为500m,基带传输方法; ●10Base-2 使用细同轴电缆,最大网段长度为185m,基带传输方法; ●10Base&am…...

c# cad二次开发通过获取excel数据 在CAD绘图,将CAD属性导出到excel
c# cad二次开发通过获取excel数据 在CAD绘图,将CAD属性导出到excel using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Runtime; using System; using System.Collections.Generic; using System.Linq; us…...

LLM之高性能向量检索库
LLM向量数据库 高性能向量检索库milvus简介安装调用 faiss简介安装调用 高性能向量检索库 milvus 简介 Milvus 是一个开源的向量数据库引擎,旨在提供高效的向量存储、检索和分析能力。它被设计用于处理大规模的高维向量数据,常用于机器学习、计算机视觉…...

实体类注解
目录 一、TableField注解 二、TableId注解 三、Table注解 四、TableLogic注解 五、Getter与Setter注解 六、EqualsAndHashCode注解 七、Accessors注解 一、TableField注解 Data NoArgsConstructor //空参构造方法 AllArgsConstructor //全参构造方法 TableName("t…...

常见数据结构种类
常见数据结构种类 数据存储的常用结构有:栈、队列、数组、链表和红黑树 a.队列(queue) – 先进先出,后进后出。 – 场景:各种排队。叫号系统。 – 有很多集合可以实现队列。 b.栈(stack) – …...

linux高级---k8s中的五种控制器
文章目录 一、k8s的控制器类型二、pod与控制器之间的关系三、状态与无状态化对特点四、Deployment1、Deployment的资源清单文件2、在配置清单中调用deployment控制器3、镜像更新4、金丝雀发布5、删除Deployment 五、Statefulset六、DaemonSet1、daemonset的资源清单文件2、在配…...