linux内核学习(一)------------模块

1.1 linux内核模块简介

模块是linux提供的动态加载代码的功能。模块具有如下特点:

1.模块一旦被加载,就和内核其它部分完全一样。

2.模块本身不被编译进内核,从而控制了内核的大小


1.2 下面是一个常见的内核模块的举例:

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

static int __init lkp_init(void)
{
printk("<1>Hello,world! frome the kernel space...\n");
return 0;
}

static void __exit lkp_cleanup(void)
{
printk("<1>Goodbye,world! leaving kernel space...\n");
}

module_init(lkp_init);
module_exit(lkp_cleanup);
MODULE_LICENSE("GPL");


对应的makefile:

#Makefile

obj-m += hellomod.o #编译为模块
CUR_PATH := $(shell pwd) #取当前路径
LINUX_KERNEL := $(shell uname -r) #取当前内核版本号
LINUX_KERNEL_PATH := /usr/src/kernels/linux-$(LINUX_KERNEL) #当前linux版本源码路径
all:
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules      #编译模块
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean #清理模块


1.3 内核模块常用的命令:

insmod:安装模块

rmmod:卸载模块

lsmod:查看已安装的模块,其实分析的是/proc/modules文件

内核中已加载的模块信息位于/sys/module目录下,加载hellomod.ko后,通过tree -a可以看到如下信息
[root@localhost hellomod]# tree -a
.
├── holders
├── initstate
├── notes
│   └── .note.gnu.build-id
├── refcnt
├── sections
│   ├── .exit.text
│   ├── .gnu.linkonce.this_module
│   ├── .init.text
│   ├── .note.gnu.build-id
│   ├── .rheldata
│   ├── .rodata.str1.4
│   ├── .strtab
│   └── .symtab
└── srcversion

3 directories, 12 files
[root@localhost hellomod]#


moinfo:查看模块信息

modprobe:该命令比insmod,rmmod要强大,如果以modprobe -r filename方式卸载,将同时卸载其依懒的模块,模块之间的依懒关系存放在根文件系统的/lib/modeuls/<kernel-version>/modules.dep文件中,这个实际上是在编译内核的时候由depmod工具生成的。


1.4 模块程序结构

(1)模块加载函数

返回值为int,如果初始化成功返回0,如果失败应该返回相应的错误码。在linux内核里,错误码是一个接近于0的负值,在<linux/errno.h>里定义,总是返回相应的错误码,可以使用户态程序通过

perror得到有意义的错误信息。

以__init标识,在linux中,所有被标识为__init的函数如果直接被编译进内核,成为内核镜像的一部分,在链接时会放在.init.text这个段内。

#define __init __attribute__((__section__(".init.text")))

所有__init函数在段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些函数,并在初始化完成后,释放init段(包括.init.text,.initcall.init)的内存。

除了函数以外,数据也可以被定义为__initdata,对于只是初始化阶段需要的数据,内核在初始化完成以后,也可以释放它们所占的内在。

另外,在Linux内核中,可以使用request_modeul(const char *fmt,...)函数加载内核模块,这样可以灵活地加载模块。

(2)模块卸载函数

在模块卸载时执行,不返回任何值。

__exit来修饰,可以告诉内核如果相关模块直接编译进内核(built-in)方式,则卸载函数会被忽略,直接不链接进最后的映像。因为模块内置,退出函数就没有必要了。


1.5模块参数

可以使用module_param(参数名,参数类型,读/写权限)为模块定义一个参数。

如下所示,为模块定义了一个整形参数和一个字符指针参数。

static int irq;
static char *interface;

module_param(interface,charp,0644);
module_param(irq,int,0644);

在装载模块时,用户可以指定参数 "insmod 模块名 参数名=参数值",如果不指定,则使用默认值。

如果模块内置,无法通过insmod指定参数,可以在bootloader通过bootargs里设置"模块名.参数名=值"的形式给模块传递参数。

参数类型可以是byte,short,ushort,int,uint,long,ulong,charp(字符指针),bool,invbool(布尔的反),在模块编译时,会将module_param中声明的类型与变量定义的类型进行比较,判断是否一致。

除此之外,模块也可以拥有参数数组,形式为module_param_array(数组名,数组类型,参数读/写权限)

模块被加载后,在/sys/module目录下将出现以模块名命名的目录。当参数读写权限为0时,表示此参数不存在sysfs文件系统下对应的文件节点。如果此模块存在读写不为0的参数,在此模块的目录下会出现parameters目录,其中包含一系列以参数名命名的文件节点,这些文件的权限就是传入module_param的参数读/写权限。

运行insmod安装模块时,应该使用逗号分隔数组元素。


1.6导出符号

/proc/kallsyms文件对应着内核的符号表,它记录了符号以及符号所在的内存地址。

模块可以使用如下宏导出符号到内核符号表中:

EXPORT_SYSBOL(符号名);

EXPORT_SYMBOL_GPL(符号名);

导出的符号可以被其它模块使用,只需要使用前声明一下即可。EXPORT_SYMBOL_GPL只适用于包含GPL许可权的模块。


1.7模块声明与描述

我们可以使用MODULE_AUTHOR,MODULE_DESCRIPTION,MODULE_VERSION,MODULE_DEVICE_TABLE,MODULE_ALIAS分别声明模块的作者,描述,版本,设备和别名。


1.8模块使用计数

Linux2.6以后的内核提供了try_module_get(&module)和module_put(&module)接口来管理模块的计数。模块的计数一般不必由模块自身管理,而且模块计数管理器还考虑了SMP与PREEMPT机制的影响。

int try_module_get(struct module *module);

用来增加模块的使用计数;返回0表示调用失败,表示使用的模块没有被加载或正在被卸载。

void module_put(struct module *module);

用来减少模块的使用计数。

这2个接口的引入,使用与2.6内核以后的内核下的设备模型密切相关。2.6内核以后为不同类型的设备定义了struct module *owner域,用来指向管理此设备的模块。当开始使用某个设备时,内核用try_module_get增加管理此设备owner模块的使用计数;当不再使用此设备时,内核使用module_put减少引用计数。这样,当设备在使用时,模块便不能被卸载。只有当设备不再使用时,才可以卸载。



你可能感兴趣的:(linux内核学习(一)------------模块)