Linux内核中增加一个系统调用_第1页
Linux内核中增加一个系统调用_第2页
Linux内核中增加一个系统调用_第3页
Linux内核中增加一个系统调用_第4页
Linux内核中增加一个系统调用_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

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

文档简介

1、选题要求:在Linux 内核中增加一个系统调用,并编写对应的 linux 应用程序。利用该系统调用能够遍历系统当前所有进程的任务描述符, 并按进程父子关系将这些描述符所对应的进程id (PID)组织成树形结构显示。目录. 程序的主要设计思路,实现方式 11.1 添加系统调用的两种方法1.1.1.1 编译内核法 11.1.2 内核模块法 11.2 程序的主要设计思路1.1.3 环境2. 程序的模块划分,及对每个模块的说明 22.1 通过内核模块实现添加系统调用 2.2.1.1 修改系统调用的模块 22.1.2 获取 sys_call_table 的地址 22.1.3 清除内存区域的写保护 32.

2、2 编写系统调用指定自己的系统调用 4.2.2.1 内核的初始化函数 42.2.2 自己的系统调用服务例程 42.2.3 移除内核模块时,将原有的系统调用进行还原62.2.4 模块注册相关 62.3 编写用户态的测试程序6.2.4 编写 Makefile 文件 7. 所遇到的问题及解决的方法 83.1 进程个数确定8.3.2 被更改的系统调用号的选择8.3.3 获取系统调用表的地址8.3.4 内核和用户态数据交换8. 程序运行结果及使用说明 84.1 将编译出来的内核模块hello.ko 加载到内核中 84.2 通过 dmesg查看输出信息是否正确 94.3 运行测试程序,输出树状打印结果(部

3、分结果截图) 94.4 卸载自定义模块1.0五. 附录 115.1 内核模块程序 hello.c1.15.2 测试程序hello_test.c 1.4.5.3 Makefile文件1.4.Linux内核分析课程大作业一.程序的主要设计思路,实现方式1.1 添加系统调用的两种方法1.1.1 编译内核法编写好源码之后 修改内核的系统调用库函数/usr/include/asm-generic/unistd.h ,在这里面可以使用在syscall_table 中没有用到的223号 添加系统调用号,让系统根据这个号,去找到syscall_table 中的相应表项。在/arch/x86/kernel/sy

4、scall_table_32.s文件中添加系统调用号和调用函数的对应关系 接着就是my_syscall的实现了,在这里有两种方法:第一种方法是在kernel下自己新建一个目录添加自己的文件,但是要编写 Makefile ,而且要修改全局的 Makefile 。第二种比较简便的方法是,在 kernel/sys.c中添加自己的服务函数,这样子不用修改Makefile.以上准备工作做完之后,然后就要进行编译内核了,以下是编译内核的一个过程1 .make menuconfig (使用图形化的工具,更新 .config 文件)2 .make -j3 bzImage(编译,-j3指的是同时使用 3个cpu

5、来编译,bzImage 指的是更新grub ,以便重新引导)3 .make modules(对模块进行编译)4 .make modules_install(安装编译好的模块)5 .depmod(进行依赖关系的处理)6 .reboot (重启看到自己编译好的内核)1.1.2内核模块法内核模块可以作为独立程序来编译的函数和数据类型的集合。之所以提供模块机制,是因为Linux本身是一个单内核。 单内核由于所有内容都集成在一起,效率很高,但可扩展性和可维护性相对较差,模块机制可以弥补这一缺陷。Linux模块可以通过静态或动态的方法加载到内核空间,静态加载是指在内核启动过程中加载;动态加载是指在内核运行

6、的过程中随时加载。一个模块被加载到内核中时,就成为内核代码的一部分。模块加载入系统时,系统修改内核中的符号表,将新加载的模块提供的资源和符号添加到内核符号表中,以便模块间通信。这种方法是采用系统调用拦截的一种方式,改变某一个系统调用号对应的服务程序为我们自己的编写的程序,从而相当于添加了我们自己的系统调用。下面的内容,会详述用内核模块法实现目标的过程。1.2 程序的主要设计思路程序分三部分,一部分是通过内核模块实现添加系统调用,二是编写系统调用指定自己的系统调用,最后是编写用户态的测试程序。1.3 环境Ubuntu14.04 + 3.13.0 内核版本内核版本:lrootli.nux virt

7、ual-nachinei/bootfr dnene -aLinux Linux virtual nachi.nc 3.13.0 24 generic #46 Ubuntu 5HP Thu Apr ID 19;OS: 14UTC 2014 1606 athlon t6B6 CNU/LtnifX|二.程序的模块划分,及对每个模块的说明2.1 通过内核模块实现添加系统调用这种方法其实是系统调用拦截的实现。系统调用服务程序的地址是放在sys_call_table中通过系统调用号定位到具体的系统调用地址,那么我们通过编写内核模块来修改sys_call_table中的系统调用的地址为我们自己定义的函数的地

