内核模块化编程讲解_第1页
内核模块化编程讲解_第2页
内核模块化编程讲解_第3页
内核模块化编程讲解_第4页
内核模块化编程讲解_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

一、实验目的学习内核的系统调用,理解、掌握系统调用的实现框架、用户界面、参数传递、进入返回过程。阅读内核源代码,通过添加一个简单的系统调用实验,进一步理解操作系统处理系统调用的统一流程。二、设备环境Linux环境:彼口口014.04,内核linux3.19.0-49-generic三、实验内容利用内核模块在现有的系统中添加一个不用传递参数的系统调用。这个系统调用的功能是实现遍历进程并打印进程树。1、模块模块是内核的一部分,但是并没有被编译到内核里面去。它们被分别编译并连接成一组目标文件,这些文件能被插入到正在运行的内核,或者从正在运行的内核中移走。内核模块至少必须有2个函数:int_module和cleanup_module。第一个函数是在把模块插入内核时调用的;第二个函数则在删除该模块时调用。由于内核模块是内核的一部分,所以能访问所有内核资源。根据对linux系统调用机制的分析,如果要增加系统调用,可以编写自己的函数来实现,然后在sys_call_tabfc中增加一项,使该项中的指针指向自己编写的函数,就可以实现系统调用。2、系统调用相关的数据结构函数名以“sys_”开头,后跟该系统调用的名字。例如,系统调用fork()的响应函数是sys_fork()(见Kernel/fork.c),exit()的响应函数是sys_exit()(见kernel/fork.c)。文件include/asm/unisted.h为每个系统调用规定了唯一的编号。假设用name表示系统调用的名称,那么系统调用号与系统调用响应函数的关系是:以系统调用号—NR—name作为下标,可找出系统调用表sys_call_table中对应表项的内容,它正好是该系统调用的响应函数sys_name的入口地址。系统调用表sys_call_table记录了各sys_name函数在表中的位置,共190项。有了这张表,就很容易根据特定系统调用在表中的偏移量,找到对应的系统调用响应函数的入口地址。系统调用表共256项,余下的项是可供用户自己添加的系统调用空间。3、task_struct数据结构task_struct是linux进程描述符的数据结构,其定义位置在include/linux/sched.h,其信息组成包括:⑴进程状态信息(state,flags,ptrace)(2)调度信息(static_prio,normal_proi,run_list,array,policy)(3)内存管S(mm,active_mm)(4)进程状态位信息(binfmt,exit_state,exit_code,exit_signal)(5)身份信息(pid,tgid,uid,suid,fsuid,gid,egid,sgid,fsgid)(6)家族信息(real_parent,parent,children,sibling)(7)进程耗间信息(realtime,utime,stime,starttime)(8)时钟信息(it_prof_expires,it_virt_expires,it_sched_expires)⑼文件系统信息(link_count,fs,files)(10)IPC信息(sysvsem,signal,sighand,blocked,sigmask,pending)本次实验所要用到的是其家族信息parent,children,sibling。4、list_head数据结构children,sibling都是list_head结构的变量,list_head其实是一个简单的双向循环链表结构,其结构定义为:structlist_head{structlist_head*next,*prev;};list_entry(ptr,type,member):如果type结构中member的地址是ptr,则返回type结构的地址。方式如下:((type*)((char*)(ptr)-(unsignedlong)(&((type*)0)->member)))5、实验主要内容⑴添加系统调用的名字碑并添加系统调用号⑵在系统调用表中添加相应表项⑶编写内核调用模块⑷使用内核模块加载模块程序⑸实现sys_mycall()⑹编写用户态程序测试四、程序模块程序一共分为三大模块,分别为初始化模块、结束模块、内核模块插入和打印进程树模块。三个模块结合可打印出系统当下的所有进程。初始化模块包括:init_syscall(void)和clear_and_return_cr0(void)。主要用途是修改sys_call_table表首地址的只读状态和现场保护。Sys_call_table首地址属性是只读,因此修改之使其可以通过系统调用号获取系统调用程序的地址,在修改完成后恢复sys_call_table首地址的只读属性。现场保护是预存被使用服务号的原来所对应的系统调用程序。结束模块包括:exit_syscall(void),setback_cr0(unsignedintval。主要用途是设置cr0可更改并恢复原有的中断向量表中的函数指针的值和恢复原有的cr0的值。内核模块插入包括:module_init(init_syscall,module_exit(exit_syscall。主要用途是将设计好的程序插入到正在运行的内核,或者从正在运行的内核中移走。五、具体实施步骤及结果1、查找系统调用号系统调用号在文件unistd.h里面定义。这个文件在ubuntu14.04下位于/usr/include/asm-generic/unist(现在我们在unistd.h中查找我们需要的系统调用号。系统调用号集中在1-278和1000以后,所以本次实验选择了300作为调用号。如图1。

flundef_NRsyscalls"define_NRsyscalls278/*Allsyscallsbelowhereshouldgoawayreally,theseareprovidedforbothreviewandasaportinghelpfortheClibraryversion.★Lastchance:areanyoftheseimportantenoughtoenablebydefault?*/tfifdef_ARCH_WANT_SYSCALL_NO_ATfldeftne_NRopen1024_S¥SCALL(_NRopen,sysopen)^define_NRlink1025_SYSCALL(_NR_link,sys_link)ffdefine_NRunlink1026_SYSCALL(_NRunlink,sysunlink)图12、在系统调用表中添加或修改相应表项添加系统调用号之后,系统才能根据这个号,作为索引,去找sys_call_table中的相应表项。这是2.6内核以前使用的方式,但是2.6以后linux隐藏了sys_call_table的地址,所以查看了System.map文件(文件位置在/boot/中,查看方式为cat/boot/System.map-$(uname-r)Igrepsys_call_table),得至^sys_call_table表的首地址,首地址为0xc16ce14。如图2。rootgubuntu:/boot#Isabi-3.19.0-25-genertcabi-3.19.0-49-genertcconftg-3.19.9-25-genericconftg-3.19.9-49-generic'grubrootgubuntu:/boot#Isabi-3.19.0-25-genertcabi-3.19.0-49-genertcconftg-3.19.9-25-genericconftg-3.19.9-49-generic'grubtnitrd.img-3.19.0-25-genertctnitrd.img-3.19.9-49-genertcFiemtest86+.btnFiemtest86+.elfFiemtest86f_multtboot*binSystem,nap-3.19.0-25-genertcSystem,nap-3.19.0-49-genertcvmlinuz-3.19.。-25-genertcvmlinuz-3.19.。-49-genertcrootgubuntu:/boot#cat/boot/System.map-$(uname-r)|grepsyscalltablecl6cel40Rrootgubuntu:/boot#|图23、实现sys_mycall()使用递归的方式,通过list数据结构,由父进程到子进程进行层次遍历。进程树第一层为linux的初始进程init,init加入list表中,再遍历init的子进程,接着遍历子进程的子进程,如此往复,可以完整遍历进程树。主要代码如下。asmlinkagevoidpstreeMy(structtask_struct*p,intb){for(l=p->children.next;l!=&(p->children);l=l->next){structtask_struct*t=list_entry(l,structtask_struct,sibling);pstreeMy(t,b+1);}}list_entry(ptr,type,member)宏定义主要作用是从一个结构的成员指针找到其容器的指针。在这里用来获取子进程task_struct结构的基地址。4、系统调用程序插入内核模块源代码写好后,编辑Makefile文件,通过make编译后,产生test.ko文件。

使用insmod命令将test.ko插入到内核模块中。查看是否加入成功,可使用lsmod命令。删除模块可使用rmmod命令。如图3。root@ubuntu:/home/luchao/linuxTest/tests#IsMakefileModule.synverstest.kotestMain.ctest.mod.omodulesiordertest.ctestMatntest*mod.ctest,oinsmodtest.koLsnod|greptestrmmodtestlsmod|insmodtest.koLsnod|greptestrmmodtestlsmod|greptestroot@ubuntu:/home/luchao/ltnuxTest/test3#rootgubuntu:/home八uchao/linuxTest/tests#root@ubuntu:/home/luchao/linuxTest/test3#图35、打印进程树首先编写用户程序testMain.c,用来测试加载在内核中的test模块。使用gcc编译文件产生运行文件testMain,在运行testMain查看进程树。如图4。rootgubuntu:/home/luchao/linuxTest/test3ifIsMakefileModule.symverstest.kotestMain»ctest.mod.omodules.ordertest.ctestMaintest.mod.ctest.orootgubuntu:/home/luchao/ltnuxTest/test3ifgcc-otestMaintestMain.cTootgubuntu:/hcime八uchaci/li_nuxTest/test3#./testMain出947root@ubuntu:/hone/luchao/linuxTest/test3#|图4使用printk无法在终端打印出进程树,printk是将信息存在linux中的ringbuffer缓存中,可以通过dmesg查看,或者查看var/log/kern.log文件。详见附件。六、所遇到的问题及解决的方法1、对list_entry()的疑问由于对遍历进程树的不熟悉,造成遍历进程树数次错误,在查找了多次资料后,看到一种使用递归且很简洁的方式,其中使用了一个宏定义——list_entry()。虽然看文档知道其可以通过成员查询成员所对应数据结构的基地址,但是对其内部实现还是不清楚。终于在程序完成后,查找资料,进一步加深了解list_entry()宏定义。#definelist_entry(ptr,type,member)((type*)((char*)(ptr)-(unsignedlong)(&((type*)0)->member)))从一个结构的成员指针找到其容器的指针。ptr是找容器的那个变量的指针,把它减去自己在容器中的偏移量的值就应该得到容器的指针。(容器就是包含自己的那个结构)。指针的加减要注意类型,用(char*)ptr是为了计算字节偏移。((type*)0)->member是一个小技巧。自己理解吧。前面的(type*)再转回容器的类型。把“0”强制转化为指针类型,则该指针一定指向“0”(数据段基址)。因为指针是“type*”型的,所以可取到以“0”为基地址的一个type型变量member域的地址。那么这个地址也就等于member域到结构体基地址的偏移字节数。(char*)(ptr)使得指针的加减操作步长为一字节,(unsignedlong)(&((type*)0)->member)等于ptr指向的member到该member所在结构体基地址的偏移字节数。二者一减便得出该结构体的地址。转换为(type*)型的指针,大功告成。2、sys_call_table表首地址无法获得由于linux因为安全的原因在2.6后隐藏了sys_call_table表的首地址,致使我所使用的linux3.16无法查看表的首地址。最初我使用的方式是:cat/proc/kallsyms|grepsys_call_tables但是并终端并没有显示任何内容,通过查找资料,最终找到查看/boot/system.map文件获得首地址。cat/boot/System.map-$(uname-r)|grepsys_call_table3、sys_call_table表首地址属性只读在获得地址后,并代入程序后,程序在insmod时,依然报错。通过对地址分析,发现地址是只读的,所以在程序更改地址时,程序出错。最后通过在linux论坛查找资料,发现可以修改内存中的权限,这时程序不再出问题。代码如下:intset_page_rw(longunsignedint_addr){structpage*pg;pgprot_tprot;pg=virt_to_page(_addr);prot.pgprot=VM_READ|VM_WRITE;returnchange_page_attr(pg,1,prot);}intset_page_ro(longunsignedint_addr){structpage*pg;pgprot_tprot;pg=virt_to_page(_addr);prot.pgprot=VM_READ;returnchange_page_attr(pg,1,prot);}最终我并没有采取这种方法,因为这种方法并不安全。最终采用的方式见附件代码。附件1、源码#include<linux/init.h>#include<linux/module.h>#include<linux/kernel.h>#include<linux/unistd.h>#include<asm-generic/uaccess.h>#include<linux/sched.h>#include<linux/list.h>#definemy_syscall300〃要查啊啊,/boot/中的System.map-$(uname-r)#definesys_call_table_adress0xc16ce140intb=0;intorig_cr0;unsignedlong*sys_call_table=0;staticint(*anything_saved)(void);staticstructtask_struct*pParent;asmlinkagelongsys_mycall(void);asmlinkagevoidpstreeMy(structtask_struct*p,intb);unsignedintclear_and_return_cr0(void){unsignedintcr0=0;unsignedintret;asm("movl%%cr0,%%eax":"=a"(cr0));ret=cr0;cr0&=0xfffeffff;asm("movl%%eax,%%cr0"::"a"(cr0));returnret;}voidsetback_cr0(unsignedintval){asmvolatile("movl%%eax,%%cr0"::"a"(val));}staticint__initinit_syscall(void){printk("hello,kernel\n");//获取系统调用服务首地址sys_call_table=(unsignedlong*)sys_call_table_adress;//保存原始系统调用的地址anything_saved=(int(*)(void))(sys_call_table[my_syscall]);〃设置cr0可更改orig_cr0=clear_and_return_cr0();//更改原始的系统调用服务地址sys_call_table[my_syscall]=(unsignedlong)&sys_mycall;setback_cr0(orig_cr0);//设置为原始的只读cr0//回溯到初始父进程for(pParent=current;pParent!=&init_task;pParent=pParent->parent){}return0;}staticvoid__exitexit_syscall(void){〃设置cr0中对sys_call_table的更改权限。orig_cr0=clear_and_return_cr0();//设置cr0可更改//恢复原有的中断向量表中的函数指针的值。sys_call_table[my_syscall]=(unsignedlong)anything_saved;〃恢复原有的cr0的值setback_cr0(orig_cr0);printk("callexit\n");}asmlinkagelongsys_mycall(void){printk("Thisismy_syscall!\n");pstreeMy(pParent,b);returncurrent->pid;}asmlinkagevoidpstreeMy(structtask_struct*p,intb){inti;structlist_head*l;for(i=0;i<b;i++){printk("");}printk("|%s\n",p->comm);for(l=p->children.next;l!=&(p->children);l=l->next){structtask_struct*t=list_entry(l,structtask_struct,sibling);pstreeMy(t,b+1);}}module_init(init_syscall);module_exit(exit_syscall);MODULE_LICENSE("GPL");2、进程树0一3、-、h7二、-t-mM———、0、.te.mM干—H【:0WM中—p-pne干—、』-』。章—m局科哥说M国图周博忘之依耳曷够层耳秘日工°忐甫甫甫甫噌位忐点甫甫6i-5,-8「Mlq。。^笔。嘈—一I』笔1'III一oi笔£。。一笔''=。一「"|"笔1'8。,PMEd^nq干一?"上干—-H。:IWM中—TypEWOS干—oopvpl羊工於喟宣——』ppe5&——一PJOIO。我3中「oluuep—3hum一一一二u一——-黑畿底器然盘器筹器建器照第胫醛甑翳噩器鳏蜜崽舞器罪罪■患斐器患翳王监翦器弱翳王岸疆翳鸵皇身器器翳瞌靛能馥髭器莪战戏戏熊熊崽然戏舞景熊疑戢然要泰最然戏线柔靛柔彭熊慧涨翅莪渴戏畿影戏皴感雅舞最熊森会戏畿馥燕器筹弱翳弱翳弱弱弱弱弱器*三sgsgsgssgsgsgssgs§s§ssgs§s§ss§§§s§ss§§§s§ss§§§s§ss§§§ssgs§s§ssgs§s§ssgs§s§ssgs§s§ss§§§s§ss§§§s§ss§§§s§ss§§§ssgs§s§ssgs§s§ssgs§s§ssg§§s§ss§§§s§ss§§§s§ss§§§s§ss§s§ssgs§s§ssgs§s§ssgs§s§ss§§§s§ss§§§s§ss§§§s§ss§§§s§ss§s§ssg§§s§sllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllillli勺除日勺倍勺等勺除日勺倍勺等勺除日勺倍勺等勺除日勺专勺等勺勺青勺号勺等勺勺青勺号勺等

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论