Linux驱动开发——内核模块

这篇具有很好参考价值的文章主要介绍了Linux驱动开发——内核模块。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

内核模块的由来

第一个内核模块程序 

内核模块工具 

将多个源文件编译生成一个内核模块 

内核模块参数

内核模块依赖

关于内核模块的进一步讨论 

习题


内核模块的由来

最近一直在玩那些其它的技术,眼看快暑假了,我决定夯实一下我的驱动方面的技能,迎接我的实习,找了一本书,接下来就跟着这本书学了

linux内核开发,驱动开发,Linux,内核,嵌入式

先来看第二章,内核模块


        Linux是宏内核(或单内核)的操作系统的典型代表,它和微内核(典型的代表是 Windows操作系统)的最大区别在于所有的内核功能都被整体编译在一起,形成一个单独的内核镜像文件。其显著的优点就是效率非常高,内核中各功能模块的交互是通过直接的函数调用来进行的。而微内核则只实现内核中相当关键和核心的一部分,其他功能模块被单独编译,功能模块之间的交互需要通过微内核提供的某种通信机制来建立。对于像 Linux 这类的宏内核而言,其缺点也是不言而喻的,如果要增加、删除、修改内核的某个功能,不得不重新编译整个内核,然后重新启动整个系统。这对驱动开发者来说基本上是不可接受的,因为驱动程序的特殊性,在驱动开发初期,需要经常修改驱动的代码,即便是经验丰富的驱动开发者也是如此。

        为了弥补这一缺点,Linux引入了内核模块(后面在不引起混淆的情况下将其简称为“模块”)。简单地说,内核模块就是被单独编译的一段内核代码,它可以在需要的时候动态地加载到内核,从而动态地增加内核的功能。在不需要的时候,可以动态地卸载,从而减少内核的功能,并节约一部分内存(这要求内核配置了模块可卸载的选项才行)。而不论是加载还是卸载,都不需要重新启动整个系统。这种特性使它非常适合于驱动程序的开发(注意,内核模块不一定都是驱动程序,驱动程序也不一定都是模块的形式)。驱动开发者可以随时修改驱动的代码,然后仅编译驱动代码本身(而非整个内核),并将新编译的驱动加载到内核进行测试。只要新加入的驱动不会使内核崩溃,就可以不重新启动系统。


        内核模块的这一特点也有助于减小内核镜像文件的体积,自然也就减少了内核所占用的内存空间(因为整个内核镜像将会被加载到内存中运行)。不必把所有的驱动都编译进内核,而是以模块的形式单独编译驱动程序,这是基于不是所有的驱动都会同时工作的原理。因为不是所有的硬件都要同时接入系统,比如一个USB 无线网卡。
讨论完内核模块的这些特性后,我们正式开始编写模块程序。

第一个内核模块程序   

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

int init_module()
{
	printk("init_module\n");
	return 0;
}

void cleanup_module()
{
	printk("cleanup_module\n");
}

使用模块init,清除模块cleanup

编译这个程序需要对应的makefile,不然连这些头文件都找不到,这些头文件是内核里的

ifeq就是逻辑与

ifneq就是逻辑或

ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /home/book/Linux_4412/kernel/linux-3.14
ROOTFS ?= /home/book/nfs_rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)

modules:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
    rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else

obj-m += vser.o

endif

细节很重要,往往简单的地方才是最容易忽视的

“=”是最普通的等号,然而在Makefile中确实最容易搞错的赋值等号,使用”=”进行赋值,变量的值是整个makefile中最后被指定的值。不太容易理解,举个例子如下:

VIR_A = A
VIR_B = $(VIR_A) B
VIR_A = AA
经过上面的赋值后,最后VIR_B的值是AA B,而不是A B。在make时,会把整个makefile展开,拉通决定变量的值


相比于前面“最普通”的”=”,”:=”就容易理解多了。”:=”就表示直接赋值,赋予当前位置的值。同样举个例子说明
VIR_A := A
VIR_B := $(VIR_A) B
VIR_A := AA
最后变量VIR_B的值是A B,即根据当前位置进行赋值。因此相比于”=”,”:=”才是真正意义上的直接赋值。


“?=”表示如果该变量没有被赋值,则赋予等号后的值。举例:

VIR ?= new_value
如果VIR在之前没有被赋值,那么VIR的值就为new_value.

