《linux0.11系统调用原理和实验总结》由会员分享,可在线阅读,更多相关《linux0.11系统调用原理和实验总结(11页珍藏版)》请在技术文库上搜索。
1、Linux0.11 系统调用原理及实验总结系统调用原理及实验总结1系统调用的原理系统调用的原理1.1概述系统调用是一个软中断,中断号是 0x80,它是上层应用程序与 Linux 系统内核进行交互通 信的唯一接口。通过 int 0x80,就可使用内核资源。不过,通常应用程序都是使用具有标准 接口定义的 C 函数库间接的使用内核的系统调用,即应用程序调用 C 函数库中的函数,C 函数库中再通过 int 0x80 进行系统调用。所以,系统调用过程是这样的:应用程序调用 libc 中的函数libc 中的函数引用系统调用宏系统调用宏中使用 int 0x80 完成系统调用并返回。另外一种访问内核的方式是直。
2、接添加一个系统调用,供自己的应用程序使用,这样 就不再使用库函数了,变得更为直接,效率也会更高。1.2相关的数据结构在说具体的调用过程之前,这里先要说几个数据结构。1.2.1 系统调用函数表系统调用函数表 sys_call_table 是在 sys.h 中定义的,它是一个函数指针数组,每个元素是 一个函数指针,它的值是各个系统提供的供上层调用的系统函数的入口地址。也就是说通 过查询这个表就可以调用软中断 0x80 所有的系统函数处理函数。1.2.2 函数指针偏移宏这是一系列宏,它们的定义在 unistd.h 中,基本形式为define _NR_name value,name 为 系统函数名字,。
3、value 是一个整数值,是 name 所对应的系统函数指针在 sys_call_table 中的 偏移量。1.2.3 系统调用宏系统调用宏_syscalln(type,name)在内核的 unistd.h 文件中定义的,对它展开就是: type name(参数列表) 调用过程; ; 其中,n 为参数个数,type 为函数返回值类型,name 为所要调用的系统函数的名字。在 unistd.h 中共定义了 4 个这样的宏(n 从 0 到 3) ,也就是说,0.11 核中系统调用最多可带3 个参数。 那么下面就说这个宏干了什么,也就是说上面的那个“调用过程”是怎么样的呢?在这个宏 中嵌入了汇编代码。
4、,做的工作就是 int 0x80,其中将字符串“_NR_”和 name 连接,组成一个 宏并将这个宏的值,也就是被调用的系统函数在 sys_call_table 中偏移量送到 eax 寄存器中; 同时指明系统函数将来的返回值放到 eax 中。1.3系统调用处理过程下面我再说一下系统调用的核心软中断 int 0x80 具体干了什么。这条指令会引起 CPU 的软 件中断,cpu 会根据中断号找到中断处理程序。这个中断处理程序是在 System_call.s 中。 在中断处理程序的工作过程大致是这样的:1.3.1 将寄存器 ds,es,fs 以及存有参数的 edx,ecx,ebx 入栈,再 ds,e。
5、s,指向 内核段,fs 指向用户段。 1.3.2 根据 eax 中的偏移值,在函数表 sys_call_table 中找到对应的系统函数指 针(函数的入口地址) 。并利用 call 指令调用系统函数,返回后,程序把返 回值加入堆栈。 1.3.3 检查执行本次系统调用的进程的状态,如果发现由于某种原因原进程没处 在就绪状态或者时间片到了,就会执行进程调度函数 schedule()。 1.3.4 通过执行这次调用的程序的代码选择符判断它是不是普通用户程序,如果 是就调用信号处理函数。若不是就直接弹出栈内容,并返回2添加一个系统调用的实验添加一个系统调用的实验2.1实验内容在 linux0.11 版。
6、本中添加两个系统调用,并编写一个简单的应用程序测试它们。第一个系统调用是 iam(),其原型为: int iam(const char * name);完成的功能是将字符串参数 name 的内容拷贝到内核中保存下来。要求 name 的长度不能 超过 23 个字符。返回值是拷贝的字符数。如果 name 的字符个数超过了 23,则返回“-1”, 并置 errno 为 EINVAL。第二个系统调用是 whoami(),其原型为: int whoami(char* name, unsigned int size);它将内核中由 iam()保存的名字拷贝到 name 指向的用户地址空间中,同时确保不会对。
7、 name 越界访存(name 的大小由 size 说明) 。返回值是拷贝的字符数。如果 size 小于需要 的空间,则返回“-1”,并置 errno 为 EINVAL。2.2代码添加修改步骤2.2.1 在 unistd.h 中添加系统调用接口 #define __NR_whoami 72 #define __NR_iam 73int whoami(void); int aim(void);2.2.2 在 exit.c 文件中添加系统调用处理函数的实现 系统调用的函数可以在其他.c 文件中添加或在新建文件中添加,只要编辑进 image 都是可 以的,这里为了调试方便就在 exit.c 文件中添。
8、加了。#define MAX 23 char N_MAX26;int sys_whoami(char* name, unsigned int size) if(strlen(N_MAX)size)return -EINVAL; int i;for(i=0;N_MAXi!=0;i+) put_fs_byte(N_MAXi, return strlen(N_MAX); int sys_iam(char *name) char c; char str100; memset(str,0,sizeof(str); int i;for(i = 0; i MAX)return -EINVAL; memset。
9、(N_MAX,0,sizeof(N_MAX);for(i=0;stri!=0;i+) N_MAXi=stri; return i; 2.2.3 在 system_call.s 汇编代码中修改系统调用的个数 #If 0 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, unsigned int,size。
10、)int main() int a = 0; char bb26 = “champion“; char cc26 = “;a = iam(bb); printf(“a=%d“, a);a = whoami(cc,8); printf(“iam=%sn“,cc); return(1); 3系统调用相关代码的分析系统调用相关代码的分析3.1初始化软件中断门。3.1.1 函数调用层次初始化软件中断门,就是把 0x80 软件中断的处理函数 system_call 挂载到中断向量表 idt 中, 以确保发生软件中断时会运行 system_call 函数,这个函数在 system_call.s 实现。初始。
11、化的 流程如下: main() sched_init () set_system_gate (0x80, errno = -__res; return -1; 传入参数说明: 其中 type 表示系统调用的返回值类型,name 表示该系统调用的名称,atype、a 分别表示 第 1 个参数的类型和名称;可以有 n 个系统调用的传入参数,它们的数目和_syscall 后面的 数字一样大。调用接口宏含义说明:它先将宏__NR_#name 存入 EAX,将参数 fd 存入 EBX,然后进行 0x80 中断调用。调用返 回后,从 EAX 取出返回值,存入__res,再通过对__res 的判断决定传给 。
12、API 的调用者什么 样的返回值。__NR_#name 就是系统调用的编号,在 include/unistd.h 中定义;在上面的例 子中,我们添加了两个自己的系统调用接口,如下:#define __NR_whoami 72 #define __NR_iam 733.3对_system_call 函数的分析处理流程图处理流程分析 _system_call:cmpl $nr_system_calls-1,%eax # 调用号如果超出范围的话就在 eax 中置-1 并退出。 ja bad_sys_callpush %ds # 保存原段寄存器值。 push %es push %fspushl %ed。
13、x # ebx,ecx,edx 中放着系统调用相应的 C 语言函数的调用参数。 pushl %ecx # push %ebx,%ecx,%edx as parameters pushl %ebx # to the system call movl $0x10, %edx # set up ds,es to kernel spacemov %dx,%ds # ds,es 指向内核数据段(全局描述符表中数据段描述符)。 mov %dx,%es movl $0x17,%edx # fs points to local data spacemov %dx,%fs # fs 指向局部数据段(局部描述符表。
14、中数据段描述符)。# 下面这句操作数的含义是:调用地址 = _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) 就去执行调度程序。# 如果该任务在。
15、就绪状态但 counter 值等于 0,则也去执行调度程序。 cmpl $0,state(%eax) # state jne reschedule cmpl $0,counter(%eax) # counter je reschedule3.4用户态和内核态之间的传递数据在内核中主要提供了四个函数实现内核态和用户态的数据传递: copy_to_user(), copy_from_user(),get_fs_byte(),put_fs_byte();上面测试用例中使用的是对字节的操作 get_fs_byte(),put_fs_byte()。4通过通过 bochs 环境如何验证系统调用环境如何验证。
16、系统调用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/loop1 hdc-0.11.img losetup -d /dev/loop1 losetup -o 512 /dev/loo。
17、p1 hdc-0.11.img mkdir /mnt/tempdir mount -t minix /dev/loop1 /mnt/tempdir 说明:用 losetup 的-d 选项把 hdc-0.11.img 文件与 loop1 的关联解除,用 losetup 的-o 选项,该选项指明关联的起始字节偏移位置。由上面分区信息可知,这里第 1 个分区的起始偏移 位置是 1 * 512 字节。 在把第 1 个分区与 loop1 重新关联后,我们就可以使用 mount 命令来访问其中的文件了。 在对分区中文件系统访问结束后, 最后请卸载和解除关联。 umount /dev/loop1 loset。
18、up -d /dev/loop11.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 是一个宏,没有办法添加断点,所以就添。
19、加了一个函数 do_nothing(), 在此处设置断点,以方便观察后面宏的运行情况;并且加入了几个 nop 命令,以方便观察 运行情况。void sched_init(void) do_nothing(); set_system_gate(0x80, 1.3.2 修改_set_gate 宏如下,加入了 nop 命令,以便调试观察。 #define _set_gate(gate_addr,type,dpl,addr) __asm__ (“nop nt“ “nop nt“ “nop nt“ “nop nt“ “movw %dx,%axnt“ “movw %0,%dxnt“ “nop nt“ “n。
20、op nt“ “movl %eax,%1nt“ “movl %edx,%2nt“ “nop nt“ “nop nt“ “nop nt“ “nop “ : : “i“ (short) (0x8000+(dpl13)+(type8), “o“ (*(char *) (gate_addr), “o“ (*(4+(char *) (gate_addr), “d“ (char *) (addr),“a“ (0x00080000)1.3.3 可以看到运行的效果如下: 在 system.map 中,看到 do_nothing 的线性地址是 0x6c1d,可以在此处设置断点。Figure 1 设置断点Figu。
21、re 2 程序运行到_set_gate 宏Figure 3 查看此时寄存器中的数值1.4调试测试程序 test.c 和 sys_whoami 和 sys_iam 函数 1.4.1 调试系统调用处理函数 sys_whoami 和 sys_iam.在 system.map 文件中,找到编译 kernel 后,函数 sys_whoami 和 sys_iam 所在的线性 地址。如下所示: 00008dba T sys_iam 00008e57 T sys_whoami在 bochs 启动 kernel 后,在 0x8dba 和 0x8e57 处设置断点,然后运行 test 程序, 就会进入系统调用处理函数,运行到设置的断点处,后面可以单步运行,以调试 sys_whoami 和 sys_iam,达到测试系统调用的目的。如下图所示:1.4.2 在 linux0.11 的系统中编译运行 test 程序 在运行起来的 linux0.11 的系统中,通过 make test 命令编译 test.c 文件,使生产 test 应用。 运行 test 程序后, iam()函数就会通过 get_fs_byte()函数把“champion”字符串存入到内存中; whoami()函数就会把前面存入到内存中的字符串取出,并且通过 put_fs_byte()内核函数返 回到用户层;并且打印出来。如下所示:。