内核模块基础

l  什么是内核模块?

1. 内核模块是一种没有经过链接,不能独立运行的目标文件,实在内核空间中运行的程序。经过链接装载到内核里面成为内核的一部分,可以访问内核的公用符号(函数和变量)

2. 内核模块可以让操作系统内核在需要时载入和执行,在不需要的时候由操作系统卸载。它们扩展了操作系统内核的功能却不需要重新启动系统

3. 如果没有内核模块,我们不得不一次又一次重新编译生成单内核操作系统的内核镜像来加入新的功能,这还意味着一个臃肿的内核。

 

l  模块机制的优缺点

优点:1.减小内核映像尺寸,增加系统灵活性

      2.节省开发时间;修改内核,不必重新编译整个内核

      3.模块的目标代码一旦被链入内核,作用和静态链接的内核目标代码完全等价

缺点:1.对系统性能有一定的损失

    2.使用不当会导致系统崩溃

  

Hello world模块

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

 

static int __init hello_init(void)

{

      printk("<1>hello world\n");

      return 0;

}

 

static void __exit hello_exit(void)

{

      printk(KERN_ALERT "hello module exit\n");

}

 

module_init(hello_init);

module_exit(hello_exit);

 

MODULE_LICENSE("GPL");

MODULE_AUTHOR("luzheng");

MODULE_DESCRIPTION("this is my first module");

 

l  __init和__exit宏

1.如果该模块被编译进内核,而不是动态加载,则__init的使用会在模块初始化完成后丢弃该函数并回收所占内存

2.如果该模块被编译进内核,__exit宏将忽略“清理收尾”的函数

3.这些宏在头文件Linux/init.h定义,用来释放内核占用的内存。例如在启动时看到的信息“Freeint unused kernel memory:236k freed”,正是内核释放这些函数所占用空间时的打印信息。

 

l  Makefile

ifneq ($(KERNELRELEASE),)

      obj-m += hello.o

#     obj-m += startstop.o

#     startstop-objs := start.o stop.o      /*把两个文件编译成一个模块 */

else

KERNELDIR ?= /lib/modules/$(shell uname -r)/build  /*定义内核路径*/

PWD := $(shell pwd)      /*表示在当前目录下编译*/

default:

      $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif

clean:

      rm hello.ko hello.mod.c hello.mod.o hello.o Module.symvers -rf

 

l  printk

内核通过 printk() 输出的信息具有日志级别,日志级别是通过在 printk() 输出的字符串前加一个带尖括号的整数来控制的,如 printk("<6>Hello, world!/n");。内核中共提供了八种不同的日志级别,在 linux/kernel.h 中有相应的宏对应。

#define KERN_EMERG    "<0>"    /* system is unusable紧急事件消息,系统崩溃之前提示,表示系统不可用 */
#define KERN_ALERT    "<1>"    /* action must be taken immediately报告消息,表示必须立即采取措施 */
#define KERN_CRIT     "<2>"    /* critical conditions 临界条件,通常涉及严重的硬件或软件操作失败*/
#define KERN_ERR      "<3>"    /* error conditions 错误条件,驱动程序常用这个来报告硬件的错误*/
#define KERN_WARNING  "<4>"    /* warning conditions 警告条件,对可能出现问题的情况进行警告*/
#define KERN_NOTICE   "<5>"    /* normal but significant 正常但又重要的条件,用于提醒*/
#define KERN_INFO     "<6>"    /* informational 提示信息,如驱动程序启动时,打印硬件信息*/
#define KERN_DEBUG    "<7>"    /* debug-level messages 调试级别的消息*/

所以 printk() 可以这样用:printk(KERN_INFO "Hello, world!/n");。

未指定日志级别的 printk() 采用的默认级别是 DEFAULT_MESSAGE_LOGLEVEL,这个宏在 kernel/printk.c 中被定义为整数 4,即对应KERN_WARNING。

在 /proc/sys/kernel/printk 会显示4个数值(可由 echo 修改),分别表示当前控制台日志级别、未明确指定日志级别的默认消息日志级别、最小(最高)允许设置的控制台日志级别、引导时默认的日志级别。当 printk() 中的消息日志级别小于当前控制台日志级别时,printk 的信息(要有/n符)就会在控制台上显示。但无论当前控制台日志级别是何值,通过 /proc/kmsg (或使用dmesg)总能查看。另外如果配置好并运行了 syslogd 或 klogd,没有在控制台上显示的 printk 的信息也会追加到 /var/log/messages.log 中。

char myname[] = "chinacodec/n";
printk(KERN_INFO "Hello, world %s!/n", myname);

 

l  写内核程序需要注意

1. 内核编程时不能访问C库

2. 内核编程时必须使用GUN C

3. 内核编程时缺乏像用户空间那样的内存保护机制

4. 内核编程时浮点数很难使用

5. 内核只有一个很小的定长堆栈

6. 由于内核支持异步中断、抢占和SMP,因此必须时刻注意同步和并发

