linux011系统调用原理及实验总结.docx

上传人:b****8 文档编号:30017423 上传时间:2023-08-04 格式:DOCX 页数:17 大小:404.25KB
下载 相关 举报
linux011系统调用原理及实验总结.docx_第1页
第1页 / 共17页
linux011系统调用原理及实验总结.docx_第2页
第2页 / 共17页
linux011系统调用原理及实验总结.docx_第3页
第3页 / 共17页
linux011系统调用原理及实验总结.docx_第4页
第4页 / 共17页
linux011系统调用原理及实验总结.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

linux011系统调用原理及实验总结.docx

《linux011系统调用原理及实验总结.docx》由会员分享,可在线阅读,更多相关《linux011系统调用原理及实验总结.docx(17页珍藏版)》请在冰豆网上搜索。

linux011系统调用原理及实验总结.docx

linux011系统调用原理及实验总结

Linux0.11系统调用原理及实验总结

1系统调用的原理

1.1概述

系统调用是一个软中断,中断号是0x80,它是上层应用程序与Linux系统内核进行交互通信的唯一接口。

通过int0x80,就可使用内核资源。

不过,通常应用程序都是使用具有标准接口定义的C函数库间接的使用内核的系统调用,即应用程序调用C函数库中的函数,C函数库中再通过int0x80进行系统调用。

 所以,系统调用过程是这样的:

 应用程序调用libc中的函数->libc中的函数引用系统调用宏->系统调用宏中使用int0x80完成系统调用并返回。

另外一种访问内核的方式是直接添加一个系统调用,供自己的应用程序使用,这样就不再使用库函数了,变得更为直接,效率也会更高。

1.2相关的数据结构

  在说具体的调用过程之前,这里先要说几个数据结构。

1.2.1   系统调用函数表

 系统调用函数表sys_call_table是在sys.h中定义的,它是一个函数指针数组,每个元素是一个函数指针,它的值是各个系统提供的供上层调用的系统函数的入口地址。

也就是说通过查询这个表就可以调用软中断0x80所有的系统函数处理函数。

1.2.2   函数指针偏移宏

 这是一系列宏,它们的定义在unistd.h中,基本形式为#define_NR_namevalue,name为系统函数名字,value是一个整数值,是name所对应的系统函数指针在sys_call_table中的偏移量。

1.2.3系统调用宏

 

系统调用宏_syscalln(type,name)在内核的unistd.h文件中定义的,对它展开就是:

typename(参数列表)

{

调用过程;

};

其中,n为参数个数,type为函数返回值类型,name为所要调用的系统函数的名字。

在unistd.h中共定义了4个这样的宏(n从0到3),也就是说,0.11核中系统调用最多可带3个参数。

那么下面就说这个宏干了什么,也就是说上面的那个“调用过程”是怎么样的呢?

在这个宏中嵌入了汇编代码,做的工作就是int0x80,其中将字符串“_NR_”和name连接,组成一个宏并将这个宏的值,也就是被调用的系统函数在sys_call_table中偏移量送到eax寄存器中;同时指明系统函数将来的返回值放到eax中。

1.3系统调用处理过程

 下面我再说一下系统调用的核心软中断int0x80具体干了什么。

这条指令会引起CPU的软件中断,cpu会根据中断号找到中断处理程序。

这个中断处理程序是在System_call.s中。

在中断处理程序的工作过程大致是这样的:

1.3.1将寄存器ds,es,fs以及存有参数的edx,ecx,ebx入栈,再ds,es,指向内核段,fs指向用户段。

1.3.2根据eax中的偏移值,在函数表sys_call_table中找到对应的系统函数指针(函数的入口地址)。

并利用call指令调用系统函数,返回后,程序把返回值加入堆栈。

1.3.3检查执行本次系统调用的进程的状态,如果发现由于某种原因原进程没处在就绪状态或者时间片到了,就会执行进程调度函数schedule()。

1.3.4通过执行这次调用的程序的代码选择符判断它是不是普通用户程序,如果是就调用信号处理函数。

若不是就直接弹出栈内容,并返回

2添加一个系统调用的实验

2.1实验内容

