建旅游网站的意义,wordpress 如果分类,网络营销平台排名,网站开发人员 kpi指标系统调用 实验目的 建立对系统调用接口的深入认识掌握系统调用的基本过程能完成系统调用的全面控制为后续实验做准备 实验内容 此次实验的基本内容是#xff1a;在Linux 0.11上添加两个系统调用#xff0c;并编写两个简单的应用程序测试它们。 iam() 第一个系统调用是iam()在Linux 0.11上添加两个系统调用并编写两个简单的应用程序测试它们。 iam() 第一个系统调用是iam()其原型为 int iam(const char * name); 完成的功能是将字符串参数name的内容拷贝到内核中保存下来。要求name的长度不能超过23个字符。返回值是拷贝的字符数。如果name的字符个数超过了23则返回“-1”并置errno为EINVAL。 在kernal/who.c中实现此系统调用。 whoami() 第二个系统调用是whoami()其原型为 int whoami(char* name, unsigned int size); 它将内核中由iam()保存的名字拷贝到name指向的用户地址空间中同时确保不会对name越界访存name的大小由size说明。返回值是拷贝的字符数。如果size小于需要的空间则返回“-1”并置errno为EINVAL。 也是在kernal/who.c中实现。 测试程序 运行添加过新系统调用的Linux 0.11在其环境下编写两个测试程序iam.c和whoami.c。最终的运行结果是 \$ ./iam lizhijun \$ ./whoami lizhijun 实验报告 在实验报告中回答如下问题 从Linux 0.11现在的机制看它的系统调用最多能传递几个参数你能想出办法来扩大这个限制吗用文字简要描述向Linux 0.11添加一个系统调用foo()的步骤。 评分标准 将 testlab2.c在/home/teacher目录下 在修改过的Linux 0.11上编译运行显示的结果即内核程序的得分。满分50%只要至少一个新增的系统调用被成功调用并且能和用户空间交换参数可得满分将脚本 testlab2.sh在/home/teacher目录下 在修改过的Linux 0.11上运行显示的结果即应用程序的得分。满分30%实验报告20% 实验提示 首先请将Linux 0.11的源代码恢复到原始状态。 操作系统实现系统调用的基本过程在MOOC课程中已经给出了详细的讲解是 应用程序调用库函数APIAPI将系统调用号存入EAX然后通过中断调用使系统进入内核态内核中的中断处理函数根据系统调用号调用对应的内核函数系统调用系统调用完成相应功能将返回值存入EAX返回到中断处理函数中断处理函数返回到API中 API将EAX返回给应用程序。 应用程序如何调用系统调用 在通常情况下调用系统调用和调用一个普通的自定义函数在代码上并没有什么区别但调用后发生的事情有很大不同。调用自定义函数是通过call指令直接跳转到该函数的地址继续运行。而调用系统调用是调用系统库中为该系统调用编写的一个接口函数叫APIApplication Programming Interface。API并不能完成系统调用的真正功能它要做的是去调用真正的系统调用过程是 把系统调用的编号存入EAX把函数参数存入其它通用寄存器触发0x80号中断int 0x80 0.11的lib目录下有一些已经实现的API。Linus编写它们的原因是在内核加载完毕后会切换到用户模式下做一些初始化工作然后启动shell。而用户模式下的很多工作需要依赖一些系统调用才能完成因此在内核中实现了这些系统调用的API。我们不妨看看lib/close.c研究一下close()的API #define __LIBRARY__ #include unistd.h _syscall1(int,close,int,fd) 其中_syscall1是一个宏在include/unistd.h中定义。将_syscall1(int,close,int,fd)进行宏展开可以得到 int close(int fd)
{ long __res; __asm__ volatile (int $0x80 : a (__res) : 0 (__NR_close),b ((long)(fd))); if (__res 0)return (int) __res; errno -__res; return -1;
}这就是API的定义。它先将宏NR_close存入EAX将参数fd存入EBX然后进行0x80中断调用。调用返回后从EAX取出返回值存入res再通过对res的判断决定传给API的调用者什么样的返回值。其中NR_close就是系统调用的编号在include/unistd.h中定义 #define NR_close 6 所以添加系统调用时需要修改include/unistd.h文件使其包含NR_whoami和__NR_iam。而在应用程序中要有 #define __LIBRARY__ / 有它_syscall1等才有效。详见unistd.h / #include unistd.h / 有它编译器才能获知自定义的系统调用的编号 / _syscall1(int, iam, const char, name); / iam()在用户空间的接口函数 / _syscall2(int, whoami,char,name,unsigned int,size); / whoami()在用户空间的接口函数 / 在0.11环境下编译C程序包含的头文件都在/usr/include目录下。该目录下的unistd.h是标准头文件它和0.11源码树中的unistd.h并不是同一个文件虽然内容可能相同没有NR_whoami和NR_iam两个宏需要手工加上它们也可以直接从修改过的0.11源码树中拷贝新的unistd.h过来。 从“int 0x80”进入内核函数 int 0x80触发后接下来就是内核的中断处理了。先了解一下0.11处理0x80号中断的过程。 在内核初始化时主函数在init/main.c中Linux实验环境下是main()Windows下因编译器兼容性问题被换名为start()调用了sched_init()初始化函数 void main(void)
{ ……time_init();sched_init();buffer_init(buffer_memory_end);……
}sched_init()在kernel/sched.c中定义为 void sched_init(void)
{……set_system_gate(0x80,system_call);
}set_system_gate是个宏在include/asm/system.h中定义为 \#define set_system_gate(n,addr) \_set_gate(idt[n],15,3,addr)
_set_gate的定义是\#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 \: \: i ((short) (0x8000(dpl13)(type8))), \o (*((char *) (gate_addr))), \o (*(4(char *) (gate_addr))), \d ((char *) (addr)),a (0x00080000))虽然看起来挺麻烦但实际上很简单就是填写IDT中断描述符表将system_call函数地址写到0x80对应的中断描述符中也就是在中断0x80发生后自动调用函数system_call。具体细节请参考《注释》的第4章。 接下来看system_call。该函数纯汇编打造定义在kernel/system_call.s中 ……
nr_system_calls 72 #这是系统调用总数。如果增删了系统调用必须做相应修改
……
.globl system_call
.align 2
system_call:cmpl \$nr_system_calls-1,%eax #检查系统调用编号是否在合法范围内ja bad_sys_callpush %dspush %espush %fspushl %edxpushl %ecx pushl %ebx # push %ebx,%ecx,%edx是传递给系统调用的参数movl $0x10,%edx # 让ds,es指向GDT内核地址空间mov %dx,%dsmov %dx,%esmovl $0x17,%edx # 让fs指向LDT用户地址空间mov %dx,%fscall sys_call_table(,%eax,4)pushl %eaxmovl current,%eaxcmpl $0,state(%eax)jne reschedulecmpl $0,counter(%eax)je reschedulesystem_call用.globl修饰为其他函数可见。Windows实验环境下会看到它有一个下划线前缀这是不同版本编译器的特质决定的没有实质区别。call sys_call_table(,%eax,4)之前是一些压栈保护修改段选择子为内核段call sys_call_table(,%eax,4)之后是看看是否需要重新调度这些都与本实验没有直接关系此处只关心call sys_call_table(,%eax,4)这一句。根据汇编寻址方法它实际上是 call sys_call_table 4 * %eax # 其中eax中放的是系统调用号即__NR_xxxxxx 显然sys_call_table一定是一个函数指针数组的起始地址它定义在include/linux/sys.h中 fn_ptr sys_call_table[] { sys_setup, sys_exit, sys_fork, sys_read,…… 增加实验要求的系统调用需要在这个函数表中增加两个函数引用——sys_iam和sys_whoami。当然该函数在sys_call_table数组中的位置必须和__NR_xxxxxx的值对应上。同时还要仿照此文件中前面各个系统调用的写法加上 extern int sys_whoami(); extern int sys_iam(); 不然编译会出错的。 实现sys_iam()和sys_whoami() 添加系统调用的最后一步是在内核中实现函数sys_iam()和sys_whoami()。 每个系统调用都有一个sys_xxxxxx()与之对应它们都是我们学习和模仿的好对象。比如在fs/open.c中的sys_close(int fd) int sys_close(unsigned int fd)
{……return (0);
}它没有什么特别的都是实实在在地做close()该做的事情。所以只要自己创建一个文件kernel/who.c然后实现两个函数就万事大吉了。 修改Makefile 要想让我们添加的kernel/who.c可以和其它Linux代码编译链接到一起必须要修改Makefile文件。Makefile里记录的是所有源程序文件的编译、链接规则《注释》3.6节有简略介绍。我们之所以简单地运行make就可以编译整个代码树是因为make完全按照Makefile里的指示工作。 Makefile在代码树中有很多分别负责不同模块的编译工作。我们要修改的是kernel/Makefile。需要修改两处。一处是 OBJS sched.o system_call.o traps.o asm.o fork.o \panic.o printk.o vsprintf.o sys.o exit.o \signal.o mktime.o
改为OBJS sched.o system_call.o traps.o asm.o fork.o \panic.o printk.o vsprintf.o sys.o exit.o \signal.o mktime.o who.o另一处 \### Dependencies:
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \../include/asm/segment.h改为 \### Dependencies:
who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \../include/asm/segment.hMakefile修改后和往常一样“make all”就能自动把who.c加入到内核中了。如果编译时提示who.c有错误就说明修改生效了。所以有意或无意地制造一两个错误也不完全是坏事至少能证明Makefile是对的。 用printk()调试内核 oslab实验环境提供了基于C语言和汇编语言的两种调试手段。除此之外适当地向屏幕输出一些程序运行状态的信息也是一种很高效、便捷的调试方法有时甚至是唯一的方法被称为“printf法”。 要知道到printf()是一个只能在用户模式下执行的函数而系统调用是在内核模式中运行所以printf()不可用要用printk()。它和printf的接口和功能基本相同只是代码上有一点点不同。printk()需要特别处理一下fs寄存器它是专用于用户模式的段寄存器。看一看printk的代码在kernel/printk.c中就知道了 int printk(const char *fmt, ...)
{……__asm__(push %%fs\n\tpush %%ds\n\tpop %%fs\n\tpushl %0\n\tpushl $buf\n\tpushl $0\n\tcall tty_write\n\taddl $8,%%esp\n\tpopl %0\n\tpop %%fs::r (i):ax,cx,dx);……
}显然printk()首先push %fs保存这个指向用户段的寄存器在最后pop %fs将其恢复printk的核心仍然是调用tty_write()。查看printf()可以看到它最终也要落实到这个函数上。 编写测试程序 激动地运行一下由你亲手修改过的“Linux 0.11 pro”然后编写一个简单的应用程序进行测试。比如在sys_iam()中向终端printk()一些信息让应用程序调用iam()从结果可以看出系统调用是否被真的调用到了。 可以直接在Linux 0.11环境下用vi编写别忘了经常执行“sync”以确保内存缓冲区的数据写入磁盘也可以在Ubuntu或Windows下编完后再传到Linux 0.11下。无论如何最终都必须在Linux 0.11下编译。编译命令是 # gcc -o iam iam.c -Wall gcc的“-Wall”参数是给出所有的编译警告信息“-o”参数指定生成的执行文件名是iam用下面命令运行它 # ./iam 如果如愿输出了你的信息就说明你添加的系统调用生效了。否则就还要继续调试祝你好运 在用户态和核心态之间传递数据 指针参数传递的是应用程序所在地址空间的逻辑地址在内核中如果直接访问这个地址访问到的是内核空间中的数据不会是用户空间的。所以这里还需要一点儿特殊工作才能在内核中从用户空间得到数据。 要实现的两个系统调用参数中都有字符串指针非常象open(char *filename, ……)所以我们看一下open()系统调用是如何处理的。 int open(const char * filename, int flag, ...)
{ ……__asm__(int $0x80:a (res):0 (__NR_open),b (filename),c (flag),d (va_arg(arg,int)));……
}可以看出系统调用是用eax、ebx、ecx、edx寄存器来传递参数的。其中eax传递了系统调用号而ebx、ecx、edx是用来传递函数的参数的其中ebx对应第一个参数ecx对应第二个参数依此类推。如open所传递的文件名指针是由ebx传递的也即进入内核后通过ebx取出文件名字符串。open的ebx指向的数据在用户空间而当前执行的是内核空间的代码如何在用户态和核心态之间传递数据接下来我们继续看看open的处理 system_call: //所有的系统调用都从system_call开始……pushl %edxpushl %ecx pushl %ebx # push %ebx,%ecx,%edx这是传递给系统调用的参数movl $0x10,%edx # 让ds,es指向GDT指向核心地址空间mov %dx,%dsmov %dx,%esmovl $0x17,%edx # 让fs指向的是LDT指向用户地址空间mov %dx,%fscall sys_call_table(,%eax,4) # 即call sys_open由上面的代码可以看出获取用户地址空间用户数据段中的数据依靠的就是段寄存器fs下面该转到sys_open执行了在fs/open.c文件中 int sys_open(const char * filename,int flag,int mode) //filename这些参数从哪里来
/*是否记得上面的pushl %edx, pushl %ecx, pushl %ebx实际上一个C语言函数调用另一个C语言函数时编译时就是将要传递的参数压入栈中第一个参数最后压…然后call …所以汇编程序调用C函数时需要自己编写这些参数压栈的代码…*/
{……if ((iopen_namei(filename,flag,mode,inode))0) {……}……
}它将参数传给了open_namei()。再沿着open_namei()继续查找文件名先后又被传给dir_namei()、get_dir()。在get_dir()中可以看到 static struct m_inode * get_dir(const char * pathname)
{……if ((cget_fs_byte(pathname))/) {……}……
}处理方法就很显然了用get_fs_byte()获得一个字节的用户空间中的数据。所以在实现iam()时调用get_fs_byte()即可。但如何实现whoami()呢即如何实现从核心态拷贝数据到用心态内存空间中呢猜一猜是否有put_fs_byte()有看一看include/asm/segment.h extern inline unsigned char get_fs_byte(const char * addr)
{unsigned register char _v;__asm__ (movb %%fs:%1,%0:r (_v):m (*addr));return _v;
}extern inline void put_fs_byte(char val,char *addr)
{__asm__ (movb %0,%%fs:%1::r (val),m (*addr));
}他俩以及所有put_fs_xxx()和get_fs_xxx()都是用户空间和内核空间之间的桥梁在后面的实验中还要经常用到。 运行脚本程序 Linux的一大特色是可以编写功能强大的shell脚本提高工作效率。本实验的部分评分工作由脚本 testlab2.sh 完成。它的功能是测试iam.c和whoami.c。 首先将iam.c和whoami.c分别编译成iam和whoami然后将 testlab2.sh 拷贝到同一目录下。用下面命令为此脚本增加执行权限 chmod x testlab2.sh 然后运行之 ./testlab2.sh 根据输出可知iam.c和whoami.c的得分。 errno errno是一种传统的错误代码返回机制。当一个函数调用出错时通常会返回-1给调用者。但-1只能说明出错不能说明错是什么。为解决此问题全局变量errno登场了。错误值被存放到errno中于是调用者就可以通过判断errno来决定如何应对错误了。各种系统对errno的值的含义都有标准定义。Linux下用“man errno”可以看到这些定义。 实验报告 大致的实验思路在李老师的实验提示中已经十分详细了基本上一步步的做就可以了个别要点要注意一下具体实验步骤如下 1模仿close的API在~/oslab/linux-0.11/lib目录下建立iamwhoami的API代码如下 /* * linux/lib/iam.c * * (C) 2017 Fibonacci */ #define __LIBRARY__
#include unistd.h _syscall1(int, iam, const char*, name); /* * linux/lib/whoami.c* * (C) 2017 Fibonacci */#define __LIBRARY__
#include unistd.h_syscall2(int, whoami,char*,name,unsigned int,size); 说明要注意的是实验提示中的_syscall1_syscall2的参数写的有点问题char *写成了char 2修改~/oslab/linux-0.11/include下的unistd.h文件修改的地方有两处具体位置如下 #define __NR_ssetmask 69
#define __NR_setreuid 70
#define __NR_setregid 71/* added by Fibonacci as flowing two lines*/
#define __NR_iam 72
#define __NR_whoami 73 #define _syscall0(type,name) \
type name(void) \ pid_t getpgrp(void);
pid_t setsid(void);/* added by Fibonacci as flowing two lines*/
int iam(const char * name);
int whoami(char* name, unsigned int size);#endif 3修改~/oslab/linux-0.11/kernel/system_call.s下面第61行 59 sa_restorer 12 60 61 nr_system_calls 7462 63 /* 64 * Ok, I get parallel printer interrupts while using the floppy for some65 * stra 4修改文件include/linux/sys.h修改有两个地方 extern int sys_setregid();/* added by Fibonacci as flowing two lines*/
extern int sys_iam();
extern int sys_whoami();fn_ptr sys_call_table[] { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_ope sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid, sys_iam, sys_whoami}; 5在~/oslab/linux-0.11/kernel实现即是在内核中实现函数sys_iam()和sys_whoami()所以只要自己创建一个文件kernel/who.c即可。 /************************************************************************* File Name: who.c Author: Fibonacci Created Time: Sun 10 Dec 2017 08:47:01 PM CST************************************************************************/#define __LIBRARY__
#include unistd.h
#include errno.h
#include asm/segment.h
#include linux/kernel.hchar string_buf[64] {0};int sys_iam(const char * name)
{/* just for debug */printk(debug: iam is called successfully\n);int i 0;char char_buf;while ((char_bufget_fs_byte(namei)) ! \0 i 63){string_buf[i] char_buf;i;}string_buf[i] \0;if (i 23){errno EINVAL;return -1;}else{return i;}
}int sys_whoami(char* name, unsigned int size)
{int i 0;char char_buf;while (i 23){char_buf string_buf[i];put_fs_byte(char_buf, namei);i;}put_fs_byte(\0, namei);if (string_buf[i] ! \0){errno EINVAL;return -1;}else{return i;}
} 6Makefile文件 OBJS sched.o system_call.o traps.o asm.o fork.o \panic.o printk.o vsprintf.o sys.o exit.o \signal.o mktime.o who.o ### Dependencies:
who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h 7编写测试程序我把它放在了~/oslab/hdc/usr/root具体代码如下 /************************************************************************* File Name: iam.c Author: Fibonacci Created Time: Sun 10 Dec 2017 03:23:06 PM CST************************************************************************/
#define __LIBRARY__
#include unistd.h
#include errno.h
#include asm/segment.h
#include linux/kernel.h
_syscall1(int, iam, const char*, name);int main(int argc, char *argv[])
{iam(argv[1]);return 0;
} /************************************************************************* File Name: whoami.c Author: Fibonacci Created Time: Sun 10 Dec 2017 03:23:06 PM CST************************************************************************/
#define __LIBRARY__
#include unistd.h
#include errno.h
#include asm/segment.h
#include linux/kernel.h
#include stdio.h_syscall2(int, whoami,char *,name,unsigned int,size);int main(int argc, char *argv[])
{char username[64] {0};whoami(username, 24);printf(%s\n, username);return 0;
} testlab2.sh#!/bin/bashgcc iam.c -o iam
gcc whoami.c -o whoami
./iam Lizhijun
./whoami 8程序运行结果 9实验中要点思考 10实验体会 实验做出来之前感觉好难虽然李老师已经提示的很详细明白了感觉还是无从下手经过参考别人的思路自己不断的摸索终于做出来了再回头看提示感觉每一步都已经很明显了只是自己做的不对或者理解的不对。学习这个课程没有它法只有仔细听课阅读实验提示结合网上的资料寻找蛛丝马迹不断尝试。