VIR := old_value
VIR ?= new_value
这种情况下,VIR的值就是old_value


“+=”和平时写代码的理解是一样的,表示将等号后面的值添加到前面的变量上

$(MAKE)是make自定义的很多变量,在这里用来实现递归调用本身

         -C $(KERNELDIR) 指明跳转到内核源码目录下读取那里的Makefile;让内核在编译内核外还编译M指明的目录也就是我们这个文件夹下的文件。

        M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。

INSTALL_MOD_PATH=$(ROOTFS)

指示安装模块的路径,也就是生成的模块放到哪里

  1. obj-y 编译到内核
  2. obj-m 编译成模块

linux内核开发,驱动开发,Linux,内核,嵌入式

rm呢就是删除这些文件

ifeq ($(KERNELRELEASE),)
第一次编译时这个宏是空的

KERNELRELEASE是一个变量,在Linux内核源代码的顶层Makefile中定义,用于指定当前正在构建的内核版本。

也就是在不断的make的第一次make我们可以执行当前目录else前面的那些配置和工作。

modules为makefile的默认目标被执行(因为是第一个),而 modules_install 和clean则是伪目标,执行make时,只执行modules 目标. 

linux内核开发,驱动开发,Linux,内核,嵌入式

内核模块工具 

 sudo insmod ./vser.ko

dmesg

linux内核开发,驱动开发,Linux,内核,嵌入式

modprobe可以自动加载模块到内核,但是我用着不好使不知道为什么,在运行这个前要更新依赖,运行depmod命令。等我学一学在看这个问题。

modinfo可以智能查找模块

rmmod卸载模块

linux内核开发,驱动开发,Linux,内核,嵌入式 

 linux内核开发,驱动开发,Linux,内核,嵌入式

 

内核模块一般的形式

在前面的模块加载实验中,我们看到内核有以下打印信息的输出。

[ 83.884417] vser: module license 'unspecified' taints kernel.

[83.884423] Disabling lock debugging due to kernel taint
        其大概意思是因为加载了 vser模块而导致内核被污染,并且因此禁止了锁的调试功能。这是什么原因造成的呢?众所周知,Linux 是一个开源的项目,为了使 Linux 在发展的过程中不成为一个闭源的项目,这就要求任何使用 Linux 内核源码的个人或组织在免费获得源码并可针对源码做任意的修改和再发布的同时,必须将修改后的源码发布。这就是所谓的GPL 许可证协议。在此并不讨论该许可证协议的详细内容,而是讨论在代码中如何来反应我们接受该许可证协议。在代码中我们需要添加如下的代码来表示该代码接受相应的许可证协议。
MODULE LICENSE("GPL");
        MODULE_LICENSE是一个宏,里面的参数是一个字符串,代表相应的许可证协议。可以是:GPL、GPL v2、GPL and additional rights、Dual BSD/GPL、Dua MIT/GPL、Dual MPL/GPL 等,详细内容请参见include/linux/module.h头文件。这个宏将会生成一些模块信息,放在ELF文件中的一个特殊的段中,模块在加载时会将该信息复制到内存中检查该信息,可能读者会认为不加这行代码,即不接受许可证协议只是导致内核报案或关闭某些调试功能而已,对于可以不开源的这个结果,这个代价似乎是可以接受的但是正如本章的后面我们会讲到的一样,没有这行代码,内核中的某些功能再数是不够调用的,而我们在开发驱动时几乎不可避免地要去使用内核中的一些基础设施,
用一些内核的API函数。
除了MODULE_LICENSE之外,还有很多类似的描述模块信息的宏,比如MODULE AUTHOR,:MODULE DESCRIPTION用于模块的详细信息说明,通常是该模块的功能说明:MODULE_ALLA提供了给用户空间使用的一个更合适的别名,也就是使用MODULE_ALIAS可以取一别名。
模块的初始化函数和清除函数的名字是固定的,入口函数基本上都叫main。这对子追求个性化和更想表达函数真实意图的我们来说显得呆板了一些。幸亏内核借助于GNU的函数别名机制,使得我们可以更灵活地指定模块的初始化函数和清除函数的别名
module init(vser_init);

