资源预览内容
第1页 / 共37页
第2页 / 共37页
第3页 / 共37页
第4页 / 共37页
第5页 / 共37页
第6页 / 共37页
第7页 / 共37页
第8页 / 共37页
第9页 / 共37页
第10页 / 共37页
亲,该文档总共37页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
模块编程实验模块编程实验 模块作为一种抽象数据类型,它具有一个可以通过静态内核中断的接口。最小的模块结构必须包括两个函数:init_module()和cleanup_module(),它们在系统加载模块和卸载模块时被调用。也可以编写一个只包括这两个函数的模块,这样该模块中唯一会被调用的函数就是模块被加载时所调用的函数init_module()和模块被卸载时所调用的函数cleanup_module()。并且用函数init_module()来启动模块加载期间的操作,用函数cleanup_module()来停止这些操作。由于模块可以实现相当复杂的功能,故可以在模块中加入很多新函数以实现所期望的功能。不过加入模块的每个新函数都必须在该模块加载到内核中时进行注册。若该模块是静态加载的,则该模块的所有函数都是在内核启动时进行注册;若该模块是动态加载的,则这些新函数必须在加载这个模块时动态注册。当然,如果该模块被动态卸载了,则该模块的函数都必须从系统中注销。通过这种方式,当这个模块不在系统中时,就不能调用该模块的函数。其中注册工作通常是在函数init_module()中完成的,而注销工作则是在函数cleanup_module()中完成。 7.2.1 7.2.1 模块的组织模块的组织结构结构#include / 说明是个内核功能 #include / 声明是一个模块 / 其它header信息 int init_module( ) / 加载时,初始化模块的编码 / 期望该模块所能实现的一些功能函数,如open()、 release()、write()、 read()、ioctl()等函数 void cleanup_module( ) / 卸载时,注销模块的编码 一般编译模块文件的命令格式如下:#gcc-O2g-Wall-DMODULE-D_KERNEL_-cf-I/usr/src/为自己编写的模块程序源代码文件7.2.2 7.2.2 模块的编译模块的编译 7.2.3 7.2.3 模块的加载模块的加载7.2.4 7.2.4 模块的卸载模块的卸载图7-1 模块链接到内核的示意图7.2.5 7.2.5 模块链接到内核的示意图模块链接到内核的示意图在内核是用一个file结构来识别模块,而且内核使用file_operations结构来访问模块程序中的函数。file_operations结构是一个定义在中的函数指针表。管理模块的文件操作,通常也称为“方法”,它们都为struct file_operations提供函数指针。在struct file_operations中的操作一般按如下顺序出现,除非特别说明,一般它们返回0值时表示访问成功;发生错误时会返回一个负的错误值(目前共有13个操作):int(*lseek)()、int(*read)()、int(*write)()int(*readdir)()、int(*select)()、int(*ioctl)()int(*mmap)()、int(*open)()、void(*release)()int(*fsync)()、int(*fasync)()int(*check_media_change)()int(*revalidate)()7.2.6 7.2.6 模块管理程序中的文件操作模块管理程序中的文件操作structfile_operationsmodulename_fops=NULL,/modulename_lseek,改变模块结构中的操作位置。modulename_read,modulename_write,NULL,/modulename_readdir,读取某个子目录中的内容。NULL,/modulename_select,允许应用程序响应来自模块的事件。NULL,/modulename_ioctl,应用程序通过I/O控制系统的系统调用来控制模块行为。NULL,/modulename_mmap,模块地址空间到用户地址空间的映射。modulename_open,modulename_release,NULL,/modulename_fsync,同步内存与磁盘上的数据状态,把输出缓冲区里尚未写到磁盘的数据写出去。NULL,/modulename_fasync,改变模块行为。NULL,/modulename_check_media_change,检查自上次操作后,介质(软盘和CD-ROM)是否更换。NULL/modulename_revalidate,若更换了介质,则更新信息。void (*release)(struct inode *,struct file *) 7.3 7.3 实验内容实验内容系统核心寄存器数值的获取系统核心寄存器数值的获取模块的编写#include/在内核模块中共享#include/一个模块/处理CONFIG_MODVERSIONS#ifCONFIG_MODVERSIONS=1#defineMODVERSIONS#include#endifintinit_module()/初始化模块printk(“Hello!Thisisatestingmodule!n”);return0;voidcleanup_module()/取消init_module()函数所做的打印功能操作printk(“Sorry!Thetestingmoduleisunloadingnow!n”);7.4 7.4 实验指导实验指导一个简单的内核模块一个简单的内核模块模块的编译、加载和卸载的过程如下:rootlinux/#gccO2WallDMODULED_KERNEL_-crootlinux/#lss/在当前目录下查看生成的目标文件用下面命令将它加载到系统中:rootlinux/#insmodf如果加载成功,则在/proc/modules文件中就可看到模块testmodule,并可看到它的主设备号。同时在终端显示:Hello!Thisisatestingmodule!如果要卸载,就用如下命令:rootlinux/#rmmodtestmodule如果卸载成功,则在/proc/devices文件中就可看到模块testmodule已经不存在了。同时在终端显示:Sorry!Thetestingmoduleisunloadingnow!寄存器cr3存放着当前进程的“页表目录结构”的地址,这个值是在进程被唤醒的时候放入的。对它的读操作必须在内核空间中进行,否则将出现错误,本实验就是向读者展示这个错误。编写如下程序:#include/用户空间的标准输入输出头文件voidGetCr3()intiValue;_asm_volatile_(movl%cr3,%0:=r(iValue);printf(“thevalueincr3is:%d”,a);/用户空间的标准输出函数intmain()GetCr3();Return0;7.4.2 7.4.2 模块加载前后的比较模块加载前后的比较对寄存器cr3的访问,必须在内核空间中完成,在用户程序中被禁止。而采用加载模块程序的方式就可以做到这一点。下面是程序源代码:文件名。此外最好先建立一个目录。#includeintinit_module()intiValue;_asm_volatile_(“movl%cr3,%0”:”=r”(iValue);printf(“cr3:%d”,iValue);return0;voidcleanup_module(void)printk(“uninstallgetcr3!n”);编写Makefile文件如下:DFLAGS=-D_KERNEL_-DMODULECFLAGS=-O2gWallWstrict-prototypespipel/user/include/linux/gccclean:rmf*.orootlinuxserverroot#/sbin/insmodCr3:234320012rootlinuxserverroot#/sbin/rmmodGetCr3UninstallGetCr3!函数函数open( ) intopen(structinode*inode,structfile*filp)MOD_INC_USE_COUNT;/增加该模块的用户数目printk(“Thismoduleisinopen!n”);return0;函数函数release( ) voidrelease(structinode*inode,structfile*filp)MOD_DEC_USE_COUNT;/该模块的用户数目减1printk(“Thismoduleisinrelease!n”);return0;#ifdefDEBUGprintk(“release(%p,%p)n”,inode,filp);#endif7.4.3 7.4.3 向模块中添加新函数向模块中添加新函数函数函数 read()intread(structinode*inode,structfile*filp,char*buf,intcount)intleave;if(verify_area(VERIFY_WRITE,buf,count)=DEFAULT)returnDEFAULT;for(leave=count;leave0;leave-)_put_user(1,buf,1);buf+;returncount;函数函数write() intwrite(structinode*inode,structfile*filp,constchar*buf,intcount)returncount;7.4.4 7.4.4 模块的测试模块的测试 在该模块程序编译加载后,再在/dev目录下创建模块设备文件moduledev,使用命令:#mknod/dev/moduledevcmajorminor其中“c”表示moduledev是字符设备,“major”是moduledev的主设备号。(该字符设备驱动程序编译加载后,可在/proc/modules文件中获得主设备号,或者使用命令:rootlinux/#cat/proc/modules|awk”$2=”moduledev”print$1”获得主设备号)#include#include#include#includemain()inti,testmoduledev;charbuf10;testmoduledev=open(“/dev/moduledev”,O_RDWR);if(testmoduledev=-1)printf(“Cantopenthefile!n”);exit(0);read(testmoduledev,buf,10);for(i=0;i10;i+)printf(“%dn”,bufi);close(testmoduledev);return0;模块程序设计思想模块程序设计思想系统核心寄存器的数值在用户态下,对于这些寄存器数值的访问一般必须要在内核空间中完成,在用户程序中是被禁止的,因此将获取寄存器数值的嵌入式汇编的语句写入模块函数,这样在加载模块之后,用户程序就可以从内核空间中获得这些寄存器的数值。模块程序由open_get()、release_get()、read_get()、write_get()、init_module()、cleanup_module()以及一个数据结构Fops_Get七部分组成,各部分的具体功能可见源程序中的注释。7.4.5 7.4.5 系系统核心寄存器数核心寄存器数值的的获取取实验file_operations结构的定义结构的定义当一个进程试图对生成的设备进行操作的时刻就利用下面这个数据结构,这个结构就是提供给操作系统的接口,它的指针保存在设备表中,在init_module()中被传递给操作系统。structfile_operationsFops_Get=read:device_read,write:device_write,open:device_open,release:device_release,;头文件及程序定义头文件及程序定义/*一些必要的头文件*/#include#include/*处理CONFIG_MODVERSIONS*/#ifCONFIG_MODVERSIONS=1#defineMODVERSIONS#include#endif/*对于不同的版本需要做一些必要的事情*/#include#include#ifndefKERNEL_VERSION#defineKERNEL_VERSION(a,b,c)(a)*65536+(b)*256+(c)#endif#ifLINUX_VERSION_CODEKERNEL_VERSION(2,4,0)#include/*为了使用_put_user这个函数*/#endif#defineSUCCESS0#defineDEVICE_NAMEget_dev/*申明设备名,它会出现在/proc/devices*/#defineBUF_LEN100/*定义此设备消息缓冲的最大长度*/staticintOpen_Get=0;/*为了防止不同的进程在同一时间使用此设备,定义此静态变量跟踪其状态*/staticcharMessageBUF_LEN;/*当提出请求的时候,设备将读写的内容放在下面的数组*/staticchar*Message_Ptr;/*在进程读取设备内容的时候,这个指针是指向读取的位置*/staticintMajor;/*主设备号作为全局变量以便于这个设备在注册和释放的时候使用*/头文件及程序定义头文件及程序定义 open()函数函数open()函数用于进程何时打开该设备。staticintopen_get(structinode*inode,structfile*file)printk(Thismoduleisinopenn);staticintcounter=0;#ifdefDEBUGprintk(device_open(%p,%p)n,inode,file);#endif/*显示驱动模块的主设备号和次设备号*/printk(Device:%d.%dn,inode-i_rdev8,inode-i_rdev&0xFF);/*这个设备是一个独占设备,采取一定的措施避免同时有两个进程使用这一个设备*/if(Open_Get)return-EBUSY;Open_Get+;/*初始化信息,注意不要使用读写内容的长度超过缓冲区的长度,特别是运行内核模式时,否则如果出现缓冲上溢则可能导致系统崩溃,因此在测试程序中只读取了10个字符*/sprintf(Message,IfItoldyouonce,Itoldyou%dtimes-%s,counter+,Helo,worldn);Message_Ptr=Message;/*当这个文件被打开的时候,必须确认该模块没有被移走然后增加此模块的用户数目,与release()函数中的MOD_DEC_USE_COUNT;这条语句相对应。在执行cleanup_module()这个函数移去模块时,根据这个数字决定是否可移去,如果不是0则表明还有进程在使用这个模块,不能移走*/MOD_INC_USE_COUNT;returnSUCCESS; open()函数函数release()函数函数release()函数用于进程关闭该设备特殊文件。#ifLINUX_VERSION_CODE=KERNEL_VERSION(2,4,0)staticintrelease_get(structinode*inode,structfile*file)#elsestaticvoidrelease_get(structinode*inode,structfile*file)#endifprintk(Thismoduleisinreleasen);#ifdefDEBUGprintk(release_get(%p,%p)n,inode,file);#endifOpen_Get-;/*为下一个使用这个设备的进程做准备*/*减少这个模块使用者的数目,否则将使得模块使用者的数目永远不会为0,就永远不能释放这个模块.与open()函数中的MOD_INC_USE_COUNT;这条语句相对应*/MOD_DEC_USE_COUNT;#ifLINUX_VERSION_CODE=KERNEL_VERSION(2,4,0)return0;#endif read()函数函数当打开此设备文件以后,read()函数用于读取数据,测试程序就是调用这个函数将10个字符读入buf数组然后输出。#ifLINUX_VERSION_CODE=KERNEL_VERSION(2,4,0)staticssize_tread_get(structfile*file,char*buffer,/*把读出的数据放到这个缓冲区,调用此函数时为数组buf*/size_tlength,/*缓冲区的长度,调用此函数时赋值为10*/loff_t*offset)/*文件中的偏移*/#elsestaticintread_get(structinode*inode,structfile*file,char*buffer,intlength)#endifinti,bytes_read=0;/*i用于后边的循环,bytes_read是实际读出的字节数*/*验证buffer是否可用*/if(verify_area(VERIFY_WRITE,buffer,length)=-EFAULT)return-EFAULT; read()函数函数/*把用户的缓冲区全部写“7”,当然也可以写其他数字*/for(i=length;i0;i-) /*调用read()函数时,系统进入核心态,不能直接使用buffer这个地 址,必须用_put_user(),这是kernel提供的一个函数,用于向用户 传送数据.注意,有的内核版本中这个函数是三个参数*/ _put_user(7,buffer); buffer +; /*地址指针向后移一位*/ bytes_read +; /*读取的字节数增加1*/ printk(Reading NO.%d character!n,bytes_read); return bytes_read; /*read()函数返回一个真正读出的字节数*/ write()函数函数write()函数用于将数据写入着这个设备文件。但这里的write()函数是个空操作,实际调用时什么也不做,仅仅为Fops结构提供函数指针。#ifLINUX_VERSION_CODE=KERNEL_VERSION(2,4,0)staticssize_twrite_get(structfile*file,constchar*buffer,size_tlength,loff_t*offset)#elsestaticintwrite_get(structinode*inode,structfile*file,constchar*bffer,intlength)#endifreturnlength; init_module()函数函数init_module()这个函数用来初始化这个模块-注册该字符设备.init_module()函数调用nodule_register_chrdev,把设备驱动程序添加到内核的字符设备驱动表中,它返回这个驱动程序所使用的主设备号。intinit_module()printk(nHello!Thisismymodule-Get!n);/*注册字符设备,注册后在/proc/devices中可以看到这个字符设备的主设备号*/Major=register_chrdev(0,DEVICE_NAME,&Fops_Get);/*异常处理*/if(Major0)printk(%sdevicefailedwith%dn,Sorry,registeringthecharacter:(,Major);returnMajor; init_module()函数函数/*一些提示信息,由于我在虚拟机中编程时无法使用中文,所以使用英文提示*/Printk(%sThemajordevicenumberis%d.nnn,Registerationisasucces:),Major);printk(Ifyouwanttotalktothedevicedriver,n);printk(youllhavetocreatadevicefile.n);printk(Isuggestyouuse:n);printk(mknodc%dn,Major);printk(Youcantrydifferentminornumbers%s,andsomethinginterestingwillhappen.nnn);printk(Herearethevalueof23importantregistersinmysystem:n);/*定义了23个整型变量用以存放寄存器的数值,并在模块加载时显示在屏幕上*/intiValue01,iValue02,iValue03,iValue04,iValue05,iValue06,iValue07,iValue08,iValue09,iValue10,iValue11,iValue12,iValue13,iValue14,iValue15,iValue16,iValue17,iValue18,iValue19,iValue20,iValue21,iValue22,iValue23;_asm_volatile_(movl%eax,%0:=r(iValue01);_asm_volatile_(movl%ebx,%0:=r(iValue02);_asm_volatile_(movl%ecx,%0:=r(iValue03);_asm_volatile_(movl%edx,%0:=r(iValue04);_asm_volatile_(movl%esp,%0:=r(iValue05);_asm_volatile_(movl%ebp,%0:=r(iValue06);_asm_volatile_(movl%esi,%0:=r(iValue07);_asm_volatile_(movl%edi,%0:=r(iValue08);_asm_volatile_(movl%cs,%0:=r(iValue09);_asm_volatile_(movl%ds,%0:=r(iValue10);_asm_volatile_(movl%ss,%0:=r(iValue11);_asm_volatile_(movl%es,%0:=r(iValue12);_asm_volatile_(movl%fs,%0:=r(iValue13);_asm_volatile_(movl%gs,%0:=r(iValue14);_asm_volatile_(movl%cr0,%0:=r(iValue15);_asm_volatile_(movl%cr2,%0:=r(iValue16);_asm_volatile_(movl%cr3,%0:=r(iValue17);_asm_volatile_(movl%dr0,%0:=r(iValue18);_asm_volatile_(movl%dr1,%0:=r(iValue19);_asm_volatile_(movl%dr2,%0:=r(iValue20);_asm_volatile_(movl%dr3,%0:=r(iValue21);_asm_volatile_(movl%dr6,%0:=r(iValue22);_asm_volatile_(movl%dr7,%0:=r(iValue23);printk(EAX:%0x,iValue01);printk(EBX:%0x,iValue02);printk(ECX:%0x,iValue03);printk(EDX:%0x,iValue04);printk(ESP:%0x,iValue05);printk(EBP:%0xn,iValue06);printk(ESI:%0x,iValue07);printk(EDI:%0x,iValue08);printk(CS:%0x,iValue09);printk(DS:%0x,iValue10);printk(SS:%0x,iValue11);printk(ES:%0xn,iValue12);printk(FS:%0x,iValue13);printk(GS:%0x,iValue14);printk(CR0:%0x,iValue15);printk(CR2:%0x,iValue16);printk(CR3:%0x,iValue17);printk(DR0:%0xn,iValue18);printk(DR1:%0x,iValue19);printk(DR2:%0x,iValue20);printk(DR3:%0x,iValue21);printk(DR6:%0x,iValue22);printk(DR7:%0xnnn,iValue23);return0;cleanup_module()函数函数cleanup_module()函数在卸载模块时被调用,主要是通过调用unregister_chrdev()从/proc中取消注册的设备文件。voidcleanup_module()printk(UninstallGet!Thanksyou!n);intret;/*取消设备文件的注册。被调用执行后可在/proc/devices里看到效果*/ret=unregister_chrdev(Major,DEVICE_NAME);/*异常处理*/if(ret0)printk(Errorinunregister_chrdev:%dn,ret);设计思想就是通过对虚拟设备文件getdev进行操作,进而调用已经被加载进内核的驱动模块Get所提供的函数。首先调用open()函数打开模块文件,然后调用read()函数给用户空间的buffer数组赋值并将buffer数组输出到屏幕上,最后调用release()函数关闭模块文件。程序结构分析测试源程序的设计与实现测试源程序的设计与实现#include /*C程序必要头文件*/ /* 头文件中定义了基本的数据类型。所有的类型定义为适当的数学类型长度。 另外,size_t是无符号整数类型,off_t是扩展的符号整数类型,pid_t是符号整数类型。*/#include /*头文件说明了函数stat()返回的数据及其结构类型,以及一些属性操作测试 宏、 函数原型。*/#include #include /*与文件操作相关*/main() int i,testgetdev; char buf10; /*字符数组,用于获取从read()写入的数据*/ testgetdev = open(/dev/getdev,O_RDWR); /*打开前边所注册的设备文件*/ /*异常处理*/ if(testgetdev = -1) printf (I Cant open the file:(n); exit(0); /*调用read()函数,read()函数将10个字符“7”写入用户的缓冲区buffer数组*/read(testgetdev,buf,10);/*输出数组buffer*/for(i=0;i10;i+)printf(NO.%dcharacteris:%dn,i+1,bufi);close(testgetdev);/*事实上是调用release()函数关闭模块*/return0;
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号