LDD-第二章 构造和运行模块

内核中的并发

内核编程区别于常见应用程序编程的地方在于对并发的处理。考虑并发的原因:1.Linux系统通常正在运行多个并发进程,并且可能有多个进程同时使用我们的驱动程序。2.大多数设备能够中断处理器,而中断程序异步运行,而且可能在驱动程序正试图处理其他任务时被调用。3.有一些软件抽象(如内核定时器)也在异步运行。3.Linux可能运行在对称多处理器(SMP)系统上,因此可能同时不止一个CPU运行我们的驱动程序。4.在2.6中,内核已经是可抢占式,意味着即使在单处理器系统上也存在许多类似多处理器系统的并发问题。所以,Linux内核代码(包括驱动程序代码)必须是可重入的,必须能同时运行在多个上下文中,因此,内核数据结构需要保证多个线程分开执行,访问共享数据的代码也必须避免破坏共享数据。内核代码需要能够处理并发问题的同时避免竞态(不同的执行顺序导致不同的、非预期的行为发生)。

当前进程

current指针指向当前正在运行的进程(struct task_struct),在open、read等系统调用执行过程中,当前进程指的是调用这些系统调用的进程,如果需要,内核代码可以通过current获得与当前进程相关的信息。printk(KERN_INFO "The process is \"%s\" (pid %i) \n", current->comm, current->pid);

编译和装载

obj-m := module.o

module-objs  := file1.o file2.o

make –C ~/kernel  M=’pwd’ modules

modprobe和insmod的区别在于:modprobe会考虑要装载的模块是否引用了一些当前内核不存在的符号,如果有这类引用,modprobe会在当前模块搜索路径中查找定义了这些符号的其他模块,如果找到,就同时把这些模块装载到内核。如果在这种情况下使用insmod,则该命令会失败(unresolved symbols)。

如果模块在使用或者内核被配置为禁止移除模块,则rmmod失败。

lsmod通过读取/proc/modules虚拟文件获取信息。也可以在/sys/module下找到。

扫描二维码关注公众号,回复: 2305178 查看本文章

版本依赖

modinfo 查看模块相关的信息。

在Linux2.6内核的linux/vermagic.h头文件中定义了“版本魔术字符串”—VERMAGIC_STRING,包含了内核版本号,内核编译所使用的gcc版本、SMP与PREEMPT等配置信息。在编译模块时,我们可以在屏幕上会显示“MODPOST”(模块后续处理),在内核源码目录下scripts/mod/modpost.c文件中可以看到模块后续处理部分的代码,在这个阶段,VERMAGIC_STRING会被添加到模块的modinfo段中,模块编译生成后,通过modinfo命令可以看到此模块的vermagic等信息,2.6内核下的模块装载器里保存有内核的版本信息,在装载模块时,装载器会比较所保存的内核vermagic与次模块modinfo段里保存的vermagic信息是否一致,两者一致时,模块才能被装载。

内核符号表

Insmod使用公共内核符号表来解析模块中未定义的符号。公共内核符号表中,包含了所有的全局内核项(函数和变量)的地址,这是实现模块化驱动程序所必需的。当模块被装入内核后,它所导出的任何符号都会变成内核符号表的一部分。

EXPORT_SYMBOL(name)和EXPORT_SYMBOL_GPL(name)。_GPL导出的模块只能被GPL许可证下的模块使用。

预备知识

    #include <linux/module.h> //包含可装载模块需要的大量符号和函数的定义

#include <linux/init.h>  //指定初始化和清除函数

 

MODULE_LICENSE("GPL");内核能够识别的许可证有“GPL”、“GPL  v2”、“GPL and additional rights”、“Dual BSD/GPL”、“Dual MPL/GPL”、“Proprietary”。如果一个模块没有被显式的标记为上述内核可识别的许可证,则会被嘉定是专用的,内核装载这种模块就会被“污染”。MODULE_AUTHOR描述模块作者、MODULE_DESCRIPTION模块简短描述、MODULE_VERSION代码修订号、MODUIE_ALIAS模块别名、MODULE_DEVICE_TABLE用来告诉用户空间模块支持的设备。

初始化和关闭

__init对内核来说是一种暗示,表明该函数仅在初始化期间使用,在模块被装载后,模块装载器就会把初始化函数扔掉,这样可将该函数占用的内存释放出来,已作他用。module_init的使用是强制性的,这个宏会在模块的目标代码中增加一个特殊的段,勇于说明内核初始化函数所在的位置,没有这个定义,初始化函数永远不会被调用。

__exit标记该代码仅用于模块卸载(编译器会把该函数放在特殊ELF段中),被__exit标记的函数只在模块卸载或系统关闭时调用。

 初始化过程中的错误处理

在内核中注册设施时,要时刻铭记注册可能会失败,即使是最简单的动作,都需要内存分配,而所需要的内存可能无法获得,因此模块代码必须始终检查返回值,并确保所请求的操作已真正成功。如果在注册设施时遇到任何错误,首先要判断模块是否可以继续初始化,通常,某个注册失败后,可以通过降低功能来继续运转,因此,只要可能,模块应该继续向前并尽可能提供其功能。如果发生某个特定类型的错误之后无法继续装载模块,则要将出错之前的任何注册工作撤销掉。内核经常用goto语句来处理错误。

模块装载竞争

首先要始终铭记,在注册完成之后,内核的某些部分可能会立即使用我们刚注册的任何设施,换句话说,在初始化函数还在运行的时候,内核就完全可能会调用我们的模块,因此,在首次注册完成之后,代码就应该准备好被内核其他部分调用,在用来支持摸个设施的所有内部初始化完成之前,不要注册任何设施。

猜你喜欢

转载自blog.csdn.net/weixin_42343585/article/details/81149497
今日推荐