7. 要考虑可移植性的重要性

 

l  内核模块参数

Linux2.6允许用户insmod的时候往内核模块里面传递参数,它主要使用module_param宏定义来实现这一功能。

module_param的定义可以在include/linux/moduleparam.h文件里面查看到,它的原型为:

module_param(name, type, perm);

module_param_array(name, type, nump, perm);

其中module_param是用来传递变量参数的,module_param_array是用来传递数组参数的。

name是在模块中定义的变量名称,type是变量的类型,perm是权限掩码,用来做一个辅助的sysfs入口。

nump是传入数组的数目,是一个int指针。

 

module_param支持传递的参数类型有:

bool:布尔型
invbool:一个布尔型( true 或者 false)值(相关的变量应当是 int 类型). invbool 类型颠倒了值, 所以真值变成 false, 反之亦然.
charp :一个字符指针值. 内存为用户提供的字串分配, 指针因此设置.
int:整形
long:长整形
short:短整形
uint:无符号整形
ulong:无符号长整形
ushort:无符号短整形
基本的变长整型值. 以 u 开头的是无符号值. 

 

perm 字段是一个权限值,表示此参数在sysfs文件系统中所对应的文件节点的属性。你应当使用 <linux/stat.h> 中定义的值. 这个值控制谁可以存取这些模块参数在 sysfs 中的表示.当perm为0时,表示此参数不存在 sysfs文件系统下对应的文件节点。 否则, 模块被加载后,在/sys/module/ 目录下将出现以此模块名命名的目录, 带有给定的权限.。

权限在include/linux/stat.h中有定义
比如:
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001
使用 S_IRUGO 参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR 允许 root 来改变参数. 注意, 如果一个参数被 sysfs 修改, 你的模块看到的参数值也改变了, 但是你的模块没有任何其他的通知. 你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.

  

下面看一下实验的例子:

#include <linux/init.h>  

#include <linux/module.h>  

#include <linux/moduleparam.h>  

///////////////////////////////////////////////////////////  

MODULE_LICENSE("Dual BSD/GPL");  

char *msg_buf = "Hello world!";  

int n_arr[] = {1,2,3,4,5};  

int n = 7;  

//module_param(n, int, S_IRUSR);  

module_param_array(n_arr, int, &n, S_IRUSR);  

module_param(msg_buf, charp, S_IRUSR);  

///////////////////////////////////////////////////////////  

static __init int hello_init(void)  

{  

    int i;      

    printk("%s/n", msg_buf);  

    for (i=0; i<n; i++)  

    {  

        printk("n_arr[%d]=%d/n", i, n_arr[i]);  

    }  

    return 0;  

}  

///////////////////////////////////////////////////////////  

static __exit void hello_exit(void)  

{  

    printk("Goodbye, kernel!/n");  

}  

module_init(hello_init);  

module_exit(hello_exit); 

运行命令:

 sudo insmod hello.ko msg_buf=veryCD

然后使用dmesg可以查看到printk的输出:

[35983.685059] veryCD

[35983.685067] n_arr[0]=1

[35983.685072] n_arr[1]=2

[35983.685075] n_arr[2]=3

[35983.685079] n_arr[3]=4

[35983.685083] n_arr[4]=5

[35983.685087] n_arr[5]=7

[35983.685091] n_arr[6]=1

可以看出,实现n_arr的长度应该为5,而n为7,驱动里面并没有检测出,n_arr[5],n_arr[6]已经越界了,,,

但是insmod的时候却是会检测n_arr的长度的,,

输入命令:

sudo insmod hello.ko msg_buf=veryCD n_arr=1,2,3,4,5,6

但是提示出错了,:

insmod: error inserting 'hello.ko': -1 Invalid parameters

因为n_arr的数组长度为5,当输入的数组长度小于等于5的时候,insmod可以加载模块成功,

sudo insmod hello.ko msg_buf=veryCD n_arr=1,2,3

[36315.732903] veryCD

[36315.732908] n_arr[0]=1

[36315.732909] n_arr[1]=2

[36315.732911] n_arr[2]=3

可以看出,module_param_array中的nump的值为实际的输入数组参数长度。

然后当使用insmod加载内核模块,并传递数据参数的时候,系统会自动检测数组的长度,当输入的数组长度小于模块的数组长度时,insmod才会成功。

 

l  内核模块静态编译

  1. 将.c文件拷贝到/drivers/char/下
  2. 修改/drivers/char下的Kconfig文件
    在Kconfig中增加如下代码:

 

  1. 回到内核根目录下面make menuconfig,发现已经添加进去

 

编译成模块的话选择<M>或者在下一步写成obj-m += hello.o

  1. 修改/drivers/char下的Makefile文件,增加如下
    obj-$(CONFIG_HELLO)        += hello.o
    当然前提是你的hello.c必须放在当前的目录了
  2. 编译内核

 

posted @ 2015-12-07 09:37  L_free  阅读(221)  评论(0编辑  收藏  举报