module exit(vser exit);
        module init 和module_exit是两个宏,分别用于指定initmodule的函数别名是 vserinit,以及cleanup_module的别名是vser_exit。这样我们的模块初始化函数和清除承数就可以用别名来定义了。
        函数名可以任意指定又带来了一个新问题,那就是可能会和内核中已有的函数重名因为模块的代码最终也属于内核代码的一部分。C语言没有类似于C++的命名空间的概念,为了避免因为重名而带来的重复定义的问题,函数可以加static关键字修饰。经过static修饰后的函数的链接属性为内部,从而解决了该问题。这就是几乎所有的驱动程序的函数前都要加static关键字修饰的原因。
        Linux是节约内存的操作系统的典范,任何可能节约下来的内存都不会被它放过。上面的模块代码看上去已经足够简单了,但仔细思考,还是会发现可以优化的地方。模块的初始化函数会且仅会被调用一次,在调用完成后,该函数不应该被再次调用。所以该函数所占用的内存应该被释放掉,在函数名前加_init可以达到此目的。_init是把标记的函数放到ELF文件的特定代码段,在模块加载这些段时将会单独分配内存,这些函数调用成功后,模块的加载程序会释放这部分内存空间。_exit用于修饰清除函数,和init的作用类似,但用于模块的卸载,如果模块不允许卸载,那么这段代码完全就不用加载。

(其实不一定被污染,我的就没有哈哈,书中的内核版本是3.14我的是5.4.0)

(具体原因不详)

 然后我们的程序就变成这样了

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

static int __init vser_init(void)
{
	printk("init_module\n");
	return 0;
}

