内核模块实践实验报告

内核模块编程学习报告

一、内核模块的概念

    内核模块是Linux内核向外部提供的一个插口,其全称为动态可加载内核模块(Loadable Kernel Module,LKM),简称为模块。Linux内核之所以提供模块机制,是因为它本身是一个单内核(monolithic kernel)。单内核的最大优点是效率高,因为所有的内容都集成在一起,但其缺点是可扩展性和可维护性相对较差,模块机制就是为了弥补这一缺陷。
    模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。
    总之,模块是一个为内核(从某种意义上来说,内核也是一个模块)或其他内核模块提供使用功能的代码块。Linux模块可以通过静态或动态地加载到内核空间,静态加载是指在内核启动过程中加载;动态加载是指在内核运行的过程中随时加载。
    一个模块被加载到内核中时,它就成为内核代码的一部分,与其他内核代码地位是一样的。模块加载如系统时,系统修改内核中的符号表,将新加载的模块提供的资源和符号加到内核符号表中,这样使模块间可进行通信。

二、内核模块的基本结构

1)  模块加载函数(一般需要)
    在用insmod或modprobe命令加载模块时,该函数被执行。完成模块的初始化工作。
    Linux内核的模块加载函数一般用__init标识声明,模块加载函数必须以module_init(函数名)的形式被指定。该函数返回整型值,如果执行成功,则返回0,初始化失败时则返回错误编码,Linux内核当中的错误编码是负值,在<linux/errno.h>中定义。
    在Linux中,标识__init的函数在连接时放在.init.text这个区段,而且在.initcall.init中保留一份函数指针,初始化的时候内核会根据这些指针调用初始化函数,初始化结束后释放这些init区段(包括前两者)。
2)  模块卸载函数(一般需要)
在用rmmod或modprobe命令卸载模块时,该函数被执行。完成与加载相反的工作。
模块的卸载函数和模块加载函数实现相反的功能,主要包括:
①若模块加载函数注册了XXX,则模块卸载函数注销XXX
②若模块加载函数动态分配了内存,则模块卸载函数释放这些内存
③若模块加载函数申请了硬件资源,则模块卸载函数释放这些硬件资源
④若模块加载函数开启了硬件资源,则模块卸载函数一定要关闭这些资源
3)  模块许可证声明(必须)
如果不声明,则在模块加载时会收到内核被污染的警告,一般应遵循GPL协议。
4)  模块参数(可选)
模块在被加载时传递给模块的值,本身应该是模块内部的全局变量。
5)  模块导出符号(可选)
使用模块导出符号,方便其它模块依赖于该模块,并使用模块中的变量和函数等。
在Linux2.6的内核中,/proc/kallsyms文件对应着符号表,它记录了符号和符号对应的内存地址。对于模块而言,使用下面的宏可以导出符号。
6)  模块信息(可选)
模块信息则是指模块的作者信息等。

三、编写内核模块的基本步骤

  • 1.根据自己的需求编写内核模块源代码
  • 2.将源代码进行编译,生成.ko文件。在编译内核模块时需要用到Makefile,如下:
    obj-m :=文件名.o
    PWD := $(shell pwd)
    KDIR:=/lib/modules/3.0.0-17-generic/build
    all:
    make -C $(KDIR) M=$(PWD) modules
    clean:
    make -C $(KDIR) M=$(PWD) clean
    • obj-m:这个变量是指定要编译的模块
    • KDIR:这是我们正在运行的操作系统内核编译目录,也就是编译模块需要的环境
    • PWD:这是当前工作路径,$(shell )是make的一个内置函数,用来执行shell命令
  • 注意:要将Makefile文件与四个内核模块源代码放在同一个文件夹中。

