手写操作系统--从“借鉴”开始(实验一:操作系统的引导)

写在前面

《自己动手写操作系统》让我有了手写操作系统的思路。《x86汇编语言-从实模式到保护模式》让我明白了保护模式,中断,分页机制。可看完这两本书,我有很多天都不敢下手去写操作系统。后来,我想到了一个思路:我可以先学习linux内核,之后,再回过头来做自己想做的事情。又不是只有我一个人“借鉴”,这个世界多了去了,绝大多数人只不过是跟跑。所有的资料都是我自己收集的。人工智能gpt是我的“老师”。为了手写操作系统,我学会了Makefile gcc gdb c语言 汇编(nasm汇编,as86汇编,gun as汇编)等等。

在思路上,我感觉手写操作系统已经没有什么问题了。现在就是边学边做。

额,我咋感觉我就是在学习大佬们写的东西 ????? 嗯,从小到大 。。。。。。

相关环境,源码,课程,书籍,linux0.11编译运行效果

课程实验内容:
操作系统实验内容:https://www.lanqiao.cn/courses/115

参考书籍----我查了好多资料:
《汇编语言程序设计(美)布鲁姆 著,马朝晖 等译》:linux内核涉及到的 gun as 汇编。
《LINUX内核完全剖析:基于0.12内核》: 不知道内核代码含义,可以看该书。
其他:
请添加图片描述

地址:https://gitee.com/YMQ_1314/linux_os_learn。
环境:deepin20.7操作系统
编译运行效果:

此次实验的基本内容是:

阅读《Linux 内核完全注释》的第 6 章,对计算机和 Linux 0.11 的引导过程进行初步的了解;
按照下面的要求改写 0.11 的引导程序 bootsect.s
有兴趣同学可以做做进入保护模式前的设置程序 setup.s。

改写 bootsect.s 主要完成如下功能:
bootsect.s 能在屏幕上打印一段提示信息“XXX is booting…”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等(可以上论坛上秀秀谁的 OS 名字最帅,也可以显示一个特色 logo,以表示自己操作系统的与众不同。)

改写 setup.s 主要完成如下功能:
bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行"Now we are in SETUP"。
setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。
setup.s 不再加载 Linux 内核,保持上述信息显示在屏幕上即可。

一、修改bootsect.s打印 Hello! LiChanghe is booting…

bios加载启动磁盘第一扇区数据到内存0x7c00 处,并跳入该地址开始执行程序。而第一扇区就是编译后的bootsect.s文件。不太了解的话,可以看《手写操作系统》。
bootsect.s主要功能就是移动自身到内存0x90000处,加载setup.s到内存0x90200处,加载system到0x10000处,最后跳入setup.s (即0x90200处)。
按照《LINUX内核完全剖析:基于0.12内核》搞懂代码逻辑。书讲得很详细,我就不在这里说了。上代码:

1、bootsect.s文件里边找到msg1

msg1:			! 开机调用BIOS中断显示的信息,共36个字符
	.byte 13,10		! 回车、换行的ACSII码
	.ascii "Hello! LiChanghe is booting..."
	.byte 13,10,13,10

2、显示设置

