内核模块编程
实验目的
熟悉内核模块编程和proc
文件系统的编程。
内核模块命令
- 插入模块:
insmod hello.ko
- 移除模块:
rmmod hello
- 列出模块:
lsmod
- 查看模块信息:
modinfo hello.ko
- 插入模块,并自动处理存在依赖关系的模块:
modprobe hello.ko
模块的加载和移除
引入3个基本的头文件,其中的kernel.h
中包含了打印函数printk()
等基本函数原型。接着module.h
的作用是动态地将模块加载到内核中,init.h
包含了模块的初始化的宏定义和函数的初始化函数。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
可以通过如下代码进行加载和移除模块。
module_init(hello_init);
module_exit(hello_exit);
在两个函数在linux/init.h
中定义,用于定义了内核模块的出入口。函数体如下:
static int __init hello_init(void){
printk(KERN_INFO "Hello Linux Module...\n");
return 0;
}
static void __exit hello_exit(void){
printk(KERN_INFO "Bye.\n");
}
最后某块需要对于一些作者信息,许可证的说明。
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Module1");
MODULE_AUTHOR("Porterlu");
模块参数传递
接着加入两个头文件linux/moduleparam.h
和linux/string.h
用于参数传递和字符串处理。
static int int_var = -9999;
static char *str_var = "Default";
static int int_array[10];
int arrNum;
module_param(int_var, int, 0644);
MODULE_PARM_DESC(int_var, "An integer variable");
module_param(str_var, charp, 0644);
MODULE_PARM_DESC(str_var, "A string variable");
module_param_array(int_array, int, &arrNum, 0644);
MODULE_PARM_DESC(int_array, "An integer array");
参数说明完毕后,进行函数编写:
static int __init hello_init(void){
int i;
if(int_var == -9999 && strcmp(str_var, "Default") == 0 && arrNum == 0){
printk(KERN_INFO "No parameters input, exit.\n");
return 0;
}
printk(KERN_INFO "The parameters are:\n");
if(int_var != -9999){
printk(KERN_INFO "Int: %d\n", int_var);
}
if(strcmp(str_var, "Default") != 0){
printk(KERN_INFO "Str: %s\n", str_var);
}
if(arrNum != 0){
for(i = 0; i < arrNum; i++){
printk(KERN_INFO "Int_array[%d]: %d\n", i, int_array[i]);
}
}
}
编译成功后,使用如下的命令进行测试
insmod module.ko int_var = 123 str_var=hello int_array=1,2,3,4,5
只读文件
需要创建proc
文件,读取文件内容,返回信息。
<linux/proc_fs.h>
, 包含了一些proc
文件系统的读写、创建函数。<linux/seq_file.h>
, 包含了seq_read
,seq_lseek
等顺序文件处理函数。linux/sched.h>
,任务调度相关,包含了系统时间的全局变量。
static int __init hello_proc_init(void) {
printk(KERN_INFO "Test for modules...\n");
proc_create("hello module", 0444, NULL, &hello_proc_fops);
return 0;
}
在入口函数中,我们注册了一个proc
伪文件。其中hello_proc_fops
是一个结构体。
static const struct proc_ops hello_proc_fops = {
.proc_open = hello_proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
除了proc_open
,全是已经封装好的函数。接下来我们的hello_proc_open
如下:
static int hello_proc_open(struct inode *inode, struct file *file){
return single_open(file, hello_proc_show, NULL);
}
在open
函数中调用了single_open
,之后会调用hello_proc_show
static int hello_proc_show(struct seq_file *m, void *v){
seq_printf(m, "Current kernel time is %ld\n", jiffies);
}
读写文件
在上面模块的基础上,还要加入linux/slab.h
和linux/uaccess.h
,分别用于内存分配和copy_from_user
函数的引入。
这里初始化添加了目录的创建和删除,要先进行目录的创建:
static int __init hello_init(void){
printk(KERN_INFO "Test for module...\n");
helloDir = proc_mkdir("helloDir", NULL);
if(!helloDir){
return -ENOMEM;
}
hello = proc_create("hello", 0644, HelloDir, &hello_proc_fops);
}
static void __exit hello_exit(void){
remove_proc_entry("hello", helloDir);
remove_proc_entry("helloDir", NULL);
}
下面是hello_proc_show
和上一板块中讲的一样,它将由single_open
进行回调,输出我们write
的信息。
static char *message = NULL;
struct proc_dir_entry *helloDir, *hello;
static int hello_proc_show(struct seq_file *m, void *v){
seq_printf(m, "Successfully read content from proc file!\n");
seq_printf(m, "The message is:%s\n", message);
return 0;
}
write
使用copy_from_user
将用户的数据拷贝到了内核的数组中进行输出。
static ssize_t hello_proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *f_pos){
char *userBuffer = kzalloc((count+1), GFP_KERNEL);
if(!userBuffer){
return -ENOMEM;
}
if(copy_from_user(userBuffer, buffer, count)){
kfree(userBuffer);
return EFAULT;
}
kfree(message);
message = userBuffer;
return count;
}