Linux内核模块开发

转载 2012年03月23日 17:48:20

笔记

调试方法

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

kernel oops messages

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

内核模块简单介绍

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

最简单的内核模块

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

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
  
/* __init 的标记是内核模块的入口,这个函数加载完后就会释放内存空间  */
static int __init hello_init(void)
{
        printk(KERN_INFO "Hello world"); /* 打印的信息会出现在 dmesg 中 释放*/
        return 0;  /*  返回 0 是正常 */
}
  
/* __exit 的标记是退出内核模块,当这个模块卸载时会执行 */
static void __exit hello_exit(void)
{
        printk(KERN_INFO "Goodbye world");
}
  
/* 下面这二个是宏,初始化和消除函数时使用和上面的装载卸载模块没关系。 */
module_init(hello_init);
module_exit(hello_exit);

放个编译上面模块的 Makefile

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 文件。

模块加载参数

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

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

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

模块的信息

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

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Test");
MODULE_AUTHOR("xxx");

模块的符号导出

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

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
例如

cat /proc/cpuinfo

echo fukei-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 的定义。
实现起来也简单。

struct proc_dir_entry *proc_entry = create_proc_entery(....);
int my_read_proc()
{
}
proc_entry->read_proc = my_read_proc();

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

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

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/mm.h>
  
#define MODULE_NAME "Memory"
  
int my_read_proc(char *page,char **start, off_t off,int count,  int *eof,void *data)
{
        struct task_struct *tsk = current;
        int len;
        len = sprintf( page, "This module info: task %s pid %d\n",tsk->comm, tsk->pid );
        return  len;
}
  
struct proc_dir_entry *proc_entry;
int init_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;
}
  
void cleanup_module(void)
{
        remove_proc_entry(MODULE_NAME, NULL); // 退出和出错记的删除
}
  
MODULE_LICENSE("GPL");

Linux-内核模块开发

Linux内核线程之深入浅出:http://blog.163.com/jiams_wang/blog/static/303391492012103010374038/ Linux内核多线程(一):ht...
  • sty23122555
  • sty23122555
  • 2016年05月22日 22:25
  • 868

Linux实验二:Linux 内核模块测试

一、实验目的 对Linux内核模块进行了解。 二、实验内容 创建一个内核模块,在加载和卸载模块时在内核中打印相关信息。 三、背景知识: 一、什么是内核模块?        内核模块是L...
  • longteng1116
  • longteng1116
  • 2013年04月10日 16:31
  • 2790

Linux内核模块编程-HelloWorld

HelloWorld内核开始断断续续学习内核,大概半年了,多少开始对内核有点感悟了,但是对于这个庞然大物我显得很渺小,在枯燥的内核源码之中似乎没有一点点成功的喜悦,因此我选择学习内核模块编程,通过编写...
  • zhangyifei216
  • zhangyifei216
  • 2015年11月07日 17:00
  • 2103

linux ubuntu编写内核模块并添加

linux ubuntu编写内核模块并添加
  • haimianxiaodao
  • haimianxiaodao
  • 2016年04月19日 20:37
  • 1296

Linux内核模块的编译基础知识

关于linux内核驱动的东西网络上有很多,但网上的东西还是感觉有点笼统,读过之后就忘了,还是需要写下来,或者写到本子上,自己形成一个概念好一些。读了这本书上的东西,把觉得好的东西写下来,已备不时之用,...
  • shanzhizi
  • shanzhizi
  • 2013年03月01日 15:31
  • 3863

【Linux开发】编写属于你的第一个Linux内核模块

曾经多少次想要在内核游荡?曾经多少次茫然不知方向?你不要再对着它迷惘,让我们指引你走向前方…… 内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了。Linux内核和它的用户空...
  • LG1259156776
  • LG1259156776
  • 2016年05月15日 20:54
  • 1945

Linux内核模块(驱动)编译详解

本文主要说说如何编译自己开发的内核模块。由于驱动通常也被编译成内核模块,因此文章的内容也适用于驱动的编译。 由于在下能力相当有限,有不当之处,还望大家批评指正^_^ 一、准备工作 ...
  • crazycoder8848
  • crazycoder8848
  • 2015年03月08日 15:50
  • 11086

Linux内核模块:模块参数

Linux内核模块:模块参数
  • bryan_3099
  • bryan_3099
  • 2015年07月06日 15:06
  • 399

内核模块开发基础

说明:只供学习交流   一,什么是内核模块 Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用需要的组件呢: 方法一:把所有的组件都编译进内核文件,即:zImage或bzImage,但这...
  • tanghui19900420
  • tanghui19900420
  • 2013年07月18日 09:39
  • 1130

linux内核模块参数

在装载内核模块时,用户可以向模块传递参数,形式为: insmod/modprobe 模块名 参数名 =   参数值 如果不传递,参数将使用模块内定义的缺省值。      我们可以使用以下方法为模...
  • weicao1990
  • weicao1990
  • 2014年07月30日 14:30
  • 426
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux内核模块开发
举报原因:
原因补充:

(最多只允许输入30个字)