第三节 第一个内核模块
hellomodule 实验
实验说明
硬件介绍
本节实验使用到STM32MP157 开发板
实验代码讲解
本章的示例代码目录为:linux_driver/module/hellomodule
从前面我们已经知道了内核模块的工作原理,这一小节就开始写代码了,跟hello world 一样,下面就展示一个最简单hello module 框架。
列表1: hello module 框架
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>static int __init hello_init(void)
{printk(KERN_EMERG "[ KERN_EMERG ] Hello Module Init\n");printk( "[ default ] Hello Module Init\n");return 0;
}static void __exit hello_exit(void)
{printk("[ default ] Hello Module Exit\n");
}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL2");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("hello module");
MODULE_ALIAS("test_module");
类比hello world,接来下理解每一行代码的含义,并最终在Linux 上运行这个模组,验证我们前面的理论,也为下一章驱动打下基础。
代码框架分析
Linux 内核模块的代码框架通常由下面几个部分组成:
- 模块加载函数(必须):当通过insmod 或modprobe命令加载内核模块时,模块的加载函数就会自动被内核执行,完成本模块相关的初始化工作。
- 模块卸载函数(必须):当执行rmmod 命令卸载模块时,模块卸载函数就会自动被内核自动执行,完成相关清理工作。
- 模块许可证声明(必须):许可证声明描述内核模块的许可权限,如果模块不声明,模块被加载时,将会有内核被污染的警告。
- 模块参数:模块参数是模块被加载时,可以传值给模块中的参数。
- 模块导出符号:模块可以导出准备好的变量或函数作为符号,以便其他内核模块调用。
- 模块的其他相关信息:可以声明模块作者等信息。
上面示例的hello module 程序只包含上面三个必要部分以及模块的其他信息声明(模块参数和导出符号将在下一节实验出现)。
头文件包含了<linux/init.h> 和<linux/module.h>,这两个头文件是写内核模块必须要包含的。模块初始化函数hello_init 调用了printk 函数,在内核模块运行的过程中,他不能依赖于C 库函数,因此用不了printf 函数,需要使用单独的打印输出函数printk。该函数的用法与printf 函数类似。完成模块初始化函数之后,还需要调用宏module_init 来告诉内核,使用hello_init 函数来进行初始化。模块卸载函数也用printk 函数打印字符串,并用宏module_exit 在内核注册该模块的卸载函数。最后,必须声明该模块使用遵循的许可证,这里我们设置为GPL2 协议。
头文件
前面我们已经接触过了Linux 的应用编程,了解到Linux 的头文件都存放在/usr/include 中。编写内核模块所需要的头文件,并不在上述说到的目录,而是在Linux 内核源码中的include 文件夹。
- #include <linux/module.h>:包含内核模块信息声明的相关函数
- #include <linux/init.h>:包含了module_init() 和module_exit() 函数的声明
- #include <linux/kernel.h>:包含内核提供的各种函数,如printk
编写内核模块中经常要使用到的头文件有以下两个:<linux/init.h> 和<linux/module.h>。我们可以看到在头文件前面也带有一个文件夹的名字linux,对应了include 下的linux 文件夹,我们到该文件夹下,查看这两个头文件都有什么内容。
列表2: init.h 头文件(位于内核源码/include/linux/init.h)
#define module_init(x) __initcall(x);#define module_exit(x) __exitcall(x);
Init.h 头文件主要包含了内核模块的加载、卸载函数的声明,还有一些宏定义,因此,只要我们涉及内核模块的编程,就需要加上该头文件。
列表3: module.h 头文件(位于内核源码/include/linux/module.h)
/* Generic info of form tag = "info" */
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)#define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)
#define MODULE_LICENSE(_license) MODULE_INFO(license, _license)#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
以上代码中,列举了module.h 文件中的部分宏定义,这部分宏定义,有的是可有可无的,但是MODULE_LICENSE 这个是指定该内核模块的许可证,是必须要有的。
模块加载/卸载函数
module_init
回忆我们使用单片机时,假设我们要使用串口等外设时,是不是都需要调用一个初始化函数,在这个函数里面,我们初始化了串口的GPIO,配置了串口的相关参数,如波特率,数据位,停止位等等参数。func_init 函数在内核模块中也是做与初始化相关的工作。
列表4: 内核模块加载函数(位于内核源码/linux/init.h)
static int __init func_init(void)
{
}
module_init(func_init);
返回值:
- 0:表示模块初始化成功,并会在/sys/module 下新建一个以模块名为名的目录,如下图中的红框处
- 非0:表示模块初始化失败
在C 语言中,static 关键字的作用如下:
- static 修饰的静态局部变量直到程序运行结束以后才释放,延长了局部变量的生命周期;
- static 的修饰全局变量只能在本文件中访问,不能在其它文件中访问;
- static 修饰的函数只能在本文件中调用,不能被其他文件调用。
内核模块的代码,实际上是内核代码的一部分,假如内核模块定义的函数和内核源代码中的某个函数重复了,编译器就会报错,导致编译失败,因此我们给内核模块的代码加上static 修饰符的话,那么就可以避免这种错误。
列表5: __init、__initdata 宏定义(位于内核源码/linux/init.h)
#define __init __section(.init.text) __cold notrace
#define __initdata __section(.init.data)
以上代码__init、__initdata 宏定义(位于内核源码/linux/init.h)中的__init 用于修饰函数,__initdata用于修饰变量。带有__init 的修饰符,表示将该函数放到可执行文件的__init 节区中,该节区的内容只能用于模块的初始化阶段,初始化阶段执行完毕之后,这部分的内容就会被释放掉,真可谓是“针尖也要削点铁”。
列表6: module_init 宏定义
#define module_init(x) __initcall(x);
宏定义module_init 用于通知内核初始化模块的时候,要使用哪个函数进行初始化。它会将函数地址加入到相应的节区section 中,这样的话,开机的时候就可以自动加载模块了。
module_exit
理解了模块加载的内容之后,来学习模块卸载函数应该会比较简单。与内核加载函数相反,内核模块卸载函数func_exit 主要是用于释放初始化阶段分配的内存,分配的设备号等,是初始化过程的逆过程。
列表7: 内核模块卸载函数
static void __exit func_exit(void)
{
}
module_exit(func_exit);
与函数func_init 区别在于,该函数的返回值是void 类型,且修饰符也不一样,这里使用的使用__exit,表示将该函数放在可执行文件的__exit 节区,当执行完模块卸载阶段之后,就会自动释放该区域的空间。
列表8: __exit、__exitdata 宏定义
#define __exit __section(.exit.text) __exitused __cold notrace
#define __exitdata __section(.exit.data)
类比于模块加载函数,__exit 用于修饰函数,__exitdata 用于修饰变量。宏定义module_exit 用于告诉内核,当卸载模块时,需要调用哪个函数。
printk 函数
-
printf:glibc 实现的打印函数,工作于用户空间
-
printk:内核模块无法使用glibc 库函数,内核自身实现的一个类printf 函数,但是需要指定打印等级。
– #define KERN_EMERG “<0>”通常是系统崩溃前的信息
– #define KERN_ALERT “<1>”需要立即处理的消息
– #define KERN_CRIT “<2>”严重情况
– #define KERN_ERR “<3>”错误情况
– #define KERN_WARNING “<4>”有问题的情况
– #define KERN_NOTICE “<5>”注意信息
– #define KERN_INFO “<6>”普通消息
– #define KERN_DEBUG “<7>”调试信息
查看当前系统printk 打印等级:cat /proc/sys/kernel/printk,从左到右依次对应当前控制台日志级别、默认消息日志级别、最小的控制台级别、默认控制台日志级别。
打印内核所有打印信息:dmesg,注意内核log 缓冲区大小有限制,缓冲区数据可能被覆盖掉。
许可证
Linux 是一款免费的操作系统,采用了GPL 协议,允许用户可以任意修改其源代码。GPL 协议的主要内容是软件产品中即使使用了某个GPL 协议产品提供的库,衍生出一个新产品,该软件产品都必须采用GPL 协议,即必须是开源和免费使用的,可见GPL 协议具有传染性。因此,我们可以在Linux 使用各种各样的免费软件。在以后学习Linux 的过程中,可能会发现我们安装任何一款软件,从来没有30 天试用期或者是要求输入激活码的。
列表9: 许可证
#define MODULE_LICENSE(_license) MODULE_INFO(license, _license)
内核模块许可证有“GPL”,“GPL v2”,“GPL and additional rights”,“Dual SD/GPL”,“DualMPL/GPL”,“Proprietary”。
相关信息声明
下面,我们介绍一下关于内核模块程序结构的最后一部分内容。这部分内容只是为了给使用该模块的读者一本“说明书”,属于可有可无的部分,有则锦上添花,若没有也无伤大雅。
表内核模块信息声明函数
函数 | 作用 |
---|---|
MODULE_LICENSE() | 表示模块代码接受的软件许可协议,Linux 内核遵循GPL V2 开源协议,内核模块与linux 内核保持一致即可。 |
MODULE_AUTHOR() | 描述模块的作者信息 |
MODULE_DESCRIPTION() | 对模块的简单介绍 |
MODULE_ALIAS() | 给模块设置一个别名 |
作者信息
列表10: 内核模块作者宏定义(位于内核源码/linux/module.h)
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
我们前面使用modinfo 中打印出的模块信息中“author” 信息便是来自于宏定义MODULE_AUTHOR。该宏定义用于声明该模块的作者。
模块描述信息
列表11: 模块描述信息(位于内核源码/linux/module.h)
#define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)
模块信息中“description”信息则来自宏MODULE_DESCRIPTION,该宏用于描述该模块的功能作用。
模块别名
列表12: 内核模块别名宏定义(位于内核源码/linux/module.h)
#define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)
模块信息中“alias”信息来自于宏定义MODULE_ALIAS。该宏定义用于给内核模块起别名。注意,在使用该模块的别名时,需要将该模块复制到/lib/modules/内核源码/下,使用命令depmod 更新模块的依赖关系,否则的话,Linux 内核怎么知道这个模块还有另一个名字。
实验准备
获取内核模块源码,将配套代码/linux_driver/module/hellomodule 解压到内核代码同级目录。
makefile 说明
对于内核模块而言,它是属于内核的一段代码,只不过它并不在内核源码中。为此,我们在编译时需要到内核源码目录下进行编译。编译内核模块使用的Makefile 文件,和我们前面编译C 代码使用的Makefile 大致相同,这得益于编译Linux 内核所采用的Kbuild 系统,因此在编译内核模块时,我们也需要指定环境变量ARCH 和CROSS_COMPILE 的值。
列表13: Makefile (位于linux_driver/module/hellomodule/Makefile)
KERNEL_DIR=../../ebf_linux_kernel/build_image/buildARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILEobj-m := hellomodule.o
all:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules.PHONE:clean copyclean:$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) cleancopy:sudo cp *.ko /home/embedfire/workdir
以上代码中提供了一个关于编译内核模块的Makefile。
- 第1 行:该Makefile 定义了变量KERNEL_DIR,来保存内核源码的目录。
- 第3-5 行:指定了工具链并导出环境变量
- 第6 行:变量obj-m 保存着需要编译成模块的目标文件名。
- 第8 行:’$ (MAKE)modules’实际上是执行Linux 顶层Makefile的伪目标modules。通过选项’-C’,可以让make
工具跳转到源码目录下读取顶Makefile。’M=$(CURDIR)’表明返回到当前目录,读取并执行当前目录的Makefile,开始编译内核模块。CURDIR是make 的内嵌变量,自动设置为当前目录。
编译命令说明
在实验目录下输入如下命令来编译驱动模块:
make
编译成功后,实验目录下会生成名为“hellomodule.ko”的驱动模块文件
程序运行结果
如何使用内核模块
我们如愿编译了自己的内核模块,接下来就该了解如何使用这个内核模块了。将hellomodule.ko通过scp 或NFS 拷贝到开发板中,我们来逐一讲解这些工具。
lsmod
lsmod 列出当前内核中的所有模块,格式化显示在终端,其原理就是将/proc/modules 中的信息调整一下格式输出。lsmod 输出列表有一列Used by,它表明此模块正在被其他模块使用,显示了模块之间的依赖关系。
insmod
如果要将一个模块加载到内核中,insmod 是最简单的办法,insmod+ 模块完整路径就能达到目的,前提是你的模块不依赖其他模块,还要注意需要sudo 权限。如果你不确定是否使用到其他模块的符号,你也可以尝试modprobe,后面会有它的详细用法。
通过insmod 命令加载hellomodule.ko 内存模块加载该内存模块的时候,该内存模块会自动执行module_init() 函数,进行初始化操作,该函数打印了‘hello module init’。再次查看已载入系统的内核模块,我们就会在列表中发现hellomodule.ko 的身影。
在我们内核模块传参与符号共享实验这一小节,calculation.ko 和parametermodule.ko。其中calculation.ko 依赖parametermodule.ko 中的参数和函数,所以先手动加载parametermodule.ko,然后再加载calculation.ko。
同样卸载的时,parametermodule.ko 中的参数和函数被calculation.ko 调用,必须先卸载calculation.ko再卸载parametermodule.ko,否则会报错”ERROR: Moduleparametermodule is in use by: calculation”
modprobe
modprobe 和insmod 具备同样的功能,同样可以将模块加载到内核中,除此以外modprobe 还能检查模块之间的依赖关系,并且按照顺序加载这些依赖,可以理解为按照顺序多次执行insmod。
在内核模块传参与符号共享实验中,calculation.ko 和parametermodule.ko 需要按照先后次序依次加载,而使用modprobe 工具,可以直接加载parametermodule.ko,当然modprobe 之前需要先用depmod -a 建立模块之间的依赖关系。
depmod
modprobe 是怎么知道一个给定模块所依赖的其他的模块呢?在这个过程中,depend 起到了决定性作用,当执行modprobe 时,它会在模块的安装目录下搜索module.dep 文件,这是depmod 创建的模块依赖关系的文件。
rmmod
rmod 工具仅仅是将内核中运行的模块删除,只需要传给它路径就能实现。
rmmod 命令卸载某个内存模块时,内存模块会自动执行*_exit() 函数,进行清理操作,我们的hellomodule 中的*_exit() 函数打印了一行内容,但是控制台并没有显示,可以使用dmesg 查看,之所以没有显示是与printk 的打印等级有关,前面有关于printk 函数有详细讲解。rmmod 不会卸载一个模块所依赖的模块,需要依次卸载,当然是modprobe -r 可以一键卸载。
modinfo
modinfo 用来显示我们在内核模块中定义的几个宏。我们可以通过modinfo 来查看hellomodule,我们从打印的输出信息中,可以了解到,该模块遵循的是GPL 协议,该模块的作者是embedfire,该模块的vermagic 等等。而这些信息在模块代码中由相关内核模块信息声明函数声明
系统自动加载模块
我们自己编写了一个模块,或者说怎样让它在板子开机自动加载呢?这里就需要用到上述的depmod和modprobe 工具了。
首先需要将我们想要自动加载的模块统一放到”/lib/modules/内核版本”目录下,内核版本使用’uname -r’查询;其次使用depmod 建立模块之间的依赖关系,命令’depmod -a’;这个时候我们就可以在modules.dep 中看到模块依赖关系,可以使用如下命令查看;
cat /lib/modules/内核版本/modules.dep | grep calculation
最后在/etc/modules 加上我们自己的模块,注意在该配置文件中,模块不写成.ko 形式代表该模块与内核紧耦合,有些是系统必须要跟内核紧耦合,比如mm 子系统,一般写成.ko 形式比较好,如果出现错误不会导致内核出现panic 错误,如果集成到内核,出错了就会出现panic。
然后重启开发板,lsmod 就能查看到我们的模块开机就被加载到内核里面了。
实验代码讲解
本节实验验证内核模块传参和符号共享。
内核模块传参代码讲解
内核模块作为一个可拓展的动态模块,为Linux 内核提供了灵活性,但是有时我们需要根据不同的应用场景给内核传递不同的参数,例如在程序中开启调试模式、设置详细输出模式以及制定与具体模块相关的选项,都可以通过参数的形式来改变模块的行为。
Linux 内核提供一个宏来实现模块的参数传递
列表14: module_param 函数(内核源码/include/linux/moduleparam.h)
#define module_param(name, type, perm) \\
module_param_named(name, name, type, perm)
#define module_param_array(name, type, nump, perm) \\
module_param_array_named(name, name, type, nump, perm)
以上代码中的module_param 函数需要传入三个参数:
- name:我们定义的变量名;
- type:参数的类型,目前内核支持的参数类型有byte,short,ushort,int,uint,long,ulong,charp,bool,invbool。其中charp表示的是字符指针,bool 是布尔类型,其值只能为0 或者是1;invbool 是反布尔类型,其值也是只能取0 或者是1,但是true值表示0,false 表示1。变量是char 类型时,传参只能是byte,char * 时只能是charp。
- perm:表示的是该文件的权限,具体参数值见下表。
表文件权限
表1: 文件权限:header: “用户组”, “标志位”,”解释”:widths: 10, 10,30
当前用户 | S_IRUSR | 用户拥有读权限 |
---|---|---|
S_IWUSR | 用户拥有写权限 | |
当前用户组 | S_IRGRP | 当前用户组的其他用户拥有读权限 |
S_IWUSR | 当前用户组的其他用户拥有写权限 | |
其他用户 | S_IROTH | 其他用户拥有读权限 |
S_IWOTH | 其他用户拥有写权限 |
上述文件权限唯独没有关于可执行权限的设置,请注意,该文件不允许它具有可执行权限。如果强行给该参数赋予表示可执行权限的参数值S_IXUGO,那么最终生成的内核模块在加载时会提示错误,见下图。
下面是我们一个实验代码:
列表15: 示例程序
static int itype=0;
module_param(itype,int,0);static bool btype=0;
module_param(btype,bool,0644);static char ctype=0;
module_param(ctype,byte,0);static char *stype=0;
module_param(stype,charp,0644);static int __init param_init(void)
{printk(KERN_ALERT "param init!\n");printk(KERN_ALERT "itype=%d\n",itype);printk(KERN_ALERT "btype=%d\n",btype);printk(KERN_ALERT "ctype=%d\n",ctype);printk(KERN_ALERT "stype=%s\n",stype);return 0;
}
- 第1-11 行:定义了四个常见变量然后使用module_param 宏来声明这四个参数
- 第13-21 行:并在param_init 中输出上面声明的四个参数。
我们定义的四个模块参数,会在‘/sys/module/模块名/parameters’下会存在以模块参数为名的文件。由于itype 和ctype 的权限是0,所以我们没有权限查看该参数。
符号共享代码讲解
在前面我们已经详细的分析了关于导出符号的内核源码,符号指的就是在内核模块中导出函数和变量,在加载模块时被记录在公共内核符号表中,以供其他模块调用。这个机制,允许我们使用分层的思想解决一些复杂的模块设计。我们在编写一个驱动的时候,可以把驱动按照功能分成几个内核模块,借助符号共享去实现模块与模块之间的接口调用,变量共享。
列表16: 导出符号
#define EXPORT_SYMBOL(sym) \\
__EXPORT_SYMBOL(sym, "")
EXPORT_SYMBOL 宏用于向内核导出符号,这样的话,其他模块也可以使用我们导出的符号了。下面通过一段代码,介绍如何使用某个模块导出符号。
列表17: parametermodule.c
... 省略代码...
static int itype=0;
module_param(itype,int,0);EXPORT_SYMBOL(itype);int my_add(int a, int b)
{return a+b;
}EXPORT_SYMBOL(my_add);int my_sub(int a, int b)
{return a-b;
}
EXPORT_SYMBOL(my_sub);
... 省略代码...
- 第2-3 行:定义了参数itype,并通过EXPORT_SYMBOL 宏导出
- 第7-12 行:和my_add,并通过EXPORT_SYMBOL 宏导出
- 第14-21 行:my_sub 函数,并通过EXPORT_SYMBOL 宏导出
以上代码中,省略了内核模块程序的其他内容,如头文件,加载/卸载函数等。
列表18: calculation.h
#ifndef __CALCULATION_H__
#define __CALCULATION_H__extern int itype;int my_add(int a, int b);
int my_sub(int a, int b);#endif
声明额外的变量itype,my_add 和my_sub 函数。
列表19: calculation.c
... 省略代码...
#include "calculation.h"... 省略代码...
static int __init calculation_init(void)
{printk(KERN_ALERT "calculation init!\n");printk(KERN_ALERT "itype+1 = %d, itype-1 = %d\n", my_add(itype,1), my_sub(itype,1));return 0;
}
... 省略代码...
calculation.c 中使用extern 关键字声明的参数itype,调用my_add()、my_sub() 函数进行计算。
实验准备
获取内核模块源码,将配套代码/linux_driver/module/parametermoudule 解压到内核代码同级目录。
makefile 说明
列表20: Makefile (位于linux_driver/module/hellomodule/Makefile)
... 省略代码...
obj-m := parametermodule.o calculation.o
... 省略代码...
以上Makefile 与上一个实验,只有目标文件不同。
编译命令说明
在实验目录下输入如下命令来编译驱动模块:
make
编译成功后,实验目录下会生成名为“parametermodule.o“和“calculation.o “的驱动模块文件
程序运行结果
通过NFS 将编译好的module_param.ko 拷贝到开发板中,加载module_param.ko 并传参,这时我们声明的四个变量的值,就是变成了我们赋的值。
sudo insmod parametermodule.ko itype=123 btype=1 ctype=200 stype=abc
查看向内核导出的符号表‘cat /proc/kallsyms’
参考资料:嵌入式Linux 驱动开发实战指南-基于STM32MP1 系列
相关文章:
第三节 第一个内核模块
hellomodule 实验 实验说明 硬件介绍 本节实验使用到STM32MP157 开发板 实验代码讲解 本章的示例代码目录为:linux_driver/module/hellomodule 从前面我们已经知道了内核模块的工作原理,这一小节就开始写代码了,跟hello world 一样&…...
从CNN到Transformer:基于PyTorch的遥感影像、无人机影像的地物分类、目标检测、语义分割和点云分类
我国高分辨率对地观测系统重大专项已全面启动,高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成,将成为保障国家安全的基础性和战略性资源。随着小卫星星座的普及,对地观测已具备多次以上的全球覆盖…...
操作系统的奋斗(三)内存管理
第三章 内存管理3.1内存管理概念3.1.1 内存管理的基本原理和要求(1)内存管理的主要功能3.1.2 覆盖和交换(1)覆盖(2)交换3.1.3 连续分配管理方式(1)单一连续分配(2&#x…...
多选多的一种通用处理逻辑
开发的时候,我们经常会涉及元素的多选多,并且还需要对选中的元素进行拖动排序 通用的设计方案如下 游戏资源集合与游戏资源的绑定关系处理(多选多的一种通用处理逻辑) 可能的情况: 1.之前被选中的资源,现…...
Redis 的安装 + SpringBoot 集成 Redis
1.安装 Redis此处的 Redis 安装是针对 Linux 版本的安装, 因为 Redis 官方没有提供 Windows 版本, 只提供了 Linux 版本. 但是我们可以通过Windows 去远程连接 Redis.1.1 使用 yum 安装 Redis使用如下命令, 将 Redis 安装到 Linux 服务器:yum -y install redis1.2 启动 Redis使…...
为什么在容器中 1 号进程挂不上 arthas?
作者:卜比 本文是《容器中的 Java》系列文章之 4/n ,欢迎关注后续连载 😃 。 系列1:JVM 如何获取当前容器的资源限制? 系列2:Java Agent 踩坑之 appendToSystemClassLoaderSearch 问题 系列3:让…...
23种设计模式之策略模式
一、概念 就是将一系列算法封装起来,并使它们之间相互替换。被封装起来的算法具有独立性外部不可改变其特性。 策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算…...
不会做UI自动化测试?一起设计框架再实践吧
目的相信做过测试的同学都听说过自动化测试,而UI自动化无论何时对测试来说都是比较吸引人的存在。相较于接口自动化来说它可以最大程度的模拟真实用户的日常操作与特定业务场景的模拟,那么存在即合理,自动化UI测试自然也是广大测试同学职业道…...
数据分析实战项目3:RFM用户分群
目录1、RFM模型介绍2、Excel实际RFM划分案例3、RFM案例3.1 数据加载和基本信息查看3.2 数据预处理和RFM的初始值计算3.3 RFM区间和划分和分值计算3.4 RFM计算结果保存3.4.1 保存到excel3.4.2 保存到数据库3.5 RFM计算结果可视化3.6 结果分析(营销建议)3.…...
消息中间件概述
目录1.为什么学习消息队列2.什么是消息中间件3.消息队列应用场景3.1 应用解耦3.2 异步处理3.3 流量削峰3.4 什么是QPS,PV3.5 什么是PV,UV,PR4. AMQP 和 JMS4.1 AMQP4.2 JMS4.3. AMQP 与 JMS 区别5. 消息队列产品6. RabbitMQ6.1 RabbitMQ简介6.2 RabbitMQ 中的相关概…...
vue和js给后端接口返回的数据(如以json数据为元素的数组)添加新的json字段
文章目录vue和js给后端接口返回的数据(如以json数据为元素的数组)添加新的json字段1. res为后端接口的响应2. 获取后端接口返回的数据3. 向 tableData 添加字段3.1. 向 tableData 中添加一个新json元素( {"time", "2023-02-09"} )3.…...
负载均衡的方式
在业务初期,我们一般会先使用单台服务器对外提供服务。随着业务流量越来越大,单台服务器无论如何优化,无论采用多好的硬件,总会有性能天花板,当单服务器的性能无法满足业务需求时,就需要把多台服务器组成集…...
python(15)--函数设计
前言 函数是可重用的程序代码块。 函数的作用,不仅可以实现代码的复用,还可以保证修改函数的代码时,所有调用该函数的地方都能得到体现。目前我已知函数的作用是:对代码实现了封装、函数调用、传递参数、返回计算结果等。 正文 …...
手把手教你用Python做可视化数据,还能调节动画丝滑度
数据可视化动画还在用Excel做? 现在一个简单的Python包就能分分钟搞定! 而且生成的动画也足够丝滑,效果是酱紫的: 这是一位专攻Python语言的程序员开发的安装包,名叫Pynimate。 目前可以直接通过PyPI安装使用。 使用…...
湖南中创教育PMP项目管理——变更管理
【变更管理】包括 一、如何理解需求变更 二、如何控制需求变更 三、项目变更管理流程 四、如何应对“奇葩”变更 一、如何理解需求变更: 1、项目中发生变更是正常现象,变更无法回避 2、哪里都可能出现变更,任何人都有权提出变更 3、…...
IC真题 —— 刷题记录(1)
引言 记录一些 我自己刷的 IC行业招聘真题,不是每题记录,只记录一些值得记录的,写下自己的看法。主要是一些数字IC行业题目,偏前端。 1、有一个逐次逼近型 8位A/D 转换器,若时钟频率为250KHz,完成一次转换…...
【C++入门】命名空间,输出输入,缺省参数,函数重载
文章目录命名空间C输入与输出缺省参数函数重载命名空间 在C/C中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标 识符的名称进行本地化࿰…...
cmu 445 poject 2笔记
2022年的任务 https://15445.courses.cs.cmu.edu/fall2022/project2/ checkpoint 1,实现b树,读,写,删 checkpoint 2, 实现b树,迭代器,并发读写删 本文不写代码,只记录遇到的一些思维盲点 checkp…...
梅开二度的 axios 源码阅读,三千字详细分享功能函数,帮助扩展开发思维
前言 第一遍看 axios 源码,更多的是带着日常开发的习惯,时不时产生出点联想。 第二遍再看 axios 源码,目标明确,就是奔着函数来的。 当有了明确清晰的目标,阅读速度上来了,思绪也转的飞快。 按图索骥&a…...
vcs仿真教程
VCS是在linux下面用来进行仿真看波形的工具,类似于windows下面的modelsim以及questasim等工具,以及quartus、vivado仿真的操作。 1.vcs的基本指令 vcs的常见指令后缀 sim常见指令 2.使用vcs的实例 采用的是全加器的官方教程,首先介绍不使用…...
java 自定义json解析注解 复杂json解析 工具类
java 自定义json解析注解 复杂json解析 工具类 目录java 自定义json解析注解 复杂json解析 工具类1.背景2、需求-各式各样的json一、一星难度json【json对象中不分层】二、二星难度json【json对象中出现层级】三、三星难度json【json对象中存在数组】四、四星难度json【json对象…...
类的 6 个默认成员函数
文章目录一、构造函数1. 构造函数的定义2. 编译器生成的构造函数3. 默认构造函数4. 初始化列表5. 内置成员变量指定缺省值(C11)二、析构函数1. 析构函数的定义2. 编译器生成的析构函数3. 自己写的析构函数的执行方式三、拷贝构造函数1. C语言值传递和返回值时存在 bug2. 拷贝构…...
基于Verilog HDL的状态机描述方法
⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。 🔥文章和代码已归档至【Github仓库…...
6年软件测试经历:成长、迷茫、奋斗
前言 测试工作6年,经历过不同产品、共事过不同专业背景、能力的同事,踩过测试各种坑、遇到过各种bug。测试职场生涯积极努力上进业务和技术能力快速进步过、也有努力付出却一无所得过、有对测试生涯前景充满希望认为一片朝气蓬勃过、也有对中年危机思考不…...
OpenMMLab AI实战营第五次课程
语义分割与MMSegmentation 什么是语义分割 任务: 将图像按照物体的类别分割成不同的区域 等价于: 对每个像素进行分类 应用:无人驾驶汽车 自动驾驶车辆,会将行人,其他车辆,行车道,人行道、交…...
【软考】系统集成项目管理工程师(二十)项目风险管理
一、项目风险管理概述1. 风险概念2. 风险分类3. 风险成本二、项目风险管理子过程1. 规划风险管理2. 识别风险3. 实施定性风险分析4. 实施定量风险分析5. 规划风险应对6. 控制风险三、项目风险管理流程梳理一、项目风险管理概述 1. 风险概念 风险是一种不确定事件或条件,一旦…...
2017-PMLR-Neural Message Passing for Quantum Chemistry
2017-PMLR-Neural Message Passing for Quantum Chemistry Paper: https://arxiv.org/pdf/1704.01212.pdf Code: https://github.com/brain-research/mpnn 量子化学的神经信息传递 这篇文献作者主要是总结了先前神经网络模型的共性,提出了一种消息传递神经网络&am…...
Python:每日一题之全球变暖(DFS连通性判断)
题目描述 你有一张某海域 NxN 像素的照片,"."表示海洋、"#"表示陆地,如下所示: ....... .##.... .##.... ....##. ..####. ...###. ....... 其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿…...
企业级安全软件装机量可能大增
声明 本文是学习大中型政企机构网络安全建设发展趋势研究报告. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 研究背景 大中型政企机构是网络安全保护的重中之重,也是国内网络安全建设投入最大,应用新技术、新产品最多的机构…...
为什么要用频谱分析仪测量频谱?
频谱分析仪是研究电信号频谱结构的仪器,用于信号失真度、调制度、谱纯度、频率稳定度和交调失真等信号参数的测量,可用以测量放大器和滤波器等电路系统的某些参数,是一种多用途的电子测量仪器。从事通信工程的技术人员,在很多时候…...
网站建设 交单流程/合肥百度推广优化排名
操作系统是Ubuntu Server 12.10 先安装Thrift sudo apt-get install libboost-dev libboost-test-dev \ libboost-program-options-dev libevent-dev automake \ libtool flex bison pkg-config g libssl-dev 如果你还要使用别的语言,也需要安装对应的包 Ruby ruby-…...
上海专业网站建设报价/网址收录
今天是机器学习的第16篇文章,我们来继续上周KD-Tree的话题。如果有没有看过上篇文章或者是最新关注的小伙伴,可以点击一下下方的传送门:机器学习——详解KD-Tree来龙去脉旋转不可行分析上周我们实现了KD-Tree建树和查询的核心功能,…...
wordpress产品参数多图/利尔化学股票
JDK安装与环境变量的配置 文章目录JDK安装与环境变量的配置一、JDK下载1.官网下载2.百度网盘二、JDK安装三、环境变量的配置一、JDK下载 1.官网下载 https://www.oracle.com/java/technologies/javase-jdk15-downloads.html 2.百度网盘 链接:https://pan.baidu.…...
卡密wordpress插件/网页推广平台
使用ajax跳转方法时,页面ctrlshifti调试报告了一个404错误,说找不到方法。页面地址栏直接指向方法的地址跳转也是404。目标方法是新增的,于是使用复制黏贴,确定各处方法名称一致之后,重启server。 调试时再次报错&…...
分析苏宁易购的网站建设/网站排名查询工具有哪些
~~~题面~~~ 题解: 这是一道强题emmmm,做法非常巧妙,,,我也是看了好久大佬题解才看明白一点 首先考虑没有限制的情况,即n个老鼠可以在同…...
wordpress收费版怎么激活/外贸建站与推广如何做
题面描述 状压dp。 设\(f[i][sta]\)为第\(i\)层状态为\(sta\)的方案数。 然后每次可以枚举上一层的状态以及本层的状态,然后如果不冲突且满足地图的要求,则转移。 时间复杂度\(O(2^{2m}*n*m)\). 然而过不了...大了一点点。考虑对于每一层预处理出哪些状态…...