Linux驱动学习笔记(1)----模块

本文主要内容:

  1. 何为Linux内核模块
  2. 其存在的意义
  3. 如何编写一个模块
    3.1 最简单的模块
    3.2 编译模块
    3.3 模块参数
    3.4 导出符号
    3.5 模块相关常用指令

1 何为Linux内核模块

模块(Module)是Linux提供的一种机制,模块里的代码并不编译进入内核镜像,而是通常被编译成一个.ko文件,在Linux系统运行中可以动态的通过insmod命令和rmmod命令加载和卸载,而模块一旦被加载,其中代码就可以正常运行,与其被编译进内核是完全一样的。


2 其存在的意义

Linux内核的整体结构已经非常庞大,如果把所有需要的功能组件都编译到Linux内核,会导致内核很大。并且,如果想要在现有的内核中新增或删除功能,就不得不重新编译内核。这对调试驱动程序是非常难以接受的,试想修改一点代码后就要编译整个模块,然后才能测试,太低效了。因此,模块机制很好地解决了上述问题。
而Linux设备驱动通常以内核模块的形式出现。


3 如何编写一个模块

3.1 最简单的模块

hello.c:

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

staticint__init hello_init(void)
{
    printk(KERN_ALERT "Hello, world!\n");
return0;
}

staticvoid__exit hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, cruel world!\n");
}

MODULE_LICENSE("GPL");

module_init(hello_init);
module_exit(hello_exit);

如上面的代码所示,一个模块至少要包含三部分内容:
(1) 模块加载函数,即hello_init()函数,通过module_init()宏告知内核模块的加载函数。
模块加载函数返回int型值,初始化成功则返回0,失败则返回相应的错误码。
模块加载函数通常以__init标示声明,告诉内核该函数仅在初始化时用到,初始化完成后,内核会将其释放[注1]。

(2)模块卸载函数,即hello_exit()函数,通过module_exit()宏告知内核模块的卸载函数。
模块卸载函数无返回值,通常完成和模块加载函数相反的功能。
模块加载函数通常以__exit标示声明。

(3) 模块许可证声明,通过宏MODULE_LICENSE()来指定模块遵循的许可证。可接受的参数包括“GPL” “GPL v2” “Dual BSD/GPL” “Dual MPL/GPL”等。
如果不声明许可证,模块加载时,会受到内核被污染(kernel tainted)的警告。


[注1] __init 宏定义为:#define __init __attribute__ ((__section__ (".text.init")))
__attribute__关键字是GNU C编译器为函数、变量、类型声明特殊属性的关键字。
GNU C 支持数十种属性,__section__即其中一种。
这里(__section__ (".text.init"))表示以此标记的代码位于.init.text 内存区域。
同理,仅用于初始化的变量可以用 __initdata标记,这些变量将位于.init.data内存区域。
而内核在初始化完成之后,不再需要这些代码和变量,将释放这部分内存。
需要注意的是,__init和__initdata标记只有在模块被静态编译进内核时才有效,对动态加载的模块是不起作用的。

