【Linux】Linux内核模块开发
Linux内核模块开发
零、关于
1、概述
最近在学习Linux相关的东西,学习了U-Boot的编译,Linux的编译,能够在开发板上运行自己编译的U-Boot和Linux了,那么接下来就是在自己编译的Linux上做应用级或者系统级的开发了。本文以字符设备驱动为例介绍如何开发Linux内核的模块,包括静态编译、动态加载和模块之间的依赖等内容。
若要继续实践下面的内容,需要以你能够自己编译Linux内核为前提。
2、内核模块
Linux内核模块是用于扩展内核功能的一些代码。本质上是.ko
格式的独立目标文件,内核模块通过与内核链接,实现对硬件驱动、文件系统、网络协议等功能的灵活扩展。
内核模块分为设备驱动程序、文件系统模块、网络协议模块、网络服务模块、硬件架构与系统支持模块、内核子系统扩展模块和特殊功能模块等类型,我们可以针对某个部分对内核进行扩展。
3、处理方式
在Linux中有两种内核模块的处理方式,一种是静态编译,另一种是动态加载。
其中,静态编译是指我们在编译Linux时把我们的模块代码一起编译进系统可执行文件中去,使其成为内核不可分割的一部分。这样做的好处是在Linux启动时我们的代码就被加载到Linux内核系统中运行了,而且不用生成另外的模块文件,方便分发。但是缺点是我们需要对内核模块代码进行改动时需要重新编译Linux,内核运行期间也无法单独卸载或更新我们的内核模块,这样不方便随时修改。
而动态加载则是指我们的内核模块代码不会与Linux内核代码一起编译,而是另外再单独编译。这样会生成一个后缀为.ko
(Kernel Object)的内核模块文件。我们需要使用此内核模块时,可以使用加载内核模块命令(insmod
/modprobe
)把内核模块加载到Linux内核中,不需要使用此模块时,可以使用卸载内核模块命令(rmmod
)把内核模块从Linux内核中卸载,加载操作和卸载操作都无需重启内核。
壹、代码模板
在介绍如何编译和使用内核模块之前,我们需要对内核模块的代码有一个大致的了解。
1、内核模块代码
下面的代码模板构建了一个简单的Linux内核模块,在模块加载时会输出hello_yu init
消息,在模块卸载时会输出hello_yu exit
消息。通过printk
函数,这些消息会被记录到内核日志中,可使用dmesg
命令查看。
一个简单的内核模块代码文件hello.c
内容如下:
#include <linux/module.h>
#include <linux/kernel.h>int __init hello_yu_init(void)
{printk("hello_yu init\n");return 0;
}void __exit hello_yu_exit(void)
{printk("hello_yu exit\n");
}MODULE_LICENSE("GPL");
module_init(hello_yu_init);
module_exit(hello_yu_exit);
下面对其做一个更详细的介绍:
1)、头文件包含
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/module.h>
:这个头文件定义了构建内核模块所需的函数和宏,像module_init
、module_exit
以及MODULE_LICENSE
等。#include <linux/kernel.h>
:此头文件包含了内核编程常用的函数和数据结构,例如printk
函数等。
2)、模块初始化函数
int __init hello_yu_init(void)
{printk("hello_yu init\n");return 0;
}
int __init hello_yu_init(void)
:这是模块的初始化函数,当模块被加载到内核时会调用此函数。__init
是一个宏,其作用是告知编译器该函数仅在模块初始化时使用,之后就可以释放相关内存。printk("hello_yu init\n");
:printk 是内核中的打印函数,功能类似于用户空间的printf
。它会把消息输出到内核日志缓冲区,可通过dmesg
命令查看。return 0;
:返回值为0
表明模块初始化成功。若返回非零值,则意味着初始化失败,模块将无法加载。
3)、模块退出函数
void __exit hello_yu_exit(void)
{printk("hello_yu exit\n");
}
void __exit hello_yu_exit(void)
:这是模块的退出函数,当模块从内核卸载时会调用此函数。__exit
是一个宏,其作用是告知编译器该函数仅在模块卸载时使用。printk("hello_yu exit\n");
:当模块卸载时,会将此消息输出到内核日志缓冲区。
4)、模块许可证声明
MODULE_LICENSE("GPL");
MODULE_LICENSE("GPL");
:此宏用于声明模块所采用的许可证。在Linux内核中,使用GPL许可证是较为常见的。若不声明许可证,内核会发出警告。
5)、模块初始化和退出函数注册
module_init(hello_yu_init);
module_exit(hello_yu_exit);
module_init(hello_yu_init);
:该宏把hello_yu_init
函数注册为模块的初始化函数,当模块被加载时会调用此函数。module_exit(hello_yu_exit);
:该宏把hello_yu_exit
函数注册为模块的退出函数,当模块被卸载时会调用此函数。
2、Kconfig配置代码
Kconfig
文件是Linux内核配置系统的一部分,它定义了内核编译时可配置的选项。用户可以通过make menuconfig
来配置这些选项,进而决定哪些功能会被编译进内核。下面的这段Kconfig
代码定义了一个名为HELLO_YU
的可配置选项,它有三种状态可供选择。选项设置的描述信息是“This is Kernel Object Test by yu.”,帮助信息为 “This is just a kernel object test.”。用户可以通过内核配置工具来选择是否将该选项对应的功能编译进内核或者编译成内核模块。
config HELLO_YUtristate "This is Kernel Object Test by yu."helpThis is just a kernel object test.
这段内核Kconfig
代码用于在内核配置系统中定义一个可配置选项,下面对配置代码做个简单的介绍:
1)、config HELLO_YU
config
是Kconfig
语法中的关键字,用于定义一个新的配置选项。HELLO_YU
是这个配置选项的名称,在内核代码中可以通过这个名称来引用该配置选项。例如,在C
代码里可以使用#ifdef HELLO_YU
来判断这个选项是否被启用。
2)、tristate "This is Kernel Object Test by yu."
tristate
表示这个配置选项有三种状态:y
:代表 “是”,意味着该选项对应的功能会被直接编译进内核。n
:代表 “否”,即该选项对应的功能不会被编译进内核。m
:代表 “模块”,表示该选项对应的功能会被编译成一个内核模块,在需要的时候可以动态加载到内核中。"This is Kernel Object Test by yu."
是该配置选项的描述信息,在配置界面中会显示这个描述,让用户了解该选项的用途。
3)、help
help
关键字用于提供该配置选项的详细帮助信息。This is just a kernel object test.
是具体的帮助文本,当用户在配置界面中选择该选项并查看帮助信息时,就会显示这段文本,进一步说明该选项的用途。
3、Makefile配置代码
Makefile
是一种用于自动化编译和构建项目的文件,它定义了一系列的规则来描述如何从源文件生成目标文件和可执行文件。Make
工具会根据Makefile
中的规则,检查哪些文件需要重新编译,从而提高编译效率。我们的内核模块代码要是手动编译的话就比较麻烦,故使用Make
工具帮助我们编译我们的内核模块项目。一个简单的用于编译内核模块的Makefile
内容如下:
ifeq ($(KERNELRELEASE),)ifeq ($(ARCH),arm)
KERNELDIR ?= /home/yu/kernel/linux-3.14
ROOTFS ?= /home/yu/share/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesmodules_install:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_installclean:rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versionselse
obj-m += hello_yu.o
hello_yu-objs := hello.oendif
整个脚本逻辑大致如下:
脚本采用了嵌套编译的方式,主要分为两个部分,通过判断$(KERNELRELEASE)
是否为空来区分。$(KERNELRELEASE)
是内核Makefile
在编译内核模块时会定义的一个变量,当它为空时,表示在顶层Makefile
环境中;当它不为空时,表示是在内核Makefile
环境中进行子Makefile
的编译。
1)、顶层Makefile
环境($(KERNELRELEASE)
为空时)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/yu/kernel/linux-3.14
ROOTFS ?= /home/yu/share/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
ifeq ($(ARCH),arm)
:判断目标架构是否为ARM。
若为ARM架构,KERNELDIR
变量被设置为/home/yu/kernel/linux-3.14
,这是ARM
内核源码的路径;ROOTFS
变量被设置为/home/yu/share/rootfs
,这是开发板根文件系统的路径。
若不是ARM架构,KERNELDIR
变量被设置为当前系统正在使用的内核源码的构建目录,通过/lib/modules/$(shell uname -r)/build
获取。PWD := $(shell pwd)
:将当前工作目录的路径赋值给PWD变量。
modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules目标
:用于编译内核模块,默认执行此目标。$(MAKE)
实际上是make
命令,-C $(KERNELDIR)
表示切换到内核源码目录$(KERNELDIR)
下进行编译,M=$(PWD)
表示将当前工作目录$(PWD)
下的代码作为外部模块进行编译,modules
是内核Makefile
中的一个目标,用于编译模块。
modules_install:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
modules_install目标
:用于安装编译好的内核模块。在编译模块后,通过INSTALL_MOD_PATH=$(ROOTFS)
指定将模块安装到$(ROOTFS)
所指向的根文件系统目录中。
clean:rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
clean
目标:用于清理编译生成的文件。它会删除所有的目标文件(.o
)、内核模块文件(.ko
)、命令文件(.cmd
)、模块相关文件(.mod.*
)、模块顺序文件(modules.order
)、符号表文件(Module.symvers
)以及临时版本目录(.tmp_versions
)。
2)、内核Makefile
环境($(KERNELRELEASE)
不为空)
obj-m += hello_yu.o
hello_yu-objs := hello.o
obj-m += hello_yu.o
:表示要将hello_yu.o
编译成一个可加载的内核模块。hello_yu-objs := hello.o
:表示hello_yu.o
这个模块是由hello.o
这个目标文件组成的。
贰、静态编译
这种处理方式需要我们把内核模块源码放置到内核源码目录之下,我们要开发的字符设备驱动是存放于目录drivers/char
之下的,故我们需要进入此目录。
1、文件编写
1)、将内核模块代码写入到drivers/char/hello.c
,使用如下命令:
yu@Yubuntu:~/kernel/linux-3.14$ vi drivers/char/hello.c
内核模块代码的代码解释见壹节,写入完之后保存退出~
注意:文件名尽量不要和模块名重名!除非你的内核模块代码只由一个.c
文件组成。
2)、编辑同一目录下的Kconfig
文件,在Kconfig
中配置新的模块代码,使用如下命令编辑Kconfig
文件:
yu@Yubuntu:~/kernel/linux-3.14$ vi drivers/char/Kconfig
Kconfig
中配置代码的位置是跟以后的设置界面有关的,为了保持分组逻辑,我们在此处加入我们的配置代码是比较合适的。Kconfig
的代码解释见壹节。
完成之后保存退出~
3)、为了让我们的代码能够被正确编译,我们还需要编辑同目录下的Makefile
文件,使用如下代码编辑同目录下的Makefile
文件:
yu@Yubuntu:~/kernel/linux-3.14$ vi drivers/char/Makefile
同样,为了保持分组逻辑,我们选择在此处加入相关代码。Makefile
的代码解释见壹节。
编辑完成后保存退出~
2、配置编译
完成上面步骤的文件编写后,我们就可以在make menuconfig
中看到我们的内核模块选项了。为了能让我们的模块能被编译进内核中,我们需要在make menuconfig
中配置一下。
1)、使用如下命令打开配置界面:
yu@Yubuntu:~/kernel/linux-3.14$ make menuconfig
根据我们模块代码的存放位置,我们在菜单中选择“Device Drivers”,然后再选择“Character devices”,就能在菜单中看到我们的模块配置标题了,我们通过空格把我们的模块设置为“将该功能模块静态编译进内核”,即设置为“*”。然后通过方向键选择“Save”以保存配置。
2)、配置完成后我们重新编译Linux内核,如何编译的本文不做介绍,我们使用如下命令重新编译Linux内核:
yu@Yubuntu:~/kernel/linux-3.14$ make uImage
可以看到我们的代码被编译了。
3、运行内核
我们把编译好的uImage
文件上传到TFTP服务器,让开发板运行我们新编译的Linux内核:
yu@Yubuntu:~/kernel/linux-3.14$ cp arch/arm/boot/uImage ~/share/tftp/linux
可以看到我们的模块代码在内核启动的时候被启动了。
成功~
叁、动态加载:模块代码与内核代码放在一起
有时候为了方便随时修改我们的模块代码,我们可以选择动态加载方式来使用我们的内核模块代码,本小节介绍的是我们的模块代码仍然放在Linux源码目录中,我们通过make menuconfig
配置我们的模块代码为“模块编译”,即M
,然后再通过make modules
命令编译成.ko
文件动态加载进内核中去使用。
1、配置编译
1)、前面的步骤是与静态编译一样的,我们在配置阶段和编译阶段略有不同,我们使用如下命令重新打开make menuconfig
界面:
yu@Yubuntu:~/kernel/linux-3.14$ make menuconfig
配置完成后记得使用方向键选择“Save”保存后再退出~
2)、接着我们需要重新编译内核,使用如下命令重新编译内核:
yu@Yubuntu:~/kernel/linux-3.14$ make uImage
编译后需要复制到TFTP服务器上,方便开发板使用:
yu@Yubuntu:~/kernel/linux-3.14$ cp arch/arm/boot/uImage ~/share/tftp/linux
3)、上一步是没有编译我们的内核模块的,对于设置为M
的内核模块,我们还需要手动编译,使用如下命令编译内核模块:
yu@Yubuntu:~/kernel/linux-3.14$ make modules
编译后需要复制到NFS服务器上,方便开发板使用:
yu@Yubuntu:~/kernel/linux-3.14$ cp drivers/char/hello.ko ~/share/rootfs/
可以看到我们的模块被编译了~
注意:在编译模块前需要编译内核,内核模块所适用的平台与内核一致。
2、动态加载使用
重启开发板,使其运行新的内核。
可以看到我们刚刚编译生成的内核模块文件hello.ko
。
1)、使用insmod
命令动态加载内核模块:
[root@yieq4412]#insmod hello.ko
可以看到,我们的内核模块成功被加载,打印出了我们初始化函数中的内容。
2)、使用lsmod
命令查看已经动态加载的模块列表:
[root@yieq4412]#lsmod
可以看到,目前开发板上的Linux内核只加载了我们这一个模块。
3)、使用rmmod
命令卸载我们的内核模块:
[root@yieq4412]#rmmod hello
内核模块成功被卸载,执行了退出函数中的内容。
肆、动态加载:模块代码与内核代码分开存放
有时候我们的模块代码太多,并不想有与Linux源码挤在一起从而导致代码结构混乱等问题的发生,因此我们需要更好的代码管理与编译方式。
我们希望Linux源码与自己开发的内核模块代码分开,分开存放、分开管理和分开编译。本小节介绍这种开发方式。
1、建立内核模块项目
我们在另外的目录中创建一个文件夹作为内核模块项目的文件夹,使用如下命令来创建项目文件夹:
yu@Yubuntu:~$ mkdir -p project/hello_yu_module
将来关于此内核模块的代码都放到这里。
我们将内核模块代码写入到此文件夹下的hello.c
文件中:
yu@Yubuntu:~/project/hello_yu_module$ vi hello.c
大家可以根据自己的需要调整相关代码,内核模块源代码的解释大家可以去看壹节。
我们将动态加载的Makefile
代码模板写入到此文件夹下的Makefile
文件中:
yu@Yubuntu:~/project/hello_yu_module$ vi Makefile
大家需要根据自己的实际情况调整一下Makefile
文件中的变量值,Makefile
文件代码的解释大家可以去看壹节。
2、编译加载内核模块:指定CPU架构为ARM
因为我们的开发板是ARM架构的CPU,而当前我们的开发环境所用的CPU架构是AMD64,故我们编译适用于ARM架构CPU的内核模块时需要特殊指定一下。
先确保你的Linux内核已经被编译完成,跟我们上次编译内核模块时的先决条件是一样的。
1)、在内核模块项目目录下执行如下代码以编译适用于ARM架构CPU的内核模块代码:
yu@Yubuntu:~/project/hello_yu_module$ make ARCH=arm
可以看到已经生成了内核模块文件hello.ko
。
2)、我们把生成的hello.ko
文件复制到NFS服务器上方便开发板上的Linux使用,使用如下命令把hello.ko
复制到NFS服务器上:
yu@Yubuntu:~/project/hello_yu_module$ cp hello.ko ~/share/rootfs/
3)、在开发板上测试我们编译好的内核模块文件:
测试成功~
3、编译加载内核模块:当前CPU架构
我们自己当前的开发环境(电脑,Ubuntu22,CPU架构:AMD64)也是用的Linux内核,那么同样能动态加载和卸载我们现在编写的内核模块,本小节介绍如何编译和使用适用于当前开发环境中CPU架构的内核模块。
1)、使用如下命令编译内核模块:
yu@Yubuntu:~/project/hello_yu_module$ make
可以看到也是成功生成了hello.ko
文件。
这两个警告是说在使用函数前没有声明该函数的原型,这样做可能会出现一些问题,你可以选择忽略,也可以选择在函数前声明一下函数原型。
2)、使用如下命令在电脑中动态加载内核模块:
yu@Yubuntu:~/project/hello_yu_module$ sudo insmod hello.ko
但是好像并没有输出我们初始化函数中的内容呢?
这是因为在我们Ubuntu电脑中,内核输出的信息并不直接输出到Bash中,需要通过dmesg
命令查看。
3)、使用如下命令查看内核输出信息:
sudo dmesg
一下流出了好多好多信息呢,下次试验前我们可以使用sudo dmesg -C
命令清空一下内核输出信息再做试验。
4)、使用如下命令查看电脑中已动态加载的内核模块:
lsmod
也是一大堆信息,不过我们也是能在其中找到了我们刚刚动态加载的内核模块。
5)、使用如下命令卸载内核模块:
sudo rmmod hello_yu
可以看到我们的模块被正常卸载了~
伍、内核模块的多文件编译
有时候我们的内核模块不止一个源代码文件,比如我们再加两个文件yu.h
和yu.c
到我们的内核模块中,那么这样的话我们该如何编写Makefile
文件呢?
因为是编译相关的问题,所以只是Makefile
的编写不一样,那么这样的话问题分成两种情况,一种是内核模块源代码与Linux源码放在一起的情况,另外一种是内核模块源代码单独存放的情况。
1、放在一起
以之前的例子为基础做修改。
1)、添加两个文件到drivers/char
目录下,yu.h
和yu.c
:
就简单的写了个头文件和C文件。
2)、另外对我们之前的drivers/char/hello.c
进行一下简单修改:
yu@Yubuntu:~/kernel/linux-3.14$ vi drivers/char/hello.c
我们在这里调用一下刚刚新加的两个文件。
3)、另外,我们的代码修改后,Makefile
文件也需要修改一下:
yu@Yubuntu:~/kernel/linux-3.14$ vi drivers/char/Makefile
涉及多个文件的模块,先是要把原来的条目更改一下,可以把文件名.o
改为模块名.o
,然后再在编译条目下一行加如下代码把需要用到的文件全部添加进来即可:
<模块名>-objs := 文件名1.o 文件名2.o ...
修改完成后保存退出~
注意:.o
不要重名!
4)、在make menuconfig
中设置为编译进内核中,然后重新编译:
yu@Yubuntu:~/kernel/linux-3.14$ make menuconfig
yu@Yubuntu:~/kernel/linux-3.14$ make uImage
5)、复制到TFTP服务器上在开发板上运行:
yu@Yubuntu:~/kernel/linux-3.14$ cp arch/arm/boot/uImage ~/share/tftp/linux
成功~
6)、或者设置成模块,然后重新编译,放到开发板上运行:
成功~
2、单独存放
1)、以之前的项目~/project/hello_yu_module
为基础,在其目录下添加两个文件,也是yu.h
和yu.c
:
2)、同样,需要在之前的hello.c
中稍微修改一下,调用我们新加的文件:
yu@Yubuntu:~/project/hello_yu_module$ vi hello.c
保存退出~
3)、同样,我们的代码文件有改动后需要修改一下Makefile
文件:
yu@Yubuntu:~/project/hello_yu_module$ vi Makefile
同样的,我们需要把原来的文件名.o
修改为模块名.o
,然后在下面添加此模块用到的所有文件,代码格式为:
<模块名>-objs := 文件名1.o 文件名2.o ...
基本与上一种情况一致。
4)、当前CPU架构的编译和加载运行:
成功~
4)、指定CPU架构为ARM的编译和加载运行:
成功~
陆、内核模块参数传递
有时候我们希望能像正常的APP那样,传递一些启动参数到主程序中,主程序通过int argc
和char* argv[]
来读取我们传入的参数,我们在内核模块上如何实现这样的操作呢?
1、宏介绍
在开始之前我们介绍两个宏的用法,一个是module_param(name, type, perm);
,另一个是module_param_array(name, type, &num, perm);
,这两个宏在Linux内核编程里的核心作用就是把指定的全局变量设置成模块参数,以此实现向内核模块传递参数。
1)、其中module_param(name, type, perm);
是设置非数组参数的,它的三个参数介绍如下:
①、 参数name
:全局变量名
②、 参数type
:设置参数的类型,因为有的类型中间包含空格,不方便使用,故使用如下表格的符号代替:
使用符号 | 实际类型 | 传参方式 |
---|---|---|
bool | bool | insmod <文件名>.ko 变量名=0 或 1 |
invbool | bool | insmod <文件名>.ko 变量名=0 或 1 |
charp | char * | insmod <文件名>.ko 变量名=“字符串内容” |
short | short | insmod <文件名>.ko 变量名=数值 |
int | int | insmod <文件名>.ko 变量名=数值 |
long | long | insmod <文件名>.ko 变量名=数值 |
ushort | unsigned short | insmod <文件名>.ko 变量名=数值 |
uint | unsigned int | insmod <文件名>.ko 变量名=数值 |
ulong | unsigned long | insmod <文件名>.ko 变量名=数值 |
③、 参数perm
:给对应的文件/sys/module/name/parameters/变量名
指定操作权限,可以直接传递数字,也可以传递下表中的宏:
宏 | 建议 |
---|---|
#define S_IRWXU 00700 | |
#define S_IRUSR 00400 | |
#define S_IWUSR 00200 | |
#define S_IXUSR 00100 | |
#define S_IRWXG 00070 | |
#define S_IRGRP 00040 | |
#define S_IWGRP 00020 | |
#define S_IXGRP 00010 | |
#define S_IRWXO 00007 | |
#define S_IROTH 00004 | |
#define S_IWOTH 00002 | 不要用,编译出错 |
#define S_IXOTH 00001 |
一般设置为0664
,即执行权限不需要,用户和组都为可读写,其他用户只读。
2)、另外一个module_param_array(name, type, &num, perm);
是设置数组参数的,其中的参数name
、type
和perm
跟module_param(name, type, perm);
是一样的,另外一个参数介绍如下:
①、 参数&num
:传递的参数为数组时,此参数为存放数组大小变量的地址,若不需要,可以填NULL,但是要确保传参个数不越界。传递数组参数的方式:
insmod <文件名>.ko 数组名=元素值0,元素值1,...元素值num-1
2、参数传递编程
1)、我们对内核模块~/project/hello_yu_module
进行修改,使之能够接收一些参数,我们编辑hello.c
:
yu@Yubuntu:~/project/hello_yu_module$ vi hello.c
这里接收了3个参数,一个整型,一个字符串,一个整型数组,修改完成后保存退出~
2)、使用如下命令进行编译:
yu@Yubuntu:~/project/hello_yu_module$ make
3)、加载测试:
成功~
柒、模块依赖
内核模块的代码与内核其他代码运行于同一环境中。尽管内核模块在形式上独立存在,但在运行时,它与内核其他部分构成一个统一整体,同属内核程序的有机组成部分。这种运行时的整体性,使得内核模块与内核其他源码能够相互访问彼此的全局变量和函数,共享内核空间的全局资源。
本小节介绍我们自己的内核模块如何导出全局资源和使用其他内核代码的全局资源。
1、小知识
1)、导出符号:一个内核模块中可以被其它内核源代码使用的全局特性的名称(变量名、函数名等)被称为导出符号。
2)、符号表:若把所有导出符号放在一个表中,这个表被称为符号表。
3)、nm
命令可以查看elf格式的可执行文件或目标文件中包含的符号表,用法:nm 文件名
。符号表中会标明符号的类型,类型说明如下:
符号 | 段类型 | 数据/代码特征 | 示例(全局符号) | 示例(局部符号) |
---|---|---|---|---|
B/b | BSS 段 | 未初始化或零初始化的全局变量 | int global_var; | static int static_var; |
D/d | 已初始化数据段 | 初始值非零的全局变量 | int var = 10; | static int s_var = 20; |
T/t | 文本段 | 可执行代码(函数) | void func(); | static void s_func(); |
U | 未定义符号 | 在目标文件中被引用,但未在当前文件中定义,需链接其他文件解析 | extern int external_var; | |
R | 只读数据段 | 字符串常量、const 全局变量等只读数据 | const int const_var = 5; | |
N | 调试符号 | 用于调试信息的符号 | ||
w | 弱符号 | 多个定义时以强符号为准 |
4)、用于导出内核模块中全局特性的名称的两个宏:EXPORT_SYMBOL(函数名或全局变量名)
和EXPORT_SYMBOL_GPL(函数名或全局变量名)
,其中后者需要GPL许可证协议验证。
在使用导出符号的地方,需要对这些位于其他内核模块的导出符号使用extern
声明后才能使用这些符号。
5)、内核模块B使用了内核模块A的导出符号,我们称内核模块B依赖于内核模块A,对于它们有如下要求:
- 编译次序:先编译内核模块A,再编译内核模块B,当两个模块源码在不同目录时,需要:①. 编译被使用了导出符号的内核模块A;②. 复制内核模块A目录中的
Module.symvers
文件到内核模块B的目录中;③. 编译使用内核模块A的导出符号的内核模块B,否则编译内核模块B时会有符号未定义的错误。 - 加载次序:先插入内核模块A,再插入内核模块B,否则内核模块B会插入失败。
- 卸载次序:先卸载内核模块B,再卸载内核模块A,否则内核模块A会卸载失败。
2、内核模块依赖:同目录
接下来会构建两个具有依赖关系的内核模块来举例说明如何搞定它们的依赖关系。
1)、首先是两个存在依赖关系的内核模块在同一目录的情况,我们在~/project
目录下创建一个ii_module
文件夹:
yu@Yubuntu:~/project$ mkdir ii_module
2)、再在里面创建两个内核模块.c
源文件:
yu@Yubuntu:~/project/ii_module$ vi yu_a.c
yu@Yubuntu:~/project/ii_module$ vi yu_b.c
3)、再创建Makefile
文件:
yu@Yubuntu:~/project/ii_module$ vi Makefile
注意这里的顺序,由于是yu_b
使用了yu_a
中的导出符号,所以yu_a
要在yu_b
之前编译。
4)、编译运行:当前CPU架构
成功~
5)、编译运行:CPU架构为ARM
成功~
3、内核模块依赖:不同目录
1)、其次是两个存在依赖关系的内核模块不在同一目录的情况,我们在~/project
目录下创建lukya_module
和lukyb_module
两个文件夹,并把之前的yu_a.c
和yu_b.c
分别复制进去,再把Makefile
也都复制过去一份:
2)、分别修改两个目录中的Makefile
:
yu@Yubuntu:~/project$ vi lukya_module/Makefile
yu@Yubuntu:~/project$ vi lukyb_module/Makefile
其中的KBUILD_EXTRA_SYMBOLS := /home/yu/project/lukya_module/Module.symvers
为指定内核模块lukya_module
的符号表的位置。
3)、尝试先编译lukyb_module
:
yu@Yubuntu:~/project/lukyb_module$ make
是直接报错的~
4)、先编译lukya_module
:
yu@Yubuntu:~/project/lukya_module$ make
通过!
5)、再编译lukyb_module
:
成功~
6)、测试:
通过~
4、Linux内核的符号表
上面的几个部分介绍的是我们自己写的内核模块之间的依赖关系,那么Linux内核提供了哪些导出符号给我们呢?
1)、在Linux运行时,文件/proc/kallsyms
中的内容即是当前Linux的符号表,可以使用文本查看相关的命令直接查看里面的内容:
cat /proc/kallsyms
由于是运行时的符号表,故地址都为0000000000000000
。
2)、另外Linux系统还在/boot/System.map-<版本号>
中存有符号表,版本号每个人都不太一样的,也是直接用文本查看相关的命令直接查看里面的内容(以下命令使用于我当前的Ubuntu系统):
yu@Yubuntu:~$ sudo cat /boot/System.map-6.8.0-51-generic
3)、另外,我们可以通过nm
命令查看我们自己编译出来的elf格式的Linux内核文件vmlinux
的符号表:
yu@Yubuntu:~/kernel/linux-3.14$ nm vmlinux
4)、我们自己编译出来的Linux中也有文本类型的符号表,在源码目录下的System.map
文件中:
yu@Yubuntu:~/kernel/linux-3.14$ cat System.map
捌、开发小知识
1、GPL许可证协议验证
1)、GPL(GNU General Public License,GNU 通用公共许可证)是自由软件基金会(FSF)发布的一种开源许可证,也是开源领域最具影响力的 “强 Copyleft” 许可证之一。它的核心目标是确保软件的自由使用、修改和分发权利,同时要求基于 GPL 许可的软件及其衍生作品必须继续遵循 GPL 条款,从而保障用户的自由不被剥夺。
2)、GPL许可证协议验证是一种确保代码合规性的机制,其核心是通过检查模块的许可证声明,确保导出的符号仅被符合GPL协议的模块使用。
2、模块传参中的参数说明
1)、可用MODULE_PARAM_DESC
宏对每个参数进行作用描述,用法如下:
MODULE_PARM_DESC(变量名, 字符串常量);
2)、重新编译后可以使用modinfo
命令查看.ko
文件的参数描述。
3)、示例如下:
3、内核模块信息宏
我们可以在内核模块源代码中加入如下一些宏来描述当前内核模块的信息:
MODULE_AUTHOR(字符串常量); // 作者信息
MODULE_DESCRIPTION(字符串常量); // 模块描述
MODULE_ALIAS(字符串常量); // 模块别名
这些宏的本质是定义static
字符数组用于存放指定字符串内容,这些字符串内容链接时存放在.modinfo
字段,可以用modinfo 内核模块文件名
命令来查看这些模块信息。
4、常用命令
1)、file
命令,用于查看elf
格式文件的相关信息,例如我们编译内核模块时指定为ARM或者默认为当前CPU架构时,使用file
命令会看到区别:
2)、dmesg
命令,用于查看内核输出信息,dmesg -C
是清除内核输出信息,我们之前介绍过。
3)、modinfo
命令,用于查看内核模块文件的相关信息,之前介绍过。
4)、nm
命令,用于查看elf
格式文件的导出符号表,之前介绍过。
玖、参考资料
- https://www.doubao.com/thread/w376d871a527cb868
- https://www.cnblogs.com/willwuss/p/13696573.html
- https://blog.csdn.net/XiaoYuHaoAiMin/article/details/147092721?spm=1011.2415.3001.5331
- https://blog.csdn.net/Lihuihui006/article/details/112199469
- https://www.bilibili.com/video/BV1tyWWeeEpp/?spm_id_from=333.337.search-card.all.click
- https://www.doubao.com/thread/w5e99f0e281945c2a
- https://www.doubao.com/thread/wbdbee496c2ad372c
相关文章:
【Linux】Linux内核模块开发
Linux内核模块开发 零、关于 1、概述 最近在学习Linux相关的东西,学习了U-Boot的编译,Linux的编译,能够在开发板上运行自己编译的U-Boot和Linux了,那么接下来就是在自己编译的Linux上做应用级或者系统级的开发了。本文以字符设…...
linux 下查看指定进程的内存CPU占用情况(用于程序崩溃类的排查)
在程序开发过程中,如果程序较为庞大,逻辑较为复杂时,容易出现运行时崩溃的问题。导致的原因有很多,我这里只对较为通用的内容占用情况作记录,如程序中对文件描述符打开未关闭(导致fd积攒过多超过了系统的标…...
ASP.NET MVC 入门指南五
26. 响应式设计与移动开发 26.1 响应式视图设计 为了使 MVC 应用程序在不同设备上都能提供良好的用户体验,需要采用响应式设计。可以使用 CSS 框架如 Bootstrap 来实现响应式布局。 引入 Bootstrap:在项目中引入 Bootstrap 的 CSS 和 JavaScript 文件。…...
字节跳动社招面经 —— BSP驱动工程师(4)
接前一篇文章:字节跳动社招面经 —— BSP驱动工程师(3) 本文内容参考: 嵌入式硬件平台修改启动地址-CSDN博客 特此致谢! 上一回开始针对于“嵌入式充电站”发的一篇文章字节跳动社招面经——BSP驱动工程师中的面试题…...
Spring MVC中自定义日期类型格式转换器
在Spring MVC中,自定义日期类型格式转换器可以通过实现Converter接口或使用DateTimeFormat注解。以下是两种方法的详细说明: 方法一:全局自定义转换器(推荐) 1. 创建日期转换器类 实现 org.springframework.core.con…...
【3D 地图】无人机测绘制作 3D 地图流程 ( 无人机采集数据 | 地图原始数据处理原理 | 数据处理软件 | 无人机测绘完整解决方案 )
文章目录 一、无人机采集数据1、多角度影像数据2、定位与姿态数据 二、无人机采集数据处理原理1、空三解算2、密集点云生成与三维重建3、地形与正射影像生成4、三维模型优化与瓦片化 三、无人机影像处理软件介绍 一、无人机采集数据 无人机原始数据采集 : 多角度影像数据 : 多…...
arduino Nano介绍
【仅供学习,具体参数参考官网或销售商】 Arduino Nano 是一款基于 ATmega328P 微控制器(或 ATmega168 旧版)的紧凑型开发板,专为嵌入式项目和原型设计而设计。 以下是Arduino Nano V3.0 328P详细介绍: 主要特性 微…...
解决 Flutter 在 iOS 真机上构建失败的问题
在开发 Flutter 应用时,有时会在尝试将应用部署到 iOS 真机时遇到构建失败的问题。错误信息通常类似于以下内容: Could not build the precompiled application for the device. Uncategorized (Xcode): Timed out waiting for all destinations matchi…...
【办公类-89-03】20250429AI写的研讨记录,清除格式,统一格式,名字替换。部分加粗,添加页眉
背景需求: 检查自即,需要AI一下院内的五次科研培训记录。 本次用了豆包 豆包写的不错,也是“水字数”的高手 把每次培训内容贴到WORD里 把AI资料贴到WORD里,发现问题: 1、字体、段落什么都是不统一的,需要统一改成宋体小四,1.5倍行距 2、十个研讨人也要改成真人。就找…...
react-native 安卓APK打包流程
一、使用keytool命令生成一个签名密钥 $ keytool -genkeypair -v -storetype PKCS12 -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000 在 Windows 上keytool命令放在 JDK 的 bin 目录中(比如C:\Program Files\…...
Android Studio中OpenCV应用详解:图像处理、颜色对比与OCR识别
文章目录 一、OpenCV在Android中的集成与配置1.1 OpenCV简介1.2 在Android Studio中集成OpenCV1.2.1 通过Gradle依赖集成1.2.2 通过模块方式集成1.2.3 初始化OpenCV 1.3 OpenCV基础类介绍 二、指定区域图像抓取与对比2.1 图像抓取基础2.2 指定区域图像抓取实现2.2.1 从Bitmap中…...
企业办公协同平台安全一体化生态入住技术架构与接口标准分析报告
全球组织数字化与智能化背景下 企业办公协同平台安全一体化生态入住技术架构与接口标准分析报告 一、背景与市场需求 市场规模与增量 根据Statista数据,全球协同办公平台市场规模预计从2023年的$480亿增长至2027年的$900亿,年复合增长率(CAG…...
从零搭建体育比分网站:技术选型与API调用实战(附完整源码)
一、前言:为什么选择体育比分项目? 体育数据网站是练手全栈开发的绝佳项目,涉及: ✅ 前端(实时数据渲染、可视化图表) ✅ 后端(API对接、数据缓存、高并发优化) ✅ 数据库ÿ…...
非凸科技受邀出席AI SPARK活动,共探生成式AI驱动金融新生态
4月19日,由AI SPARK社区主办的“生成式AI创新与应用构建”主题沙龙在北京举行。活动聚焦生成式AI的技术突破与产业融合,围绕大模型优化、多模态应用、存内计算等前沿议题展开深度探讨。非凸科技受邀出席并发表主题演讲,深入解析金融垂直大模型…...
深入蜂窝物联网 第五章 EC-GSM-IoT 及其他技术:混合组网与前瞻
1. 前言与应用场景 在一些地区,GSM 网络仍然大面积覆盖且运营成本低廉,运营商可通过 EC-GSM-IoT(Extended Coverage GSM for IoT)在现有GSM基站上升级,实现物联网互联。同时,为了满足不同场景的需求,常常需要与 NB-IoT、LTE-M、5G RedCap 等技术混合组网,形成多层次、…...
2025年深圳软件开发公司推荐
随着移动互联网的深度发展,软件开发已成为企业实现数字化转型的重要途径。作为中国科技创新中心的深圳,汇聚了众多技术实力雄厚的软件开发企业。本文将为您精选推荐6家在深圳表现突出的软件开发服务商,帮助企业找到合适的数字化转型合作伙伴。…...
仿腾讯会议——注册登录UI
1、加载素材 2、新添加资源类 3、加载图片 4、添加左侧图片 在左侧添加一个标签 选择图片 选择图片 勾选保证图片不变形 5、修改组件名称 6、设置密码输入框 5、切换 6、编辑提示框 7、定义提交和清空的槽函数 8、设置页面标题和最先显示页面 9、清空登录信息函数实现 10、清空…...
香港科技大学广州|可持续能源与环境学域博士招生宣讲会—四川大学专场
香港科技大学广州|可持续能源与环境学域博士招生宣讲会—四川大学专场 时间:2025年5月8日(星期四)16:30开始 地点:四川大学基础教学楼A座504 宣讲嘉宾:肖殿勋 助理教授 一经录取,享全额奖学金…...
设计模式(工厂模式)
工厂设计模式:打造你的代码生产线 引言 想象一下,你正站在一家现代化的玩具工厂门前。工厂内部,各种机器有条不紊地运转,原材料在传送带上流动,最终变成精美的玩具。你不需要了解每个玩具的具体制作工艺,…...
本地大模型编程实战(29)查询图数据库NEO4J(2)
上一篇文章 用大语言模型LLM查询图数据库NEO4J(1) 介绍了使用GraphQACypherChain查询NEO4J。用它实现简单快捷,但是不容易定制,在生产环境中可能会面临挑战。 本文将基于langgraph 框架,用LLM(大语言模型)查询图数据库NEO4J。它可以定义清晰复…...
Python爬虫(10)Python数据存储实战:基于pymongo的MongoDB开发深度指南
目录 一、为什么需要文档型数据库?1.1 数据存储的范式变革1.2 pymongo的核心优势 二、pymongo核心操作全解析2.1 环境准备2.2 数据库连接与CRUD操作2.3 聚合管道实战2.4 分批次插入百万级数据(进阶)2.5 分批次插入百万级数据(进阶…...
从遍历序列构造二叉树:前序+中序与中序+后序的递归解法详解
文章目录 1. 问题背景2. 核心思路3. 从前序与中序遍历序列构造二叉树3.1 递归分治思路3.2 代码实现与注释 4. 从中序与后序遍历序列构造二叉树4.1 递归分治思路4.2 代码实现与注释 5. 复杂度分析6. 总结 1. 问题背景 二叉树的遍历方式包括前序(根-左-右)…...
数据挖掘专栏介绍:用 Python + 大语言模型 (LLM) 重塑电商数据价值
写在前面 —— 不止于挖掘,更要智能涌现:用 Python + 大语言模型 (LLM) 重塑电商数据价值 或许你已经跟随我们之前的 “零基础上手Python数据分析” 专栏,掌握了 Pandas 的数据操纵、Matplotlib/Seaborn 的可视化呈现,甚至对传统的数据挖掘技术如聚类、分类、回归有了初步…...
CSS Transition入门指南
CSS Transition 完全指南 目录 Transition 的作用核心属性代码示例使用场景性能优化常见问题思维导图 1. Transition 的作用 CSS Transition 用于在 属性值变化时 创建平滑的过渡效果。例如: 鼠标悬停时按钮放大元素颜色渐变切换位置移动的缓动效果 2. 核心属性…...
Nginx 核心功能
目录 一、基于授权的访问控制 (1)使用htpasswd 生成用户认证文件 (2)修改密码文件权限为400,将所有者改为nginx ,设置 Nginx 的运行用户能够读取 (3)修改主配置文件 nginx.conf&…...
排序版研究方向
姓 名研究方向电子邮箱办公电话办公地点曹培根代数cao2024ustc.edu.cn 新楼412陈洪佳代数hjchenustc.edu.cn0551-636076931529陈小伍代数xwchenmail.ustc.edu.cn0551-636062351321梁永祺代数yqliangustc.edu.cn0551-636006171613欧阳毅代数yiouyangustc.edu.cn0551-63600337…...
AI国学智慧语录视频,条条视频10W+播放量
家人们!图书类带货玩法真的非常多,之前也分享过蛮多,例如情感语录、育儿教育、爆款图书金句类、AI历史人物解说类等等。 本期继续来分享一个对于普通人来说,上手相当简单,容易起号,可作为长线深耕的AI带货…...
RN 获取视频封面,获取视频第一帧
严格的说,没有解决这个问题,实际上是绕过了这个问题,严格的说获取的也不是第一帧。 RN的video 视频应用,大多是这样的 1、安装 yarn add react-native-video 2、导入 import Video from react-native-video; 3、使用 …...
缓存分片哈希 vs 一致性哈希:优缺点、区别对比及适用场景(图示版)
📜 引言 在分布式缓存系统中,数据分布策略是设计的关键之一。缓存分片哈希和一致性哈希是两种常见的数据分布算法,它们各有优缺点和适用场景。本文将通过图示对比表格,深入解析这两种算法的核心原理、优缺点及适用场景。 &#x…...
iOS - 音频: Core Audio - 播放
环境 iOS 18 Xcode 16.3 swift-driver version: 1.120.5 Apple Swift version 6.1 (swiftlang-6.1.0.110.21 clang-1700.0.13.3) Target: x86_64-apple-macosx15.0 Core Audio 的架构 声音数据的描述 sample: 一个声道采样的值,采样率定义了每秒从连续信号中提取并组成离散信号…...
Nerfstudio 环境配置与自有数据集(图片和视频)测试全方位全流程实战【2025最新版!!】
一、引言 神经辐射场(Neural Radiance Fields,简称NeRF)是近年来计算机视觉和图形学领域的一项革命性技术,它能够从2D图像中学习复杂的3D场景表示。然而,NeRF技术的实现和应用门槛较高,需要较为专业的计算机视觉和深度学习知识。…...
【Java学习】动态代理有哪些形式?
Java动态代理的两种主要形式 动态代理在Java中有两种主要的实现方式,它们各有特点和使用场景: 1. JDK动态代理 (基于接口) 特点: Java标准库自带的功能(java.lang.reflect.Proxy)只能代理接口,不能代理…...
Android Studio 中实现方法和参数显示一行
Android Studio 中实现方法和参数显示一行,可通过以下步骤配置: 一、基础格式化设置 快捷键格式化 选中代码后使用 Ctrl Alt L(Windows/Linux)或 Cmd Option L(Mac)进行快速格式化27。 菜单操作…...
SQLyog中DELIMITER执行存储过程时出现的前置缩进问题
在SQLyog中执行存储过程时出现的前置缩进问题,实际上反映了SQLyog对SQL语句解析的一个特殊行为。以下是详细解释和解决方案: 问题根源 SQLyog的语句分隔逻辑: SQLyog默认会根据分号(;)和换行自动分隔SQL语句 当代码有缩进时,SQLy…...
基于Spring Boot 3.0、ShardingSphere、PostgreSQL或达梦数据库的分库分表
要实现基于Spring Boot 3.0、ShardingSphere、PostgreSQL或达梦数据库的分库分表,首先需要对ShardingSphere进行一些基本配置。你提到的溯源码、批次号等数据需要考虑到跨年数据的存储,因此要设计一个能够动态扩展的分表策略 添加ShardingSphere依赖 在…...
vscode chrome调试怎么在所有浏览器都好使
chrome调试时只能在打开的浏览器里进行调试,其它打开的chrome浏览器就不能调试了,怎么解决。 右键点击 Chrome 的快捷方式图标,选择属性 在目标一栏,最后加上--remote-debugging-port9222 注意要用空格隔开 lanch.json 文件配置 …...
20250429在Ubuntu 20.04.6下安装VMware Workstation16
20250429在Ubuntu 20.04.6下安装VMware Workstation16 2025/4/29 20:16 缘起:1、在ubuntu14.04下git clone异常该如何处理呢? 2、请问 现在 编译NanoPi NEO的FriendlyCore系统使用ubuntu哪一个版本比较好? ubuntu14.04 编译异常/下载不了&am…...
Java高频面试之并发编程-10
hello啊,各位观众姥爷们!!!本baby今天来报道了!哈哈哈哈哈嗝🐶 面试官:ThreadLocalMap 怎么解决 Hash 冲突的? ThreadLocalMap 是 ThreadLocal 的核心实现,它采用 开放…...
【Tauri2】035——sql和sqlx
前言 这篇就来看看插件sql SQL | Taurihttps://tauri.app/plugin/sql/ 正文 准备 添加依赖 tauri-plugin-sql {version "2.2.0",features ["sqlite"]} features可以是mysql、sqlite、postsql 进去features看看 sqlite ["sqlx/sqlite&quo…...
C++/SDL 进阶游戏开发 —— 双人塔防(代号:村庄保卫战 16)
🎁个人主页:工藤新一 🔍系列专栏:C面向对象(类和对象篇) 🌟心中的天空之城,终会照亮我前方的路 🎉欢迎大家点赞👍评论📝收藏⭐文章 文章目录 […...
VScode与远端服务器SSH链接
这里写自定义目录标题 简介步骤 简介 这里是使用密钥文件链接 步骤 首先在windows CMD中运行 ssh-keygen -t rsa,生成本机的公钥和私钥 这里id rsa.pub就是公钥,在服务器端使用,id rsa是私钥在用户端使用;通常目录在C:/Users/Your name/…...
数据结构入门:详解顺序表的实现与操作
目录 1.线性表 2.顺序表 2.1概念与结构 2.2分类 2.2.1静态顺序表 2.2.2动态顺序表 3.动态顺序表的实现 3.1.SeqList.h 3.2.SeqList.c 3.2.1初始化 3.2.2销毁 3.2.3打印 3.2.4顺序表扩容 3.2.5尾部插入及尾部删除 3.2.6头部插入及头部删除 3.2.7特定位置插入…...
Reactor框架介绍
Reactor(反应器模式)是一种事件驱动的设计模式,广泛用于高性能网络编程和异步I/O处理。它的核心思想是将事件分发与业务逻辑解耦,通过统一的机制处理多路I/O事件。 这个在android蓝牙中大量使用,如果这里不懂,那么很难看懂底层的逻辑,所以我们在这片文章中做一个介绍 …...
Nacos 3.0 正式发布:MCP Registry、安全零信任、链接更多生态
Nacos 3.0 正式版本发布啦!升级 MCP Registry,围绕着 MCP 服务管理,MCP 多种类型注册,包含 MCP Server 注册、编排、动态调试和管理,并且提供 Nacos-MCP-Router 可以进行 MCP 动态发现,可以自动安装、代理 …...
前端安全中的XSS(跨站脚本攻击)
XSS 类型 存储型 XSS 特征:恶意脚本存储在服务器(如数据库),用户访问受感染页面时触发。场景:用户评论、论坛帖子等持久化内容。影响范围:所有访问该页面的用户。 反射型 XSS 特征:恶意脚本通过…...
go单向链表
需求 实现单向链表的节点顺序添加、顺序遍历。 实现 package mainimport ("fmt" )type zodiac_sign struct {number intdizhi stringanimal stringyear intnext *zodiac_sign }// 添加 // func add_node_by_order(previous_node zodiac_sign, current_node z…...
Python小程序:上班该做点摸鱼的事情
系统提醒 上班会忘记一些自己的事,所以你需要在上班的的时候突然给你弹窗,你就知道要做啥了 源码 # -*- coding:utf-8 -*- """ 作者:杨桃清 日期: 2025年04日29 21:51:24 """ import datetime import time import thre…...
uni-app中使用RenderJs 使用原生js
RenderJs运行的层叫【视图层】,Uniapp原生Script叫【逻辑层】,逻辑层要调用视图层需要使用一个叫【watcher】,具体怎么调用呢 为了实现这两层之间的通信,uniapp提供了一些特定的机制。以下是对这些通信机制的详细解释,…...
51c自动驾驶~合集37
我自己的原文哦~ https://blog.51cto.com/whaosoft/13878933 #DETR->DETR3D->Sparse4D 走向长时序稀疏3D目标检测 一、DETR 图1 DETR架构 DETR是第一篇将Transformer应用到目标检测方向的算法。DETR是一个经典的Encoder-Decoder结构的算法,它的骨干网…...
uniapp 小程序 安卓苹果 短视频解决方案
需求 要做类似抖音小程序的功能 思路 uniapp 使用swiper滑块 实现滑动 使用video播放视频 遇到的问题 1 video组件在小程序可以使用 uni.createVideoContext api控制 2 但是在app端会有层级问题(因为使用的原生组件具体看官方文档)导致无法正常滑动…...