在linux0.11版本中添加两个系统调用,并编写一个简单的应用程序测试它们。

第一个系统调用是iam(),其原型为:

intiam(constchar*name);

完成的功能是将字符串参数name的内容拷贝到内核中保存下来。

要求name的长度不能超过23个字符。

返回值是拷贝的字符数。

如果name的字符个数超过了23,则返回“-1”,并置errno为EINVAL。

第二个系统调用是whoami(),其原型为:

intwhoami(char*name,unsignedintsize);

它将内核中由iam()保存的名字拷贝到name指向的用户地址空间中,同时确保不会对name越界访存(name的大小由size说明)。

返回值是拷贝的字符数。

如果size小于需要的空间,则返回“-1”,并置errno为EINVAL。

2.2代码添加修改步骤

2.2.1在unistd.h中添加系统调用接口

#define__NR_whoami72

#define__NR_iam73

intwhoami(void);

intaim(void);

2.2.2在exit.c文件中添加系统调用处理函数的实现

系统调用的函数可以在其他.c文件中添加或在新建文件中添加,只要编辑进image都是可以的,这里为了调试方便就在exit.c文件中添加了。

#defineMAX23

charN_MAX[26];

intsys_whoami(char*name,unsignedintsize)

{

if(strlen(N_MAX)>size)

return-EINVAL;

inti;

for(i=0;N_MAX[i]!

='\0';i++)

{

put_fs_byte(N_MAX[i],&name[i]);

}

returnstrlen(N_MAX);

}

intsys_iam(char*name)

{

charc;

charstr[100];

memset(str,'\0',sizeof(str));

inti;

for(i=0;i<=MAX;i++)

{

if((c=get_fs_byte(&name[i]))!

='\0')

{

str[i]=c;

}

else

break;

}

if((c!

='\0')||(i>MAX))

{

return-EINVAL;

}

memset(N_MAX,'\0',sizeof(N_MAX));

for(i=0;str[i]!

='\0';i++)

{

N_MAX[i]=str[i];

}

returni;

}

2.2.3在system_call.s汇编代码中修改系统调用的个数

#If0

nr_system_calls=72

#else

nr_system_calls=74

#endif

2.2.4测试代码的编写test.c的代码如下:

#define__LIBRARY__

#include

_syscall1(int,iam,char*,name)

_syscall2(int,whoami,char*,name,unsignedint,size)

intmain()

{

inta=0;

charbb[26]="champion";

charcc[26]="";

a=iam(bb);

printf("a=%d",a);

a=whoami(cc,8);

printf("iam=%s\n",cc);

return

(1);

}

 

3系统调用相关代码的分析

3.1初始化软件中断门。

3.1.1函数调用层次

初始化软件中断门,就是把0x80软件中断的处理函数system_call挂载到中断向量表idt中,以确保发生软件中断时会运行system_call函数,这个函数在system_call.s实现。

初始化的流程如下:

main()àsched_init()àset_system_gate(0x80,&system_call)à_set_gate

3.1.2初始化宏_set_gate的原型

/*

传入的四个参数说明如下:

gate_addr=&idt[0x80]软件中断门的地址。

type=15type为门类型

dpl=3dpl为请求特权级

addr=&system_call=0x7119通过查找system.map可以查到中断处理程序的地址

*/

#define_set_gate(gate_addr,type,dpl,addr)\

__asm__("movw%%dx,%%ax\n\t"\

"movw%0,%%dx\n\t"\

"movl%%eax,%1\n\t"\

"movl%%edx,%2\n\t"\

:

\

:

"i"((short)(0x8000+(dpl<<13)+(type<<8))),\

"o"(*((char*)(gate_addr))),\

"o"(*(4+(char*)(gate_addr))),\

"d"((char*)(addr)),"a"(0x00080000))

3.1.3分析初始化宏_set_gate的实现

●__asm__格式为嵌入式汇编的格式,分析可知代码有5个传入的参数%0,%1,%2,%3,%4如下:

%0,立即数

"i"((short)(0x8000+(dpl<<13)+(type<<8)))

这样,%%edx中高16位为addr的高16位,而低16位的P位为1(因为是0x8000),

