LINUX实践之模块
模块实践
--关于模块代码部分
---首先是.c代码:
一定会用到的函数有这几个:module_init()、module_exit()、MODULE_LICENSE()
会用到的头文件:module.h、kernel.h、init.h
---Makefile代码:
有几个地方需要注意的:
obj-m :=test.o
这里的.o文件的命名,是以你.c文件同名,编译为相应的.o文件
all:
make -C $(KDIR) M=$(PWD) modules
KDIR: 正在运行的操作系统内核编译目录。也就是编译模块需要的环境M= : 指定源文件的位置。
PWD : 当前工作路径$(shell )是make 的一个内置函数。用来执行shell 命令。
这里的路径,需要自己到usr/src/目录下,ls一下,查看自己的内核源码版本号
温馨提示:记得把.c文件和Makefile放在同一个目录下。
--关于模块编译过程
make
make之后,第一次一般都会出现很多错误或者warning(就算没错的话,也会有一些warning)
一般是自己的.c文件内容输入有错,要么就是Makefile文件里路径没有输入成功或者代码敲错。
改正后再次make,第二次make相同文件后,就不会出现warning的提示了。
之后ls一下,看看目录下是否产生了.ko模块,如果有的话,就表明你编译成功。
insmod
插入模块:insmod 模块名.ko
注意一个问题:普通用户是没有插入模块权限的,解决方法有两种,要么是sudo insmod xx.ko,要么就是直接sudo su root 切换到root权限插入模块。
dmesg
查看模块内核信息
rmmod
卸载模块:rmmod 模块名
注意的细节同插入模块
--页表模块
原理及工作机制聂影学长的实践报告中以及描述得很详细,我就说说我在实践中碰到的问题及如何解决的吧。
问题1:
第一次make之后,显示没有找到访问的内核源码地址;
解决:考虑后觉得应该是我KDIR的路径没有写对,应该要按我自己的目录下,即/usr/src/····
问题2:
第二次make后,编译0个模块;
解决:怀疑是Makefile文件有问题,果不其然,第一行就打错,ifneq ($(KERNELRELEASE),),因此就没有编译模块成功。
问题3:
make编译成功后,输入ps -ef |grep gredit 没有找到我模块的进程描述符
解决:我在用gedit打开.c文件后,就随手关闭了。后来我另外开启了一个命令行,再次查看进程描述符,这次就找到了。(其实是个很白痴的问题)
问题4:
通过入口地址插入模块失败
解决:原因是我之前重启了一次gedit查看代码,因此进程pid改变了,而我依旧用的是原来的那个,而后面的va(即虚拟地址)是正确的。虚拟地址不会改变,进程pid会变。
问题5:
在查看dmesg时,后面都是0;

