各种终端使用代理访问网络的方法

之前遇到了 git clone 炒鸡慢的情况,随即查一下怎么 Git Bash 下怎么配置代理,结果搜索到的清一色都是黏贴复制如何配置 http 代理的教程,ssh 模式下根本不可用。最后找了 N 久才查到正确的方法,赶紧记下来,顺便把各类终端如何配置代理科学上网的方法整理一下。

阅读更多

计算机传统 BIOS/MBR 引导启动过程

最近在复习操作系统相关的知识,重温一些经典的概念。说到操作系统,一个很经典的问题是:计算机在启动的过程过程中究竟发生了什么?或者简单地说整个开机过程是怎样的?我觉得这是一个很好的问题,虽然操作系统很大、很特殊、日常应用都要在它基础上运行,但是操作系统也是一个程序,就跟我用汇编写的 Helloworld 一样运行在机器上(当然操作系统更复杂)。虽然现在已经出现好几种系统引导启动方式了,但本质上都是对硬件控制权不断交接的过程。下面是我查阅、整理资料后,对传统 BIOS-MBR 引导方式启动计算机的主要流程的总结:

第一阶段:通电

按下电源开关,电源模块接收到通电信号后就会给计算机的各个设备供电(主板、硬盘、CPU 等),要是时候你的计算机没反应或者有什么问题,那八成是开不了机了。等到各个设备接上电后,计算机上的一系列设备都会恢复到初始状态,这时候就要运行烧录在 EEPROM 的 BIOS 程序了。

第二阶段:BIOS

第一阶段里说过,上电后计算机内的设备会进行初始化,其中 CPU 会自动将 CS 寄存器设置为 0xffff,将 IP 寄存器设置为 0x0000。而 0xffff:0x0000 正是 BIOS 程序所在的地址,因此计算机第一个执行的程序就是 BIOS。BIOS 的工作很简单,运行开机自检程序(Power-On Self Test),然后把控制权移交给主引导记录的引导程序。

开机自检程序

开机自检就是检查计算机必要的设备是否连接好、是否工作正常,如果发现有个别设备异常情况就会把异常信息打印到屏幕上,例如 keyboard error or no keyboard present 表示键盘有问题,然后计算机有可能就定住不再启动了。另外,它还会扫描机器上连接的外部存储设备,像硬盘、光驱等,到这里如果没有问题就可以继续下一阶段了,不过还有一种情况就是除了主板有 BIOS,其他设备也是可以有 BIOS,例如 RAID 阵列卡,我们还可以进入阵列卡的 BIOS 进行相关的配置。

读取主引导记录(MBR)

我们的系统是存储在外存上的(通常是硬盘),加入机器上连接这很多个存储设备,BIOS 是怎么知道哪个存储有操作系统的呢?这里就要引入主引导记录(MBR)了,BIOS 会认定硬盘的第一个扇区(512字节)内容为 MBR,如果 MBR 的最后两个字节为 0x55aa,那 BIOS 就会认为这个硬盘是可以启动的,接着把 MBR 的内容载入到 0x07c00,而引导程序就编写在 MBR 的前 446 个字节中。

第三阶段:主引导记录(MBR)

MBR 的基本组成

  1. 第 1 ~ 446 字节:启动引导程序
  2. 第 447 ~ 510 字节:分区表(Partition table)
  3. 第 511 ~ 512 字节:MBR 有效标志:0x55aa

有效标志 0x55aa

这个没什么好说的,当作是规定就好了。

分区表(Partition Table)

先说分区表,分区表有 64 个字节数据,而每个“磁盘分区表”(DPT)需要 16 个字节,因此一块硬盘最多可以有 4 个主分区。每个主分区可以有自己的文件系统,也就意味着我们可以在每个主分区上安装不一样的系统,装过双系统的同学一定不会陌生。

启动引导程序

MBR 也就 512 个字节,不太可能把操作系统塞到这里,所以最好的办法就是继续转移控制权,也就是去执行下一阶段的启动程序,那么要怎么找到这段程序呢?上面说过,一个硬盘可以有 4 个主分区,而我们可以将其中一个主分区标记被“激活分区”。对于 MBR 启动引导程序来说,一般约定下一阶段启动程序就存放在激活分区的第一个扇区(512字节)内,而这一扇区被称为分区引导扇区 DBR。那么到这里,计算机的控制权就从 MBR 转移到 DBR 了,而 DBR 是属于操作系统的,因此接下来就是操作系统自身的装载、启动了。

启动管理器(The Bootloader)

另一种情况是,MBR 引导程序没有把控制权转移到任何一个安装了操作系统的分区上,而是交给了一个称作启动管理器的程序中。这么做的用意是为什么呢?其实上文也有提到,我们可以把不同的系统安装到各个分区中,在计算机启动时,我们希望可以自由选择要启动的系统,这就是启动管理器的一大好处。这类启动管理器有很多,Linux 上最流行的莫过于 GRUB2 了。

第四阶段:启动操作系统

