您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 公司方案 > linux内核系统调用实验指导书
1of31——内核出口系统调用2of31基本知识3of31…xyz()…system_call:…sys_xyz()…ret_from_sys_call:…iretxyz(){…int0x80…}sys_xyz(){…}用户态内核态在应用程序中使用系统调用glibc标准库中的封装例程(系统调用函数的具体实现)系统调用处理程序系统调用服务例程(内核函数)系统调用处理过程4of31为什么有系统调用?一般用户进程不能直接访问系统内核,不能直接使用或修改内核数据,以免干扰内核程序的执行,妨碍系统安全。系统调用是什么?用户进程要使用内核功能时,只能通过内核提供的接口——系统调用来实现,系统调用实际是操作系统内核提供的、功能较强的一系列函数。系统调用好比一个中间人,把用户进程的系统调用请求传达给内核,待内核把请求处理完毕后再将处理结果送回给用户空间。系统调用是用户空间访问内核的唯一手段。系统调用发生时会从用户态转到内核态,完成功能后又由内核态转回用户态。5of31使用系统调用的两种方式1、通过C库函数2、使用syscall函数syscall函数原型为:intsyscall(intnumber,…);通过指定系统调用号和一组函数来调用系统调用*2.6.19版前使用_syscall宏C库函数内核系统调用应用程序代码6of31C库函数系统调用例子用户程序中使用C库中的函数malloc函数free函数srccpy函数open函数brk系统调用open系统调用7of31系统如何响应调用的?系统调用函数中的int$0x80汇编指令,会产生向量为128的异常。内核通过查中断向量表找到128号异常对应的处理程序——系统调用处理程system_call()如何找到对应的内核函数?system_call()利用系统调用号查系统调用表sys_call_table,找到对应每个系统调用号的处理函数。8of31实验9of31一、实验目的学习如何产生一个系统调用往内核中添加一个新的函数实现对用户空间的读写理解、掌握Linux系统调用的实现框架、用户界面、参数传递、进入/返回过程。10of31二、主要实验环境Linux环境:CentOS6.0,linuxkernel2.6.32.71欲编译内核:linux-2.6.35.13命令uname–r11of31三、实验指导获得内核源代码本次下载的内核版本为2.6.35.13将内核源码保存到/usr/src目录下cd/usr/srctar–xjvflinux-2.6.35.13.tar.bz2tarzxvflinux-2.6.35.13.tar.gz定义系统调用编号和修改系统调用表12of311)系统调用表系统调用表sys_call_table存储了所有系统调用对应的服务例程的函数地址。对于X8632位体系结构的系统调用表位于arch/X86/kernel/syscall_table_32.S文件中定义。系统调用服务例程的名字均遵守一定的规则:系统调用名称前增加“sys_”前缀,比如open系统调用对应sys_open函数。可以使用man2syscalls浏览所有系统调用的添加历史13of31系统调用表位置cd/usr/src/linux-2.6.35.13/arch/x86/kernelvisyscall_table_32.S第n个表项对应了系统调用号为n的服务例程的入口地址的指针修改系统调用表:将.longsys_my_new_call添加到arch/x86/kernel/syscall_table_32.S中最后一行14of312)系统调用号/arch/x86/include/asm/unistd_32.h每个系统调用号都是唯一的,依次对应sys_call_table中的一项,系统调用号写在unistd.h文件中,以“__NR_”开头。内核通过系统调用号作为下标去获取sys_call_table中的服务例程函数地址。系统调用号一旦分配就不能再有任何变更,系统运行中即使该系统调用被删除,它所拥有的系统调用号也不能被回收利用。15of31对于32位x86架构系统调用号位于arch/x86/include/asm/unistd_32.h16of314.编译内核的方法makemrpropermakecleanmakeoldconfigmakeallmakemodules_installmakeinstall命令“makeall”用于生成期望的内核映像及模块;“makemodules_install”将安装模块到“默认目录/lib/module/内核版本号”下面;“makeinstall”最终将内核映像等几个文件复制到“/boot”目录,并修改引导程序的配置以启用该新内核。17of31以上命令执行完毕后,会在当前目录下生成一个名为System.map的文件,会在/usr/src/linux-版本号/arch/i386/boot/下生成一个bzImage文件和vmlinuz文件。cd/boot(进入/boot目录)18of315.修改引导程序GRUBcd/boot/grubvimenu.lst为了以后能直接操作菜单,可把menu.lst文件中hiddenmenu那一行注释掉(前加#)或删除,并且可以根据需要设置其中的default和timeout的值,分别表示默认启动项及等待时间。19of31#hiddenmenudefault=0timeout=15splashimage=(hd0,0)/grub/splash.xpm.gztitlecentos(2.6.35.13)root(hd0,0)kernel/vmlinuz-2.6.35.13roroot=/dev/mapper/vg_wufeifei-lv_rootinitrd/initramfs-2.6.35.13.imgtitlecentos(2.6.32-71.el6.i686)root(hd0,0)kernel/vmlinuz-2.6.32-71.el6.i686roroot=/dev/mapper/vg_wufeifei-lv_root20of31reboot重启系统就可以看到GRUB菜单已经包含了新编译的内核选择新编译的内核启动系统。可用uname–r测试当前内核版本号。任务完成后也可修改/boot/grub/menu.1st文件中移去不需要的引导内核信息21of31测试小例子:在现有的系统中添加一个传递数值参数的系统调用。这个系统调用的功能打印传入内核的参数。主要内容:在系统调用表中添加相应表项添加系统调用号sys_my_sys_call的实现编写用户态测试程序22of31在系统调用表中添加或修改相应表项在2.6.35.13的内核下,只需要修改arch/x86/kernel/syscall_table_32.S.longsys_rt_tgsigqueueinfo/*335*/.longsys_perf_event_open.longsys_recvmmsg.longsys_my_new_call/*338*/.longsys_pedagogictime/*339*/23of31添加系统调用号系统调用号在文件unistd_32.h里面定义这个文件在kernel2.6.35.13位于/arch/x86/include/asm/unistd_32.h。现在我们在unistd.h中添加我们的系统调用号:__NR_my_new_call,如下所示:#define__NR_rt_tgsigqueueinfo335#define__NR_perf_event_open336#define__NR_recvmmsg337#define__NR_my_new_call338#define__NR_pedagogictime33924of31sys_my_new_call函数实现cd/usr/src/linux-2.6.35.13/kernel添加一个打印输入值的系统调用visys.c上面在系统调用表中指明了系统调用号_NR_my_new_call对应的系统调用服务例程是sys_my_new_call25of31重新编译内核。成功后,重启。此时,在启动项中有2.6.32和2.6.35两个选项,其中新的内核是2.6.35。选择它并进入系统。至此,我们已经成功添加了一个自己的系统调用。编译成功之后编写用户空间程序进行测试testcall.c#includelinux/unistd.hintmain(){syscall(338,50);/*338是新添加的系统调用号,50是参数*/return0;}终端编译gcca–otestcall运行./a查看结果:终端运行命令dmesg会看到在最后一行输出callnumberis5026of31问题A设计并实现一个新的内核函数pedagogictime(),该函数通过使用一个引用参数的调用返回当前的系统时间。如果flag的参数为TRUE,内核就把当前的系统时间打印输出intpedagogictime(intflag,structtimeval*tv)新函数基本和gettimeofday()类似cd/usr/src/linux2.6.35.13/kernelvitime.c添加实现代码27of31#includelinux/time.h#includelinux/kernel.h#includelinux/mm.hasmlinkageintsys_pedagogictime(intflag,structtimeval*tv){if(tv){structtimevalktv;do_gettimeofday(&ktv);if(copy_to_user(tv,&ktv,sizeof(ktv)))return-EFAULT;}if(flag==true)printk(tv.sec:%ld\n,(*tv).tv_sec);return0;}28of31问题B编写用户空间程序来测试pedagogictime()的执行情况#includelinux/time.h#includelinux/unistd.hintmain(){structtimevaltv;tv.tv_sec=10;syscall(339,1,&tv);printf(“userfirstgettv_sec\n”,tv.tv_sec);printf(“nowsleep10seconds\n”);sleep(10);printf(“userfirstgettv_sec\n”,tv.tv_sec);return0;}29of31shell下用dmesg命令观察内存打印信息最下面两行即为内核函数输出30of31心得、体会全部编译一次内核会很长,大约3个小时。编过一次内核后,由于.o文件都在存在,所以第二次编译时间非常快。添加一个系统调用类似于MFC中添加一个自定义的消息,首先要注册这个消息,以便系统知道有这么个消息,然后用户在程序中才能使用它。在2.6.35中,有unistd.h,unistd_32.h,unistd_64.h,其实unistd.h中的内容主要是用来判断要使用unistd_32.h还是unistd_64.h。不同的版本编译方法差别很大,本文只针对2.6版,2.4与2.2需另外搜集资料,但原理是相同的。31of31谢谢大家!
本文标题:linux内核系统调用实验指导书
链接地址:https://www.777doc.com/doc-3392079 .html