8、址,就可以实现系统调用的拦截。通过模块加载时,将系统调用表里面的那个系统调用号的那个系统调用号对应的系统调用服务例程改为我们自己实现的系统历程函数地址。2.1.1 修改系统调用的模块在 /usr/include/i386-linux-gnu/asm/unistd_32.h 文件中查看系统调用序号:Irootglinux-virtual-machine/usr/include/t3a6-lT.nux-gnu/as*i# gedit unistd_32,h找到结果(部分截图):#deftne _NR_fcrtl64 221Itfdeftne224define NR_readahead 225#de

9、fIne _MR._setxattr 226tfdeftne _R_lsetx&ttr 22?可以看到,222号和223号系统调用是空白1因此选取223作为新的系统调用号。2.1.2 获取 sys_call_table 的地址在/boot/System.map-3.16.0-30-generic查看系统调用表的内存地址:rootgltnux-virtual-machine:/boot* gedit Systen.nap-3.13 + O'2l-generic找到结果:cl65el40 R sys_caXl_table为 0xc165e1402.1.3 清除内存区域的写保护得到了

10、 sys_call_table 的地址,该符号对应的内存区域是只读的。所以我们要修改它,必须对它进行清除写保护,这里介绍两种方法:第一种方法:我们知道控制寄存器cr0的第16位是写保护位。cr0的第16位置为了禁止超级权限,若清零了则允许超级权限往内核中写入数据,这样我们可以再写入之前,将那一位清零,使我们可以写入。然后写完后,又将那一位复原就行了。/使cr0寄存器的第17位设置为0 (即是内核空间可写)unsignedint clear_and_return_cr0(void)unsignedint cr0 = 0;unsignedint ret;asm("movl %cr0, %

11、eax":"=a"(cr0);/将cr0寄存器的值移动到 eax寄存器中,同时输出到cr0变量中ret = cr0;cr0 &= 0xfffeffff;/ 将 cr0 变量的第 17 位清 0asm("movl %eax, %cr0":"a"(cr0);/将cr0变量的值放入寄存器eax中,并且放入cr0寄存器中return ret;/读取val的值到eax寄存器,再将eax寄存器的值放入 cr0寄存器中-改变内核地址 空间参数void setback_cr0(unsigned intval)asm volatile

12、("movl %eax, %cr0":"a"(val);第二种方法:通过设置虚拟地址对应的也表项的读写属性来设置。intmake_rw(unsigned long address)unsigned int level;pte_t *pte = lookup_address(address, &level);/查找虚拟地址所在的页表地址if (pte->pte& _PAGE_RW) /设置页表读写属性 pte->pte |= _PAGE_RW;return 0;intmake_ro(unsigned long address)u

13、nsigned int level;pte_t *pte = lookup_address(address, &level);pte->pte&= _PAGE_RW; / 设置只读属性return 0;2.2 编写系统调用指定自己的系统调用2.2.1 内核的初始化函数此函数内采用的是2.1.3中的第一种方法。static int _initinit_addsyscall(void)printk("hello,yinyu kerneln");/获取系统调用服务首地址sys_call_table = (unsigned long *)sys_call_ta

14、ble_address;printk("%xn",sys_call_table);保存系统调用表中的NUM位置上的系统调用anything_saved = (int(*)(void) (sys_call_tablemy_syscall_num);/使内核地址空间可写orig_cr0 = clear_and_return_cr0();用自己的系统调用替换NUM位置上的系统调用sys_call_tablemy_syscall_num= (unsigned long)&sys_mycall;/使内核地址空间不可写setback_cr0(orig_cr0);return 0

15、;2.2.2 自己的系统调用服务例程部分一:创建进程树voidprocesstree(structtask_struct * p,int b);结果需要以树状形式展示所有进程的父子关系。为此,我们定义processtree()递归函数来访问遍历,并且将结果存储在数组中,以便提供给用户态访问。void processtree(structtask_struct * p,int b)创建进程树(进程,深度)structlist_head * l;acounter.pid = p ->pid;acounter.depth = b;counter +;for(l = p ->childre

16、n.next; l != &(p->children); l = l->next)struct task_struct *t = list_entry(l,structtask_struct,sibling);processtree(t,b+1);其中,特别使用了宏:#define list_entry(ptr, type, member) / (type *)(char *)(ptr)-(unsigned long)(&(type *)0)->member)ptr是指向list_head类型链表的指针;type为一个结构;member为结构type中的一个域

17、,类型为 list_head;这个宏返回指向type结构的指针。目的:从一个结构的成员指针找到其容器的指针部分二:创建自己的系统调用服务asmlinkage long sys_mycall(char _user * buf);在sys_mycall()中,从当前进程开始,递归调用 processtree()函数,将进程信息存储在数 组中。然后利用copy_to_user函数将内核信息传递给用户态下,用户态下的测试程序对结果进行展示。asmlinkage longsys_mycall(char _user * buf) int b = 0;struct task_struct * p;print

18、k("This is yinyu_syscall!n");for(p = current; p != &init_task; p = p->parent );processtree(p,b);if(copy_to_user(struct process *)buf,a,512*sizeof(struct process)return -EFAULT;elsereturn sizeof(a);2.2.3 移除内核模块时,将原有的系统调用进行还原static void _exit exit_addsyscall(void)/设置cr0中对sys_call_tabl

19、e 的更改权限。orig_cr0 = clear_and_return_cr0();恢复原有的中断向量表中的函数指针的值。sys_call_tablemy_syscall_num= (unsigned long)anything_saved;恢复原有的cr0的值setback_cr0(orig_cr0);printk("call yinyu exit n");2.2.4 模块注册相关模块构造函数module_init(init_addsyscall);执行insmod或modprobe指令加载内核模块时会调用的初始化函数。函数原型必须是module_init(),内是函数指

20、针。模块析构函数module_exit(exit_addsyscall);执行rmmod指令卸载模块时调用的函数。函数原型是module_exit();模块许可声明MODULE_LICENSE("GPL");函数原型是MODULE_LICENSE(),告诉内核程序使用的许可证,不然在加载时它会提示该模块污染内核。一般会写GPL。2.3 编写用户态的测试程序#include <linux/unistd.h>#include <syscall.h>asmlinkage#include <sys/types.h>#include <std

21、io.h>struct processintpid;int depth;;struct process a512;int main()inti,j;/在内核中将223本来对应的系统调用,临时链到我们自定义的 sys_mycall()中。通过该系统调用后获得数组aprintf("the result is:%dn",syscall(223,&a);for(i = 0; i < 512; i+)for(j = 0; j < ai.depth; j+)printf("|-");printf("%dn",ai.pid

22、);if(ai+1.pid = 0)break;return 0;2.4 编写Makefile文件KVERS = $(shell uname -r)# Kernel modulesobj-m += hello.o# Specify flags for the module compilation.#EXTRA_CFLAGS=-g -O0build: kernel_modulesuser_testkernel_modules:make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modulesuser_test:gcc -o hello_test hel

23、lo_test.cclean:make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean三.所遇到的问题及解决的方法3.1 进程个数确定系统可运行的最大进程数,通过 ulimit - u查看有7863个|rootli.nuX-Virtual. -nachlne: /usr/include/i386-Linux-gnu/asn# ulinit -u 7863我们通过ps- ef|wc - l命令实际查看当前运行进程数量为191个vitu3lFmchtne:/us/includ总/imBS-ltnux - gnu/asm# ps -ef|c -I 1

24、91存储进程信息的数组大小为512是够用的。3.2 被更改的系统调用号的选择见 2.1.1。3.3 获取系统调用表的地址见 2.1.2。3.4 内核和用户态数据交换我们在内核模块程序中,将进程遍历信息存储在数组中,然后需要将其传递给用户态下。采用copy_from_user()和copy_to_user()这两个函数,这两个函数负责在用户空间和内核 空间传递数据。因此我们在测试程序中,将空数组a的地址作为参数传递给内核模块程序,在内核中使用copy_to_user()函数将内核中的数组信息传递给用户态下的地址。四.程序运行结果及使用说明4.1 将编译出来的内核模块hello.ko加载到内核中加

25、载内核模块命令:insmodhello.koIrootQ'Linjx-vtrtoal-nachtne:/hoyitnux/unix/llnuX-H-Sft insmod hello.koroot(3linux virtual machine: /honc/ltniJx/Unix/lLnux_li_2fi Ismod |headModuleSizeUsed byhello167416sn<l_ehsi371245472*;nd_ac97_coc1ec1a57991 snd_ens1371ac97_bus126421 snd_ac97_codecganeport151891 snd_

26、ens1371.snd_pcm&5S012 snJ_ac57_codec tsnd_ensl371snd paqe allac14Z3B1 5nd pencrc32_pclniul12967boep1839 5z4.2 通过dmesg查看输出信息是否正确root(5linux-virtual*nachlne; /卜口料e/ltnux/Ucix/linu)c_ll_2# dnesg | tall 10074.233391 task: e53067fifi ti: e62S6flafl 七启亏 k. th100744 23Q52SieeT4*:3a90Z1&074.Z38963:1

27、8074.23fi999:L8®74.239838:18074.23969220303.62517410309,955935EIP: 0073: <b752dS86J EF JG0 1 O00102G6 CPU: 0LIP s at 0xby5Nd886EPX: bfMefZ7 EEX: bTO5dO00 ECX: 90000096 EDX: 00090003ESI: b633045fi EDI: bfb4ef77 EBP: &0000014 ESP: b6c6KdeDS: 007b ES: 067b FS: 6000 GS: 0033 SS: 007bcall yin

28、yu 眨1七hello,ytnyu kernel95598® ci6Se14O4.3 运行测试程序,输出树状打印结果(部分结果截图)rootlinux-virtual-machine:/hone/linux/Unix/ltrux_lt_2# ./hello_testthe result isi 4056十卜|-| - 2304 -1-1'1-12322 -I-I-M-2340 一|十卜2334-I-I-I-I-235S 十|一|十|237后 -I-h!-1-2461 “十 |-|-卜2468 十卜|十卜2459-1-1-HI- 1-1930|-3237 十|十卜卜3555 &

29、#39;l-l'M-l'M-5556HI-l-kl-4079 十卜卜|小卜卜|十4。加 十17十卜I十卜I十6334 -IT-【-卜299。-I-PI-1-3016- | -1018 -j-1061- | -1102- I-1116-1-12534.4卸载自定义模块卸载内核模块命令:insmodhello.ko,。0tlinux-virtuaLwachtne:,hDM7ltnux/Untit/lAnuK_lt_2# rmnod hellorootgltnux-virtual- Modulesnd_ensl371 snd_ac97_codec ac97_busgapieport

30、snd_pcn sfid_page_alloc crcB2_pclnul bnep rtconn-machine:/hone/ltnux/Unix/linuKt_2tf Isnod Sice Ued by24547 2105705 1 snd_ensl37112642 1 sndacSTcodecL5189 1 snd_ensl371 ssstn 2 £(id_ac97_codec ,snd_ensi371H236 1 snd_pcn 12957 6 18895 2 5*84 O| head五.附录5.1内核模块程序hello.c#include <linux/init.h&g

31、t;#include <linux/module.h>#include <linux/kernel.h>list_head#include <linux/unistd.h>#include <asm/uaccess.h>#include <linux/sched.h>task_struct#define my_syscall_num 223#define sys_call_table_address 0xc165e140static int counter = 0;struct processintpid;int depth;stru

32、ct process a512;unsigned int clear_and_return_cr0(void);void setback_cr0(unsigned intval);asmlinkage long sys_mycall(char _user *buf);int orig_cr0;unsigned long *sys_call_table = 0;创建进程树(进程,深度)static int (*anything_saved)(void);void processtree(structtask_struct * p,int b)structlist_head * l;acounte

33、r.pid = p ->pid;acounter.depth = b;counter +;for(l = p ->children.next; l != &(p->children); l = l->next)struct task_struct *t = list_entry(l,structtask_struct,sibling);processtree(t,b+1); unsigned int clear_and_return_cr0(void)/ 使 cr0 寄存器的第 17 位设置为 0 (即是内核 空间可写)unsigned int cr0 = 0;

34、unsigned int ret;asm("movl %cr0, %eax":"=a"(cr0);将cr0寄存器的值移动到eax寄存器中,同时输出到cr0变量中ret = cr0;cr0 &= 0xfffeffff;/ 将 cr0 变量的第 17 位清 0asm("movl %eax, %cr0":"a"(cr0);将cr0变量的值放入寄存器 eax中,并且放入cr0寄存器中return ret;void setback_cr0(unsigned intval)/ 读取val的值到eax寄存器,再将 eax

35、寄存器的值 放入cr0寄存器中-改变内核地址空间参数asm volatile("movl %eax, %cr0":"a"(val);static int _initinit_addsyscall(void)保存原来系统调用表中此地址中的系统调用printk("hello,yinyu kerneln");sys_call_table = (unsigned long *)sys_call_table_address;/获取系统调用服务首地址printk("%xn",sys_call_table);anything_s

36、aved = (int(*)(void) (sys_call_tablemy_syscall_num);/保存系统调用表中的NUM 位置上的系统调用orig_cr0 = clear_and_return_cr0();使内核地址空间可写sys_call_tablemy_syscall_num= (unsigned long)&sys_mycall;用 自 己的系统调用替换NUM位置上的系统调用setback_cr0(orig_cr0);/使内核地址空间不可写return 0;asmlinkage long sys_mycall(char _user * buf)int b = 0;str

37、uct task_struct * p;printk("This is yinyu_syscall!n");for(p = current; p != &init_task; p = p->parent );processtree(p,b);if(copy_to_user(struct process *)buf,a,512*sizeof(struct process)/将内核空间内容复制到用户空间return -EFAULT;elsereturn sizeof(a);static void _exit exit_addsyscall(void)/设置cr0中对sys_call_table 的更改权限。orig_cr0 = clear_and_return_cr0();恢复原有

温馨提示

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

评论

0/150

提交评论