内核模块开发(笔记)

来源:互联网 发布:pptv聚力网络电视 编辑:程序博客网 时间:2024/05/08 14:01
个人笔记..在不放过来都快找不到了.有空还得好好整理一下了.

调试方法

printk() 是用来调试内核最常用的一种技术,他打印的信息会输出在 dmesg 中,所以调试前最好使用 dmesg -c 来清掉以前 dmesg 的信息. 使用的例子如下:
printk(KERN_DEBUG "Here i am:%s:%d\n",FUNCTION, LINE);
可以打印的级别可以看看 linux/kernel.h 中的定义. strace 这个命令超级强大,可以显示程序所有的系统调用,还可以显示调用时使用的参数. 但这个时候不需要麻烦的配置就可以直接使用,但不能象 gdb 调试 c 程序一样,所以内核为我们提供了一个 kdb ,可以支持动态修改变量,断点设置,单步执行

kernel oops messages

这是内核开发时常会出现的一个错误信息.主要原因是由于 NULL 指针引用,和其它不正常的指针操作引起的.这时 oops 会显示故障时的处理器信息, 模块 CPU 寄存器内容,页描述符表的位置之类的信息.

内核模块简单介绍

模块是工作在内核空间的 模块实际是目标文件(由函数和数据结构组成),不象普通程序有个链接的过程,不能独立运行,只能在运行时链接到系统做为内核的一部分运行,从面扩展内核功能 内核模块会占用内核空间的内存,所以会影响内存使用,它还会修改内核中的一些内容,所以容易造成系统挂掉.在内核中需要维护符号表.并且内核之间有依赖性.

最简单的内核模块

注:如果是 redhat 安装的话,需要安装 kernel-devel 才能写内核模块,如果是自己编译内核,记的不要删除源码,不然没法开发模块.

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
 
/* __init 的标记是内核模块的入口,这个函数加载完后就会释放内存空间  */
staticint __init hello_init(void)
{
        printk(KERN_INFO"Hello world"); /* 打印的信息会出现在 dmesg 中 释放*/
        return0;  /*  返回 0 是正常 */
}
 
/* __exit 的标记是退出内核模块,当这个模块卸载时会执行 */
staticvoid __exit hello_exit(void)
{
        printk(KERN_INFO"Goodbye world");
}
 
/* 下面这二个是宏,初始化和消除函数时使用和上面的装载卸载模块没关系. */
module_init(hello_init);
module_exit(hello_exit);

放个编译上面模块的 Makefile

?
1
2
3
4
5
obj-m := hello.o
all:
        make-C /lib/modules/$(shell uname-r)/build M=$(shell pwd) modules
clean:
        make-C /lib/modules/$(shell uname-r)/build M=$(shell pwd) clean

给 Makefile 放到上面 hello.c 的相同的目录中(如果上面写的模块代码叫 hello.c 的话).然后使用 make 就能编译了.

insmod lsmod rmmod

调用 insmod 时会给需要的模块加载进内核,会给 ko 的文件以目标代码加载.装载时会调用 module_init 指定的函数.退出也调用相应的 module_exit.

lsmod 可以显示你写的模块,其实是读 /proc/modules .接下来我写写怎么样自己通过内核来建 proc 文件.

模块加载参数

如果在模块加载时,想指定参数,也提供了相应的头文件

?
1
2
3
#include <linux/moduleparam.h>
static int test;
module_param(test, int, 0644);

这样以后,直接在内核模块内使用 test 的变量就行了.

模块的信息

在程序中可以为模块加一些描述,发行版权声明,和作者.

 
1
2
3
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Test");
MODULE_AUTHOR("xxx");

模块的符号导出

在 Perl 中,模块是可以导出变量和方法到其它的模块中的.在 Linux 内核中也有这样的方法.

 
1
2
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);

这二个可以导出指定的全局变量,也可以是方法.这个要加载 <linux/module.h> 的头文件,不要忘记了.
其它的模块要使用这个,直接使用 extern void name(void); 就可以使用了.这些导出的函数只能内核和内核模块使用.不能用户调用,可以由 /proc/kallsyms 来查看导出的变量和方法

实例

写个内核模块,通过 proc 可以见到一些信息,通过 proc 的读和写的功能.来实现设置和读取信息.

proc 介绍

proc 是一个非常方便的用来动态的向 Linux 内核加入和禁用代码的一个方法.
proc/sys 中是用来配置内核的参数,可以通过 sysctl -w key=value
象普通文件可以支持 open,read,write,close
例如

 
1
cat /proc/cpuinfo

 
1
echofukei-name > /proc/sys/kernel/hostname

proc 的功能实现
proc 在 c 中是一个结构体来实现的,是 struct proc_dir_entry .它可以给读写绑定到特定的函数上.然后通过别人对 proc 中文件的操作来触发和回调相应的绑定的函数.
read_proc 和 write_proc 是这个结构体的成员,也是一种结构体.函数就注册在这个上面.有兴趣的同学可以看看 include/linux/proc_fs.h 中的 read_proc_t 和 write_proc_t 的定义.
实现起来也简单.

 
1
2
3
4
5
structproc_dir_entry *proc_entry = create_proc_entery(....);
intmy_read_proc()
{
}
proc_entry->read_proc = my_read_proc();

在这的 create_proc_entery 会返回一个 proc_dir_entry 的结构体的引用.失败就是 NULL .

这样,当用户空间进行 read 的系统调用时,如使用 cat proc 中的内容时.内核会调用注册到 read_proc 上的这个 my_read_proc 来实现的.

 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/mm.h>
 
#define MODULE_NAME "Memory"
 
intmy_read_proc(char*page,char **start, off_t off,int count,  int *eof,void*data)
{
        structtask_struct *tsk = current;
        intlen;
        len =sprintf( page,"This module info: task %s pid %d\n",tsk->comm, tsk->pid );
        return len;
}
 
 
structproc_dir_entry *proc_entry;
intinit_module(void)
{
        proc_entry= create_proc_entry(MODULE_NAME, 0644, NULL);
        if(proc_entry==NULL){
                remove_proc_entry(MODULE_NAME, NULL);
        }
        proc_entry->read_proc = my_read_proc;
        return 0;
}
 
 
 
voidcleanup_module(void)
{
        remove_proc_entry(MODULE_NAME, NULL);// 退出和出错记的删除
}
 
MODULE_LICENSE("GPL");
0 0
原创粉丝点击