问题还待解决中。。。。
我觉得应该是代码的问题,后来我又重新找了个页表的模块代码。
代码部分内容如下:
static void mtest_dump_vma_list(void)
{
struct task_struct *pi=current;//建立task_struct类型的指针pi,并赋初值为当前进程
struct mm_struct *mm = pi->mm;//将当前进程的虚拟地址空间赋值给mm
struct vm_area_struct *vma;
printk("the current process is %s\n",pi->comm);
printk("mtest_dump_vma_list\n");
down_read(&mm->mmap_sem);//对信号量进行p操作
for(vma=mm->mmap;vma;vma=vma->vm_next){//给vma赋初值为指向线性区对象的聊标头,并对VMA链表进行遍历
printk("vma from 0x%lx-0x%lx",vma->vm_start,vma->vm_end);//输出每段的开始地址和结束地址
if(vma->vm_flags&VM_WRITE)
printk("WRITE");
if(vma->vm_flags&VM_READ)
printk("READ");
if(vma->vm_flags&VM_EXEC)
printk("EXEC");
printk("\n");
}//将每段的权限flags与“写”“读”“执行”进行and操作,当其满足条件时,输出相应的单词。
up_read(&mm->mmap_sem);//对信号量进行v操作
}
static void mtest_find_vma(unsigned long addr)
{
struct vm_area_struct *vma;
struct mm_struct *mm=current->mm;//将当前进程的虚拟地址空间赋给mm
printk("mtest_find_vma\n");
down_read(&mm->mmap_sem);//对信号量进行p操作
vma=find_vma(mm,addr);//通过find_vma函数在mm中找到,第一个尾地址大于addr的段
if(vma&&addr>=vma->vm_start){ //若该段的首地址小于addr,该段为所找的段
printk("found vma 0x%lx-0x%lx flag %lx for addr 0x%lx\n",vma->vm_start,vma->vm_end,vma->vm_flags,addr);
}
else{
printk("no vma found for %lx\n",addr);// 若不是,输出不存在符合条件的段
}
up_read(&mm->mmap_sem);//对信号量进行v操作
}
static struct page *my_follow_page(struct vm_area_struct *vma,unsigned long addr)
{
pud_t *pud; //页上级目录项
pmd_t *pmd;//页中间目录项
pgd_t *pgd;//页全局目录项
pte_t *pte;//页表项
spinlock_t *ptl;
struct page *page=NULL;//将页面赋初值NULL
struct mm_struct *mm=vma->vm_mm;//将当前进程的虚拟地址空间赋值给mm
pgd=pgd_offset(mm,addr);//offset函数找到pgd即页全局目录
if(pgd_none(*pgd)||unlikely(pgd_bad(*pgd))){ //若不存在pgd,则跳到out
goto out;
}
pud=pud_offset(pgd,addr); //offset函数找到pud即页上级目录
if(pud_none(*pud)||unlikely(pud_bad(*pud))){ //若不存在pud,则跳到out
goto out;
}
pmd=pmd_offset(pud,addr); //offset函数找到pmd即页中间目录
if(pmd_none(*pmd)||unlikely(pmd_bad(*pmd))){ //若不存在pmd,则跳到out
goto out;
}
pte=pte_offset_map_lock(mm,pmd,addr,&ptl); //通过offset_map找到页表项
if(!pte)//如果不存在满足条件的页表项,则跳到out
goto out;
if(!pte_present(*pte))//如果该页表不在内存中,跳到unlock
goto unlock;
page=pfn_to_page(pte_pfn(*pte));//找到相应的页框,并找到页框对应的描述符
if(!page)page//返回为空,跳到unlock
goto unlock;
get_page(page);// 为page赋值
unlock:
pte_unmap_unlock(pte,ptl); //
out:
return page;
}
static void mtest_find_page(unsigned long addr)
{
struct vm_area_struct *vma;
struct mm_struct *mm=current->mm;//将当前进程的虚拟地址空间赋值给mm
unsigned long kernel_addr;//表示物理地址
struct page *page;
printk("mtest_write_val\n");
down_read(&mm->mmap_sem);//对信号量进行P操作
vma=find_vma(mm,addr);//定位到虚拟存储区的某一段
page=my_follow_page(vma,addr);//找到物理页面
if(!page)
{
printk("page not found for 0x%lx\n",addr);
goto out;
}
printk("page found for 0x%lx\n",addr);
kernel_addr=(unsigned long)page_address(page);//将物理页框号赋值给物理地址
kernel_addr+=(addr&~PAGE_MASK);//物理地址+页大小得到所求
printk("find 0x%lx to kernel address 0x%lx\n",addr,kernel_addr);
out:
up_read(&mm->mmap_sem);//对信号量进行V操作
}
static void mtest_write_val(unsigned long addr,unsigned long val)
{
struct vm_area_struct *vma;
struct mm_struct *mm=current->mm;
struct page *page;
unsigned long kernel_addr;
printk("mtest_write_val\n");
down_read(&mm->mmap_sem);
vma=find_vma(mm,addr);
if(vma&&addr>=vma->vm_start&&(addr+sizeof(val))<vma->vm_end)
{
if(!(vma->vm_flags&VM_WRITE))
{
printk("vma is not writeable for 0x%lx\n",addr);
goto out;
}
page=my_follow_page(vma,addr);
if(!page)
{
printk("page not found for 0x%lx\n",addr);
goto out;
}
kernel_addr=(unsigned long)page_address(page);
kernel_addr+=(addr&~PAGE_MASK);
printk("write 0x%lx to address 0x%lx\n",val,kernel_addr);
*(unsigned long *)kernel_addr=val;//将val的值赋给物理地址
put_page(page);
}
else
{
printk("no vma found for %lx\n",addr);
}
out:
up_read(&mm->mmap_sem);
}
static ssize_t mtest_write(struct file *file,const char __user * buffer,size_t count,loff_t * data)
{
printk("mtest_write........\n");
char buf[128];
unsigned long val,val2;
if(count>sizeof(buf)) //如果字符串长度超过最大长度
return -EINVAL; //返回—无效的参数
if(copy_from_user(buf,buffer,count))//完成用户空间到内核空间的复制,失败则返回
return -EINVAL;
if(memcmp(buf,"listvma",7)==0)//将前7个字节取出,与“listvma”进行比较,相同则调用mtest_dump_vma_list();
mtest_dump_vma_list();
else if(memcmp(buf,"findvma",7)==0) //将前7个字节取出,与“findvma”进行比较
{
if(sscanf(buf+7,"%lx",&val)==1) //如果输入了参数
{
mtest_find_vma(val);//调用该函数
}
}
else if(memcmp(buf,"findpage",8)==0)//与“findpage”比较
{
if(sscanf(buf+8,"%lx",&val)==1)//有参数
{
mtest_find_page(val);//调用函数
}
}
else if(memcmp(buf,"writeval",8)==0)//与writeval比较
{
if(sscanf(buf+8,"%lx %lx",&val,&val2)==2)//有两个参数
{
mtest_write_val(val,val2);//调用函数
}
}
return count;
}
static struct file_operations proc_mtest_operations={ //文件操作数
.write =mtest_write
};
static struct proc_dir_entry *mtest_proc_entry;//
static int __init mtest_init(void)
{
//mtest_proc_entry=create_proc_entry("mtest",0777,NULL);
//mtest_proc_entry=proc_create("mtest",0777,NULL);
mtest_proc_entry=proc_create("mtest",0x0777,NULL,&proc_mtest_operations);//创建文件
if(mtest_proc_entry==NULL)
{
printk("error creating proc entry\n");
return -1;
}
printk("create the filename metest mtest_init success \n");
//mtest_proc_entry->proc_fops=&proc_mtest_operations;
//mtest_dump_vma_list();
return 0;
}
static void __exit mtest_exit(void)
{
printk("exit the module......mtest_exit\n");
remove_proc_entry("mtest",NULL);
}
加载模块成功后,打印当前地址的虚拟存储区,输入:
echo "listnma">/proc/mtest

查看输出结果:

选取一个虚拟地址,这里我选择0x8158000,打印出它的虚拟存储区,输入:
echo "findvma0x8158000">/proc/mtest
dmesg mtest查看:得到它所属的虚拟内存区和权限标志。

查找该虚拟地址的物理地址:
输入:
echo "findvmapage0x8158000">/proc/mtest
查看后结果:

修改其物理地址,这里改成了我的学号:20135311
输入:
echo "writeval0x8158000 20135311">/proc/mtest
查看输出结果:


浙公网安备 33010602011771号