DPL位段为DPL(因为dpl<<3),而D位加上类型位段则为type(因为type<<8)其余各位皆为0

%1是用内存地址,并且可以加偏移量

"o"(*((char*)(gate_addr))),

gate_addr=&idt[0x80]

%2是用内存地址,并且可以加偏移量

"o"(*(4+(char*)(gate_addr))),

gate_addr=&idt[0x80]

%3,edx做为参数

"d"((char*)(addr)),

&system_call=0x7119

edx=&system_call=0x7119

%4,eax做为参数

"a"(0x00080000))

eax=0x00080000

●__asm__格式为嵌入式汇编的格式,分析可知四条命令含义如下:

"movw%%dx,%%ax\n\t"

先将%%dx的低16位移入%%ax的低16位

这样,在%eax中就形成了所需要的中断门的第一个长整数,

其高16位为_KERNEL_CS,而低16位为addr的低16位。

"movw%0,%%dx\n\t"

字操作16位操作,操作低16位,

高16位已经有调用函数的地址.

"movl%%eax,%1\n\t"

双字操作,为门的0--31位赋值

"movl%%edx,%2\n\t"

双字操作,为门的32--63位赋值

3.2以_syscall1为例,分析系统调用入口宏的含义。

其中_syscall1是一个宏,在include/unistd.h中定义。

将_syscall1(int,close,int,fd)进行宏展开,可以得到:

#define_syscall1(type,name,atype,a)\

typename(atypea)\