四、内核模块编程

  • 1、最简单的内核模块——输出姓名与学号的myname.c
    ①首先编写输出姓名与学号的module1.c文件。代码如下:
    #include<linux/init.h>
    #include<linux/module.h>
    MODULE_LICENSE("Dual BSD/GPL"); //声明许可
    static char *name="wangjianqiao";
    static int num=20135316;
    static int __init name_init(void)
    {
    printk(KERN_ALERT "name :%s\n",name); //输出姓名
    printk(KERN_ALERT "num :%d\n",num); //输出学号
    return 0;
    }
    static void __exit name_exit(void)
    {
    printk(KERN_INFO"Name module exit\n");
    }
    module_init(name_init);
    module_exit(name_exit);
    module_param(num,int,S_IRUGO); //可传入参数给num
    module_param(name,charp,S_IRUGO); //可传入参数给name
    //作者等信息声明
    MODULE_AUTHOR("wangjianqiao");
    MODULE_VERSION("v1.0");
    MODULE_DESCRIPTION("A simple module for testing printk and module params");
    ②编写Makefile文件,代码如下:
    obj-m :=module1.o
    PWD := $(shell pwd)
    KDIR:=/lib/modules/3.0.0-17-generic/build
    all:
    make -C $(KDIR) M=$(PWD) modules
    clean:
    make -C $(KDIR) M=$(PWD) clean
    ③make之后生成.ko文件
    ④使用命令:sudo insmod module1.ko加载模块。

  • 2、显示进程信息的模块设计。
    ①首先编写输出进程信息的module2.c文件,代码如下:
    #include<linux/kernel.h>
    #include<linux/module.h>
    #include<linux/init.h>
    #include<linux/sched.h>
    static struct task_struck *pcurrent;
    int print_current_task_info(void);
    static int __init print_init(void)
    {
    printk(KERN_INFO "prit current task info\n");
    print_current_task_info();
    return 0;
    }
    static void __exit print_exit(void)
    {
    printk(KERN_INFO "Finished\n");
    }
    int print_current_task_info(void)
    {
    pcurrent = get_current();
    printk(KERN_INFO "Task state: %ld\n",current->state);
    printk(KERN_INFO "pid : %d\n",current->pid);
    printk(KERN_INFO "tgid : %d\n",current->tgid);
    printk(KERN_INFO "prio : %d\n",current->prio);
    return 0;
    }
    module_init(print_init);
    module_exit(print_exit);
    ②编写Makefile文件,代码如下:
    obj-m :=module2.o
    PWD := $(shell pwd)
    KDIR:=/lib/modules/3.0.0-17-generic/build
    all:
    make -C $(KDIR) M=$(PWD) modules
    clean:
    make -C $(KDIR) M=$(PWD) clean
    ③make之后生成.ko文件
    ④使用命令:sudo insmod module2.ko加载模块。

  • 3、proc文件模块
    ①首先编写输出进程信息的module3.c文件,代码如下:
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/fs.h> // for basic filesystem
    #include <linux/proc_fs.h> // for the proc filesystem
    #include <linux/seq_file.h> // for sequence files
    #include <linux/jiffies.h> // for jiffies
    #include <linux/slab.h> // for kzalloc, kfree
    #include <linux/uaccess.h> // for copy_from_user
    #define BUF_SIZE 128
    static char *str = NULL;
    static int jif_show(struct seq_file *m, void *v)
    {
    char buf[BUF_SIZE];
    int ret = 0;
    ret = sprintf(buf, "current kernel time is %llu\n", (unsigned long long) get_jiffies_64());
    ret += sprintf(buf + ret, "str is %s\n", str);
    seq_printf(m, "%s", buf);
    return 0; //!! must be 0, or will show nothing T.T
    }
    static ssize_t jif_write(struct file *file, const char __user *buffer, size_t count, loff_t *f_pos)
    {
    //分配临时缓冲区
    char *tmp = kzalloc((count+1), GFP_KERNEL);
    if (!tmp)
    return -ENOMEM;
    //将用户态write的字符串拷贝到内核空间
    //copy_to|from_user(to,from,cnt)
    if (copy_from_user(tmp, buffer, count)) {
    kfree(tmp);
    return -EFAULT;
    }
    //将str的旧空间释放,然后将tmp赋值给str
    kfree(str);
    str = tmp;
    return count;
    }
    static int jif_open(struct inode *inode, struct file file)
    {
    return single_open(file, jif_show, NULL);
    }
    static const struct file_operations jif_fops =
    {
    .owner = THIS_MODULE,
    .open = jif_open,
    .read = seq_read,
    .write = jif_write,
    .llseek = seq_lseek,
    .release = single_release,
    };
    static int __init jif_init(void)
    {
    struct proc_dir_entry
    jif_file;

      	jif_file = proc_create("jif", 0, NULL, &jif_fops);
      	if (NULL == jif_file)
      	{
      	    return -ENOMEM;
      	}
      	return 0;
      }
      static void __exit jif_exit(void)
      {
      	remove_proc_entry("jif", NULL);
      	kfree(str);
      }
      module_init(jif_init);
      module_exit(jif_exit);
      MODULE_AUTHOR("aran");
      MODULE_LICENSE("GPL");
      ②编写Makefile文件,代码如下:
          obj-m :=module3.o
          PWD := $(shell pwd)
          KDIR:=/lib/modules/3.0.0-17-generic/build
          all:
          make -C $(KDIR) M=$(PWD) modules
          clean:
          make -C $(KDIR) M=$(PWD) clean
      ③make之后生成.ko文件
      ④使用命令:sudo insmod module3.ko加载模块。
    
  • 4、proc文件与进程信息模块结合
    ①首先编写输出进程信息的module4.c文件,代码如下:
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/kernel.h>
    #include <linux/fs.h> // for basic filesystem
    #include <linux/proc_fs.h> // for the proc filesystem
    #include <linux/seq_file.h> // for sequence files
    #include <linux/jiffies.h> // for jiffies
    #include <linux/slab.h> // for kzalloc, kfree
    #include <linux/uaccess.h> // for copy_from_user
    static struct task_struct *pcurrent;
    int print_current_task_info(void);
    static char *str = NULL;
    static int jif_show(struct seq_file *m, void *v)
    {
    seq_printf(m, "current kernel time is %llu\n", (unsigned long long) get_jiffies_64());
    seq_printf(m,KERN_INFO "ORINT CURRENT TASK INFO\n");
    seq_printf(m,"pid\ttgid\tprio\tstate\n");
    for_each_process(pcurrent)
    {
    seq_printf(m,"%d\t",pcurrent->pid);
    seq_printf(m,"%d\t",pcurrent->tgid);
    seq_printf(m,"%d\t",pcurrent->prio);
    seq_printf(m,"%ld\n",pcurrent->state);
    }
    seq_printf(m, "str is %s\n", str);
    return 0;
    }
    static ssize_t jif_write(struct file *file, const char __user *buffer, size_t count, loff_t *f_pos)
    {
    char *tmp = kzalloc((count+1), GFP_KERNEL);
    if (!tmp)
    return -ENOMEM;
    //将用户态write的字符串拷贝到内核空间
    //copy_to|from_user(to,from,cnt)
    if (copy_from_user(tmp, buffer, count)) {
    kfree(tmp);
    return -EFAULT;
    }
    //将str的旧空间释放,然后将tmp赋值给str
    kfree(str);
    str = tmp;
    return count;
    }
    static int jif_open(struct inode *inode, struct file file)
    {
    return single_open(file, jif_show, NULL);
    }
    static const struct file_operations jif_fops =
    {
    .owner = THIS_MODULE,
    .open = jif_open,
    .read = seq_read,
    .write = jif_write,
    .llseek = seq_lseek,
    .release = single_release,
    };
    static int __init jif_init(void)
    {
    struct proc_dir_entry
    jif_file;

          jif_file = proc_create("test", 0, NULL, &jif_fops);  
          if (NULL == jif_file)  
          {  
              return -ENOMEM;  
          }  
        
          return 0;  
      }   
      static void __exit jif_exit(void)  
      { 
      printk("******************************************\n");
          remove_proc_entry("test", NULL);  
          kfree(str);
      printk(KERN_INFO"Finished\n");
      }  
      module_init(jif_init);  
      module_exit(jif_exit);  
      MODULE_AUTHOR("wangjianqiao");
      MODULE_LICENSE("GPL");  
      ②编写Makefile文件,代码如下:
          obj-m :=module4.o
          PWD := $(shell pwd)
          KDIR:=/lib/modules/3.0.0-17-generic/build
          all:
          make -C $(KDIR) M=$(PWD) modules
          clean:
          make -C $(KDIR) M=$(PWD) clean
      ③make之后生成.ko文件
      ④使用命令:sudo insmod module4.ko加载模块。
    
posted @ 2016-05-23 22:07  20135316王剑桥  阅读(759)  评论(0编辑  收藏  举报