0%

操作系统实战-读书笔记

阅读更多

1 重点

linux_kernel_map

4-1

{"t":"list_item","d":2,"p":{"lines":[0,1]},"v":"Linux五大功能组件","c":[{"t":"list_item","d":4,"p":{"lines":[1,2]},"v":"系统(System)","c":[{"t":"list_item","d":6,"p":{"lines":[2,3]},"v":"API接口:用于应用程序调用系统功能"},{"t":"list_item","d":6,"p":{"lines":[3,4]},"v":"设备驱动模型:规范各种驱动程序的开发"},{"t":"list_item","d":6,"p":{"lines":[4,5]},"v":"系统运行:完成系统启动初始化,电源管理等功能"},{"t":"list_item","d":6,"p":{"lines":[5,6]},"v":"总线与设备驱动:各种驱动程序实现控制访问具体设备"},{"t":"list_item","d":6,"p":{"lines":[6,7]},"v":"I/O设备:物理硬件如PCI、USB总线等设备控制器"}]},{"t":"list_item","d":4,"p":{"lines":[7,8]},"v":"进程(Processing)","c":[{"t":"list_item","d":6,"p":{"lines":[8,9]},"v":"进程接口:创建进程、加载应用、处理信号等功能"},{"t":"list_item","d":6,"p":{"lines":[9,10]},"v":"内核态线程:实现工作队列"},{"t":"list_item","d":6,"p":{"lines":[10,11]},"v":"同步机制:实现了自旋锁、信号量、互斥锁、读写锁、RCU锁"},{"t":"list_item","d":6,"p":{"lines":[11,12]},"v":"进程调度器:负责进程的调度,即给进程分配CPU使之运行"},{"t":"list_item","d":6,"p":{"lines":[12,13]},"v":"中断处理:负责处理硬件中断、软件中断和管理中断控制器"},{"t":"list_item","d":6,"p":{"lines":[13,14]},"v":"CPU:负责执行所有代码指令"}]},{"t":"list_item","d":4,"p":{"lines":[14,15]},"v":"内存(Memory)","c":[{"t":"list_item","d":6,"p":{"lines":[15,16]},"v":"内存接口:使得应用能分配、释放内存空间,映射和共享内存"},{"t":"list_item","d":6,"p":{"lines":[16,17]},"v":"虚拟内存:使得应用有统一、独立且比实际内存大的多的地址空间"},{"t":"list_item","d":6,"p":{"lines":[17,18]},"v":"内存映射:负责建立页表、完成虚拟内存地址到物理内存地址的转换"},{"t":"list_item","d":6,"p":{"lines":[18,19]},"v":"内存对象分配:如slab、slub,用内核自身数据结构对象的分配与释放"},{"t":"list_item","d":6,"p":{"lines":[19,20]},"v":"物理内存管理:用于管理、分配、释放物理内存页"},{"t":"list_item","d":6,"p":{"lines":[20,21]},"v":"物理内存:真实的机器内存条、MMU部件,DMA部件"}]},{"t":"list_item","d":4,"p":{"lines":[21,22]},"v":"存储(Storage)","c":[{"t":"list_item","d":6,"p":{"lines":[22,23]},"v":"存储接口:主要是实现文件和目录的读写访问"},{"t":"list_item","d":6,"p":{"lines":[23,24]},"v":"虚拟文件系统:规范了真实文件系统,实现了文件、目录等抽象的结构"},{"t":"list_item","d":6,"p":{"lines":[24,25]},"v":"I/O Cache:实现了文件数据块的缓存、同步、交换等功能"},{"t":"list_item","d":6,"p":{"lines":[25,26]},"v":"具体文件系统:实现具体的文件系统,如EXT4、XFS、NTFS等"},{"t":"list_item","d":6,"p":{"lines":[26,27]},"v":"块设备层:是文件系统的支持,其特点是按照具体大小的块进行读写"},{"t":"list_item","d":6,"p":{"lines":[27,28]},"v":"存储设备:如IDE、SATA、SCSI、NVME存储设备及对应的驱动程序"}]},{"t":"list_item","d":4,"p":{"lines":[28,29]},"v":"网络(Network)","c":[{"t":"list_item","d":6,"p":{"lines":[29,30]},"v":"套接字接口:实现网络连接,监听、绑定、访问等功能"},{"t":"list_item","d":6,"p":{"lines":[30,31]},"v":"套接字:实现统一的网络访问方式"},{"t":"list_item","d":6,"p":{"lines":[31,32]},"v":"网络协议:实现了UDP、TCP、IP等网络协议"},{"t":"list_item","d":6,"p":{"lines":[32,33]},"v":"网络接口:实现网络数据的接受、发送缓冲区和队列"},{"t":"list_item","d":6,"p":{"lines":[33,34]},"v":"网卡驱动:操作具体的网络设备"},{"t":"list_item","d":6,"p":{"lines":[34,35]},"v":"网卡:具体的以太网卡、无线WIFI等设备"}]}]}

