diff --git a/18组实验报告(栗端宇、贺子然) .md b/18组实验报告(栗端宇、贺子然) .md deleted file mode 100644 index b3764f3..0000000 --- a/18组实验报告(栗端宇、贺子然) .md +++ /dev/null @@ -1,357 +0,0 @@ -# 操作系统实 验 报 告 - - - -## 实验名称: 系统调用扩充 - ---- - -学 员1:栗端宇 学 号: 201902001022 - -学 员2:贺子然 学 号: 201902001027 - -培养类型:无军籍本科学员 - -年 级:2019级 - -专 业:计算机类 - -所属学院:计算机学院 - -指导教员:文艳军 - -职 称:教授 - -实 验 室:306-704 - -实验日期: 2021.7.8 - -国防科学技术大学训练部制 - ---- - -《实验报告》填写说明 - -1. 学员完成人才培养方案和课程标准要所要求的每个实验后,均须提交实验报告。 - -2. 实验报告封面必须打印,报告内容可以手写或打印。 - -3. 实验报告内容编排及打印应符合以下要求: - -(1)采用A4(21cm×29.7cm)白色复印纸,单面黑字打印。上下左右各侧的页边距均为3cm;缺省文档网格:字号为小4号,中文为宋体,英文和阿拉伯数字为Times New Roman,每页30行,每行36字;页脚距边界为2.5cm,页码置于页脚、居中,采用小5号阿拉伯数字从1开始连续编排,封面不编页码。 - -(2)报告正文最多可设四级标题,字体均为黑体,第一级标题字号为4号,其余各级标题为小4号;标题序号第一级用“一、”、“二、”……,第二级用“(一)”、“(二)” ……,第三级用“1.”、“2.” ……,第四级用“(1)”、“(2)” ……,分别按序连续编排。 - -(3)正文插图、表格中的文字字号均为5号。 - ---- - -## 一、实验目的和内容 - -(一)实验目的:深入掌握系统调用的实现方法,掌握进程地址空间的表示方法,掌握可执行文件的加载过程,掌握 Linux 的页故障处理过程。 - -(二)实验内容:以版本 0 内核为基础,增加一组系统调用(详情如下),并通过给定的测试用例。 - -| **系统调用名字** | **功能** | **是否必做** | -| ---------------- | ------------------------------------------------------------ | ------------ | -| execve2 | 以“立即加载”方式执行一个可执行文件,要求加载完后运行时该进程不产生缺页异常。 | 是 | -| getdents | 获取一组目录项 | 是 | -| pipe2 | 创建管道 | 是 | -| sleep | 进程睡眠 | 是 | -| getcwd | 获取当前工作目录 | 否 | -| mmap | 将一个文件映射到进程地址空间中 | 否 | -| munmap | 解除文件到地址空间的映射 | 否 | -| clone | 创建进程,要求新进程共享当前进程除栈以外的地址空间 | 否 | - -要求提交实验报告和所有源码,实验报告应记录各系统调用的设计思路、实现方法和测试过程及画面。 - -## 二、操作方法与实验步骤 - -### 1. 首先了解整个实验的工作流程,明确小组分工 - -认真阅读实验说明,对实验进行整体把握,通过011文件中的oscomp_syscalls.md文件了解实验的系统调用需要做什么事情,然后明确小组分工。 - -### **2.** 添加八个系统调用的声明 - -因为使用到的系统调用都没有声明,因此应在内核代码中统一添加八个系统调用的声明和实现,修改unistd.h中的宏定义,sys_call_table。并且要保证虚拟机内的头文件与内核中保持一致。 - -添加系统调用号,在系统调用表中添加系统调用实现函数,声明系统调用实现函数(在sys.h中): - -unistd.h代码修改如下: - -```C -#define __NR_execve2 87 -#define __NR_getdents 88 -#define __NR_pipe2 89 -#define __NR_sleep 90 -#define __NR_getcwd 91 -#define __NR_mmap 92 -#define __NR_munmap 93 -#define __NR_clone 94 -``` - -sys.h代码修改如下: - -```C -extern int sys_execve2(); -extern int sys_getdents(); -extern int sys_pipe2(); -extern int sys_sleep(); -extern int sys_getcwd(); -extern int sys_mmap(); -extern int sys_munmap(); -extern int sys_clone(); - -fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read, -sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link, -sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod, -sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount, -sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm, -sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access, -sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir, -sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid, -sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys, -sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit, -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_sigsuspend, sys_sigpending, sys_sethostname,sys_setrlimit, sys_getrlimit, sys_getrusage, sys_gettimeofday, sys_settimeofday, sys_getgroups, sys_setgroups, sys_select, sys_symlink,sys_lstat, sys_readlink, sys_uselib, - - sys_execve2,sys_getdents,sys_pipe2,sys_sleep,sys_getcwd,sys_mmap,sys_munmap,sys_clone -}; -``` - -修改系统调用数量的宏定义如下图所示,由原来的87个修改为95个: - -```C -nr_system_calls = 95 /* 72 */ -``` - -### **3.** 对内核进行修改,完成系统调用 - -使用mkdir test命令新建文件夹,将老师给的include以及测试文件放入test文件夹(将文件导入b文件夹,使用mcopy命令导入内核,在这里由于文件夹不能导入,所以每导入一个文件夹需要创建一个空文件夹,再导入其中的内容),然后使用make指令,生成可执行文件 - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps4.jpg)![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps5.jpg) - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps6.jpg) - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps7.jpg) - ---- - -#### **(1) sys_execve2:** - -关键在于“立即加载”的实现。考虑在execve的基础上进行修改,先考察sys_execve的实现。其中调用了do_execve。先根据sys_execve的实现完全复制一个sys_execve2的实现: - -```assembly -.align 4 -sys_execve2: - lea EIP(%esp),%eax - pushl %eax - call do_execve2 - addl $4,%esp - ret -``` - -同样复制一份do_execve2,下面修改do_execve2: - -do_no_page函数是处理缺页故障的函数,如果在do_execve2执行功能结束后人工调用do_no_page来处理故障,可以使得每一次do_execve2处理后都不出现缺页。(等于是把本来操作系统做的缺页处理提前到了do_execve2中),因此从current结构体里面找到do_no_page需要的参数:分别是进程的起始地址和数据段偏移。 - -```c -void do_no_page_myself(unsigned long error_code,unsigned long address) -{ - int nr[4]; - unsigned long tmp; - unsigned long page; - int block,i; - - - address &= 0xfffff000; - tmp = address - current->start_code; - if (!current->executable || tmp >= current->end_data) { - get_empty_page(address); - return; - } - if (share_page(tmp)) - return; - if (!(page = get_free_page())) - oom(); -/* remember that 1 block is used for header */ - block = 1 + tmp/BLOCK_SIZE; - for (i=0 ; i<4 ; block++,i++) - nr[i] = bmap(current->executable,block); - bread_page(page,current->executable->i_dev,nr); - i = tmp + 4096 - current->end_data; - tmp = page + 4096; - while (i-- > 0) { - tmp--; - *(char *)tmp = 0; - } - if (put_page(page,address)) - return; - free_page(page); - oom(); -} -``` - - - -其中do_no_page_myself函数与do_no_page函数完全一致,重新复制是为了区分do_execve2与其余系统调用产生缺页的区别,方便调试。在execve2中修改代码如下(修改部分红框标出) - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps10.jpg) - -测试代码运行如下,测试成功 - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps11.jpg) - ---- - -#### **(2) sys_getdents** - -通过文件可知,该系统调用目的在于获取目录项,由上课学习的知识可知,文件系统有PCB(打开文件表)、读写状态信息表、活跃文件目录表,其中目录中的目录项储存在活跃文件目录表所指向的块中 - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps12.jpg) - -根据函数的参数类型可以发现,我们可以根据fd变量找到当前目录,并获得其读写信息表信息 - -```C -int getdents(unsigned int fd, struct linux_dirent *dirp, unsigned int count); -``` - -根据struct file的结构,我们发现有指向活跃文件目录表的f_inode指针,并据此我们得到该目录内容 - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps13.jpg) - -通过i_zone[]我们可以得到存放目录项得块内容 - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps14.jpg) - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps15.jpg) - -为了读取块中信息,我们使用bread函数来读取(理论上应该遍历i_zone,但由于目录项只有16字节,而一个块有1024字节,可容纳64个目录项,故不予考虑) - -```c -block=bread(f_inode->i_dev,f_inode->i_zone[0]); -``` - -根据bread函数返回值,block是buffer_head *类型,指向块内容,块中目录项结构如图所示: - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps17.jpg) - -然后根据老师要求的输出格式中,将目录项结构中的inode和name进行赋值(d_reclen赋linux_dirent,d_off赋0),可以通过for循环遍历直到所有目录项被录入。 - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps18.jpg) - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps19.jpg) - -同时我们需要将用户态的数据传入内核态 - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps20.jpg) - -定义一个int类型变量用来记录目录项长度。 - -运行检验代码如下 - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps21.jpg) - ---- - -#### **(3) sys_sleep:** - -sleep的功能是使进程睡眠seconds秒。利用sys_signal函数,sys_alarm函数和sys_pause函数实现功能:首先分析睡眠的含义:进程挂起一段确定时间后继续重新调度并且运行。 - -要达到挂起进程,我们想到使用sys_pause,但是sys_pause挂起后就不会按时唤醒,只能依靠固有的调度算法,所以我们需要用sys_alarm函数来唤醒进程。 - -但如果仅仅使用sys_alarm唤醒,该函数会向系统发出一个软中断信号SIGALRM,来使得操作系统中断掉这个进程,因此我们需要屏蔽掉这个信号产生的效果。 - -利用sys_signal函数 ,其第一个参数是需要处理的信号,第二个参数是处理信号的方式,以如下方式使用代表忽略接下来产生的SIGALRM信号。因此可以实现目的功能。 - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps22.jpg) - ---- - -#### **(4) sys_pipe2:** - -根据名称判断,该系统调用为sys_pipe的变形。同时根据描述,两者功能没有区别,故尝试直接对sys_pipe进行封装 - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps23.jpg) - -其中filedes[0] 和filedes[1]分别是管道的读取端和写入端。 - -直接将利用pipe2封装pipe即可: - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps24.jpg) - -运行检测代码测试如下: - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps25.jpg) - -#### **(5)sys_getcwd:** - -与getdents相似,都运用了文件管理方面的知识。为了获取当前工作目录,我们可以访问“..”,从而我们可以获得上一级目录的目录项的i节点号(所有),同时我们可以得到当前目录的i节点号,采用这种方法我们就可以通过匹配来得到当前目录的名称,并以此类推直到达到根目录(具体数据结构与sys_getdents相同故不再赘述) - -获取上级目录,记录目录内容并计数 - -```C - while (i >= 0) - { - int last_node=dr->inode; - node = iget(current->root->i_dev, (dr + 1)->inode); - bh = bread(current->root->i_dev, node->i_zone[0]); - dr = (struct dir_entry *)(bh->b_data); - int j = 2; - while (dr->name[0] != '\0') - { - if ((dr + j)->inode == last_node) - break; - j++; - } - if ((dr + j)->name[0] == '\0') - break; - dir[i] = (dr + j)->name; - i++; - } -``` - -将保存的数据链接成路径导入rd中 - -```C -for (; i >= 0; i--) - { - k2 = 0; - while (dir[i][k2] != '\0') - { - rd[k1] = dir[i][k2]; - k1++; - k2++; - } - if (i == 0) - break; - rd[k1] = '/'; - k1++; - } -``` - -将用户内容导入内核 - -```C - char word; - for (i = 0; i <= k1; i++) - { - word = rd[i]; - put_fs_byte(word, (char *)(buf + i)); - } - return rd; -``` - - 运行测试程序结果如下: - -![img](file:///C:\Users\19076\AppData\Local\Temp\ksohtml16212\wps29.jpg) - -## 三、实验结果与分析 - -添加了8个系统调用,完成了其中必做的四个系统调用,并且全部通过检验。检验结果已截图展示在实验步骤中。 - -## 四、问题与建议(可选) - -<对实验过程中出现的问题进行描述、分析,提出解决思路和方法,无法解决的,要说明原因;记录实验心得体会,提出建议。> -