3.2编译模块
由于本文仅仅探讨模块的基本知识,与开发环境关系不大,因此,就在虚拟机中编译运行,而不再交叉编译,放到mini2440上跑了(需要了解mini2440交叉编译环境搭建,可以参考这篇博文:http://blog.csdn.net/geyue12345/article/details/49207981)。
以下为Makefile内容:

#Makefile 2.6

obj-m:= hello.o
KERNEL:=/usr/src/kernels/$(shell uname -r)/
PWD:=$(shell pwd)
modules:
    make -C $(KERNEL) M=$(PWD) modules
.PHONEY:clean
clean:
    rm -f *.o *.ko *.mod.o *.mod.c *.symvers *.order

通过make编译模块,得到hello.ko
这里写图片描述

通过insmod命令加载模块
这里写图片描述

通过lsmod命令可以看到刚刚加载的模块及其大小和引用计数
这里写图片描述


3.3模块参数

如果模块内的变量希望在模块加载时由用户传入或希望模块加载后仍可以由用户更改(如在调试驱动时,修改是否打印调试信息的标志位或者I2C设备的总线地址等),可以将该变量定义为一个模块参数。
在模块代码中需要做的:
module_param(参数名,参数类型,读写权限);
如: module_param(Num, int, S_IRUGO);
其中: 参数名即模块内的变量名;
参数类型需要和变量的类型相符;
这里的读写权限即文件读写权限。
用户如何修改模块参数:
a. 通过insmod或modprobe加载模块时传入,如:
insmod hello.ko Num=10
如果加载模块时不传入参数,则参数使用模块内定义的默认值。
b. 模块加载之后,/sys/module文件下会生成一个以该模块名命名的文件夹,如果有模块参数,且该参数的权限不为0,该文件下的parameter文件夹中会产生一个以该参数名命名的文件,而该文件的读写权限即通过module_param()传入的权限。可以通过对该文件的读写来读取或修改模块参数。
举例:param.c

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

staticchar*name ="hg";
staticint age =25;
module_param(name, charp, S_IRUGO | S_IWUSR);
module_param(age,int, S_IRUGO | S_IWUSR);

staticint __init hello_init(void)
{
    printk(KERN_ALERT "Module init.\n");
    printk(KERN_ALERT "Name: %s\n", name);
    printk(KERN_ALERT "Age: %d\n", age);
return0;
}

staticvoid __exit hello_exit(void)
{
    printk(KERN_ALERT "Age: %d\n", age);
    printk(KERN_ALERT "Name: %s\n", name);
    printk(KERN_ALERT "Module exit.\n");
}

MODULE_LICENSE("GPL");

module_init(hello_init);
module_exit(hello_exit);

编译完成后,加载模块,不传入任何参数,可以看到参数为默认值。
这里写图片描述

读取和修改/sys/module/param/parameters/下的age和name文件
这里写图片描述

卸载模块,可以看到参数值已经为修改后的值。
这里写图片描述

在加载时就传入参数,可以看到参数值为传入的值。
这里写图片描述


3.4 导出符号

模块可以通过以下宏将符号(函数名、变量名)导出到内核符号表:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);
/proc/kallsyms文件中记录了内核符号表中的符号及符号所在的内存地址。导出的符号可以被其他模块使用,使用前声明一下即可。
以下为一个例子
calculate.c:

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hg");

int add_integar(int a, int b)
{
    return (a + b);
}

int sub_integar(int a, int b)
{
    return (a - b);
}

static int calculate_init(void)
{
    printk(KERN_ALERT "Module calculate is installed\n");
    return 0;
}

static void calculate_exit(void)
{
    printk(KERN_ALERT "Module calculate is removed.\n");
}

module_init(calculate_init);
module_exit(calculate_exit);

EXPORT_SYMBOL(add_integar);
EXPORT_SYMBOL(sub_integar);

module_export.c:

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Hg");
MODULE_DESCRIPTION("An example to show how to export func by module.");
MODULE_ALIAS("a simplest module");

extern int add_integar(int a, int b);
extern int sub_integar(int a, int b);

static int hello_init(void)
{
    int res = add_integar(1,2);
    printk(KERN_ALERT "1 + 2 = %d\n", res);
    return 0;
}

static void hello_exit(void)
{
    int res = sub_integar(2,1);
    printk(KERN_ALERT "2 - 1 = %d\n", res);
}

module_init(hello_init);
module_exit(hello_exit);

分别编译两个模块。
首先试试直接加载module_export模块,结果加载失败,因为内核符号表中此时还没有add_integar()和sub_integar()函数。
这里写图片描述

加载calculate.ko,接着查看内核符号表:
这里写图片描述

此时再加载module_export.ko,即可正常调用add_integar()和sub_integar():
这里写图片描述

3.5常用命令

insmod                              加载模块
modprobe                            加载模块,可以自动处理模块的依赖关系
rmmod                               卸载模块
lsmod                               查看所有模块
cat /proc/kallsyms                  查看内核符号表
/sys/module/模块名/parameters/      模块参数存放的文件夹

欢迎转载:http://blog.csdn.net/geyue12345/article/details/50776481

猜你喜欢

转载自blog.csdn.net/geyue12345/article/details/50776481
今日推荐