以前是在ok6410上学习linux的,所以在ubuntu下搭建的环境编译出来的内核模块是交叉编译到ARM平台的,直接在虚拟机上运行不了。所以这里说一下,在虚拟机上编译内核模块,然后安装到虚拟机内核上的方法和过程。
首先,我还是不厌其烦的介绍一些内核模块的基本知识。
一、什么是模块
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。
为了加深对内核模块的了解,表一给出应用程序与内核模块程序的比较。
表一应用程序与内核模块程序的比较
从表一我们可以看出,内核模块程序不能调用libc库中的函数,它运行在内核空间,且只有超级用户可以对其运行。另外,模块程序必须通过module_init()和module-exit()函数来告诉内核“我来了”和“我走了”。
二、编写一个简单的模块
模块和内核都在内核空间运行,模块编程在一定意义上说就是内核编程。因为内核版本的每次变化,其中的某些函数名也会相应地发生变化,因此模块编程与内核版本密切相关。以下例子针对Ubuntu10.04内核3.2.0-23-generic-pae。要提醒读者的是,要看懂此模块必须先了解一下proc文件系统方面的知识,可以查看我的相关博客。
1.示范程序:axp192.c
#include <linux/module.h> #include <linux/proc_fs.h> #include <linux/kernel.h> #include <asm/access.h> #define MAX_BUFFER_SIZE 256 #define AXP192_PROC_FILE "driver/axp192" static struct proc_dir_entry *axp192_proc_file; static int index; static u8 buff[MAX_BUFFER_SIZE]; static void axp192_write(int index, u8 reg_val) { buff[index] = reg_val; } static void axp192_read(int index, u8 *reg_val) { *reg_val = buff[index]; } static ssize_t axp192_proc_read(struct file *filp, char *buffer, size_t length,loff_t *offset) { u8 reg_val; if ((index < 0) || (index >MAX_BUFFER_SIZE)) return 0; axp192_read(index, ®_val); printk(KERN_INFO "register0x%x: 0x%x\n", index, reg_val); return 0; } static ssize_taxp192_proc_write(struct file *filp, const char*buff, size_t len, loff_t *off) { u8 reg_val; char messages[256], vol[256]; if (len > 256) len = 256; if (copy_from_user(messages, buff,len)) return -EFAULT; if ('-' == messages[0]) { /* set the register index */ memcpy(vol,messages+1, len-1); index = (u8) simple_strtoul(vol, NULL, 16); } else { /* set theregister value */ reg_val =(u8)simple_strtoul(messages, NULL, 16); axp192_write(index,reg_val & 0xFF); } return len; } static struct file_operationsaxp192_proc_ops = { .read = axp192_proc_read, .write = axp192_proc_write, }; static voidcreate_axp192_proc_file(void) { printk("setup proc controlinterface\n"); axp192_proc_file = create_proc_entry(AXP192_PROC_FILE,0644, NULL); if (axp192_proc_file) { axp192_proc_file->proc_fops =&axp192_proc_ops; } else printk(KERN_INFO "proc filecreate failed!\n"); } static voidremove_axp192_proc_file(void) { remove_proc_entry(AXP192_PROC_FILE,NULL); } static int __init axp192_init(void) { printk("---AXP192_init\n"); create_axp192_proc_file(); return 0; } static void __exitaxp192_exit(void) { remove_axp192_proc_file(); } module_init(axp192_init); module_exit(axp192_exit); MODULE_DESCRIPTION("Axp192Driver"); MODULE_LICENSE("GPL");
说明
所有模块都要使用头文件module.h,此文件必须包含进来。
头文件kernel.h包含了常用的内核函数。
头文件init.h包含了宏_init和_exit,它们允许释放内核占用的内存。
lkp_init是模块的初始化函数,它必需包含诸如要编译的代码、初始化数据结构等内容。
使用了printk()函数,该函数是由内核定义的,功能与C库中的printf()类似,它把要打印的信息输出到终端或系统日志。
lkp_cleanup是模块的退出和清理函数。此处可以做所有终止该驱动程序时相关的清理工作。
module_init()和cleanup_exit()是模块编程中最基本也是必须的两个函数。
module_init()是驱动程序初始化的入口点。而cleanup_exit()注销由模块提供的所有功能。
2 编写Makefile文件,与axp192.c放在同一个目录里
(注意makefile里面要求的tab)
KDIR:=/lib/modules/3.2.0-23-generic-pae/build是编译内核模块需要的Makefile的路径,Ubuntu 10.04下是
/lib/modules/3.2.0-23-generic-pae /build
make-C$(KDIR) M=$(PWD)modules编译内核模块。
-C将工作目录转到KDIR,调用该目录下的Makefile,并向这个Makefile传递参数M的值是$(PWD)modules。
3.编译模块
#sudo make(调用第一个命令default)
这时,在axp192.c所在文件夹就会有axp192.ko,这个就是我们需要的内核模块啦
#sudo make clean清理编译垃圾,axp192.ko也会清理掉。
4.插入模块,让其工作。注意必须是root权限
生成apx192.ko后,在虚拟机中insmod,成功后在/proc/driver下将会生成名为axp192的文件,改变其属性对其操作。操作过程和结果如图(ubuntu的文字界面模式下):
第一次echo “-0x21”>axp192 ,指定寄存器,由于没有设置值,故默认值为0 所以:register 0x21: 0x0
第二次echo “0x78”>axp192是改变寄存器0x21的值,设为0x78。设置后,观察:register 0x21: 0x78
这就是在Ubuntu虚拟机下一个最简单的模块编写过程。