取指、访问内存数据

  • 代码段是由CSIP确定的
  • 栈段是由SSSP段确定的

5-1

2 夺权:启动初始化

2.1 设置环境

2.1.1 Ubuntu-18.04.6

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
base_dir=/root/os
loop_device=/dev/loop20
mkdir -p ${base_dir}

# step1:生产虚拟硬盘
dd bs=512 if=/dev/zero of=${base_dir}/hd.img count=204800

# step2:格式化虚拟硬盘
# 2.1 将 hd.img 设置为回环设备
losetup ${loop_device} ${base_dir}/hd.img
# 2.2 格式化
mkfs.ext4 -q ${loop_device}

# step3:安装grub
mkdir -p ${base_dir}/hdisk
# 3.1 挂载硬盘文件
mount -o loop ${base_dir}/hd.img ${base_dir}/hdisk
mkdir -p ${base_dir}/hdisk/boot
# 3.2 安装
grub-install --boot-directory=${base_dir}/hdisk/boot --force --allow-floppy ${loop_device}
# 3.3 编写grub配置文件
cat > ${base_dir}/hdisk/boot/grub/grub.cfg << 'EOF'
menuentry 'HelloOS' {
insmod part_msdos
insmod ext2
set root='hd0,msdos1' #我们的硬盘只有一个分区所以是'hd0,msdos1'
multiboot2 /boot/HelloOS.eki #加载boot目录下的HelloOS.eki文件
boot #引导启动
}
set timeout_style=menu
if [ "${timeout}" = 0 ]; then
set timeout=10 #等待10秒钟自动启动
fi
EOF

# step4:用VirtualBox的工具将文件格式转成 VDI
VBoxManage convertfromraw ${base_dir}/hd.img --format VDI ${base_dir}/hd.vdi

# step4:安装虚拟硬盘
# 4.1 SATA的硬盘其控制器是intelAHCI,其中 HelloOS 是虚拟机的名称
VBoxManage storagectl HelloOS --name "SATA" --add sata --controller IntelAhci --portcount 1
# 4.2 删除虚拟硬盘UUID并重新分配
VBoxManage closemedium disk ${base_dir}/hd.vdi
# 4.3 将虚拟硬盘挂到虚拟机的硬盘控制器,其中 HelloOS 是虚拟机的名称
VBoxManage storageattach HelloOS --storagectl "SATA" --port 1 --device 0 --type hdd --medium ${base_dir}/hd.vdi

# step5:启动虚拟机,其中 HelloOS 是虚拟机的名称
VBoxManage startvm HelloOS

2.1.2 CentOS-7.9-2009

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
base_dir=/root/os
mkdir -p ${base_dir}

# step1:生产虚拟硬盘
dd bs=512 if=/dev/zero of=${base_dir}/hd.img count=204800

# step2:格式化虚拟硬盘
# 2.1 将 hd.img 设置为回环设备
losetup /dev/loop0 ${base_dir}/hd.img
# 2.2 格式化
mkfs.ext4 -q /dev/loop0

# step3:安装grub
mkdir -p ${base_dir}/hdisk
# 3.1 挂载硬盘文件,mount这步执行完后,出现了另一个回环设备 /dev/loop1,不知道为啥
mount -o loop ${base_dir}/hd.img ${base_dir}/hdisk
mkdir -p ${base_dir}/hdisk/boot
# 3.2 安装,这里要用 /dev/loop1 否则会报错
grub2-install --boot-directory=${base_dir}/hdisk/boot --force --allow-floppy /dev/loop1
# 3.3 编写grub配置文件
cat > ${base_dir}/hdisk/boot/grub2/grub.cfg << 'EOF'
menuentry 'HelloOS' {
insmod part_msdos
insmod ext2
set root='hd0,msdos1' #我们的硬盘只有一个分区所以是'hd0,msdos1'
multiboot2 /boot/HelloOS.eki #加载boot目录下的HelloOS.eki文件
boot #引导启动
}
set timeout_style=menu
if [ "${timeout}" = 0 ]; then
set timeout=10 #等待10秒钟自动启动
fi
EOF

# step4:用VirtualBox的工具将文件格式转成 VDI
VBoxManage convertfromraw ${base_dir}/hd.img --format VDI ${base_dir}/hd.vdi