{\

long__res;\

__asm__volatile("int$0x80"\

:

"=a"(__res)\

:

"0"(__NR_##name),"b"((long)(a)));\

if(__res>=0)\

return(type)__res;\

errno=-__res;\

return-1;\

}

●传入参数说明:

其中type表示系统调用的返回值类型,name表示该系统调用的名称,atype、a分别表示第1个参数的类型和名称;可以有n个系统调用的传入参数,它们的数目和_syscall后面的数字一样大。

●调用接口宏含义说明:

它先将宏__NR_##name存入EAX,将参数fd存入EBX,然后进行0x80中断调用。

调用返回后,从EAX取出返回值,存入__res,再通过对__res的判断决定传给API的调用者什么样的返回值。

__NR_##name就是系统调用的编号,在include/unistd.h中定义;在上面的例子中,我们添加了两个自己的系统调用接口,如下:

#define__NR_whoami72

#define__NR_iam73

3.3对_system_call函数的分析

●处理流程图

●处理流程分析

_system_call:

cmpl$nr_system_calls-1,%eax#调用号如果超出范围的话就在eax中置-1并退出。

jabad_sys_call

push%ds#保存原段寄存器值。

push%es

push%fs

pushl%edx#ebx,ecx,edx中放着系统调用相应的C语言函数的调用参数。

pushl%ecx#push%ebx,%ecx,%edxasparameters

pushl%ebx#tothesystemcall

movl$0x10,%edx#setupds,estokernelspace

mov%dx,%ds#ds,es指向内核数据段(全局描述符表中数据段描述符)。

mov%dx,%es

movl$0x17,%edx#fspointstolocaldataspace

mov%dx,%fs#fs指向局部数据段(局部描述符表中数据段描述符)。

#下面这句操作数的含义是:

调用地址=_sys_call_table+%eax*4。

参见列表后的说明。

#对应的C程序中的sys_call_table在include/linux/sys.h中,其中定义了一个包括72个

#系统调用C处理函数的地址数组表。

call_sys_call_table(,%eax,4)

pushl%eax#把系统调用号入栈。

movl_current,%eax#取当前任务(进程)数据结构地址。

#下面查看当前任务的运行状态。

如果不在就绪状态(state不等于0)就去执行调度程序。

#如果该任务在就绪状态但counter值等于0,则也去执行调度程序。

cmpl$0,state(%eax)#state

jnereschedule

cmpl$0,counter(%eax)#counter

jereschedule

3.4用户态和内核态之间的传递数据

在内核中主要提供了四个函数实现内核态和用户态的数据传递:

copy_to_user(),copy_from_user(),get_fs_byte(),put_fs_byte();上面测试用例中使用的是对字节的操作get_fs_byte(),put_fs_byte()。

4通过bochs环境如何验证系统调用

1.1Bochs+Linux0.11调试环境建立。

可以分为两个部分的工作:

搭建调试环境和Bochs命令的使用;这两部分网上资料较多,就不在此描述了。

1.2测试程序的修改和添加方法。

1.2.1使用mount命令访问文件系统hdc-0.11.img

要想把测试程序test.c运行起来,一定要放入文件系统才行,也就是一定要把test.c程序放进hdc-0.11.img中去才行,可以用如下的方法打开文件系统:

losetup/dev/loop1hdc-0.11.img

losetup-d/dev/loop1

losetup-o512/dev/loop1hdc-0.11.img

mkdir/mnt/tempdir

mount-tminix/dev/loop1/mnt/tempdir

说明:

用losetup的-d选项把hdc-0.11.img文件与loop1的关联解除,用losetup的-o选项,

该选项指明关联的起始字节偏移位置。

由上面分区信息可知,这里第1个分区的起始偏移位置是1*512字节。

在把第1个分区与loop1重新关联后,我们就可以使用mount命令来访问其中的文件了。

在对分区中文件系统访问结束后,

最后请卸载和解除关联。

umount/dev/loop1

losetup-d/dev/loop1

1.2.2编译test.c测试程序

把测试程序放到/mnt/tempdir/user/root目录下,这样就可以任意修改test.c文件的内容,并可以把修改的内容保存到hdc-0.11.img文件系统中去了。

1.3通过bochs调试观察,是如何把0x80的中断函数system_call的地址挂载上去的。

1.3.1通过添加do_nothing()函数,然后在此函数设置断点,可以查看0x80中断处理函数是如何放到中断向量表中去的。

加入调试辅助代码如下:

因为set_system_gate是一个宏,没有办法添加断点,所以就添加了一个函数do_nothing(),在此处设置断点,以方便观察后面宏的运行情况;并且加入了几个nop命令,以方便观察运行情况。

voidsched_init(void)

{

do_nothing();

set_system_gate(0x80,&system_call);

}

1.3.2修改_set_gate宏如下,加入了nop命令,以便调试观察。

#define_set_gate(gate_addr,type,dpl,addr)\

__asm__("nop\n\t"\

"nop\n\t"\

"nop\n\t"\

"nop\n\t"\

"movw%%dx,%%ax\n\t"\

"movw%0,%%dx\n\t"\

"nop\n\t"\

"nop\n\t"\

"movl%%eax,%1\n\t"\

"movl%%edx,%2\n\t"\

"nop\n\t"\

"nop\n\t"\

"nop\n\t"\

"nop"\

:

\

:

"i"((short)(0x8000+(dpl<<13)+(type<<8))),\

"o"(*((char*)(gate_addr))),\

"o"(*(4+(char*)(gate_addr))),\

"d"((char*)(addr)),"a"(0x00080000))

1.3.3可以看到运行的效果如下:

在system.map中,看到do_nothing的线性地址是0x6c1d,可以在此处设置断点。

Figure1设置断点

Figure2程序运行到_set_gate宏

Figure3查看此时寄存器中的数值

1.4调试测试程序test.c和sys_whoami和sys_iam函数

1.4.1调试系统调用处理函数sys_whoami和sys_iam.

●在system.map文件中,找到编译kernel后,函数sys_whoami和sys_iam所在的线性地址。

如下所示:

00008dbaTsys_iam

00008e57Tsys_whoami

●在bochs启动kernel后,在0x8dba和0x8e57处设置断点,然后运行test程序,

就会进入系统调用处理函数,运行到设置的断点处,后面可以单步运行,以调试sys_whoami和sys_iam,达到测试系统调用的目的。

如下图所示:

 

1.4.2在linux0.11的系统中编译运行test程序

在运行起来的linux0.11的系统中,通过maketest命令编译test.c文件,使生产test应用。

运行test程序后,

iam()函数就会通过get_fs_byte()函数把“champion”字符串存入到内存中;

whoami()函数就会把前面存入到内存中的字符串取出,并且通过put_fs_byte()内核函数返回到用户层;并且打印出来。

如下所示:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 人文社科 > 广告传媒

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1