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

下载本文档

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

文档简介

选题要求:在Linux内核中增加一个系统调用,并编写对应的Iinux应用程序。

利用该系统调用能够遍历系统当前所有进程的任务描述符,并按进程父子关系将

这些描述符所对应的进程id(PID)组织成树形结构显示。

目录

一.程序的主要设计思路,实现方式4

1.1添加系统调用的两种方法4

1.1.1编译内核法1

M.2内核模块法4

I.2程序的主要设计思路2

1.3环境2

二.程序的模块划分,及对每个模块的说明2

2.1通过内核模块实现添加系统调用2

2.I.I修改系统调用的模块2

2.1.2获取sys_call_(able的地址2

2.1.3清除内存区域的写保护3

2.2编写系统调用指定自己的系统调用3

2.2.1内核的初始化函数3

2.2.2自己的系统调用服务例程4

2.2.3移除内核模块时,将原有的系统调用进行还原5

2.2.4模块注册相关6

2.3编写用户态的测试程序6

2.4编写Makefile文件7

三.所遇到的问题及解决的方法7

3.1进程个数确定7

3.2被更改的系统调用号的选择7

3.3获取系统调用表的地址7

3.4内核和用户态数据交换8

四.程序运行结果及使用说明8

4.1将编译出来的内核模块hello.ko加载到内核中8

4.2通过dmesg查看输出信息是否正确8

4.3运行测试程序,输出树状打印结果(部分结果截图)8

4.4卸载自定义模块9

五•附录11

5.1内核模块程序hello.cII

5.2测试程序hello_test.c14

5.3Makefile文件14

一•程序的主要设计思路,实现方式

1.1添加系统调用的两种方法

1.1.1编译内核法

编写好源码之后

•修改内核的系统调用库函数/usr/indude/asm-generic/unistd.h,在这里面可以使用

在syscall.table中没有用到的223号

•添加系统调用号,让系统根据这个号,去找到syscalLtable中的相应表项。在

/arch/x86/kernel/syscall_table_32.s文件中添加系统调用号和调用函数的对应关系

•接着就是my_syscall的实现了,在这里有两种方法:第一种方法是在kernel下自己新

建一个目录添加自己的文件,但是要编写Makefile,而且要修改全局的Makefile。第二

种比较简便的方法是,在kernel/sys.c中添加自己的服务函数,这样子不用修改

Makefile.

以上准备_E作做完之后,然后就要进行编译内核了,以下是编译内核的一个过程

1.makemenuconfig(使用图形化的工具,更新.config文件)

2.make-j3bzlmage(编译,-j3指的是同时使用3个cpu来编译,bzlmage指的是更新

grub,以便重新引导)

3.makemodules(对模块进行编译)

4.makemodules_install(安装编译好的模块)

5.depmod(进行依赖关系的处理)

6.reboot(重启看到自己编译好的内核)

1.L2内核模块法

内核模块可以作为独立程序来编译的函数和数据类型的集合。之所以提供模块机制,是

因为Linux本身是一个单内核。单内核由于所有内容都集成在一起,效率很高,但可扩展性

和可维护性相对较差,模块机制可以弥补这一缺陷。

Linux模块可以通过静态或动态的方法加载到内核空间,静态加载是指在内核启动过程

中加载:动态加载是指在内核运行的过程中随时加载。

一个模块被加载到内核中时,就成为内核代码的一部分。模块加载入系统时,系统修改

内核中的符号表,将新加载的模块提供的资源和符号添加到内核符号表中,以便模块间通信.

这种方法是采用系统调用拦截的一种方式,改变某一个系统调用号对应的服务程序为我

们自己的编写的程序,从而相当于添加了我们自己的系统调用。

下面的内容,会详述用内核模块法实现目标的过程。

1.2程序的主要设计思路

程序分三部分,一部分是通过内核模块实现添加系统调用,二是编写系统调用指定自己

的系统调用,最后是编写用户态的测试程序。

1.3环境

Ubuntu14.04+3.13.0内核版本

内核版本:

root@ltnux-virtual-machine:/boot#uname-a

LinuxXtnux-virtual-machine3.u.o-24-genertc»4o-ubuntuSNPthuAprioI9:oa:i4

UTC20141686athlon1686GNU/LtnuxI

二.程序的模块划分,及对每个模块的说明

2.1通过内核模块实现添加系统调用

这种方法其实是系统调用拦截的实现。系统调用服务程序的地址是放在sys_call_table

中通过系统调用号定位到具体的系统调用地址,那么我们通过编写内核模块来修改

sys_call_table中的系统调用的地址为我们自己定义的函数的地址,就可以实现系统调用的拦

截。

通过模块加载时,将系统调用表里面的那个系统调用号的那个系统调用号对应的系统调

用服务例程改为我们自己实现的系统历程函数地址。

2.1.1修改系统调用的模块

it/usr/include/i386-linux-gnu/asm/unistd_32.h文件中查看系统调用序号:

Irootgltnux-vtrtual-nachtne:/usr/tnclude/tSSG-ltnux-gnu/asm#gedttuntstd_32.h

找到结果(部分截图):

#define_NR_fcntl64221

#define_NR_getttd224

#define_NR_readahead225

^define_NR_setxattr226

^define_NR_lsetxattr227

可以看到,222号和223号系统调用是空的,因此选取223作为新的系统调用号。

2.1.2获取syscalLtable的地址

1?t/boot/System.map-3.16.0-30-generic查看系统调用表的内存地址:

root@linux-virtual-machine:/boot#gedttSystem.map-3.13.0-24-generic

找到结果:

C165el40Rsys_call_table

为0xcl65el40

2.1.3清除内存区域的写保护

得到了syscalltable的地址,该符号对应的内存区域是只读的。所以我们要修改它,

必须对它进行清除写保护,这里介绍两种方法:

第一种方法:我们知道控制寄存器crO的第16位是写保护位。crO的第16位置为了禁止超

级权限,若清零了则允许超级权限往内核中写入数据,这样我们可以再写入之前,将那一位

洁等,使我们可以写入。然后写完后,又将那一位更原就行了。

〃使crO寄存器的第17位设置为0(即是内核空间可写)

unsignedintclear_and_return_crO(void)

(

unsignedintcrO=0;

unsignedintret;

asm("movl%%crO,%%eax":"=a"(crO));

〃将crO寄存器的值移动到eax寄存器中,同时输出到crO变量中

ret=crO;

crO&=Oxfffeffff;〃将crO变量的第17位清0

asm("movl%%eax,%%crO"::"a"(crO));

〃将crO变量的值放入寄存器eax中,并且放入crO寄存器中

returnret;

)

〃读取val的值到eax寄存器,再将eax寄存器的值放入crO寄存器中一改变内核地址

空间参数

voidsetback_crO(unsignedintval)

(

asmvolatile(nmovl%%eax,%%crO"::"a"(val));

)

第二种方法:通过设置虚拟地址对应的也表项的读写属性来设置。

intmake_rw(unsignedlongaddress)

(

unsignedintlevel;

pte_t*pte=lookup_address(address,&level);〃查找虚拟地址所在的页表地址

if(pte->pte&~_PAGE_RW)〃设置页表读写属性

pte->pte|=_PAGE_RW;

return0;

)

intmake_ro(unsignedlongaddress)

(

unsignedintlevel;

pte_t*pte=lookup_address(address,8ilevel);

pte->pte&=~_PAGE_RW;〃设置只读属性

return0;

)

2.2编写系统调用指定自己的系统调用

2.2.1内核的初始化函数

此函数内采用的是2.1.3中的第一种方法。

staticint_initinit_addsyscall(void)

{

printk('hello,yinyukernel\n");

//获取系统调用服务首地址

sys_call_table=(unsignedlong*)sys_caIl_tabIe_address;

printkC%x\n",sys_call_table);

〃保存系统调用表中的NUM位置上的系统调用

anything_saved=(int(*)(void))(sys_call_table[my_syscall_num]);

〃使内核地址空间可写

orig_crO=clear_and_return_crO();

〃用自己的系统调用替换NUM位置上的系统调用

sys_call_table[my_syscall_num]=(unsignedlong)&sys_mycall;

〃使内核地址空间不可与

setback_crO(orig_crO);

return0;

)

2.2.2自己的系统调用服务例程

部分一:创建进程树

voidprocesstree(structtask_struct*b);

结果需要以树状形式展示所有进程的父子关系。为此,我;门定义processlree。递归函数

来访问遍历,并且将结果存储在数组中,以便提供给用户态访问。

voidprocesstree(structtask_struct*p,intb)〃创建进程树(进程,深度)

structlist_head*I;

a[counter].pid=p->pid;

a[counter].depth=b;

counter++;

for(l=p->children.next;I!=&(p->children);I=I->next)

(

structtask_struct*t=list_entry(l,structtask_struct,sibling);

processtree(t,b+l);

)

}

其中,特别使用了宏:

#definelist_entry(ptr,type,member)/((type*)((char*)(ptr)-(unsigned

long)(8i((type*)0)->member)))