# step4:安装虚拟硬盘
# 4.1 SATA的硬盘其控制器是intelAHCI,其中 HelloOS 是虚拟机的名称
VBoxManage storagectl HelloOS --name "SATA" --add sata --controller IntelAhci --portcount 1
# 4.2 删除虚拟硬盘UUID并重新分配
VBoxManage closemedium disk ${base_dir}/hd.vdi
# 4.3 将虚拟硬盘挂到虚拟机的硬盘控制器,其中 HelloOS 是虚拟机的名称
VBoxManage storageattach HelloOS --storagectl "SATA" --port 1 --device 0 --type hdd --medium ${base_dir}/hd.vdi

第四步报错:

1
2
3
4
5
6
7
8
9
10
11
Kernel driver not installed (rc=-1908)

The VirtualBox Linux kernel driver is either not loaded or not set up correctly. Please try setting it up again by executing

'/sbin/vboxconfig'

as root.

If your system has EFI Secure Boot enabled you may also need to sign the kernel modules (vboxdrv, vboxnetflt, vboxnetadp, vboxpci) before you can load them. Please see your Linux system's documentation for more information.

where: suplibOsInit what: 3 VERR_VM_DRIVER_NOT_INSTALLED (-1908) - The support driver is not installed. On linux, open returned ENOENT.

2.2 二级引导器

计算机如何启动:

  1. 通电
  2. 读取ROM里面的BIOS,用来检查硬件(工作在实模式)
  3. 硬件检查通过
  4. BIOS根据指定的顺序,检查引导设备的第一个扇区(即MBR,在Linux中的MBR就是grub2,严格来说,是grub2中的boot.img),加载在内存地址0x7C00
  5. MBR经过一系列操作,最终把控制权交给操作系统

二级引导器的作用:

  1. 收集机器信息
  2. 对CPU、内存、显卡进行一些初级配置
  3. 放置好内核相关的文件

Layout of Multiboot header

Offset Type Field Name Note
0 u32 magic required
4 u32 flags required
8 u32 checksum required
12 u32 header_addr if flags[16] is set
16 u32 load_addr if flags[16] is set
20 u32 load_end_addr if flags[16] is set
24 u32 bss_end_addr if flags[16] is set
28 u32 entry_addr if flags[16] is set
32 u32 mode_type if flags[2] is set
36 u32 width if flags[2] is set
40 u32 height if flags[2] is set
44 u32 depth if flags[2] is set

细节知识点:

  1. jmp dword far [cpmty_mode]:长跳转这里表示把cpmty_mode处的第一个4字节装入eip,把其后的2字节装入cs

整个引导过程:

2.3 GRUB与vmlinuz的结构

这里提到的GRUB都是指GRUB2

2.3.1 从BIOS到GRUB

硬件工程师设计CPU时,硬性地规定在加电的瞬间,强制将CS寄存器的值设置为0XF000IP寄存器的值设置为0XFFF0

  • 由于开始的时候处于实模式,在实模式下的段地址要乘以16,也就是左移4位,于是0xF000:0xFFF0的等效地址将是0xFFFF0。此地址就是BIOS的入口地址
  • 在这个物理地址上连接了主板上的一块小的ROM芯片。这种芯片的访问机制和寻址方式和内存一样,只是它在断电时不会丢失数据,在常规下也不能往这里写入数据,它是一种只读内存,BIOS程序就被固化在该ROM芯片里

当设备初始化和检查步骤完成之后,BIOS会在内存中建立中断表和中断服务程序,这是启动Linux至关重要的工作,因为Linux会用到它们

  • BIOS会从内存地址(0x00000)开始用1KB的内存空间(0x00000~0x003FF)构建中断表,在紧接着中断表的位置,用256KB的内存空间构建BIOS数据区(0x00400~0x004FF),并在0x0E05B的地址加载了8KB大小的与中断表对应的中断服务程序
  • 中断表中有256个条目,每个条目占用4个字节,其中两个字节是CS寄存器的值,两个字节是IP寄存器的值。每个条目都指向一个具体的中断服务程序

Linux 通常是从硬盘中启动的。硬盘上的第1个扇区(每个扇区512字节空间),被称为MBR(主启动记录),其中包含有基本的GRUB启动程序和分区表,安装GRUB时会自动写入到这个扇区,当MBRBIOS装载到0x7C00地址开始的内存空间中后,BIOS就会将控制权转交给了MBR。在当前的情况下,其实是交给了GRUB

2.3.2 GRUB是如何启动的

