内核模块实践实验报告
内核模块编程学习报告
一、内核模块的概念
内核模块是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加载模块。