ptr是指向lisjhcad类型链表的指针:

type为一个结构;

member为结构lypc中的一个域,类型为lis(_hcad;

这个宏返回指向lypc结构的指针。

目的:从一个结构的成员指针找到其容器的指针

部分二:创建自己的系统调用服务

asmlinkagclongsys_mycall(char_user*buf);

在sys_mycall()中,从当前进程开始,递归调用processtree。函数,将进程信息存储在数

组中。然后利用copy」o_uscr函数将内核信息传递给用户念下,用户念下的测试程序对结果

进行展示。

asmlinkagelongsys_mycall(char_user*buf)

{

intb=0;

structtask_struct*p;

printk("Thisisyinyu_syscall!\n');

for(p=current;p!=&init_task;p=p->parent);

processtree(p,b);

if(copy_to_user((structprocess*)bufa,512*sizeof(structprocess)))

return-EFAULT;

else

returnsizeof(a);

)

2.2.3移除内核模块时,将原有的系统调用进行还原

staticvoid_exitexit_addsyscall(void)

(

〃设置crO中对sys_call_table的更改权限。

orig_crO=clear_and_return_crO();

〃恢复原有的中断向量表中的函数指针的值。

sys_call_table[my_syscall_num]=(unsignedlong)anything_saved;

〃恢复原有的crO的值

setback_crO(orig_crO);

printk('callyinyuexit\n");

)

2.2.4模块注册相关

•模块构造函数

module_init(init_addsyscaI);

执行insmod或modprobe指令加载内核模块时会调用的初始化函数。函数原型必须是

module_init(),内是函数指针。

•模块析构函数

module_exit(exit_addsyscall);

执行rmmod指令卸载模块时调用的函数。函数原型是module_exit();

•模块许可声明

MODUIF」ICFNSFfGPI");

函数原型是MODULE_LICENSE(),告诉内核程序使用的许可证,不然在加载时它会提示该

模块污染内核。一般会写GPL。

2.3编写用户态的测试程序

#include<linux/unistd.h>

#include<syscall.h>//asmlinkage

#include<sys/types.h>

#include<stdio.h>

structprocess

(

intpid;

intdepth;

);

structprocessa[512];

intmain()

(

intij;

〃在内核中将223本来对应的系统调用,临时链到我们自定义的sys_mycall()中。通

过该系统调用后获得数组a

printf("theresultis:%d\n",syscall(223,&a));

for(i=0;i<512;i十十)

(

for(j=0;j<a[i].depth;j++)

printf("|-");

printf("%d\n"(a[i].pid);

if(a[i+l].pid==0:

break;

)

return0;

)

2.4编写Makefile文件

KVERS=$(shelluname-r)

#Kernelmodules

obj-m+=hello.o

#Specifyflagsforthemodulecompilation.

#EXTRA_CFLAGS=-g-OO

build:kernel_modulesuser_test

kernel_modules:

make-C/lib/modules/$(KVERS)/buildM=$(CURDIR)modules

user_test:

gcc-ohello_testhello_test.c

clean:

make-C/lib/modules/$(KVERS)/buildM=$(CURDIR)clean

三.所遇到的问题及解决的方法

3.1进程个数确定

系统可运行的最大进程数,通过ulimit-u查看有7863个

root@linux-virtual-machine:/usr/incl.ude/i386-linux-gnu/asm#ulimit-u

7863

我们通过ps-eflwc-1命令实际查看当前运行进程数量为191个

root@linux-virtual-machine:/usr/include/i386-linux-gnu/asm#ps-ef|wc-I

191

存储进程信息的数组大小为512是够用的。

3.2被更改的系统调用号的选择

见2.1.1。

3.3获取系统调用表的地址

见2.1.2。

3.4内核和用户态数据交换

我们在内核模块程序中,将进程遍历信息存储在数组中,然后需要将其传递给用户态下。

采用copy_from_uscr0和copy_io_uscr0这两个函数,这两个函数负责在用户空间和内核

空间传递数据。因此我们在测试程序中,将空数组a的地址作为参数传递给内核模块程序,

在内核中使用copy」o_uscrO函数将内核中的数组信息传递给用户态下的地址。

四.程序运行结果及使用说明

4.1将编译出来的内核模块hcllo.ko加载到内核中

加我内核模块命令:

insmodhello.ko

root@linux-virtual-macninel7nome/ltnux/Unix/lAnux_n^^tnsmodnel.loTKo

root@linux-virtual-machine:/home/llnux/Untx/ltnux_'li_2#Isnod|head

ModuleSizeUsedby

hello167430

snd_ensl371245472

snd_ac97_codec1657091snd_ensl371

ac97_bus126421snd_ac97_codec

gameport151091snd_ens1371

snd_pcm855012snd_ac97_codec,snd_ensl371

snd_page_alloc1423。1snd_pcm

crc32_pclmul129670

bnep188952

4.2通过dmesg查看输出信息是否正确

root(dltnux-virtual-machine:/home/ltnux/untx/l.tnux_lt_Zffdmesg|tail

[18074.238391]task:e6036780tt:e625OO0Otask.tt:e6250OOO

[18674.238526]EIP:0073:[<b752d886>]EFLAGS:00010266CPU:0

[18074.238902]EIPisat0xb752d886

[18074.238963]EAX:bfb4ef77EBX:b765dO00ECX:00090000EDX:00000003

[18074.238999]ESI:b6303468EDI:bfb4ef77EBP:00090014ESP:b6c6aldc

[18674.239038]DS:007bES:007bFS:0000GS:6033SS:007b

[18074.239092]

[20303.625174]callytnyuexit

[20309.955936]hello,ytnyukernel

[20309.955980]cl65el40

4.3运行测试程序,输出梃状打印结果(部分结果截图)

rootfllinux-vi.rtual-machine:/home/ltnux/Unix/linuxli.2#./hellotest

theresultis:4096

O

|-1

|-|-324

1-1-328

I-|-463

I-|-469

1-1-499

1-1-509

1-1-513

I-|-568

1-1-1-576

|-|-616

I-|-662

I-|-769

1-1-854

I-|-928

1-1-933

|-|-940

I-|-941

I-I-944

I-1-976

-|-|-|-2304

-|-|-|-2322

-|-|-|-2340

-|-|-1-2334

-|-|-|-2355

-I-I-I-I-2370

-|-|-|-2461

-||||-2468

-||||-2469

-1-1-1-1-1-2930

-I-I-I-I-I-I-2931

-|||-|-3237

-I-I-I-I-I-3555

-I-I-I-I-I-I-3556

-1-1-1-1-1-1-1-4079

-I-I-I-I-I-I-1-1-4080

-I-HI-I-I-I-I-I-I-6334

-|-|-|-2990

-||-|-3016

-1018

-1061

-1102

-1116

-1253

4.4卸载自定义模块

卸载内核模块命令:

insmodhello.ko

root@ltnux-vtrtual.-machtne:/home/ltnux/Untx/ltnux_lt_2#rmmodhello

root@linux-virtual-machine:/home/linux/Untx/linux_li_2#Ismod|head

ModuleSizeUsedby

snd_ensl371245472

snd_ac97_codec1057691snd_ensl371

ac97_bus126421snd_ac97_codec

gameport151891snd_ensl371

snd_pcm855612snd_ac97_codec,snd_ensl371

snd_page_all.oc142301snd_pcm

crc32_pclmul129670

bnep188952

rfcomm53664e

五.附录

5.1内核模块程序hello.c

#include<linux/init.h>

#include<linux/module.h>

#include<linux/kernel.h>//list_head

#include<linux/unistd.h>

#include<asm/uaccess.h>

#include<linux/sched.h>//task_struct

#definemy_syscall_num223

#definesys_call_table_address0xcl65el40

staticintcounter=0;

structprocess

{

intpid;

intdepth;

);

structprocessa[512];

unsignedintclear_and_return_crO(void);

voidsetback_crO(unsignedintval);

asmlinkagelongsys_mycall(char_user*buf);

intorig_crO;

unsignedlong*sys_call_table=0;

staticint(*anything_saved)(void);

voidprocesstree(structtask_struct*p,intb)〃创建进程树(进程,深度)

|

structlist_head*I;

a[counter].pid=p->pid;

a[counter].depth=b;

counter++;

for(l=p->children.next;I!=&(p->children);I=I->next)

(

structtask_struct*t=list_entry(l,structtask_struct,sibling);

processtree(t,b+l);

)

)

unsignedintcIear_and_return_crO(void)//(^crO寄存器的第17位设置为0(即是内核

空间可写)

{

unsignedintcrO=0;

unsignedintret;

asm("movl%%crO,%%eax':"=a"(crO));

〃将crO寄存器的值移动到eax寄存器中,同时输出到crO变量中

ret=crO;

crO&=Oxfffeffff;〃将crO变量的第17位清0

asm("movl%%eax,%%crO"::"a"(crO));

〃将crO变量的值放入寄存器eax中,并且放入crO寄存器中

returnret;

)

voidsetback_crO(unsignedintval)〃读取val的蜃I」eax寄存器,再将eax寄存器的值

放入crO寄存器中改变内核地址空间参数

(

asmvolatile("movl%%eax,%%cr0"::'a"(val));

)

staticint_initinit_addsyscall(void)〃保存原来系统调用表中此地址中的系统调用

{

printk("hello,yinyukernel\n");

sys_call_table=(unsignedlong*)sys_call_table_address;〃获取系统调用服务首

地址

printk("%x\n",sys_call_table);

anything_saved=(int[*)(void))(sys_call_table[my_syscall_numD;〃保存系统调用

表中的NUM位置上的系统调用

orig_crO=clear_and_return_crO();〃使内核地址空间可写

sys_call_table[my_syscall_num]=(unsignedlong)&sys_mycall;〃用自己的系统调

用替换NUM位置上的系统调用

setback_crO(orig_crO);〃使内核地址空间不可写

return0;

)

asmlinkagelongsys_mycall(char_user*buf)

(

intb=0;

structtask_struct*p;

printk("Thisisyinyu_syscall!\n');

for(p=current;p!=&init_task;p=p->parent);

processtree(p,b);

if(copy_to_user((structprocess*)buf,a,512*sizeof(structprocess)))〃将内核空间

内容复制到用户空间

return-EFAUL

温馨提示

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

评论

0/150

提交评论