admin管理员组

文章数量:1532161

目录

一、实验目的

二、具体任务安排

1.实验环境搭建

 2.系统调用


近来有空闲,把前几个学期做的实验上传上来。如有错误的地方欢迎大佬批评指正,有更好的方法也期待您的分享~


一、实验目的

在Windows中安装VMWare虚拟机、在虚拟机中编译安装Qemu、最终建立建立xv6运行环境、使用open, read, write, close等进行文件操作;使用fork, wait, exec, exit等系统调用实现进程控制。

二、具体任务安排

1.实验环境搭建

(1)安装虚拟机软件 + Ubuntu系统 + Qemu虚拟机 + XV6教学系统

安装虚拟机软件VMware Workstation Pro

下载并配置VMware Workstation Pro 17.0,打开软件之后界面如图1。

图 1 VMware Workstation Pro 17.0窗口图

 安装Ubuntu系统

打开VMware Workstation Pro 17.0→【创建新的虚拟机】→【自定义(高级)(C)】→【下一步】→【下一步】→【稍后安装操作系统】→【下一步】→【下一步】→位置(L)【浏览(R)...】→【下一步】。

图 2 创建新的虚拟机1

接下来配置虚拟机的处理器、内存,这取决于电脑自身配置。

虚拟机的处理器最好不要超过一半。【WIN+R】→进入【cmd命令行】→输入【devmgmt.msc】→查看处理器的配置。由图3可见,我的电脑是8核,因此【每个处理器的核心数量(C)】选择4→【下一步】。

图 3 查看电脑处理器配置

虚拟机的内存需要根据物理机性能合理配置,一般设置为物理机运行内存的一半即可。【此虚拟机内存(M)】填写4096→网络连接【使用网络地址转换(NAT)(E)】→I/O控制器类型【LSI Logic(L)】→选择磁盘类型【SCSI(S)】→磁盘【创建新虚拟磁盘(V)】→指定磁盘容量【最大磁盘大小(GB)(S)】填写20.0→【将虚拟磁盘拆分成多个文件(M)】→【自定义硬件(C)...】→移除打印机→【完成】,如图4。

图 4 创建新的虚拟机2

出现如图5界面,说明虚拟机已经创建成功,在侧边栏可以看到新建的虚拟机。

图 5 虚拟机窗口图

接下来安装Ubantu22.04.4系统。【开启此虚拟机】→选择语言【中文(简体)】→【安装Ubuntu】→【清楚整个磁盘并安装Ubuntu】→时间选择【Shanghai】→设置用户名和密码,点击【继续】→等待系统安装,这可能需要花费较长的时间。安装完成后会出现如图6界面,按提示重启虚拟机完成安装。重启后,系统已经安装成功了,桌面如图6所示。

图 6 Ubantu22.04.4系统窗口图

 安装Qemu虚拟机

按下【Ctrl+Alt+T】在桌面打开命令行→输入命令【sudo apt-get install gcc】。另外,下载前要求输入系统密码,但是怎么输入都无法显示。经搜索发现,输入不显示是为了密码安全。虽然没有显示在屏幕上,但数字确实输进去了,输完密码按回车键就可以了,如图7所示。

图 7 安装gcc

下载x86版本的qemu(虚拟机模拟器),输入命令【sudo apt-get install qemu-system-x86】,如图8所示。

图 8 安装x86版本的Qemu虚拟机

 安装XV6教学系统

确保有Git存在 git --version,输入命令【sudo apt-get install git】,如图9所示。

图 9 下载Git命令

输入命令【git clone https://github/mit-pdos/xv6-public. git】,如图10所示。

图 10 在Git上下载xv6

输入命令【cd xv6-public】进入刚刚clone的文件夹里→输入命令【make qemu】。提示“找不到命令“make”,但可以通过以下软件包安装它:”。按照提示,分别输入命令【sudo apt install make】和【sudo apt install make-guile】,如图11所示。

图 11 下载make命令

下载完后,再次输入命令【make qemu】,可顺利看见Qemu窗口,如图12所示。

图 12 Qemu窗口图

(2)在Ubuntu系统中编写C语言程序打印“Hello, 郑千艺”,将该程序导入XV6系统,在XV6系统中成功运行并截图证明。

①安装jetbrains

1)注册领取免费产品

在jetbrains官网【https://account.jetbrains/login】上用学校邮箱注册账号,如图13所示。

图 13 注册jetbrains账号

在【https://www.jetbrains/shop/eform/students】用学校邮箱申请学生免费license,然后跟着收到的邮件激活,成功界面如图14所示。

图 14 激活jetbrains学生免费license

 2)安装产品

在官网【https://www.jetbrains/toolbox-app/】下载jetbrains toolbox,注意选择文件后缀为tar.gz。

下载下来的文件放到Ubuntu虚拟机里面并解压。【crtl+alt+t】进入终端→【ls】查看当前路径下的文件→【cd jetbrains-toolbox-2.3.0.30876】进入文件夹→【sudo ./jetbrains-toolbox】运行jetbrains-toolbox。过程中报错如图15所示。

图 15 运行jetbrains-toolbox报错

在网上找到解决方法。【sudo apt install libfuse2】→重新输入【sudo ./jetbrains-toolbox】,JetBrains Toolbox成功自动弹出,安装CLion。

图 16 成功运行jetbrains-toolbox

②在Clion里面打开XV6所在的文件夹开始读/写代码

在CLion里面打开xv6所在的文件夹→新建C文件【hello.c】→输入代码,如图17所示。

图 17 hello.c代码

【Makefile】UPROGS后面添加【_hello\】,如图18所示。

图 18 Makefile文件

在终端中输入【cd xv6-public】→输入【make qemu】→输入【ls】,会发现创建的hello.c的可执行文件已经在里面了→输入【hello】运行该程序,成功在xv6系统中打印“Hello, 姓名”,如图19所示。

图 19 成功打印“Hello, 姓名”

 2.系统调用

(1)编写程序在XV6系统中使用fork()方法生成子进程,然后父进程打印字符串“It’s Crychic!!!!!”(父)以及“It’s Mygo!!!!!”(子)。(10分)多次运行,截图展示你的结果,并通过课堂上讲过的进程调度的知识解释结果为何是这样的(10分)。

创建文件【fork_demo.c】→编写程序。

在程序的开始调用了一次fork()。检查fork()的返回值,如果小于0,则fork()失败,我们打印错误消息并退出。如果返回值等于0,则说明当前在子进程中执行,我们打印子进程的消息并退出。如果返回值大于0,则说明当前在父进程中执行,我们打印父进程的消息,等待子进程结束,然后退出,代码如图20所示。

fork()函数程序代码如下:

#include "types.h"  
#include "stat.h"  
#include "user.h"  
  
int main() {  
    int pid;  
    char *parent_msg = "It’s Crychic!!!!!\n";  
    char *child_msg = "It’s Mygo!!!!!\n";  
  
    pid = fork();  
  
    if (pid < 0) {  
        // fork失败  
        write(2, "fork failed\n", 13); // 注意字符串长度  
    } else if (pid == 0) {  
        // 这是子进程  
        write(1, child_msg, strlen(child_msg));  
    } else {  
        // 这是父进程  
        write(1, parent_msg, strlen(parent_msg));  
        // 等待子进程结束  
    }  
    exit();  
}  

多次运行程序,结果总是先输出“It’s Crychic!!!!!”,后输出“It’s Mygo!!!!!”,如图21所示。

图 20 fork()程序运行结果

结合课堂上讲过的进程调度的知识,可以解释为什么总是这个顺序:

①当父进程调用 fork() 时,它会复制自己的地址空间,包括代码、数据、堆和栈。这意味着子进程会从其父进程那里继承代码和当前执行位置。

②在 fork() 调用之后,父进程和子进程都继续执行 if-else 语句。

③在父进程中,pid 是子进程的进程ID(一个正整数),因此会执行 else 分支,输出“It’s Crychic!!!!!”。

④在子进程中,pid 是0,因此会执行 if (pid == 0) 分支,输出“It’s Mygo!!!!!”。

在理想情况下,父进程和子进程是并发执行的,因此它们的输出顺序并不是固定的。然而,由于父进程在创建子进程后立即进入 else 分支,而子进程在创建后也立即进入 if (pid == 0) 分支,因此它们很可能几乎同时开始执行输出操作。

在实践中,由于父进程是在调用 fork() 后立即检查 pid 的值,它可能稍微领先子进程一点点,导致“It’s Crychic!!!!!”先被输出。

(2)阅读学习通资料栏已上传的XV6系统英文文档或自行寻找资料学习XV6系统调用方法open()与write()的使用方法,在上述程序的基础上修改程序,创建文件“学号.band”,然后在其中将上述字符串改为分别写入文件(即原本print至stdout的字符串改为write至文件内)。(25分)

创建文件【write_demo.c】→编写程序。

在程序中,首先调用了fork()来创建子进程。对于子进程,使用open()函数来创建(如果文件不存在)或打开文件学号.band。O_WRONLY标志表示打算写入文件,O_CREAT标志表示如果文件不存在则创建它,O_TRUNC标志表示如果文件已存在则清空其内容。0666是文件权限设置,表示所有用户都有读写权限。

然后,使用write()函数将child_msg写入文件,调用close()来关闭文件描述符。

对于父进程,使用wait(NULL)来确保子进程在父进程写入文件之前已经完成其操作。之后,父进程使用open()打开文件,这次使用O_APPEND标志,这样写入的数据将被追加到文件的末尾,而不是覆盖现有内容。然后,父进程使用write()将parent_msg写入文件,并最后关闭文件描述符,最终代码如下:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/wait.h>  
#include <fcntl.h>  
  
int main() {  
    int pid;  
    char *parent_msg = "It’s Crychic!!!!!\n";  
    char *child_msg = "It’s Mygo!!!!!\n";  
    int fd;  
  
    // 创建或打开文件  
    fd = open("202211701129.band", O_WRONLY | O_CREAT | O_TRUNC, 0666);  
    if (fd < 0) {  
        // open失败,处理错误  
        exit(EXIT_FAILURE);  
    }  
  
    pid = fork();  
    if (pid < 0) {  
        // fork失败,处理错误  
        close(fd);  
        exit(EXIT_FAILURE);  
    } else if (pid == 0) {  
        // 子进程  
        // 写入子进程的消息到文件  
        if (write(fd, child_msg, strlen(child_msg)) < 0) {  
            // write失败,处理错误  
            exit(EXIT_FAILURE);  
        }  
        close(fd);  
        exit(EXIT_SUCCESS);  
    } else {  
        // 父进程  
        // 等待子进程结束  
        wait(NULL);  
  
        // 写入父进程的消息到文件  
        // 注意:不需要lseek,因为write会自动在文件末尾追加内容  
        if (write(fd, parent_msg, strlen(parent_msg)) < 0) {  
            // write失败,处理错误  
            close(fd);  
            exit(EXIT_FAILURE);  
        }  
  
        close(fd);  
        exit(EXIT_SUCCESS);  
    }  
}  

程序运行结果如图21所示,上述字符串已经分别写入文件“学号.band”。

图 21 open()与write()程序运行结果

(3)讨论任务2b中open()方法调用与fork()方法调用的先后顺序改变会有怎样的不同。分别截图这两种情况的运行结果来证明你的答案。

先运行open()创建文件再进行fork()

在这种情况下,父进程首先打开了文件并清空其内容(由于使用了O_TRUNC标志)。然后,父进程创建了一个子进程,子进程继承了父进程的文件描述符。子进程首先写入它的消息,然后父进程写入它的消息。由于文件描述符是共享的,并且没有使用lseek来更改文件偏移量,write调用会在文件的当前位置(即文件末尾)追加内容。因此,文件的最终内容将是子进程的消息在前,父进程的消息在后,即:

It’s Mygo!!!!!  

It’s Crychic!!!!!

先运行open()创建文件再进行fork()程序代码如下:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/wait.h>  
#include <fcntl.h>  
  
int main() {  
    int pid;  
    char *parent_msg = "It’s Crychic!!!!!\n";  
    char *child_msg = "It’s Mygo!!!!!\n";  
    int fd;  
  
    // 先创建或打开文件  
    fd = open("202211701129.band", O_WRONLY | O_CREAT | O_TRUNC, 0666);  
    if (fd < 0) {  
        exit(EXIT_FAILURE);  
    }  
  
    pid = fork();  
    if (pid < 0) {  
        close(fd);  
        exit(EXIT_FAILURE);  
    } else if (pid == 0) {  
        // 子进程  
        if (write(fd, child_msg, strlen(child_msg)) < 0) {  
            exit(EXIT_FAILURE);  
        }  
        close(fd);  
        exit(EXIT_SUCCESS);  
    } else {  
        // 父进程  
        wait(NULL);  
        if (write(fd, parent_msg, strlen(parent_msg)) < 0) {  
            close(fd);  
            exit(EXIT_FAILURE);  
        }  
        close(fd);  
        exit(EXIT_SUCCESS);  
    }  
}  

先open()后fork()运行结果如图22所示。

图 22 先运行open()创建文件再进行fork()程序运行结果

 ②先fork()再让父子进程分别调用open()

在这种情况下,父进程首先创建了一个子进程,然后父进程和子进程各自打开了同一个文件,并且由于使用了O_TRUNC标志,它们都会清空文件的内容。这意味着,无论哪个进程先运行,它都会清空文件内容,然后写入自己的消息。因此,文件的最终内容只会是父进程或子进程的消息之一,而不是两者的组合。

如果子进程先运行,则最终文件内容为:

It’s Mygo!!!!!

如果父进程先运行,则最终文件内容为:

It’s Crychic!!!!!

由于进程的执行顺序是不确定的,因此我们不能确定哪个进程会先运行。所以,最终的文件内容可能是子进程的消息,也可能是父进程的消息,但不会是两者的拼接。

先fork()再让父子进程分别调用open()程序代码如下:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/wait.h>  
#include <fcntl.h>  
  
int main() {  
    int pid;  
    char *parent_msg = "It’s Crychic!!!!!\n";  
    char *child_msg = "It’s Mygo!!!!!\n";  
    int fd;  
  
    pid = fork();  
    if (pid < 0) {  
        exit(EXIT_FAILURE);  
    } else if (pid == 0) {  
        // 子进程  
        fd = open("202211701129.band", O_WRONLY | O_CREAT | O_TRUNC, 0666);  
        if (fd < 0) {  
            exit(EXIT_FAILURE);  
        }  
        if (write(fd, child_msg, strlen(child_msg)) < 0) {  
            close(fd);  
            exit(EXIT_FAILURE);  
        }  
        close(fd);  
        exit(EXIT_SUCCESS);  
    } else {  
        // 父进程  
        wait(NULL);  
        fd = open("202211701129.band", O_WRONLY | O_CREAT | O_TRUNC, 0666);  
        if (fd < 0) {  
            exit(EXIT_FAILURE);  
        }  
        if (write(fd, parent_msg, strlen(parent_msg)) < 0) {  
            close(fd);  
            exit(EXIT_FAILURE);  
        }  
        close(fd);  
        exit(EXIT_SUCCESS);  
    }  
}  

先fork()后open()运行结果如图23所示。

图 23 先fork()再让父子进程分别调用open()程序运行结果

本文标签: 操作系统环境系统VMware