到这里就已经是操作系统自身的启动过程了,至于这一步还得具体系统具体分析,例如 Linux 的启动过程,日后再填坑。

自制操作系统(03):汇编语言学习与Makefile入门

将代码继续简化

上一篇中的「程序主体」部分依然看不出是什么意思,现在继续简化代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
; hello-os
; TAB=4

ORG 0x7c00 ; 指明程序装载地址

; 标准FAT12格式软盘专用的代码 Stand FAT12 format floppy code

JMP entry
DB 0x90
DB "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的长度(必??9扇区)
DW 18 ; 一个磁道(track)有几个扇区(必须为18)
DW 2 ; 磁头数(必??2)
DD 0 ; 不使用分区,必须是0
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明(固定)
DD 0xffffffff ; (可能是)卷标号码
DB "HELLO-OS " ; 磁盘的名称(必须为11字?,不足填空格)
DB "FAT12 " ; 磁盘格式名称(必??8字?,不足填空格)
RESB 18 ; 先空出18字节

; 程序主体

entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,AX

MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
fin:
HLT ; 让CPU停止,等待指令
JMP fin ; 无限循环

msg:
DB 0x0a, 0x0a ; 换行两次
DB "hello, world"
DB 0x0a ; 换行
DB 0

RESB 0x1fe-($-$$) ; 填写0x00直到0x001fe

DB 0x55, 0xaa

; 以下是启动区以外部分的输出

DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432

书中是只列出了主要的代码,我把全部代码贴上,方便大家。

同样经过编译后的文件内容和之前汇编写的是一模一样的,因此其启动后也是显示 “helloworld”。

那么究竟修改的代码是什么意思呢?我们一步一步解析(有汇编基础会更好理解):

  1. ORG 0x7c00 指明程序装载地址;

  2. JMP entry 跳转到 entry 代码段,JMP 指令有点类似 C 里的 goto;

  3. entry 段代码中,初始化了几个寄存器,此外 MOV SI,msg 中将 msg 标号对应的内存地址赋值给 SI 寄存器;

  4. putloop 段的代码其实是一段循环语句,大概流程就是遍历 msg 中的数据(读到 0 就结束,跳到 fin),将每个字符和颜色分别放到 AX、BX 寄存器,再执行 0x10 终端,打印到显示器上;

  5. fin 段代码大概意思可以理解为让 CPU “休眠”。

大概就成就是这样,但实际上还可以深挖很多东西的,比如寄存器的使用、如何寻址读数据等,都需要汇编基础才行。

制作启动区

我们把前 512 字节作为系统的启动区,制作启动区的好处是方便管理剩余的磁盘空间。

首先把启动程序后面的代码删掉,也就是以下这一段:

1
2
3
4
DB      0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432

接着重命名 helloos.asm 为 ipl.asm,重新编译启动程序:

1
$ nasm ipl.asm -o ipl.bin

创建一个新的空白镜像,把启动程序写入启动区:

1
2
$ dd if=/dev/zero of=helloos.img bs=512 count=2880
$ dd if=ipl.bin of=helloos.img conv=notrunc

重新启动模拟器,发现结果是一样的。

Makefile 入门

使用 Makefile 来简化我们的操作,少打几条命令吧,挺方便的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 默认动作

default :
make img

# 镜像文件生成

ipl.bin : ipl.asm Makefile
nasm ipl.asm -o ipl.bin -l ipl.lst

helloos.img : ipl.bin Makefile
dd if=/dev/zero of=helloos.img bs=512 count=2880 &&\
dd if=ipl.bin of=helloos.img conv=notrunc

# 其他指令

asm :
make -r ipl.bin

img :
make -r helloos.img

run :
make img
qemu-system-i386 -fda helloos.img

clean :
-rm ipl.bin
-rm ipl.lst

src_only :
make clean
-rm helloos.img

自制操作系统(02):从计算机结构到汇编程序入门

通电就能运行的程序

我们的 CPU 本身就内置了一些计算指令的,只要给它喂不同组合的电信号(开 / 关),它就能完成特定的任务。而二进制只有 0 和 1,正好符合了电信号的两种状态,因此我们就可以把 0、1 组合预先写好在文件上,然后等机器开启自动读取里面的 0、1,不就可以间接控制电信号切换,达到控制 CPU 的目的咯。

其实我以前很疑惑,为什么机器可以读取文件的,后来才明白我们把文件写到软盘,其实是在改变软盘上每个点的磁性,所以机器读取软盘内容实际上是和软盘上每个点做出了不通的物理反应而已。

生成一个 “新的软盘”

使用 dd 命令填充一个 helloos.img 文件:

1
$ dd if=/dev/zero of=helloos.img count=1474560 bs=1

把数据刻录到 “软盘” 上

用 Hex Fiend 打开 helloos.img 文件,然后一个一个 16 进制数敲进去就好了(感觉就像是在纸带上打孔):

YUyyG9.png

大概在 0001F0 的位置上填充以下数据:

YUyqMt.png

通过 “软盘” 启动计算机

这一步用 QEMU 模拟器来实现(QEMU 貌似可以模拟很多古董机)