! Print some inane message
! 显示信息:“‘Loading’+回车+换行”,并显示包括回车和换行控制符在内的9个字符。
! BIOS中断0x10功能号ah = 0x03,读光标位置。
! 输入:bh = 页号
! 返回:ch = 扫描开始线; cl= 扫描结束线; dh = 行号(0x00顶端);  dl = 列号(0x00最左边)。
! BIOS中断0x10功能号  ah = 0x13,显示字符串。
! 输入: al = 放置光标的方式及规定属性。 0x01-表示使用bl中的属性值,光标停在字符串结尾处。
! es:bp 此寄存器对指向要显示的字符串起始位置处。cx = 显示的字符串字符数。bh = 显示页面号;
! bl = 字符属性。 dh = 行号; dl = 列号。
	mov	ah,#0x03		! read cursor pos
	xor	bh,bh			! 首先读光标位置。返回光标位置值在dx中
	int	0x10			! dh - 行(0--24; dl - 列(0--79)
	
	mov	cx,#36			! 共显示36个字符
	mov	bx,#0x0007		! page 0, attribute 7 (normal)
	mov	bp,#msg1		! es:bp 指向要显示的字符串
	mov	ax,#0x1301		! write string, move cursor
	int	0x10			! 写字符串并移动光标到串结尾处

3、修改build.c

这个文件非常熟悉,我在base目录里边的更有意思。一开始我是用自己写的工具writeToDisk.c,writeToVhd.c。 在接触build.c之后,我就更新了下。build.c最有意思的地方-----去掉文件的特殊头部信息。
如下图:

现在我要修改build.c的话,我就直接用strncmp了。
其实就是在system处理之前加个判断。

if(strncmp(argv[3],"none",4) == 0){
	return(0);
}
// system处理
if ((id=open(argv[3],O_RDONLY,0))<0)

4、编译运行

编译 make BootImage

运行 cd … && ./run

二、修改setup.s输出"Now we are in SETUP"以及硬件参数

setup.s主要利用实模式下bios中断读取机器信息的特点,然后把这些数据写到0x90000处(即覆盖掉bootsect代码),接着将system移动到0x00000处,关中断,设置中断描述符表(IDT)跟全局描述符(GDT),并在gdt中设置当前内核代码段的描述和数据段的描述符,最后跳入到system里边的head.s(head.s才会去设置内存分页)代码处。

如果你想要了解idt跟gdt的话,可以看《x86汇编语言-从实模式到保护模式》。
在这里插入图片描述
由于当时的硬件条件的限制,system模块长度不会超过512KB。可是现在我在64位机器编译,其objcopy工具处理之后的kernel内存大小为128.3M。倘若我要让其在64位系统下编译运行,看来我需要再了解一些东西。

1、setup.s 向屏幕输出一行"Now we are in SETUP"

在未关中断、进入保护模式之前进行操作。直接将bootsect.s里边的代码复制过来,但是需要注意es地址。
如果已经关中断,进入保护模式呢? ---- 可以直接在8086显存映射的内存区域(即文本模式下,段地址0xB800, 偏移地址0x0000~0xFFFF)里边写数据。

 mov ax,0xb800                 ;指向文本模式的显示缓冲区
 mov es,ax
 ;以下显示字符串"Label offset:"
 mov byte [es:0x00],'L'
 mov byte [es:0x01],0x07
 mov byte [es:0x02],'a'
 mov byte [es:0x03],0x07
 mov byte [es:0x04],'b'
 mov byte [es:0x05],0x07
 mov byte [es:0x06],'e'
 mov byte [es:0x07],0x07
 mov byte [es:0x08],'l'
 mov byte [es:0x09],0x07
 mov byte [es:0x0a],' '
 mov byte [es:0x0b],0x07
 mov byte [es:0x0c],"o"
 mov byte [es:0x0d],0x07
 mov byte [es:0x0e],'f'
 mov byte [es:0x0f],0x07
 mov byte [es:0x10],'f'
 mov byte [es:0x11],0x07
 mov byte [es:0x12],'s'
 mov byte [es:0x13],0x07
 mov byte [es:0x14],'e'
 mov byte [es:0x15],0x07
 mov byte [es:0x16],'t'
 mov byte [es:0x17],0x07
 mov byte [es:0x18],':'
 mov byte [es:0x19],0x07
 jmp $

由于中断还是可用的,我这里就直接复制粘贴了。
打印信息:

entry start
start:
! 还没有关中断跟进入保护模式。但是程序已经加载到0x90200处,所以es需要设置成0x90200
	mov	ax,#SETUPSEG
	mov	es,ax
	mov	ah,#0x03		! read cursor pos
	xor	bh,bh			! 首先读光标位置。返回光标位置值在dx中
	int	0x10			! dh - 行(0--24; dl - 列(0--79)
	mov	cx,#26			! 共显示36个字符
	mov	bx,#0x0007		! page 0, attribute 7 (normal)
	mov	bp,#msg			! es:bp 指向要显示的字符串
	mov	ax,#0x1301		! write string, move cursor
	int	0x10			! 写字符串并移动光标到串结尾处

信息:

msg:			! 开机调用BIOS中断显示的信息
	.byte 13,10		! 回车、换行的ACSII码
	.ascii "Now we are in SETUP"
	.byte 13,10,13,10

效果:
请添加图片描述

2、setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。

主要思路: 利用bios中断获取硬件信息,然后进行打印。其打印的思路 ----- 先打印单个字符,再以此为基础进行扩展(比如打印字符串,十进制打印数字,十六进制打印数字,打印回车换行等等)。
注意:字符请对照acsii码表。
核心函数:

! 该子程序使用中断0x10的OxOE功能,以电传方式在屏幕上写一个字符。光标会自动移到下一个
! 位置处。如果写完一行光标就会移动到下一行开始处。如果已经写完一屏最后-行,则整个屏幕
! 会向上滚动一行。字符0x07 (BEL)0x08 (BS)OxOA(LF)OxOD (CR)被作为命令不会显示。
! 输入:AL——欲写字符;BH——显示页号;BL——前景显示色(图形方式时)。
print_char:
	push ax
	push cx
	mov  bh,#0x00   !显示页面
	mov  cx,#0x01
	mov  ah,#0x0e
	int  0x10
	pop cx
	pop ax
	ret

以上边的代码为基础进行扩展
打印字符串:

! 显示位于DS:SI处以NULL0x00)结尾的字符串。
print_string: 
	lodsb
	and al,al
	jz fin
	call print_char   ! 显示al中的一个字符。
	jmp print_string
fin:
	ret

十六进制打印:

! 显示十六进制数字的子程序。显示值放在寄存器al中(0255! idiv 有符号除
! 如果除数为8位,则被除数为16位,则结果的商存放与al中,余数存放于ah中。
! 如果除数为16位,则被除数为32位,则结果的商存放与ax中,余数存放于dx中。
! 如果除数为32位,则被除数为64位,则结果的商存放与eax中,余数存放于edx中。
print_hex:
	push ax
	push cx
	mov ah,#0x00
	mov cl,#0x10
	idiv cl			! 除法  ah---余数  al---商
	cmp al,#0x0f
	jbe lthex 		! 不大于0x0f则跳转
	call print_hex
	jmp skiphex
lthex:
	cmp al,#0x09
	jbe lthex_9
	add al,#0x57
	call print_char
	jmp skiphex
lthex_9:
	add al,#0x30
	call print_char
skiphex:
	mov al,ah
	cmp al,#0x09
	jbe skiphex_9
	add al,#0x57
	call print_char
	jmp print_hex_end
skiphex_9:
	add al,#0x30
	call print_char
print_hex_end:
	pop cx
	pop ax
	ret

获取扩展内存大小:

! Get memory size (extended mem, kB)
! 取扩展内存的大小值(KB)
! 利用BIOS中断0x15功能号 ah = 0x88 取系统所含扩展内存大小并保存在内存0x90002处。
! 返回:ax =0x1000001m)处开始的扩展内存大小(KB)。若出错则CF置位,ax = 出错码。
	mov	ah,#0x88
	int	0x15
	mov	[2],ax
	call print_extend_memory_info


! 打印扩展内存信息  ax存放扩展内存大小
print_extend_memory_info:
	push ax
	push bx
	push cx
	push dx
	push ds
	push es	! 寄存器入栈
	mov dx,ax      ! dx存放扩展内存大小
	mov ax,#SETUPSEG
	mov ds,ax
	mov es,ax
	call print_enter_wrap	! 打印回车换行
	sub si,si
	lea si,extend_memeory_size_title
	call print_string
	sub ax,ax		! ax清零
	mov al,dh
	call print_oct
	sub ax,ax		! ax清零
	mov al,dl
	call print_oct
	sub si,si
	lea si,unit_KB	! 打印大小单位
	call print_string
	call print_enter_wrap	! 打印回车换行
	pop es
	pop ds
	pop dx
	pop cx
	pop bx
	pop ax	! 寄存器出栈
	ret

在上边的思路之上,更新之前欢迎信息:

! 还没有关中断跟进入保护模式。但是程序已经加载到0x90200处,所以es,ds需要设置成0x90200
	call print_welcome
	call print_lab_job_info
	! 测试 print_oct
	mov al,#0x61
	call print_oct
	call print_enter_wrap
	! 测试 print_oct
	mov al,#0xaf
	call print_hex
	call print_enter_wrap

! 打印实验信息
print_lab_job_info:
	push ax
	push bx
	push cx
	push dx
	push ds
	push es
	mov	ax,#SETUPSEG
	mov	es,ax
	mov	ah,#0x03		! read cursor pos
	xor	bh,bh			! 首先读光标位置。返回光标位置值在dx中
	int	0x10			! dh - 行(0--24; dl - 列(0--79)
	mov	cx,#25			! 共显示多少个字符
	mov	bx,#0x0007		! page 0, attribute 7 (normal)
	mov	bp,#lab_job_info			! es:bp 指向要显示的字符串
	mov	ax,#0x1301		! write string, move cursor
	int	0x10			! 写字符串并移动光标到串结尾处
	pop es
	pop ds
	pop dx
	pop cx
	pop bx
	pop ax
	ret

! 打印欢迎信息
print_welcome:
	push ax
	push bx
	push cx
	push dx
	push ds
	push es
	mov ax,#SETUPSEG
	mov ds,ax
	lea si,welcome_info
	call print_string
	pop es
	pop ds
	pop dx
	pop cx
	pop bx
	pop ax
	ret

! 打印光标信息
print_cursor_info:
	push ax
	push bx
	push cx
	push dx
	push ds
	push es
	mov ax,#SETUPSEG
	mov ds,ax
	mov es,ax
	sub si,si
	lea si,cursor_info
	call print_string
	call print_enter_wrap
	sub si,si
	lea si,cursor_line_number
	call print_string
	sub al,al
	mov al,bh
	call print_oct
	call print_tab
	sub si,si
	lea si,cursor_column_number
	call print_string
	sub al,al
	mov al,dl
	call print_oct
	call print_enter_wrap
	pop es
	pop ds
	pop dx
	pop cx
	pop bx
	pop ax
	ret


	pop es
	pop ds
	pop dx
	pop cx
	pop bx
	pop ax
	ret

! 打印tab
print_tab:
	call print_space
	call print_space
	call print_space
	call print_space
	ret

! 打印一个空格
print_space: 
	mov al,#0x20	! 显示一个空格
	call print_char
	ret

! 打印回车换行
print_enter_wrap:
	push ax
	push bx
	push cx
	push dx
	push ds
	push es
	mov al,#0x0a
	call print_char
	mov al,#0x0d
	call print_char
	pop es
	pop ds
	pop dx
	pop cx
	pop bx
	pop ax
	ret

效果:
在这里插入图片描述

总结

主要是提了自己学了啥,用了啥工具,看了啥书以及做了几个有趣的实验。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实验目的: 使用卷积神经网络(CNN)实现对MINIST手写数字0-9的识别,掌握CNN在图像识别任务中的应用。 实验步骤: 1. 数据集准备 使用MINIST手写数字数据集,该数据集包含60000个训练样本和10000个测试样本,每个样本都是28x28像素的灰度图像。可以使用PyTorch自带的torchvision.datasets.MNIST类进行数据集的加载。 2. 数据预处理 对数据集进行预处理,包括数据增强和归一化操作。数据增强可以提高模型的泛化能力,常见的数据增强方式有旋转、平移、缩放、翻转等。归一化操作可以将像素值缩放到[0,1]之间,有利于训练模型。 ```python transform_train = transforms.Compose([ transforms.RandomRotation(10), transforms.RandomAffine(0, shear=10, scale=(0.8,1.2)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) transform_test = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform_train) trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2) testset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform_test) testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=False, num_workers=2) ``` 3. 模型设计与训练 使用PyTorch搭建卷积神经网络模型,对手写数字图像进行分类。具体网络结构如下: ```python class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 32, 3, 1) self.conv2 = nn.Conv2d(32, 64, 3, 1) self.dropout1 = nn.Dropout2d(0.25) self.dropout2 = nn.Dropout2d(0.5) self.fc1 = nn.Linear(9216, 128) self.fc2 = nn.Linear(128, 10) def forward(self, x): x = self.conv1(x) x = F.relu(x) x = self.conv2(x) x = F.relu(x) x = F.max_pool2d(x, 2) x = self.dropout1(x) x = torch.flatten(x, 1) x = self.fc1(x) x = F.relu(x) x = self.dropout2(x) x = self.fc2(x) output = F.log_softmax(x, dim=1) return output ``` 模型训练过程: ```python device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") net = Net() net.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9) for epoch in range(10): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): # get the inputs; data is a list of [inputs, labels] inputs, labels = data[0].to(device), data[1].to(device) # zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics running_loss += loss.item() if i % 100 == 99: # print every 100 mini-batches print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100)) running_loss = 0.0 print('Finished Training') ``` 4. 模型测试 使用测试集对训练好的模型进行测试,并计算准确率。 ```python correct = 0 total = 0 with torch.no_grad(): for data in testloader: images, labels = data[0].to(device), data[1].to(device) outputs = net(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print('Accuracy of the network on the 10000 test images: %d %%' % ( 100 * correct / total)) ``` 实验结果: 使用上述模型,在MNIST数据集上进行训练,最终得到的准确率为98.94%。可以看出使用CNN实现手写数字识别是非常有效的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值