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

查看输出结果:

 

posted @ 2016-05-24 16:05  20135311不是富东京  阅读(749)  评论(0编辑  收藏  举报