static void __exit vser_exit(void)
{
	printk("cleanup_module\n");
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("姓名 <邮箱>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");

一定不要忘记函数的()里要写void

之前那个modprobe能用了,但是要把ko文件复制到/lib/modules/5.4.0-137-generic下

感觉应该是我的depmod有问题。

linux内核开发,驱动开发,Linux,内核,嵌入式

将多个源文件编译生成一个内核模块 

对于一个比较复杂的驱动程序,将所有的代码写在一个源文件中通常是不太现实的。我们通常会把程序的功能进行拆分,由不同的源文件来实现对应的功能,应用程序是这样的,驱动程序也是如此。下面这个简单的例子演示了如何用多个源文件生成一个内核模块。

其实很简单就是修改一下makefile

#include <linux/kernel.h>

void bar(void)
{
	printk("bar\n");
}
#include <linux/kernel.h>

void bar(void)
{
	printk("bar\n");
}
book@100ask:~/makeru/driver/kernal$ cat foo.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

extern void bar(void);
static int __init vser_init(void)
{
	printk("vser_init\n");
	return 0;
}

static void __exit vser_exit(void)
{
	printk("vser_exit\n");
}

linux内核开发,驱动开发,Linux,内核,嵌入式

内核模块参数

         通过前面的了解,我们知道模块的初始化函数在模块被加载时调用。但是该函数不接受参数,如果我们想在模块加载时对模块的行为进行控制,就不是很方便了。比如编写了一个串口驱动,想要在串口驱动加载时波特率由命令行参数设定,就像运行普通的应用程序时,通过命令行参数来传递信息一样。为此模块提供了另外一种形式来支持这种行为,这就叫作模块参数。
        模块参数允许用户在加载模块时通过命令行指定参数值,在模块的加载过程中,加载程序会得到命令行参数,并转换成相应类型的值,然后赋值给对应的变量,这个过程发生在调用模块初始化函数之前。内核支持的参数类型有:bool、invbool(反转值 bool类型)、charp(字符串指针)、short、int、long、ushort、uint、ulong。这些类型又可以复合成对应的数组类型。为了说明模块参数的用法,下面分别以整型、整型数组和字符串类型为例进行说明

内核中没有字符类型(char),但有byte类型,使用时可以byte类型代替char类型,
但是在传递参数时不能直接传递字符,只能传递整形数,否则会报错

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

static int baudrate = 9600;
static int port[4] = {0,1,2,3};
static char *name = "vser";

module_param(baudrate, int, S_IRUGO);
module_param_array(port, int, NULL, S_IRUGO);
module_param(name, charp, S_IRUGO);


static int __init vser_init(void)
{
	int i;

	printk("vser_init\n");

	printk("baudrate: %d\n", baudrate);
	printk("prot:");
	for(i = 0; i < ARRAY_SIZE(port); i++)
		printk("%d", port[i]);

	printk("\n");
	printk("name: %s\n", name);
	return 0;
}

static void __exit vser_exit(void)
{
	printk("vser_exit\n");
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");

代码第5行到第7行分别定义了一个整型变量、整型数组和字符串指针。代码第9行到第11行将这三个类型的变量声明为模块参数,分别用到了moduleparam和module param_array 两个宏,两者的参数说明如下。
module_param(name, type, perm)
module param array(name, type,nump; perm)

name:变量的名字。
type:变量或数组元素的类型。
nump:数组元素个数的指针,可选。
perm:在sysfs文件系统中对应文件的权限属性。
权限的取值请参见<linux/stat.h>头文件,含义和普通文件的权限是一样的。但是如果perm为0,则在sysfs文件系统中将不会出现对应的文件。
编译、安装模块后,在加载模块时,如果不指定模块参数的值,那么使用的命令和内核的打印信息如下。
linux内核开发,驱动开发,Linux,内核,嵌入式
可见打印的值都是代码中的默认值。如果需要指定模块参数的值,可以使用下面的命令。
modprobe vser baudrate-115200 port-1,2,3,4 name-"virtual-serial”

dmesg

(我的modprobe不太好使我就用的insmod)

linux内核开发,驱动开发,Linux,内核,嵌入式

 


参看 sysfs文件系统下的内容,可以发现和模块参数对应的文件及相应的权限。
linux内核开发,驱动开发,Linux,内核,嵌入式

 

 虽然在代码中增加模块参数的写权限可以使用户通过sysfs文件系统来修改模块参数的值,但并不推荐这样做。因为通过这种方式对模块参数进行的修改模块本身是一无所知的。

内核模块依赖

        在介绍模块依赖之前,首先让我们学习一下导出符号。在之前的模块代码中,都用到了printk函数,很显然,这个函数不是我们来实现的,它是内核代码的一部分。我们的模块之所以能够编译通过,是因为对模块的编译仅仅是编译,并没有链接。编译出来的,ko文件是一个普通的ELF目标文件,使用file命令和nm命令,可以得到相关的细节信息。

linux内核开发,驱动开发,Linux,内核,嵌入式
使用nm命令查看模块目标文件的符号信息时,可以看到vser_exit和vser_init的符号类型是t,表示它们是函数;而printk的符号类型是U,表示它是一个未决符号。这表示在编译阶段不知道这个符号的地址,因为它被定义在其他文件中,没有放在模块代码中一起编译。那printk函数的地址问题怎么解决呢,让我们来看看printk的实现代码

(位于内核源码kernel/printk/printk.c)。
linux内核开发,驱动开发,Linux,内核,嵌入式

/usr/src/linux-headers-5.4.0-137-generic/kernel
这是我的内核版本

其实这里都是软连接

真正的源码在makefile指定的那个路径里

cat printk.c | head -n 1693 | tail -n +1674

asmlinkage int printk(const char *fmt, ...)
{
	va_list args;
	int r;

#ifdef CONFIG_KGDB_KDB
	if (unlikely(kdb_trap_printk)) {
		va_start(args, fmt);
		r = vkdb_printf(fmt, args);
		va_end(args);
		return r;
	}
#endif
	va_start(args, fmt);
	r = vprintk_emit(0, -1, NULL, 0, fmt, args);
	va_end(args);

	return r;
}

        通过一个叫作EXPORT SYMBOL 的宏将printk导出,其目的是为动态加载的模块提供printk的地址信息。大致的工作原理是:利用EXPORT_SYMBOL 宏生成一个特定的结构并放在 ELF 文件的一个特定段中,在内核的启动过程中,公将符号的确切地场填充到这个结构的特定成员中。模块加载时,加载程字将去处理未决符号,在特殊段中2索符号的名字,如果找到,则将获得的地址填充在被加载模块的相应段中,这样符号的地址就可以确定。使用这种方式处理未决符号,其实相当于把链接的过程推后,进行了动态链接,和普通的应用程序使用共享库函数的道理是类似的。可以发现,内核将会有大量的符号导出。为模块提供了丰富的基础设施,
        通常情况下,一个模块只使用内核导出的符号,自己不导出符号。但是如果一个初块需要提供全局变量或函数给另外的模块使用,那么就需要将这些符号导出。这在一个驱动程序代码调用另一个驱动程序代码时比较常见。这样模块和模块之间就形成了依赖关系,使用导出符号的模块将会依赖于导出符号的模块,下面的代码说明了这一点

 

 

vser.c 

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

extern int expval;
extern void expfun(void);

static int __init vser_init(void)
{
	printk("vser_init\n");
	printk("expval: %d\n", expval);
	expfun();
	return 0;
}

static void __exit vser_exit(void)
{
	printk("vser_exit\n");
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");

 dep.c

#include <linux/kernel.h>
#include <linux/module.h>

static int expval = 5;
EXPORT_SYMBOL(expval);

static void expfun(void)
{
	printk("expfun");
}

EXPORT_SYMBOL_GPL(expfun);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");

 

linux内核开发,驱动开发,Linux,内核,嵌入式

 linux内核开发,驱动开发,Linux,内核,嵌入式

 喵喵的在开发板就好使,大概是我的ubuntu环境有问题。这几个驱动是之前做的开机自启脚本加载的。

        在上面的代码中,dep.c里定义了一个全局变量expval,定义了一个函数expfun,并分别使用EXPORT SYMBOL 和EXPORT SYMBOL GPL 导出。在vser.c首先用extern声明了这个变量和函数,并打印了该变量的值和调用了该函数。在 Makefile中添加了第20行的代码,增加了对 dep模块的编译。编译、安装模块后,使用下面的命令加载并查看内核的打印信息。
$ modprobe vser

$ dmesg
这里有几点需要特别说明。
(1)如果使用insmod命令加载模块,则必须先加载 dep 模块,再加载vser模块。因为vser模块使用了dep模块导出的符号,如果在dep模块没有加载的情况下加载vser模块,那么将会在加载的过程中因为处理未决符号而失败。从这里可以看出,modprobe令优于 insmod 命令的地方在于其可以自动加载被依赖的模块。而这又要归功于depmod命令,depmod 命令将会生成模块的依赖信息,保存在/1ib/modules/3.13.0-32-gene modules.dcp 文件中。其中,3.13.0-32-generic 是内核源码的版本,视版本的不同而不同查看该文件可以发现vser模块所依赖的模块。
$cat /lib/modules/3.13.0-32-generic/modules.dep

extra/vser.ko: extra/dep.ko

extra/dep.ko:

(我的ubuntu18.04和书中描述的不一致,我在我的开发板上也找不到depmod的文件,不过效果差不多,而且我的ubuntu里可能是ko文件太多了,还有大量的重名ko文件所以更新依赖失败了。也不是失败了,更新的不是我想要的。)
(2)两个模块存在依赖关系,如果分别编译两个模块,将会出现类似于下面的警告
信息,并且即便加载顺序正确,加载也不会成功。
NARNING:“expfun”【/home/farsight/fs4412/driver/module/ex5/vser.ko) undefined! WARNING:"expval”[/home/farsight/fs4412/driver/module/ex5/vser.kol)undefined!
$ sudo insmod dep.ko

$ sudo insmod vser.ko
insmod: error inserting 'vser.ko': -1 Invalid parameters
这是因为在编译vser模块时在内核的符号表中找不到expval和expfun的项,而vser模块又完全不知道dep模块的存在。解决这个问题的方法是将两个模块放在一起编译或者将dep模块放在内核源码中,先在内核源码下编译完所有的模块,再编译 vser 模块
(3)卸载模块时要先卸载vser模块,再卸载dep模块,否则会因为dep 模块被 vsa模块使用而不能卸载。内核将会创建模块依赖关系的链表,只有当依赖于这个模块的表为空时,模块才能被卸载。

关于内核模块的进一步讨论 

        Linux的内核是由全世界的志愿者来开发的,这个组织中的内核开发者会毫不顾虑地删除不适合的接口或者对接口进行修改,只要认为这是必要的。所以,往往在前一个篇本这个接口函数以一种形式存在,而到了下一个版本函数的接口就发生了变化。这对内核模块的开发具有重要的影响,就是所谓的内核模块版本控制。在一个版本上编译出来的内核模块,ko文件中详细记录了内核源码版本信息、体系结构信息、函数接口信息(通过CRC校验实现)等,在开启了版本控制选项的内核中加载一个模块时,内核将核对这些信息,如果不一致,则会拒绝加载。下面就是把一个在3.13 内核版本上编译的内核模块放在 3.5 内核版本的系统上加载的相关输出信息。
modinfo vser,ko


filename: vser,ko 
alias: virtual-serial 
deneriptioni: A simple module 
authori : 手动打码
licese: GPL 
sroveraloni BABBD66A92DF5D4C7VA3110 
depends:
vermagic:  3.13.0-32-generie BME mod unload modversionn 606
 

uname sr
3.5.0-23-generic

insmod veer.ko
lnsmod: error inserting 'veoriko’! -i Invalid module format

dmesg
vsert: disagreen about vernion of symbol module_layout

最后再总结一下内核模块和普通应用程序之间的差异。
(1)内核模块是操作系统内核的一部分,运行在内核空间:而应用程序运行在用户空间。
(2)内核模块中的函数是被动地被调用的,比如初始化的数和清除函数分别是在内核模块被加载和被卸载的时候调用,模块通常注册一些服务性质的函数供其他功能单元在之后调用,而应用程序则是顺序执行,然后通常进入一个循环反复调用某些函数。
(3)内核模块处于C函数库之下,自然就不能调用C库函数(内核源码中会实现类似的函数);而应用程序则可以随意调用C库函数。
(4)内核模块要做一些清除性的工作,比如在一个操作失败后或者在内核的清除函数中:而应用程序有些工作通常不需要做,比如在程序退出前关闭所有已打开的文件。
(5)内核模块如果产生了非法访问(比如对野指针的访问),将很有可能导致整个系统的崩溃;而应用程序通常只影响自己。
(6)内核模块中的并发更多,比如中断、多处理器:而应用程序一般只考虑多进程或多线程。
(7)整个内核空间的调用链上只有4KB或8KB的栈,相对于应用程序来说非常的小。所以如果需要大的内存空间,通常应该动态分配。
(8)虽然printk和printf的行为非常相似,但是通常printk不支持浮点数,例如要打印一个浮点变量,在编译时通常会出现如下警告,并且模块也不会加载成功。
WARNING:" extendsfdf2"[/home/faraight/fe4412/driver/module/ex5/vser.ko)undefined! 
WARNING:" truncdfaf2"【/home/farsight/fs4412/driver/module/ex5/vser.ko)undetined! WARNING:“ divdf3”【/home/farsight/f84412/driver/module/ex5/vser.ko]undefined!
WARNING:"foatsidf”【/home/farsight/fs4412/driver/module/ex5/vser.ko] undefined!

