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
查看输出结果: