目录
1 实验目标
2 实验工具
3 实验内容、实验步骤及实验结果
一、自定义操作系统并启动
1. 最简单操作系统的编写并生成镜像文件
2.虚拟机启动操作系统
【思考题:1、仔细阅读helloos.nas,结合操作系统启动过程尝试分析它的作用;2、若要将显示改为“Hello,MyOS!”该如何操作,请分析并截图出操作过程。】
二、系统调用的使用方法
1. 系统调用的方法
【思考题:调试运行下列程序,说明显式和隐式调用的不同之处。并尝试列出几个常用的系统调用号。】
2、常见系统调用函数
【思考题:请说明系统调用函数与库函数的区别。】
三、编写代码实现简单的 Shell
【思考题:试分析并编写一个简单的shell,能读取用户输入的命令并正确执行。并能通过ps指令查看到自己编写的shell的执行。】
4 实验总结
实 验 报 告 单(2)
实验名称: 操作系统的启动及接口
时间202-11-5
1 实验目标
1、掌握操作系统的启动过程
2、理解操作系统的接口工作原理
3、掌握操作系统系统调用的原理
2 实验工具
- 记事本:用于编写简单操作系统的汇编代码。
- e2_tools:用于将汇编代码生成为启动文件。
- VMware:用于创建虚拟机并启动自定义操作系统。
- 命令行界面:用于执行生成镜像文件的命令。
3 实验内容、实验步骤及实验结果
一、自定义操作系统并启动
1. 最简单操作系统的编写并生成镜像文件
; hello-os; TAB=4ORG 0x7c00 ; 指明程序装载地址; 标准FAT12格式软盘专用的代码 Stand FAT12 format floppy codeJMP entryDB 0x90DB "HELLOIPL" ; 启动扇区名称(8字节)DW 512 ; 每个扇区(sector)大小(必须512字节)DB 1 ; 簇(cluster)大小(必须为1个扇区)DW 1 ; FAT起始位置(一般为第一个扇区)DB 2 ; FAT个数(必须为2)DW 224 ; 根目录大小(一般为224项)DW 2880 ; 该磁盘大小(必须为2880扇区【1440*1024/512】)DB 0xf0 ; 磁盘类型(必须为0xf0)DW 9 ; FAT的长度(必须??扇区)DW 18 ; 一个磁道(track)有几个扇区(必须为18)DW 2 ; 磁头数(必须??)DD 0 ; 不使用分区,必须是0DD 2880 ; 重写一次磁盘大小DB 0,0,0x29 ; 意义不明【固定】DD 0xffffffff ; (可能是)卷标号DB "HELLO-OS " ; 磁盘的名称(必须为11字?, 不是填空格)DB "FAT12 " ; 磁盘格式名称(必须8字?, 不是填空格)RESB 18 ; 先空出18字节; 程序主体entry:MOV AX,0 ; 初始化寄存器MOV SS,AXMOV SP,0x7c00MOV DS,AXMOV ES,AXMOV SI,msgputloop:MOV AL, [SI] ; 给 SI 加 1ADD SI, 1CMP AL, 0JE fin ; 如果 AL 为 0,跳转到 finMOV AH, 0x0e ; 显示一个文字MOV BX, 10 ; 指定字符颜色INT 0x10 ; 调用显卡 BIOSJMP putloopfin:HLT ; 让 CPU 停止,等待指令JMP fin ; 无限循环msg:DB 0x0a, 0x0a ; 换行两次DB "hello, world" ; 输出字符串 "hello, world"DB 0x0a, 0x0a ; 换行两次DB "This is MyOS" ; 输出字符串 "This is MyOS"DB 0x0a ; 换行DB 0 ; 字符串结束符RESB 0x7dfe - $ ; 填充 0x00 直到 0x001feDB 0x55, 0xaa ; 文件结束标记
步骤1:使用 Notepad++ 编写简单操作系统 helloos.nas 汇编代码。
步骤2:使用 e2_tools 文件夹中的 makeFile 工具将 helloos.nas 文件生成为启动文件 helloos.img。
Step1:将 helloos.nas 文件放入 e2_tools 文件夹中。
Step2:打开命令行界面,并将当前目录重定位至实验二工具“e2_tools”位置。
Step3:输入命令“make img”在当前位置生成 helloos.img 文件。
nask.exe helloos.nas helloos.bin helloos.lst
edimg.exe imgin:../e2_tools/fdimg0at.tek wbinimg src:helloos.bin len:512 from:0 to:0 imgout:helloos.img
2.虚拟机启动操作系统
Step1:启动 VMware,创建一个新的虚拟机 MyOS(注意加上软盘驱动器)。
Step2:设置从软盘镜像文件中启动系统,文件为刚做好的 helloos.img。
Step3:启动该虚拟机。
【思考题:1、仔细阅读helloos.nas,结合操作系统启动过程尝试分析它的作用;2、若要将显示改为“Hello,MyOS!”该如何操作,请分析并截图出操作过程。】
- helloos.nas是一个操作系统启动代码,它定义了操作系统的启动扇区(Boot Sector),负责从磁盘加载操作系统并启动。通过汇编语言编写,它直接操作硬件,设置CPU寄存器、初始化堆栈、加载操作系统内核等。
- 在helloos.nas文件中找到字符串显示部分,修改字符串内容。
二、系统调用的使用方法
1. 系统调用的方法
隐式调用:用户通过封装了系统调用的 API 使用系统调用。
显式调用:使用 syscall() 函数实现调用,int syscall(int number,…),number 是系统调用号。
思考题:
调试运行下列程序,说明显式和隐式调用的不同之处。并尝试列出几个常用的系统调用号。
#include <syscall.h>#include <unistd.h>#include <stdio.h>#include <sys/types.h>int main (void) {long ID1, ID2;ID1 = syscall(SYS_getpid); /* 显式系统调用 */printf("syscall(SYS_getpid) = %ld\n", ID1);ID2 = getpid(); /* 使用 libc 封装的隐式系统调用 */printf("getpid() = %ld\n", ID2);return 0;}
【思考题:调试运行下列程序,说明显式和隐式调用的不同之处。并尝试列出几个常用的系统调用号。】
显式和隐式调用的不同之处:
显式调用:直接使用syscall()函数,通过系统调用号调用内核服务,程序员需要知道具体的系统调用号。
隐式调用:通过C库函数(如getpid())间接调用系统调用,程序员无需知道具体的系统调用号,由库函数封装。
常用系统调用号:
- SYS_getpid:获取进程ID。
- SYS_open:打开文件。
- SYS_read:读取文件。
- SYS_write:写入文件。
- SYS_exit:退出程序
2、常见系统调用函数
请用open()、read()、write()等常见系统调用函数完成创建或打开某目录下的文件,并写入指定语句“This is system call”。再次打开文件,将文字读出并打印输出到屏幕。
【给出代码截图、调试过程、运行结果。】
代码截图:
调试过程:
写入文件时使用sizeof(data)是不正确的,sizeof(data)返回的是指针的大小,应该使用strlen(data)来获取字符串的实际长度并写入.
运行结果:
【思考题:请说明系统调用函数与库函数的区别。】
- 系统调用函数:直接调用内核服务,执行特权指令,通常在用户态和内核态之间切换。
- 库函数:在用户态执行,通常是对系统调用的封装,提供更高层次的接口。
三、编写代码实现简单的 Shell
操作系统不信任用户,所以提供了一系列系统接口让用户通过命令行的形式去使用内核。Shell 就是一个命令行解释器,将用户输入的命令行解释为 OS 能够理解的可执行程序并执行该程序以运行。因此,Shell 实际上就是读取用户输入的信息,并创建新的子进程并用系统调用函数 exec 类函数调用该信息所指向的程序。
【思考题:试分析并编写一个简单的shell,能读取用户输入的命令并正确执行。并能通过ps指令查看到自己编写的shell的执行。】
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define MAX_INPUT_SIZE 1024int main(void) {char input[MAX_INPUT_SIZE];pid_t pid;while (1) {printf("myshell> ");fgets(input, MAX_INPUT_SIZE, stdin);input[strcspn(input, "\n")] = 0; // Remove newline characterif (strcmp(input, "exit") == 0) {break;}pid = fork();if (pid == 0) { // Child processexeclp(input, input, NULL);perror("exec");exit(1);} else if (pid > 0) { // Parent processwait(NULL);} else {perror("fork");}}return 0;
}
4 实验总结
通过这次实验报告册的编写与实验操作,我对操作系统的启动过程、接口工作原理以及系统调用的原理有了更深入的理解。以下是我的实验总结:
操作系统的启动及接口:首先学习了操作系统的启动过程。通过编写并生成自定义操作系统的镜像文件,我掌握了操作系统从磁盘加载到内存的过程,并且理解了操作系统如何初始化寄存器、堆栈以及加载内核。此外,我还学会了使用汇编语言编写简单的操作系统启动代码,并通过虚拟机成功启动了自定义操作系统。系统调用的使用方法:在这部分实验中,我了解了系统调用的基本方法,包括显式调用和隐式调用。通过实际编程实践,我体会到了直接使用syscall()函数进行显式调用和使用C库函数进行隐式调用的不同之处。此外,我还尝试了使用open()、read()、write()等系统调用函数来创建和操作文件,这增强了我对文件系统操作的理解。简单Shell的实现:最后,我尝试编写了一个简单的Shell程序。这个程序能够读取用户输入的命令,并通过创建子进程和调用exec类函数来执行相应的程序。这个过程让我对操作系统如何管理进程和执行命令有了更加直观的认识。