模块源代码 hds.c文件
# include //任何模块都必须包含,定义了可动态加载到内核的模块所需要的必要信息
# include //必须包含,包含了宏__init(指定初始化函数)和__exit(指定清除函数)
# include //里面包含常用的内核API,例如内核打印函数printk()
static int __init hds_init(void) /*__init将函数hds_init()标记为初始化函数,在 模块被装载到内核时调用hds_init()*、
{
int sum = 0;
int i;
for(i = 1; i < 11; i++) //函数功能为1-10累加求和
sum +=i;
printk(KERN_ALERT "sum is %d\n",sum); //打印级别设为<1>,将求和结果立即打印
return 0;
}
static void __exit hds_exit(void) /*清除函数,在模块被卸载之前调用*/
{
printk(KERN_CRIT "Goodbye"); //在模块卸载前,将Goodbye这句话打印到日志
}
module_init(hds_init); //引导内核,模块从这里进来
module_exit(hds_exit); //引导内核,模块从这里出去
MODULE_LICENSE("GPL"); //(必选项)模块许可证,缺省此句,将导致内核被污染
MODULE_AUTHOR("hds"); //(可选项) 描述模块作者
MODULE_DESCRIPTION("for fun"); //(可选项) 描述模块功能
Makefile文件
obj-m:=hds.o
CURRENT_PATH:=$(shell pwd) #将模块源码路径保存在CURRENT_PATH中
LINUX_KERNEL:=$(shell uname -r) #将当前内核版本保存在LINUX_KERNEL中
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) #将内核源代码的绝对路径保存在LINUX_KERNEL_PATH中
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules #编译模块
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #清理
编译模块
make
装载模块
sudo insmod hds.ko
卸载模块
sudo rmmod hds
头文件+初始化函数+清除函数+引导内核的模块入口+引导内核的模块出口+模块许可证
编写内核代码所用到的头文件包含在内核代码树的include/及其子目录中,例如module.h,kernel.h,init.h,这三个头文件全部包含在/include/linux/中。这三个头文件以预处理指令的形式写在模块源代码的首部:
# include
# include
# include
在编译模块源文件之前,由预处理程序对预处理指令进行预处理。对于#include
# include
在内核代码树的位置为linux-2.6.0/include/linux/module.h,头文件module.h包含了对模块的结构定义以及模块的版本控制,可装载模块需要的大量符号和函数定义(模块里具体包含什么内容,我自己还没有深入源代码了解,这些是从参考书搬过来的,初学阶段知道写模块必须包含这个头文件,还有头文件大致的内容即可,先知道是什么,后面再深入分析源代码)。module.h的源码如下(只是开头部分):
#ifndef _LINUX_MODULE_H
#define _LINUX_MODULE_H
/*
* Dynamic loading of modules into the kernel. //动态加载到内核的模块
*
* Rewritten by Richard Henderson Dec 1996
* Rewritten again by Rusty Russell, 2002
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* Not Yet Implemented */
#define MODULE_SUPPORTED_DEVICE(name)
#define print_modules()
# include
在内核代码树的位置为linux-2.6.0/include/linux/init.h,在init.h这个文件中包含了两个非常重要的宏__init 和 __exit。在init.h的源代码中,对于两种宏的用法和作用给出了说明
源代码说明如下
/* These macros are used to mark some functions or
* initialized data (doesn't apply to uninitialized data)
* as `initialization' functions. The kernel can take this
* as hint that the function is used only during the initialization
* phase and free up used memory resources after
*
* Usage:
* For functions:
*
* You should add __init immediately before the function name, like:
*
* static void __init initme(int x, int y)
* {
* extern int z; z = x * y;
* }
*
*/
宏__init用于将一些函数标记为“初始化”函数。内核可以将此作为一个提示,即该函数仅在初始化阶段使用,并在初始化阶段之后释放使用的内存资源。模块被装载之后,模块装载器就会将初始化函数扔掉,这样可将该函数占用的资源释放出来。
宏__init的用法如下:
static void __init initme(int x, int y) //放在函数返回值类型和函数名之间
{
extern int z; z = x * y;
}
宏__exit的用法和__init一样,它的作用是标记该段代码仅用于模块卸载(编译器将把该函数放在特殊的ELF段中)。即被标记为__exit的函数只能在模块被卸载时调用。
# include
kernel.h包含了内核常用的API,比如printk()在kernel.h源代码的定义如下:
int printk(const char * fmt, ...)
模块功能函数的定义如下:
static int __init name_function(void)
{
/* 模块要实现的功能 */
return 0;
}
module_init(name_function);
模块功能函数是在模块被装入内核后调用的,也就是在模块的代码被装入内核内存后,才调用模块功能函数。注意: __init 标记只是一个可选项,并不是写所有模块代码都要加 __init。但是在测试我们自己写的模块时,最好加上 __init。因为我们在写一个模块功能函数的时候,可能这个函数里面有定义的变量,当调用这个函数的时候,就要为变量分配内存空间,但注意,此时分配给变量的内存,是在内核空间分配的,也就是说分配的是内核内存。所以说如果只是想要测试一下模块的功能,并不需要让模块常驻内核内存,那就应该在执行完函数后,将当初分配给变量的内存释放。为了达到这个效果,只需要把这个函数标记为 __init属性。
清除函数的定义如下:
static void __exit name_function(void)
{
/* 这里是清除代码*/
}
module_exit(name_function);
__exit标记该段代码仅用于模块卸载,被标记为 __exit的函数只能在模块被卸载或者系统关闭时调用。如果一个模块未被定义为清除函数,则内核不允许卸载该模块。
源码定义如下:
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
# define module_init(x) __initcall(x);
模块init()——驱动程序初始化入口点。 在内核引导时运行的函数,或者在do initcalls期间(如果* build tin)调用模块init(),或者在模块插入时(如果是模块)调用模块init()。每个模块只能有一个。
源码定义如下:
**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
# define module_exit(x) __exitcall(x);
模块出口()-驱动程序出口入口点。当驱动程序被删除时运行的函数。模块出口()将用cleanup模块()包装驱动程序清理代码当驱动程序是一个模块时与rmmod一起使用。如果驱动程序被静态编译到内核中,则module exit()没有作用。每个模块只能有一个。
编写内核模块,需要添加模块许可证。如果没有添加模块许可证,会收到内核被污染的警告
module license unspecified taints kernel
可能会导致驱动程序的一些系统调用无法使用。