实验目的

​ 熟悉内核模块编程和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.hlinux/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.hlinux/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;
}