BIOS只会加载硬盘上的第1个扇区。不过这个扇区仅有512字节,这512字节中还有64字节的分区表加2字节的启动标志,很显然,剩下446字节的空间,是装不下GRUB这种大型通用引导器的

于是,GRUB的加载分成了多个步骤,同时GRUB也分成了多个文件,其中有两个重要的文件boot.imgcore.img

  • 其中,boot.imgGRUB的安装程序写入到硬盘的MBR中,同时在boot.img文件中的一个位置写入core.img文件占用的第一个扇区的扇区号
  • core.img文件是由GRUB安装程序根据安装时环境信息,用其它GRUB的模块文件动态生成。如下图所示
  • 14-1
  • 如果是从硬盘启动的话,core.img中的第一个扇区的内容就是diskboot.img文件。diskboot.img文件的作用是,读取core.img中剩余的部分到内存中
  • 由于这时diskboot.img文件还不识别文件系统,所以我们将core.img文件的全部位置,都用文件块列表的方式保存到diskboot.img文件中。这样就能确保diskboot.img文件找到core.img文件的剩余内容,最后将控制权交给kernel.img文件
  • 因为这时core.img文件中嵌入了足够多的功能模块,所以可以保证GRUB识别出硬盘分区上文件系统,能够访问/boot/grub目录,并且可以加载相关的配置文件和功能模块,来实现相关的功能,例如加载启动菜单、加载目标操作系统等

正因为GRUB大量使用了动态加载功能模块,这使得core.img文件的体积变得足够小。而GRUBcore.img文件一旦开始工作,就可以加载Linux系统的vmlinuz内核文件了

2.3.3 详解vmlinuz文件结构

vmlinuz名字:

  • vmVirtual Memory
  • linuLinux
  • z:压缩

vmlinuzbzImage复制而来

  • bzImagesetup.binvmlinux.bintools/build这三者构建而成(详见arch/x86/boot/Makefile
    • 14-2
    • 其中tools/build只是一个构件工具,用于将setup.binvmlinux.bin拼接成bzImage文件
    • setup.binobjcopy工具根据setup.elf生成
      • setup.elfarch/x86/boot/目录下的源码编译链接而来
    • vmlinux.binobjcopy工具根据vmlinux生成
      • objcopy工具在处理过程中只是删除了vmlinux文件中.comment段,以及符号表和重定位表(通过参数-S指定),而 vmlinux文件的格式是ELF格式的,所以vmlinux.bin仍然是ELF格式的
      • vmlinux文件就是编译整个Linux内核源代码文件生成的
      • vmlinux.bin文件它依然是ELF格式的文件

3 土地革命:内存

真实的物理内存地址空间不是连续的,这中间可能有空洞,可能是显存,也可能是外设的寄存器

16-1

  • 硬件区:它占用物理内存低端区域,地址区间为0~32MB。从名字就能看出来,这个内存区域是给硬件使用的,我们不是使用虚拟地址吗?虚拟地址不是和物理地址无关吗,一个虚拟可以映射到任一合法的物理地址。但凡事总有例外,虚拟地址主要依赖于CPU中的MMU,但有很多外部硬件能直接和内存交换数据,常见的有DMA,并且它只能访问低于 24MB的物理内存。这就导致了我们很多内存页不能随便分配给这些设备,但是我们只要规定硬件区分配内存页就好,这就是硬件区的作用
  • 内核区:内核也要使用内存,但是内核同样也是运行在虚拟地址空间,就需要有一段物理内存空间和内核的虚拟地址空间是线性映射关系。很多时候,内核使用内存需要大的、且连续的物理内存空间,比如一个进程的内核栈要16KB连续的物理内存、显卡驱动可能需要更大的连续物理内存来存放图形图像数据。这时,我们就需要在这个内核区中分配内存了
  • 应用区:这个区域主是给应用用户态程序使用。应用程序使用虚拟地址空间,一开始并不会为应用一次性分配完所需的所有物理内存,而是按需分配,即应用用到一页就分配一个页。如果访问到一个没有与物理内存页建立映射关系的虚拟内存页,这时候CPU就会产生缺页异常。最终这个缺页异常由操作系统处理,操作系统会分配一个物理内存页,并建好映射关系

4 缩写表

缩写 全称
HAL Hardware Abstraction Layer

5 源码命名

关于源码中的命名,个人感觉是该课程的不足之处,大量缩写,缺少解释说明,没有相关经验的话,基本全靠猜,对新人特别不友好

缩写 全称
adr Address
chk Check
krl Kernel
mem Memory
phy Physical
sz Size
vrm Video RAM

6 参考