内核模块编程学习报告
1.编写一个简单的输出信息的模块
源代码:
Makefile
编译模块
加载模块
测试模块(dmesg)
卸载模块
Sudo rmmod 1
使用dmesg查看情况
2.输出当前进程信息
源代码(作为2.c)
修改makefile中的目标文件为2.o
编译,加载并测试模块
3.进程列表读取功能(3.c)
源代码
修改makefile为3.o
编译,加载并测试模块:
4.可读写的文件节点内核模块
在/proc文件系统中创建一个文件节点exp2
源代码
#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);
// global var
static char *str = NULL;
// seq_operations -> show
static int jif_show(struct seq_file *m, void *v)
{
//seq_printf(m, "current kernel time is %llu
", (unsigned long long) get_jiffies_64());
seq_printf(m, "str is %s
", str);
return 0; //!! must be 0, or will show nothing T.T
}
// file_operations -> write
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;
}
// seq_operations -> open
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,
};
// module init
static int __init jif_init(void)
{
struct proc_dir_entry* jif_file;
jif_file = proc_create("exp2", 0, NULL, &jif_fops);
if (NULL == jif_file)
{
return -ENOMEM;
}
return 0;
}
// module exit
static void __exit jif_exit(void)
{
printk("******************************************
");
remove_proc_entry("exp2", NULL);
kfree(str);
}
module_init(jif_init);
module_exit(jif_exit);
MODULE_AUTHOR("why");
MODULE_LICENSE("GPL");
编译,加载模块
往新建立的exp2文件中写入字符串”zhuli”,可见结果:
5.虚拟地址转换模块
源代码:
#include <linux/module.h>
#include <asm/pgtable.h>
#include <linux/version.h>
#include <asm/page.h>
#include <linux/gfp.h>
#include <linux/page-flags.h>
#include <linux/sched.h>//find_task_by_vpid
#include <linux/mm.h>//find_vma
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CONVERT USER VIRTUAL ADDRESS TO PHYADDRESS");
static int pid;
static unsigned long va;
module_param(pid,int,0644);
module_param(va,ulong,0644);
static int find_pgd_init(void)
{
unsigned long pa=0;
struct task_struct *pcb_tmp=NULL;
pgd_t *pgd_tmp=NULL;
pud_t *pud_tmp=NULL;
pmd_t *pmd_tmp=NULL;
pte_t *pte_tmp=NULL;
printk(KERN_ALERT "test:va=0x%lx,pid=%d.
",va,pid);
rcu_read_lock();
if( !( pcb_tmp = pid_task(find_vpid(pid), PIDTYPE_PID) ) )
{
rcu_read_unlock();
printk(KERN_ALERT "Can't find the task %d.
",pid);
return 0;
}
rcu_read_unlock();
printk("The page index_table address = 0x%p
",pcb_tmp->mm->pgd);
printk(KERN_ALERT "pgd=0x%p
",pcb_tmp->mm->pgd);
if(!find_vma(pcb_tmp->mm,va))
{
printk(KERN_ALERT "virt_addr 0x%lx not available.
",va);
return 0;
}
pgd_tmp=pgd_offset(pcb_tmp->mm,va);
printk(KERN_ALERT "pgd_tmp=0x%p
",pgd_tmp);
printk(KERN_ALERT "pgd_val(*pgd_tmp)=0x%lx
",pgd_val(*pgd_tmp));
if(pgd_none(*pgd_tmp))
{
printk(KERN_ALERT "Not mapped in pgd.
");
return 0;
}
pud_tmp=pud_offset(pgd_tmp,va);
pmd_tmp=pmd_offset(pud_tmp,va);
pte_tmp=pte_offset_kernel(pmd_tmp,va);
if(pte_none(*pte_tmp))
{
printk(KERN_ALERT "Not mapped in pte.
");
return 0;
}
if(!pte_present(*pte_tmp))
{
printk(KERN_ALERT "pte not in RAM,maybe swaped.
");
return 0;
}
pa=(pte_val(*pte_tmp)&PAGE_MASK)|(va&~PAGE_MASK);
printk(KERN_ALERT "Virtual address: 0x%lx in RAM is 0x%lx.
",va,pa);
printk(KERN_ALERT "Part content in 0x%lx is 0x%lx.
",pa,*(unsigned long*)((char *)pa+PAGE_OFFSET));
int i;
printk("some content:
");
for(i=0;i<40;i=i+4)
{
printk("%lx
",*(unsigned long*)((char*)pa+PAGE_OFFSET+i));
}
return 0;
}
static void find_pgd_exit(void)
{
printk(KERN_ALERT "Goodbye.
");
}
module_init(find_pgd_init);
module_exit(find_pgd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Why");
MODULE_DESCRIPTION("GET WESSAGE");
源代码中find_pgd_init()为模块初始化函数,find_pgd_exit为模块析构函数。
代码中还定义了所要传入的参数,即所指定进程的进程号pid,数据类型为int,模块参数以module_param(name,type,perm)的形式定义,其中name为参数名,type为参数的数据类型,perm是一个权限值,控制谁可以存取模块参数在sysfs中的表示。此处还要注意的是要对内核模块的初始化函数和析构函数进行声明,即指明哪个函数是内核模块的初始化函数,哪个是内核模块的析构函数。
module_init()指明的是模块初始化函数,module_exit()指明的是模块析构函数。
MODULE LICENSE、MODULE_AUTHOR、MODULE_DESCRIPTION 分别为模块证书、模块作者和模块描述。
选取gedit验证模块
查询当前开启的gedit的pid
查询gedit的虚拟地址
可见此时gedit的pid为5207,虚拟地址的16进制表示为0x805e604,使用计算器转换为10进制为134604292
编译,加载并测试模块,可见:(加载模块时需要做如下操作)
测试结果:
可见,输出序列为53,83,ec ,08…
验证模块功能
6.使用文件节点存储页表查询结果
源代码:
#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
#include <asm/pgtable.h>
#include <linux/version.h>
#include <asm/page.h>
#include <linux/gfp.h>
#include <linux/page-flags.h>
#include <linux/sched.h>//find_task_by_vpid
#include <linux/mm.h>//find_vma
static unsigned long pid=5263;
static unsigned long va=134604292;
int i=0;
unsigned long pa = 0;
//static struct task_struct *pcurrent;
int print_current_task_info(void);
static int jif_show(struct seq_file *m, void *v);
// global var
//static char *str = NULL;
// file_operations -> write
static ssize_t jif_write(struct file *file, const char __user *buffer, size_t count, loff_t *f_pos)
{
unsigned long *tmp = kzalloc((count+1), GFP_KERNEL);
if (!tmp)
return -ENOMEM;
//copy_to|from_user(to,from,cnt)
if (copy_from_user(tmp, buffer, count)) {
kfree(tmp);
return -EFAULT;
}
kfree(tmp);
//pid = *tmp;
return count;
}
// seq_operations -> open
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,
};
// module init
static int __init jif_init(void)
{
struct proc_dir_entry* jif_file;
jif_file = proc_create("exp4", 0, NULL, &jif_fops);
if (NULL == jif_file)
{
return -ENOMEM;
}
return 0;
}
// module exit
static void __exit jif_exit(void)
{
printk("******************************************
");
remove_proc_entry("exp4", NULL);
kfree(pid);
}
static int find_pgd_init(struct seq_file *m)
{
struct task_struct *pcb_tmp = NULL;
pgd_t *pgd_tmp = NULL;
pte_t *pte_tmp = NULL;
pud_t *pud_tmp = NULL;
pmd_t *pmd_tmp = NULL;
seq_printf(m,"test:va=0x%lx,pid=%ld.
",va,pid);
rcu_read_lock();
if(!(pcb_tmp = pid_task(find_vpid(pid),PIDTYPE_PID)))
{
seq_printf(m,"can not find pro %ld .
", pid);
return 0;
}
rcu_read_unlock();
seq_printf(m,"ye_mu_lu_biao_ji_zhi = 0x%p
",pcb_tmp->mm->pgd);
if(!find_vma(pcb_tmp->mm,va))
{
seq_printf(m,"xu_ni_di_zhi%lxbu_cun_zai
",va);
return 0;
}
pgd_tmp=pgd_offset(pcb_tmp->mm,va);
seq_printf(m,"
pgd=----0x%p
",pgd_tmp);
pud_tmp=pud_offset(pgd_tmp,va);
pmd_tmp=pmd_offset(pud_tmp,va);
pte_tmp=pte_offset_kernel(pmd_tmp,va);
seq_printf(m,"
pte=----0x%p
",pte_tmp);
seq_printf(m,"
pid=%ld
va=%lx
",pid,va);
pa=(pte_val(*pte_tmp)&PAGE_MASK)|(va&~PAGE_MASK);
seq_printf(m, "xu_ni_di_zhi %lx qi_zai_nei_cun_zhong %lx.
",va,pa);
seq_printf(m, "bu_fen_nei_rong 0x%lx is 0x%lx.
.
",pa,pa);
for(i=0;i<40;i=i+4)
seq_printf(m," %lx
",*(unsigned long*)((char*)pa+PAGE_OFFSET+i));
return 0;
}
// seq_operations -> show
static int jif_show(struct seq_file *m, void *v)
{
//seq_printf(m, "current kernel time is %llu
", (unsigned long long) get_jiffies_64());
//seq_printf(m, "str is %s
", str);
find_pgd_init(m);
return 0; //!! must be 0, or will show nothing T.T
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ny");
MODULE_DESCRIPTION("GET WESSAGE");
module_init(jif_init);
module_exit(jif_exit);
其中参数pid和va在一开始写入源代码。
编译,加载模块后,直接显示创建的文件exp4内容,可见:
模块功能实现