习题

1,在默认情况下,模块初始化函数的名字是( A),模块清除函数的名字是( B)。
[A]init_module[B]cleanup_module[C] mod_init [D] mod_exit 

2、加载模块可以用哪个命令( AD)。
[A]insmod [B] rmmod [C] depmod [D] modprobe 
3、查看模块信息用哪个命令(C)。
[A] insmod [B] rmmod [C] modinfo [D] modprobe 
4.内核模块参数的类型不包括(D)。
[A]布尔 [B]字符串指针 [C]数组 [D] 结构 

b) type:数据类型内核支持模块参数类型有:bool、invbool(bool的发转,true变为false,false变为true)、charp(char类型指针值)、int、long、short、uint、ulong、ushort


5.内核模块导出符号用哪个宏(C )。
[A]MODULE EXPORT [B]MODULE_PARAM 
[C]ENPORT SYMBOL [D] MODULE_LICENSE 

pararm是传参数的

MODULE EXPORT这个我都没找到好像没有这个宏


6.内核模块能否调用C库的函数接口(B )。
[A]能 [B] 不能 
7.在内核模块代码中,我们能否定义任意大小的局部变量( B)。
[A] 能 [B]不能

 这题有歧义,只能是一定范围内的自由文章来源地址https://www.toymoban.com/news/detail-756628.html