运行命令:

1
$ qemu-system-i386 -fda helloos.img

YU6KzR.png

这样就可以看到一个 Helloworld 了 (^O^)

汇编程序的简单尝试

一个个字节写数据进去也太麻烦了,而且也不通看出是什么意思。因此就有了汇编程序,我们可以通过汇编语句写出人容易理解的代码,然后通过编译器将汇编代码翻译成二进制数到文件当中。

直接往文件里写数据

下面通过汇编的方式完成上文的 helloos.img:

创建并编辑 helloos.asm 文件,写入以下语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DB	0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x49, 0x50, 0x4c, 0x00, 0x02, 0x01, 0x01, 0x00
DB 0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00
DB 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff
DB 0xff, 0xff, 0xff, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x2d, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x46, 0x41
DB 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00
RESB 16
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, 0x0a, 0x0a, 0x68, 0x65
DB 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72
DB 0x6c, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 368
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432

使用 Nasm 编译汇编代码,得到 helloos.img:

1
$ nasm helloos.asm -o helloos.img

使用 QEMU 启动 helloos.img:

1
$ qemu-system-i386 -fda helloos.img

可以看到启动后的结果是和之前一致的。

这段代码用到两个指令:

  • DB(define byte),意思是往文件里写入 1 个字节的数据
  • RESB(reserve byte),意思是留空 n 个字节空间,填充为 0X00

更容易理解的代码

上面的代码虽然很短,但单纯看代码完全不知道是干嘛的,下面给出更容易理解的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
; hello-os
; TAB=4

; 以下这段是标准 FAT12 格式软盘专用的代码

DB 0xeb, 0x4e, 0x90
DB "HELLOIPL" ; 启动区的名称可以是任意的字符串(8字节)
DW 512 ; 每个扇区的大小必须为 512 字节
DB 1 ; 簇的大小必须为 1 个扇区
DW 1 ; FAT 的起始位置
DB 2 ; FAT 的个数必须为 2
DW 224 ; 根目录的大小一般设成 224 项
DW 2880 ; 该磁盘的大小必须是 2880 扇区
DB 0xf0 ; 磁盘的种类必须是 0xf0
DW 9 ; FAT 的长度必须是 9 扇区
DW 18 ; 1 个磁道必须有 18 个扇区
DW 2 ; 磁头数必须是 2
DD 0 ; 不适用分区,必须是 0
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明,固定
DD 0xffffffff ; 卷标号码
DB "HELLO-OS " ; 磁盘的名称(11字节)
DB "FAT12 " ; 磁盘格式名称(8字节)
RESB 18 ; 先空出 18 字节

; 程序主体

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,($-$$) 大概指的是当前行所在的字节数

DB 0x55, 0xaa

; 以下是启动区以外部分的输出

DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432

新的汇编指令:

  • DW(define word),意思是往文件里写入 1个字的数据(2 个字节)

今天就到这里吧,大三学的汇编都忘光光了,还要继续复习!

自制操作系统(01):开始前的准备

为什么要开这个坑?

不知不觉在新公司也有两个月时间了,在这短时间里真的接触了很多新知识,尤其是在多线程、多进程开发、数据库等方面,真的有种打开了新世界的感觉。说出来都真的是惭愧,作为一个即将毕业的计算机本科生,对操作系统方面的内容真的太匮乏了,就连线程、进程这些概念都搞不太清楚,总是要在上班时间恶补,工作效率奇差。一方面为了恶补操作系统知识,另一方面正好从 V2EX 上看到了有网友在研究《30天自制操作系统》这本书,于是就有了开坑,跟着做一个简单操作系统的想法。

这本书的内容难吗?

这本书我也只看了一小部分,很难直接告诉你难不难。如果你是有一点开发基础的话(排错、查文档的能力),那我觉得还是没问题的,而且作者说了就算没什么基础也能学。不过还是有很多卡壳的地方,一方面是自身理论知识薄弱,另一方面是这本书真的太老了,很多工具根本没法配出来,而且书中很多链接都已经打不开了。不过,还好有很多前辈把自己的事件日志以博客的形式记了下来,所以很多问题都可以 Google 解决的。也因为如此,我也决定把自己的实现、调试过程记下来,希望帮到更多的同学!

我的开发环境以及工具

书中的开发环境 Windows 系统,而我个人用的是 MacOS 10.14.6,其实还好,很多工具在 Mac 下都能找到替代品。基本就是解决编译和运行的问题,编译器的话大家找自己系统对应的就好了,而运行环境当然就不是真的搞一台古董记回来,我们可以用模拟器或者虚拟机解决,网上也有很多这方面的内容。

下面是我安装的工具:

有什么用大家自行 Google 一下就知道了,非 MacOS 的话就找回自己系统的类似工具即可。

现在我还没看完全书,可能后续还会用到别的工具,后续更新。

推荐参考资源

在我搞这个自制系统的过程中遇到了很多问题,基本 Google 解决了,下面是一些我觉得挺好的资源,希望能方便大家:

You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.