Day01内容阅读
本章任务汇总:
- 使用二进制编辑器写入启动扇区(直接写入机器码,不敲,直接复制了)
- 使用汇编语言编写启动扇区
- 熟悉bochs调试环境,学会配置文件的编写
- 熟悉VM虚拟机上安装自己的操作系统
- 看懂书中的汇编代码的基本结构
最终效果:
myos.nas汇编代码:
; hello-os
; TAB=4
; RESB指令会有个空间未初始化的告警,索性全换成times指令了
; FAT32软盘格式化代码,不用记,找相关文档看格式就行,这里是直接复制随书文件的,注释也是直接照抄的,后面明白了再说
DB 0xeb, 0x4e, 0x90
DB "hxbos " ; 启动扇区名称,8字节
DW 512 ; 单个扇区的大小
DB 1 ; 簇的大小,必须为1个扇区
DW 1 ; FAT文件的起始位置(一般从第一个扇区的第一个字节开始)
DB 2 ; FAT的个数,2
DW 224 ; 根目录的大小,一般设置为224项
DW 2880 ; 磁盘的大小,必须为2880扇区
DB 0xf0 ; 磁盘的种类
DW 9 ; FAT的长度,必须是9扇区
DW 18 ; 1个磁道几个扇区,必须为18
DW 2 ; 磁头数必须为2
DD 0 ; 不使用分区,必须为0
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明,固定
DD 0xffffffff ; (可能是卷标号码)
DB "HXB-OS " ; 磁盘的名称(11字节),不足加空格
DB "FAT12 " ; 磁盘格式名称,不足加空格
;RESB 18 ; reserve byte,预留18字节
times 18 db 0x00
; 程序主体
DB 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
DB 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
DB 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
DB 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
DB 0xee, 0xf4, 0xeb, 0xfd
; 信息显示部分
DB 0x0a, 0x0a ; 2个换行
DB "hello, world"
DB 0x0a ; 换行
DB 0
;RESB 0x1fe-($-$$) ; 填写0x00,直到0x001fe, 书上的那个写法我用的nasm编译器报错,这个写法是于渊那本书上用的
times 0x1fe-($-$$) db 0x00
DB 0x55, 0xaa ; 启动扇区标识
; 启动区之外的部分
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
;RESB 4600
times 4600 db 0x00
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
;RESB 1469432
times 1469432 db 0x00
此部分汇编代码主要有一下几个部分
- 启动扇区
- fat32软盘格式化代码
- 主程序部分,实际上就是通过中断实现一个显示字符的操作
- 显示的信息数据部分,以及填充0到扇区末尾
- 启动扇区外的部分,有几个地方写了几个特殊的机器码,其他地方填0,到1.44MB大小
总结:
- 这一章中,先是直接通过二进制编辑器往文件中写入二进制机器码,完成一个包含了启动扇区的简单操作系统编写。
- 接着通过汇编代码简单说明了上面那些机器码都是干什么的。
- 启动扇区,机器开机后一个个的读取扇区,当读取到的扇区是启动扇区(512字节的最后两字节是55AA),那么从当前扇区开始执行。
- IPL,initial program loader,启动程序加载器,存放在启动扇区中,负责加载操作系统程序(操作系统很大,就一个启动扇区肯定放不了,所以需要通过加载器去加载在某个地方的操作系统程序)。
- FAT,一种文件格式。
Day02
本章任务汇总
- 将上一节中的程序完全转为汇编程序,搞懂启动扇区都做了什么
- 制作引导扇区,真正的引导程序下一章编写,这里只需要分离出来即可
- 小工具编写1:实现一个程序,创建一个指定大小的文件
- 小工具编写2:实现一个程序,实现将启动扇区的内容拷贝到软盘镜像的第一个扇区。(工具编写看个人,建议直接通过带参数命令行的方式运行,方便后边直接在Makefile中调用执行
- )
- 弄明白核心程序代码,也就是那个调用中断显示字符的那段
- make的使用,使用Makefile编写:软盘文件生成,引导程序生成,引导扇区写入到软盘镜像,加载配置文件运行bochs虚拟机
笔记
这一章的内容其实并不多,就是对前面一个章节章节的启动区启动扇区代码的主要代码进行一个解释(这个主要代码其实就是一个调用中断往显存中写入几个字符),然后就是简单的讲解了一些MakeFile的使用。
- 启动扇区核心代码解释
下面是昨天的那个汇编程序,不同的是将中间的直接写机器码的部分换成了汇编程序。
这部分换掉了而已
完整地汇编程序是下面这个,核心程序就是个中断调用,不熟悉去看一下汇编书籍就行(比如王爽的《汇编语言》):
; hello-os
; TAB=4
; RESB指令会有个空间未初始化的告警,索性全换成times指令了
; FAT32软盘格式化代码,不用记,找相关文档看格式就行,这里是直接复制随书文件的,注释也是直接照抄的,后面明白了再说
ORG 0x7c00
JMP entry
DB 0x90
DB "hxbos " ; 启动扇区名称,8字节
DW 512 ; 单个扇区的大小
DB 1 ; 簇的大小,必须为1个扇区
DW 1 ; FAT文件的起始位置(一般从第一个扇区的第一个字节开始)
DB 2 ; FAT的个数,2
DW 224 ; 根目录的大小,一般设置为224项
DW 2880 ; 磁盘的大小,必须为2880扇区
DB 0xf0 ; 磁盘的种类
DW 9 ; FAT的长度,必须是9扇区
DW 18 ; 1个磁道几个扇区,必须为18
DW 2 ; 磁头数必须为2
DD 0 ; 不使用分区,必须为0
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明,固定
DD 0xffffffff ; (可能是卷标号码)
DB "HXB-OS " ; 磁盘的名称(11字节),不足加空格
DB "FAT12 " ; 磁盘格式名称(8字节),不足加空格
;RESB 18 ; reserve byte,预留18字节
times 18 db 0x00
; 程序主体
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00 ; 为什么是从这个地址开始,由内存分布决定
MOV DS,AX
MOV ES,AX
MOV SI,msg
putloop:
MOV AL,[SI] ; [SI]地址中的值存放到AL中,AL显示的字符
ADD SI,1 ; 地址加1
CMP AL,0 ; 没有字符则退出
JE fin
MOV AH,0x0e ; 调用中断,显示字符
;MOV BX,1 ; BX = 0x000F, BL = 0F, 前景色,至于为什么这个中断调用设置的字符颜色无效,现在我也不明白,后面会清楚的
mov BX, 000ch
INT 0x10 ; int 0x10中断
JMP putloop
fin:
HLT ; CPU停止,等待指令
JMP fin ; 死循环
; 信息显示部分
msg:
DB 0x0a, 0x0a ; 2个换行
DB "hello, hxbos"
DB 0x0a ; 换行
DB 0
;RESB 0x1fe-($-$$) ; 填写0x00,直到0x001fe, 书上的那个写法我用的nasm编译器报错,这个写法是于渊那本书上用的
times 0x1fe-($-$$) db 0x00
DB 0x55, 0xaa ; 启动扇区标识
- 工具编写1:生成镜像文件工具。
懂点c语言就能写,简单的文件IO,往文件中写0填充到指定大小即可。
下面是示例程序:
#include <iostream>
#include <cstring>
#include <fstream> // std::ifstream
#include <cstdio>
const long long int DEFAULT_LEN = 1440*1024;
using namespace std;
char option[200];
struct Config{
char ofile[200]; // 输出文件名
long long len = DEFAULT_LEN; // 生成的文件长度
};
typedef struct Config Config;
Config globalConfig;
/**
相关知识点:
1. 软盘有2个面,80个磁道,18个扇区,每区512B
软盘的大小最早是这么算出来的
80*12*512*2=1440*512*2=1440*1024(这样简称为1.44M)
2. 根据前面两章节的阅读,发现启动扇区之外部分也写入了几个特殊的机器码,由于现在还不明白这几个机器码是干嘛用的,就直接启动扇区之外全部填0吧
*/
/**
字符串转longlong
*/
long long int stoi(char *str){
long long res = 0;
int i = 0;
while(str[i]){
res = res * 10 + (str[i] - '0');
i++;
}
return res;
}
void printUsage(){
printf("mkimage -of 【要生成的空镜像文件名】 -len 【生成的空镜像文件大小】\n");
}
/**
cnt, 参数个数
argvs[0], 路径
args[1...n] , 具体输入参数
*/
int parseArgs(int cnt, char **argvs){
for(int i = 1; i < cnt; i++){
if(argvs[i][0] == '-'){
memset(option, 0, sizeof(option));
sprintf(option, "%s", &argvs[i][1]);
if(strcmp(option, "of") == 0){
}else if(strcmp(option, "len") == 0){
}else{
printf("不合法的参数\n");
printUsage();
return -1;
}
}else{
printf("option = %s, vallue = %s\n", option, argvs[i]);
if(strcmp(option, "of") == 0){
sprintf(globalConfig.ofile, "%s", argvs[i]);
}else if(strcmp(option, "len") == 0){
globalConfig.len = stoi(argvs[i]);
}else{
printf("不合法的参数\n");
printUsage();
return -1;
}
}
}
return 0;
}
void showConfig(){
printf("\n =========== 当前配置的参数:==================\n");
printf("输出文件名:%s\n", globalConfig.ofile);
printf("长度 %lld字节\n", globalConfig.len);
}
void cWriteFileWithZero(){
char *buf = new char[globalConfig.len];
FILE *ofp;
ofp = fopen(globalConfig.ofile, "wb");
char output[512];
long long cur = 0, block = 1024 * 100, res, len = 0;
while(cur < globalConfig.len){
block = min(block, globalConfig.len - cur);
res = fwrite(buf, sizeof(char), block, ofp);
cur += block;
// 光标回退
for(int i = 0; i < len; i++){
printf("\b");
}
memset(output, 0, sizeof(output));
sprintf(output, "write: %lld byte, current write: %lld byte, progress = %.2f%c", block, cur, cur * 1.0 / globalConfig.len * 100, '%');
len = strlen(output);
printf("%s", output);
}
fclose(ofp);
delete buf;
}
int main(int argv, char **argvs){
parseArgs(argv, argvs);
showConfig();
cWriteFileWithZero();
printf("\n镜像生成完毕, 文件大小:%lld字节\n", globalConfig.len);
return 0;
}
/**
./dd.exe -if boot.bin -of
*/
- 工具编写2:将引导扇区写入到软盘文件中
示例程序:
#include <iostream>
#include <string>
#include <cstring>
#include <fstream> // std::ifstream
#include <cstdio>
using namespace std;
char option[200];
struct Config{
char ifile[200]; // 输入文件名
char ofile[200]; // 输出文件名
long long int iStartPos; // 输入文件开始位置偏移
long long int oStartPos; // 输出文件开始位置偏移
long long int ifSize; // 输入文件拷贝多少到输出文件
};
typedef struct Config Config;
Config globalConfig;
/**
字符串转longlong
*/
long long int stoi(char *str){
long long res = 0;
int i = 0;
while(str[i]){
res = res * 10 + (str[i] - '0');
i++;
}
return res;
}
/**
cnt, 参数个数
argvs[0], 路径
args[1...n] , 具体输入参数
example: dd.exe -if boot.bin -of boot.img -len 512
*/
int parseArgs(int cnt, char **argvs){
for(int i = 1; i < cnt; i++){
if(argvs[i][0] == '-'){
memset(option, 0, sizeof(option));
sprintf(option, "%s", &argvs[i][1]);
if(strcmp(option, "if") == 0){
}else if(strcmp(option, "of") == 0){
}else if(strcmp(option, "ioffset") == 0){
}else if(strcmp(option, "ooffset") == 0){
}else if(strcmp(option, "len") == 0){
}else{
printf("不合法的参数\n");
return -1;
}
}else{
printf("option = %s, vallue = %s\n", option, argvs[i]);
if(strcmp(option, "if") == 0){
sprintf(globalConfig.ifile, "%s", argvs[i]);
}else if(strcmp(option, "of") == 0){
sprintf(globalConfig.ofile, "%s", argvs[i]);
}else if(strcmp(option, "ioffset") == 0){
globalConfig.iStartPos = stoi(argvs[i]);
}else if(strcmp(option, "ooffset") == 0){
globalConfig.oStartPos = stoi(argvs[i]);
}else if(strcmp(option, "len") == 0){
globalConfig.ifSize = stoi(argvs[i]);
}else{
return -1;
}
}
}
return 0;
}
void showConfig(){
printf("\n =========== 当前配置的参数:==================\n");
printf("输入文件名:%s\n", globalConfig.ifile);
printf("输出文件名:%s\n", globalConfig.ofile);
printf("输入文件开始位置偏移 %lld\n", globalConfig.iStartPos);
printf("输出文件开始位置偏移 %lld\n", globalConfig.oStartPos);
printf("输入文件拷贝多少到输出文件 %lld字节\n", globalConfig.ifSize);
}
void cWriteFile(){
char *buf = new char[globalConfig.ifSize];
FILE *ifp, *ofp;
ifp = fopen(globalConfig.ifile, "rb");
ofp = fopen(globalConfig.ofile, "rb+");
if(ofp == NULL || ifp == NULL){
printf("error\n");
exit(-1);
}
fseek(ofp, globalConfig.oStartPos, SEEK_SET);
fseek(ifp, globalConfig.iStartPos, SEEK_SET);
long long cur = 0, block = 512;
while(cur < globalConfig.ifSize){
block = min(block, globalConfig.ifSize - cur);
int res = fread(buf, sizeof(char), block, ifp);
cout<<"read = "<<res<<endl;
res = fwrite(buf, sizeof(char), block, ofp);
cout<<"write = "<<res<<endl;
cur += block;
}
fclose(ifp);
fclose(ofp);
delete buf;
}
int main(int argv, char **argvs){
parseArgs(argv, argvs);
showConfig();
cWriteFile();
printf("程序写入完毕\n");
return 0;
}
/**
./dd.exe -if boot.bin -of
*/
- Makefile编写,编译、运行等合到一块。