到了这里,关于Linux驱动开发——内核模块的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 嵌入式linux之iMX6ULL驱动开发 | 移远4G模块EC800驱动移植指南

    回顾下移远4G模块移植过程, 还是蛮简单的。一通百通,无论是其他4G模块都是一样的。这里记录下过程,分享给有需要的人。环境使用正点原子的imax6ul开发板,板子默认支持中兴和移远EC20的驱动,这里要移植使用的是移远4G模块EC800。 imax6ul开发板 虚拟机(Ubuntu18.04) 交叉编译

    2024年02月12日
    浏览(19)
  • 【嵌入式Linux内核驱动】SPI子系统 | 硬件原理 | 应用编程 | 内核驱动 | 总体框架

    1.1 SPI通信协议 SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select) 同步,全双工 支持总线挂载多设备(一主多从) 1.2 硬件连接 多NSS独立片选方式 菊花

    2024年02月16日
    浏览(21)
  • 【嵌入式环境下linux内核及驱动学习笔记-(5-驱动的并发控制机制)】

    在讨论并发前,先要了解以下几个概念:执行流,上下文,共享与临界等。 什么叫执行流: 【执行流】:有开始有结束总体顺序执行的一段代码 又称 上下文 。 上下文分类: 【任务上下文】:普通的,具有五种状态(就绪态、运行态、睡眠态、暂停态、僵死态),可被阻塞

    2023年04月21日
    浏览(19)
  • 【嵌入式Linux】编译应用和ko内核模块Makefile使用记录

    在Makefile中,变量的赋值可以使用以下几种方式: = :最基本的赋值符号,表示简单的延迟展开(lazy expansion)方式。变量的值将会在使用变量的时候进行展开。 := :立即展开(immediate expansion)的赋值方式。变量的值在赋值的时候立即展开,并且在后续的使用中不再改变。

    2024年02月08日
    浏览(20)
  • 嵌入式开发之linux内核移植

    目录  前言 一、下载内核源码 1.1 下载linux-3.0.1 1.2 解压源码文件 二、 内核添加yaffs2文件系统支持 2.1 下载yaffs2 2.2 内核添加yaffs2文件补丁 三、配置开发板 3.1 修改机器ID 3.2 添加开发板初始化文件 3.3 配置NandFalsh 3.3.1 添加NandFlash设备 3.3.2 添加NandFlash驱动 3.3 修改Kconfig(支持

    2024年02月07日
    浏览(27)
  • 【嵌入式Linux内核驱动】05_IIC子系统 | 硬件原理与常见面试问题 | 应用编程 | 内核驱动 | 总体框架

    1.1 IIC 基础 IIC协议简介—学习笔记_iic标准协议_越吃越胖的黄的博客-CSDN博客 I2C(Inter-Integrated Circuit)是一种串行通信协议,用于连接微控制器、传感器、存储器和其他外设。 I2C使用两条线(SDA和SCL)进行通信,可以连接多个设备,每个设备都有一个唯一的地址。I2C总线上的

    2024年02月09日
    浏览(14)
  • 嵌入式Linux底层系统开发 +系统移植+内核文件系统(基础)

    搭建交叉编译开发环境 bootloader的选择和移植 kernel的配置、编译、移植和调试 根文件系统的制作 前两个要点通常芯片厂家提供。后边两个要点是公司的工作重点。 学习方法:先整体后局部,层层推进 如何编译—如何添加命令和功能—如何定义自己的开发板。 移植的基本步

    2024年02月03日
    浏览(27)
  • 正点原子嵌入式linux驱动开发——Linux CAN驱动

    CAN是目前应用非常广泛的现场总线之一,主要应用于汽车电子和工业领域 ,尤其是汽车领域,汽车上大量的传感器与模块都是通过CAN总线连接起来的。CAN总线目前是自动化领域发展的热点技术之一,由于其高可靠性,CAN总线目前广泛的应用于工业自动化、船舶、汽车、医疗和

    2024年02月06日
    浏览(26)
  • 正点原子嵌入式linux驱动开发——Linux WIFI驱动

    WIFI的使用已经很常见了,手机、平板、汽车等等,虽然可以使用有线网络,但是有时候很多设备存在布线困难的情况,此时WIFI就是一个不错的选择。 正点原子STM32MP1开发板支持USB和SDIO这两种接口的WIFI ,本章就来学习一下如何在STM32MP1开发板上使用USB和SDIO这两种WIFI。 正点原

    2024年02月05日
    浏览(26)
  • 嵌入式Linux驱动开发之点灯

      使用驱动开发的方式点亮一个LED灯。看看两者有啥区别不? 首先查看原理图,看看我们的板子上的LED等接在哪一个IO口上面。 好了,看原理图我们知道LED灯接在芯片的GPIO1的第三个引脚上面,也就是GPIO1_IO03。 先掌握三个名词 CCM: Clock Controller Module (时钟控制模块) IOMUXC : I

    2024年02月01日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包