├── .gitignore ├── .idea ├── .gitignore ├── deployment.xml ├── encodings.xml ├── linux-learning-notes.iml ├── modules.xml └── vcs.xml ├── README.md ├── codes └── implement-an-os-from-scratch │ ├── README.md │ ├── c10 │ ├── app │ │ └── app1.S │ ├── boot16.S │ ├── boot32.S │ ├── build.c │ ├── head64.S │ ├── include │ │ ├── interrupt.h │ │ ├── mm.h │ │ ├── print.h │ │ ├── sched.h │ │ ├── segment.h │ │ ├── string.h │ │ ├── tss.h │ │ └── types.h │ ├── kernel │ │ ├── handler.S │ │ ├── interrupt.c │ │ ├── sched.c │ │ └── tss.c │ ├── lib │ │ └── string.c │ ├── main.c │ ├── makefile │ └── mm │ │ ├── malloc.c │ │ ├── memory.c │ │ └── page_alloc.c │ ├── c11 │ ├── app │ │ ├── app1.S │ │ └── app2.S │ ├── boot16.S │ ├── boot32.S │ ├── build.c │ ├── head64.S │ ├── include │ │ ├── interrupt.h │ │ ├── mm.h │ │ ├── print.h │ │ ├── sched.h │ │ ├── segment.h │ │ ├── string.h │ │ ├── tss.h │ │ └── types.h │ ├── kernel │ │ ├── handler.S │ │ ├── interrupt.c │ │ ├── sched.c │ │ └── tss.c │ ├── lib │ │ └── string.c │ ├── main.c │ ├── makefile │ └── mm │ │ ├── malloc.c │ │ ├── memory.c │ │ └── page_alloc.c │ ├── c12 │ ├── c12-1 │ │ ├── app │ │ │ ├── app1.S │ │ │ └── app2.S │ │ ├── boot16.S │ │ ├── boot32.S │ │ ├── build.c │ │ ├── head64.S │ │ ├── include │ │ │ ├── interrupt.h │ │ │ ├── mm.h │ │ │ ├── print.h │ │ │ ├── sched.h │ │ │ ├── segment.h │ │ │ ├── string.h │ │ │ ├── syscall.h │ │ │ ├── tss.h │ │ │ └── types.h │ │ ├── kernel │ │ │ ├── handler.S │ │ │ ├── interrupt.c │ │ │ ├── sched.c │ │ │ ├── syscall.c │ │ │ └── tss.c │ │ ├── lib │ │ │ └── string.c │ │ ├── main.c │ │ ├── makefile │ │ └── mm │ │ │ ├── malloc.c │ │ │ ├── memory.c │ │ │ └── page_alloc.c │ └── c12-2 │ │ ├── app │ │ ├── app1.c │ │ ├── app2.c │ │ └── libc │ │ │ ├── start.S │ │ │ ├── std.h │ │ │ └── syscall.S │ │ ├── boot16.S │ │ ├── boot32.S │ │ ├── build.c │ │ ├── head64.S │ │ ├── include │ │ ├── interrupt.h │ │ ├── mm.h │ │ ├── print.h │ │ ├── sched.h │ │ ├── segment.h │ │ ├── string.h │ │ ├── syscall.h │ │ ├── tss.h │ │ └── types.h │ │ ├── kernel │ │ ├── handler.S │ │ ├── interrupt.c │ │ ├── sched.c │ │ ├── syscall.c │ │ └── tss.c │ │ ├── lib │ │ └── string.c │ │ ├── main.c │ │ ├── makefile │ │ └── mm │ │ ├── malloc.c │ │ ├── memory.c │ │ └── page_alloc.c │ ├── c13 │ ├── app │ │ ├── app1.c │ │ ├── app2.c │ │ └── libc │ │ │ ├── start.S │ │ │ ├── std.h │ │ │ └── syscall.S │ ├── boot16.S │ ├── boot32.S │ ├── build.c │ ├── head64.S │ ├── include │ │ ├── interrupt.h │ │ ├── mm.h │ │ ├── print.h │ │ ├── sched.h │ │ ├── segment.h │ │ ├── string.h │ │ ├── syscall.h │ │ ├── tss.h │ │ └── types.h │ ├── ipc │ │ └── shm.c │ ├── kernel │ │ ├── handler.S │ │ ├── interrupt.c │ │ ├── sched.c │ │ ├── syscall.c │ │ └── tss.c │ ├── lib │ │ └── string.c │ ├── main.c │ ├── makefile │ └── mm │ │ ├── malloc.c │ │ ├── memory.c │ │ └── page_alloc.c │ ├── c14 │ ├── app │ │ ├── app1.c │ │ ├── app2.c │ │ ├── libc │ │ │ ├── start.S │ │ │ ├── std.h │ │ │ └── syscall.S │ │ └── libdraw │ │ │ ├── draw.c │ │ │ ├── draw.h │ │ │ └── fonts.c │ ├── boot16.S │ ├── boot32.S │ ├── build.c │ ├── drivers │ │ ├── atkbd.c │ │ └── vesa.c │ ├── head64.S │ ├── include │ │ ├── interrupt.h │ │ ├── mm.h │ │ ├── print.h │ │ ├── sched.h │ │ ├── segment.h │ │ ├── string.h │ │ ├── syscall.h │ │ ├── tss.h │ │ ├── types.h │ │ └── vesa.h │ ├── ipc │ │ └── shm.c │ ├── kernel │ │ ├── handler.S │ │ ├── interrupt.c │ │ ├── sched.c │ │ ├── syscall.c │ │ └── tss.c │ ├── lib │ │ └── string.c │ ├── main.c │ ├── makefile │ └── mm │ │ ├── malloc.c │ │ ├── memory.c │ │ └── page_alloc.c │ ├── c3 │ └── kernel.bin │ ├── c4 │ ├── s01 │ │ └── hello.s │ ├── s07-1 │ │ ├── hello.elf │ │ └── hello.s │ ├── s07-3 │ │ └── hello.s │ ├── s09-1 │ │ └── hello.s │ ├── s09-2 │ │ └── hello.s │ ├── s09-3 │ │ └── hello.s │ ├── s09-4 │ │ └── hello.s │ ├── s09-5 │ │ └── hello.s │ ├── s11 │ │ └── hello.s │ └── s12 │ │ ├── hello.s │ │ ├── makefile │ │ └── sum.s │ ├── c5 │ ├── global-var │ │ ├── hello │ │ └── hello.c │ ├── inline-asm │ │ ├── hello │ │ └── hello.c │ ├── local-var-static │ │ ├── hello │ │ └── hello.c │ ├── local-var │ │ ├── hello │ │ └── hello.c │ └── s03 │ │ ├── hello │ │ └── hello.c │ ├── c6 │ ├── boot16.S │ ├── boot32.S │ ├── build.c │ └── makefile │ ├── c7 │ ├── boot16.S │ ├── boot32.S │ ├── build.c │ ├── head64.S │ ├── include │ │ └── segment.h │ ├── main.c │ └── makefile │ ├── c8 │ ├── boot16.S │ ├── boot32.S │ ├── build.c │ ├── head64.S │ ├── include │ │ ├── mm.h │ │ ├── print.h │ │ ├── segment.h │ │ ├── string.h │ │ └── types.h │ ├── lib │ │ └── string.c │ ├── main.c │ ├── makefile │ └── mm │ │ ├── malloc.c │ │ ├── memory.c │ │ └── page_alloc.c │ └── c9 │ ├── app │ └── app1.S │ ├── boot16.S │ ├── boot32.S │ ├── build.c │ ├── head64.S │ ├── include │ ├── mm.h │ ├── print.h │ ├── sched.h │ ├── segment.h │ ├── string.h │ ├── tss.h │ └── types.h │ ├── kernel │ ├── sched.c │ └── tss.c │ ├── lib │ └── string.c │ ├── main.c │ ├── makefile │ └── mm │ ├── malloc.c │ ├── memory.c │ └── page_alloc.c ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── implement-an-os-from-scratch │ ├── ch06.md │ ├── ch07.md │ ├── ch08.md │ ├── ch09.md │ ├── ch10.md │ ├── ch11.md │ ├── ch12.md │ ├── ch13.md │ ├── ch14.md │ ├── content.md │ └── images │ │ ├── ch06.png │ │ ├── ch07.png │ │ ├── ch09.png │ │ ├── ch10-01.png │ │ ├── ch10-02.png │ │ ├── ch11-01.png │ │ ├── ch11-02.png │ │ ├── ch12-01.png │ │ ├── ch12-02.png │ │ ├── ch13.png │ │ ├── ch14-01.png │ │ ├── ch14-02.png │ │ └── ch14-03.png ├── index.html └── linux-source-code-reading │ ├── content.md │ ├── part01 │ ├── ch01.md │ ├── ch02.md │ ├── ch03.md │ ├── ch04.md │ ├── ch05.md │ ├── ch06.md │ ├── ch07.md │ ├── ch08.md │ ├── ch09.md │ ├── ch10.md │ ├── images │ │ ├── ch01-memory-after-1st-sector-loaded.png │ │ ├── ch02-from-0x7c00-to-0x90000.png │ │ ├── ch04-memory-after-os-compiled.png │ │ ├── ch05-current-memory.png │ │ ├── ch06-memory-after-setup-idt-and-gdt.png │ │ ├── ch08-copy-idt-and-gdt.png │ │ ├── ch09-pagination.png │ │ ├── ch09-setup-paging.png │ │ ├── ch10-memory-before-main.png │ │ └── ch10-processes-before-main.png │ ├── references-interrupt.md │ └── references-software-interrupt.md │ ├── part02 │ ├── ch11.md │ ├── ch12.md │ ├── ch13.md │ ├── ch14.md │ ├── ch15.md │ ├── ch16.md │ ├── ch17.md │ ├── ch18.md │ ├── ch19.md │ ├── ch20.md │ └── images │ │ ├── ch12-calc-memory.png │ │ ├── ch14-mem-after-trap-init.png │ │ ├── ch15-read-blk.png │ │ ├── ch18-tss-ldt.png │ │ └── ch19-buffer-memory.png │ ├── part03 │ ├── ch21.md │ ├── ch22.md │ ├── ch23.md │ ├── ch24.md │ ├── ch25.md │ ├── ch26.md │ ├── ch27.md │ ├── ch29.md │ ├── ch30.md │ └── images │ │ ├── ch25-fork.png │ │ ├── ch27-pde-or-pte.png │ │ ├── ch27-segment.png │ │ ├── ch29-intel-pagination.png │ │ └── ch30-disk-blocks.png │ ├── part04 │ ├── ch31.md │ ├── ch32.md │ ├── ch33.md │ ├── ch34.md │ ├── ch35.md │ ├── ch36.md │ └── images │ │ ├── ch32-loading-root-system.png │ │ ├── ch33-dev-tty0.png │ │ ├── ch34-fork-process2.png │ │ └── ch35-create-table.png │ └── part05 │ ├── ch42.md │ ├── ch43.md │ ├── ch44.md │ ├── ch45.md │ ├── ch46.md │ ├── ch47.md │ ├── ch48.md │ └── images │ ├── ch42-command-display.png │ ├── ch42-termios.png │ ├── ch43-shell-read-cmd.png │ ├── ch44-proc-list.png │ ├── ch45-create-pipe.png │ └── ch47-ll_rw_block.png └── references └── BIOS Interrupts and Functions.pdf /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/linux-learning-notes.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linux操作系统学习笔记 2 | 3 | 本项目主要是对Linux操作系统的学习,包括源码解读和动手实现,主要阅读以下书籍: 4 | - 《Linux源码趣读》(推荐学时:19天):本书主要通过小说的讲解方式,从内核启动、系统初始化、进程创建、shell程序执行和命令执行等方面,讲解内核源码的相关知识。最喜欢本书的知识回顾,每一回中遇到了其他的知识点,都能简要回顾执行过程,并且给下一回做概览性地介绍,从全局角度上了解执行的全貌。 5 | - 《穿越操作系统迷雾》(推荐学时:21天):本书从计算机的软硬件开始讲起,从电路设计到机器语言、汇编语言、C语言的知识介绍,之后开始从0到1实现操作系统,并详细介绍引导区、内存管理、进程、中断和异常、进程调度、系统调用、进程间通信、图形输出等内容。 6 | 7 | ## 在线阅读地址 8 | 9 | 在线阅读地址:https://relph1119.github.io/linux-learning-notes/#/ 10 | 11 | ## 项目结构 12 | 13 |
14 | codes--------------------------------------书中的实现代码
15 | +---implement-an-os-from-scratch---------------《穿越操作系统迷雾》的操作系统实现代码
16 | docs---------------------------------------学习笔记
17 | +---linux-source-code-reading------------------Linux源码趣读
18 | +---implement-an-os-from-scratch---------------穿越操作系统迷雾
19 | references---------------------------------参考资料
20 | 
21 | 22 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/app/app1.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code64 3 | # 无限循环,每次循环向串口输出一个字符 4 | 1: 5 | movb $'F', (0x12345678) 6 | mov (0x12345678), %al 7 | 8 | mov $0x3f8, %dx 9 | out %al, %dx 10 | 11 | jmp 1b -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/boot16.S: -------------------------------------------------------------------------------- 1 | #define IO_PIC 0x20 2 | #define IRQ_OFFSET 32 3 | 4 | .text 5 | .code16 6 | start16: 7 | # 清除标志寄存器中的中断位 8 | cli 9 | 10 | # BIOS使用一个称为E820的数据结构实例表示内存地址的每一个段 11 | # 中断0x15的处理函数从寄存器DI中读取这个地址 12 | mov $e820_entry, %di 13 | xor %ebx, %ebx 14 | e820_rd_entry: 15 | # 设置EAX寄存器的功能号 16 | mov $0xe820, %eax 17 | # 告知0x15中断处理函数,复制每条E820记录的前20个字节(第1个8字节表示内存段起始地址,第2个8字节表示段的尺寸,剩下4字节表示段的类型) 18 | mov $20, %ecx 19 | # 发起中断,执行BIOS中0x15中断处理函数 20 | int $0x15 21 | 22 | # 指向下一个E820记录的内存地址 23 | add $20, %di 24 | # 记录读取的E820的条数 25 | incb e820_nr_entry 26 | 27 | # 如果全部读取完毕,0x15中断处理函数会将寄存器EBX设置为0 28 | cmp $0, %ebx 29 | jne e820_rd_entry 30 | 31 | # 初始化8259A 32 | # 初始化ICW1命令字 33 | # D0=0:关闭IC4关联的特性 34 | # D1=0:单片模式 35 | # D3=0:KVM的虚拟8295A不支持电平触发,设置为边沿触发 36 | mov $0x13, %al 37 | # 主8259A的端口地址是0x20 38 | mov $(IO_PIC), %dx 39 | out %al,%dx 40 | # 初始化ICW2命令字 41 | # 外设的中断向量从32开始分配 42 | mov $(IRQ_OFFSET), %al 43 | mov $(IO_PIC+1), %dx 44 | out %al, %dx 45 | # 初始化ICW4命令字 46 | # D0=1:模式是8086及以上系统 47 | # D1=0:关闭自动复位ISR寄存器,由中断处理函数负责向中断芯片发送EOI(中断结束)命令 48 | # D2=0,D3=0:KVM忽略了缓冲模式和主从位 49 | # D4=0:固定优先级策略 50 | mov $0x1, %al 51 | mov $(IO_PIC+1), %dx 52 | out %al, %dx 53 | 54 | # 加载段描述符表地址到寄存器GDTR 55 | lgdt gdtr 56 | 57 | # 开启处理器的保护模式,CR0寄存器的第0位PE用于控制处理器是否开启保护模式 58 | mov %cr0, %eax 59 | # 将CR0的最后一位置为1,即开启保护模式 60 | or $0x1, %eax 61 | mov %eax, %cr0 62 | 63 | # 段选择子是0x8(段索引是1,使用全局段描述符表TI是0,特权级是00,即0000000000001000) 64 | # 保护模式的入口地址是0x20000,段基址为0,所以段内偏移地址为0x20000 65 | # 长跳转指令ljmpl [段选择子] [段内偏移地址] 66 | ljmpl $0x8, $0x20000 67 | 68 | gdt: 69 | # 段描述符表的第0项保留不用 70 | .quad 0x0000000000000000 71 | # 第1项定义内核代码段 72 | .quad 0x00c09a00000007ff 73 | # 第2项定义内核数据段 74 | .quad 0x00c09200000007ff 75 | gdt_end: 76 | 77 | gdtr: 78 | # 低16位对应段描述符表的长度 79 | .word gdt_end - gdt 80 | # 高16位对应段描述符表的地址(0x1000<<4 + gdt) 81 | .word gdt, 0x1 82 | 83 | .org 0x3000 84 | e820_nr_entry: 85 | .long 0 86 | e820_entry: 87 | .fill 1024, 1, 0 88 | 89 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/build.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | * 将多个文件成为一个kernel.bin文件 8 | */ 9 | int main() { 10 | int fd, fd_kernel; 11 | int c; 12 | char buf[512]; 13 | 14 | // 创建kernel.bin文件并以读写方式打开 15 | fd_kernel = open("kernel.bin", O_WRONLY | O_CREAT, 0664); 16 | 17 | // 将boot16.bin文件的数据写入kernel.bin文件中 18 | fd = open("boot16.bin", O_RDONLY); 19 | while (1) { 20 | c = read(fd, buf, 512); 21 | if (c > 0) { 22 | write(fd_kernel, buf, c); 23 | } else { 24 | break; 25 | } 26 | }; 27 | close(fd); 28 | 29 | // 将内核保护模式部分加载到内存0x20000处 30 | lseek(fd_kernel, 0x20000 - 0x10000, SEEK_SET); 31 | 32 | // 将boot32.bin文件的数据写入kernel.bin文件中 33 | fd = open("boot32.bin", O_RDONLY); 34 | while (1) { 35 | c = read(fd, buf, 512); 36 | if (c > 0) { 37 | write(fd_kernel, buf, c); 38 | } else { 39 | break; 40 | } 41 | }; 42 | close(fd); 43 | 44 | // 将内核64位部分加载到内存0x100000处 45 | lseek(fd_kernel, 0x100000 - 0x10000, SEEK_SET); 46 | 47 | // 将system.bin文件的数据写入kernel.bin文件中 48 | fd = open("system.bin", O_RDONLY); 49 | while (1) { 50 | c = read(fd, buf, 512); 51 | if (c > 0) { 52 | write(fd_kernel, buf, c); 53 | } else { 54 | break; 55 | } 56 | }; 57 | close(fd); 58 | 59 | // 将app1程序映像加载到内存0xc800000处 60 | lseek(fd_kernel, 0xc800000 - 0x10000, SEEK_SET); 61 | fd = open("app/app1.bin", O_RDONLY); 62 | while (1) { 63 | c = read(fd, buf, 512); 64 | if (c > 0) { 65 | write(fd_kernel, buf, c); 66 | } else { 67 | break; 68 | } 69 | }; 70 | close(fd); 71 | 72 | close(fd_kernel); 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/head64.S: -------------------------------------------------------------------------------- 1 | #include "include/segment.h" 2 | 3 | .text 4 | .code64 5 | .globl gdt 6 | .globl ret_from_kernel 7 | 8 | lgdt gdtr 9 | # 将中断描述符表地址写入IDTR寄存器中 10 | lidt idtr 11 | 12 | # 初始化寄存器 13 | mov $KERNEL_DS, %ax 14 | mov %ax, %ds 15 | mov %ax, %ss 16 | mov %ax, %es 17 | mov %ax, %fs 18 | mov %ax, %gs 19 | 20 | # 使用RSP寄存器指向栈底 21 | mov $task0_stack, %rsp 22 | # 跳转到main方法 23 | push $main 24 | ret 25 | 26 | # 所有段的TI为0,内核段的特权级为0,用户段的特权级为3 27 | .align 64 28 | gdt: 29 | # 空描述符(保留不用) 30 | .quad 0x0000000000000000 31 | # 内核代码段描述符 32 | .quad 0x00209a0000000000 33 | # 内核数据段描述符 34 | .quad 0x0000920000000000 35 | # 32位用户代码段描述符 36 | .quad 0x0000000000000000 37 | # 用户数据段描述符 38 | .quad 0x0000f20000000000 39 | # 64位用户代码段描述符 40 | .quad 0x0020fa0000000000 41 | .fill 128, 8, 0 42 | gdt_end: 43 | 44 | gdtr: 45 | .word gdt_end - gdt 46 | .quad gdt 47 | 48 | idtr: 49 | # 中断描述符表长度,256项中断描述符,每个是16字节 50 | .word 16 * 256 51 | # 中断描述符表地址 52 | .quad idt_table 53 | 54 | # 4KB大小的栈空间 55 | .fill 4096, 1, 0 56 | task0_stack: 57 | 58 | # 在执行iret前,初始化其他段寄存器,之后再从栈中弹出断点信息,返回用户空间 59 | ret_from_kernel: 60 | mov $USER_DS, %rax 61 | movw %ax, %ds 62 | movw %ax, %es 63 | movw %ax, %fs 64 | movw %ax, %gs 65 | iretq -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/include/interrupt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void init_8254(); 4 | void interrupt_init(); 5 | 6 | void timer_handler(); 7 | void pf_handler(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/include/mm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | #define E820MAX 32 6 | #define E820_RAM 1 7 | 8 | // 定义最大页面数,每个页面大小为4K,一共表示4GB物理内存 9 | #define MAX_PAGES (1024 * 1024) 10 | // 定义内核所需的页面,16K个页面 11 | #define KERNEL_PAGE_NUM (1024 * 16) 12 | 13 | #define PAGE_SIZE 4096 14 | 15 | #define PAGE_OFFSET 0xffff888000000000 16 | #define VA(x) ((void*)((unsigned long)(x) + PAGE_OFFSET)) 17 | #define PA(x) ((unsigned long)(x) - PAGE_OFFSET) 18 | 19 | #define TASK0_PML4 0x30000 20 | 21 | // 内存大小 22 | extern unsigned long mem_size; 23 | 24 | extern uint8_t pages[MAX_PAGES]; 25 | 26 | // 使用packed告知GCC编译器,分配结构体变量时不要进行对齐 27 | struct e820entry { 28 | uint64_t addr; // 内存段的起始地址 29 | uint64_t size; // 内存段的尺寸 30 | uint32_t type; // 内存段的类型 31 | } __attribute__((packed)); 32 | 33 | // 存储E820信息的内存区域 34 | struct e820map { 35 | uint32_t nr_entry; 36 | struct e820entry map[E820MAX]; // 多条E820记录 37 | }; 38 | 39 | // 内存管理初始化 40 | void mm_init(); 41 | // 分配页面 42 | unsigned long alloc_page(); 43 | // 释放页面 44 | void free_page(uint64_t addr); 45 | void* malloc(int size); 46 | void free(void* obj); 47 | // 内存空间映射 48 | void map_range(unsigned long pml4_pa, unsigned long from_va, unsigned long to_pa, char us, long npage); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/include/print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 如果是32位,向串口输出1次,如果是64位,向串口输出2次 4 | #define print(x) \ 5 | do { \ 6 | int size = sizeof(x); \ 7 | if (size <= 4) { \ 8 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 9 | "out %%eax, %%dx\n\t" \ 10 | : \ 11 | : "a"(x) \ 12 | : "dx"); \ 13 | } else if (size == 8) { \ 14 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 15 | "out %%eax, %%dx\n\t" \ 16 | "shr $32, %%rax\n\t" \ 17 | "out %%eax, %%dx\n\t" \ 18 | : \ 19 | : "a"(x) \ 20 | : "dx"); \ 21 | } \ 22 | } while (0) -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/include/sched.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 记录进程的信息和状态 6 | struct task { 7 | unsigned long id; 8 | unsigned long rip; // 任务调度切换时,指令的地址 9 | unsigned long rsp0; // 任务调度切换时,内核栈的栈顶 10 | unsigned long kstack; // 内核栈的栈底,用于在内核时,任务状态段中特权级为0的栈指针指向当前任务的内核栈栈底 11 | unsigned long pml4; // 根页表的物理地址,用于更新寄存器CR3指向当前任务的页表 12 | 13 | // 支撑任务链的链表 14 | struct task* next; 15 | struct task* prev; 16 | }; 17 | 18 | extern unsigned long ret_from_kernel; 19 | extern struct task* current; 20 | 21 | void sched_init(); 22 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/include/segment.h: -------------------------------------------------------------------------------- 1 | // 内核代码段选择子 2 | #define KERNEL_CS 0x8 3 | // 内核数据段选择子 4 | #define KERNEL_DS 0x10 5 | // 32位用户代码段选择子 6 | #define USER32_CS 0x1b 7 | // 用户数据段选择子 8 | #define USER_DS 0x23 9 | // 64位用户代码段选择子 10 | #define USER_CS 0x2b 11 | 12 | // 段描述符表的第6项作为任务状态段的段描述符 13 | #define GDT_TSS_ENTRY 6 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/include/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | void* memset(void *s, char c, unsigned long n); 6 | void memcpy(void *dest, const void *src, unsigned long n); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/include/tss.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 可以寻址64K(65536)个端口 6 | #define IO_BITMAP_BYTES (65536 / 8) 7 | 8 | struct tss { 9 | uint32_t reserved1; // 保留 10 | uint64_t rsp0; // 特权级为0的栈指针 11 | uint64_t rsp1; // 特权级为1的栈指针 12 | uint64_t rsp2; // 特权级为2的栈指针 13 | uint64_t reserved2; // 保留 14 | uint64_t ist[7]; // IST的7个专用栈 15 | uint32_t reserved3; // 保留 16 | uint32_t reserved4; // 保留 17 | uint16_t reserved5; // 保留 18 | uint16_t io_bitmap_offset; // 程序I/O权限位图相对于任务状态段基址的16位偏移 19 | uint8_t io_bitmap[IO_BITMAP_BYTES + 1]; // I/O权限位图 20 | } __attribute__((packed)); 21 | 22 | struct tss_desc { 23 | uint16_t limit0; // 段长度 24 | uint16_t base0; // 段基址(0~15) 25 | uint16_t base1 : 8, type : 4, desc_type : 1, dpl : 2, p : 1; 26 | uint16_t limit1 : 4, avl : 1, zero0 : 2, g : 1, base2 : 8; 27 | uint32_t base3; // 段基址(32~63) 28 | uint32_t zero1; // 保留 29 | } __attribute__((packed)); 30 | 31 | extern struct tss tss; 32 | // TSS任务状态段初始化 33 | void tss_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef signed char int8_t; 4 | typedef short int int16_t; 5 | typedef int int32_t; 6 | typedef long int int64_t; 7 | 8 | typedef unsigned char uint8_t; 9 | typedef unsigned short int uint16_t; 10 | typedef unsigned int uint32_t; 11 | typedef unsigned long int uint64_t; 12 | 13 | #define NULL ((void*)0) 14 | 15 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/kernel/handler.S: -------------------------------------------------------------------------------- 1 | # 各种中断处理函数 2 | 3 | .text 4 | .code64 5 | .globl timer_handler 6 | .globl pf_handler 7 | 8 | # 为进程保存完整环境,调用者负责的寄存器:RDI、RSI、RDX、RCX、RAX、R8~R11 9 | .macro SAVE_CONTEXT save_rax = 1 10 | pushq %rdi 11 | pushq %rsi 12 | pushq %rdx 13 | pushq %rcx 14 | .if \save_rax 15 | pushq %rax 16 | .endif 17 | pushq %r8 18 | pushq %r9 19 | pushq %r10 20 | pushq %r11 21 | .endm 22 | 23 | .macro RESTORE_CONTEXT rstore_rax = 1 24 | popq %r11 25 | popq %r10 26 | popq %r9 27 | popq %r8 28 | .if \rstore_rax 29 | popq %rax 30 | .endif 31 | popq %rcx 32 | popq %rdx 33 | popq %rsi 34 | popq %rdi 35 | .endm 36 | 37 | timer_handler: 38 | # 保护上下文 39 | SAVE_CONTEXT 40 | 41 | # 将OCW2写入8259A的端口0x20处,D5=1表示处理器已经处理完中断了 42 | movb $0x20,%al 43 | outb %al,$0x20 44 | 45 | # 调用do_timer函数 46 | call do_timer 47 | 48 | # 恢复上下文 49 | RESTORE_CONTEXT 50 | # IF位自动复位,再次开启中断 51 | iretq 52 | 53 | pf_handler: 54 | SAVE_CONTEXT 55 | 56 | # 发生缺页异常时,处理器会将缺页地址存储到寄存器CR2中 57 | # 需要从寄存器CR2中取出引起缺页的地址 58 | mov %cr2, %rdi 59 | call do_page_fault 60 | 61 | RESTORE_CONTEXT 62 | # 越过错误码,避免弹出到指令指针寄存器RIP中 63 | add $8, %rsp 64 | iretq 65 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/kernel/interrupt.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/interrupt.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | // 产生100Hz的中断,即每秒向8259A发出100次时钟信号,该值表示每秒的中断信号次数 7 | #define COUNTER (1193181 / 100) 8 | // 中断类型 9 | #define GATE_INTERRUPT 0xe 10 | // 异常类型 11 | #define GATE_EXCEPTION 0xf 12 | 13 | // 中断描述符 14 | struct gate_desc { 15 | uint16_t offset_low; // 中断处理函数地址0~15位 16 | uint16_t segment; // 段选择子 17 | uint16_t ist : 3, zero : 5, type : 4, zero2 : 1, dpl : 2, p : 1; // ist:中断栈索引;type:中断类型(中断1110/异常1111);dpl:特权级;p:存在位 18 | uint16_t offset_middle; // 中断处理函数地址16~31位 19 | uint32_t offset_high; // 中断处理函数地址32~63位 20 | uint32_t reserved; // 保留 21 | } __attribute__((packed)); 22 | 23 | // 定义256项中断和异常,中断描述符表 24 | struct gate_desc idt_table[256]; 25 | 26 | // 初始化8254计数器芯片 27 | void init_8254() { 28 | // D7D6=00:SC选择计数器0 29 | // D5D4=11:RW设置读取计数值的方式,11表示首先写入低8位,然后写入高8位 30 | // D3D2D1=011:工作模式为3,使用方波 31 | // D0=0:编码格式为二进制格式 32 | // 将控制字写入0x43端口 33 | __asm__ ("outb %%al, $0x43"::"a"(0x36)); 34 | // 分别将低8位和高8位写入0x40端口 35 | __asm__ ("outb %%al, $0x40"::"a"(COUNTER & 0xff)); 36 | __asm__ ("outb %%al, $0x40"::"a"(COUNTER >> 8)); 37 | } 38 | 39 | // 设置中断描述符 40 | // index:中断向量,对应中断描述符表中的索引号 41 | // addr:中断处理函数地址 42 | // type:描述符类型 43 | static void set_gate(unsigned char index, unsigned long addr, char type) { 44 | struct gate_desc* desc = &idt_table[index]; 45 | 46 | // 中断描述符初始化为0 47 | memset(desc, 0, sizeof(struct gate_desc)); 48 | // 设置为内核代码段 49 | desc->segment = KERNEL_CS; 50 | // 设置中断处理函数地址 51 | desc->offset_low = (uint16_t)addr; 52 | desc->offset_middle = (uint16_t)(addr >> 16); 53 | desc->offset_high = (uint32_t)(addr >> 32); 54 | // 特权级为0,内核空间 55 | desc->dpl = 0; 56 | desc->type = type; 57 | // 存在位设置为1 58 | desc->p = 1; 59 | } 60 | 61 | // 中断系统初始化 62 | void interrupt_init() { 63 | // 添加缺页异常处理函数 64 | set_gate(14, (unsigned long)&pf_handler, GATE_EXCEPTION); 65 | // 设置时钟中断描述符,时钟中断的向量号是32(0x20) 66 | set_gate(0x20, (unsigned long)&timer_handler, GATE_INTERRUPT); 67 | 68 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/kernel/tss.c: -------------------------------------------------------------------------------- 1 | #include "include/tss.h" 2 | #include "include/sched.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | extern unsigned long gdt[64]; 7 | struct tss tss; 8 | 9 | void tss_init() { 10 | // 允许应用程序访问所有I/O端口 11 | memset(&tss, 0, sizeof(tss)); 12 | tss.io_bitmap_offset = __builtin_offsetof(struct tss, io_bitmap); 13 | tss.io_bitmap[IO_BITMAP_BYTES] = ~0; 14 | // 设置任务状态段的RSP0指向当前应用程序的内核栈地址 15 | tss.rsp0 = current->kstack; 16 | 17 | // 得到GDT表的第6项 18 | struct tss_desc* desc = (struct tss_desc*)&gdt[GDT_TSS_ENTRY]; 19 | // 将任务状态段的段描述符清0 20 | memset(desc, 0, sizeof(struct tss_desc)); 21 | // 计算tss结构体的长度的低16位 22 | desc->limit0 = sizeof(tss) & 0xffff; 23 | desc->base0 = (unsigned long)(&tss) & 0xffff; 24 | desc->base1 = ((unsigned long)(&tss) >> 16) & 0xff; 25 | // 段类型为0x9 26 | desc->type = 0x9; 27 | // 存在位为1 28 | desc->p = 1; 29 | // 段长度的第16~19位 30 | desc->limit1 = (sizeof(tss) >> 16) & 0xf; 31 | desc->base2 = ((unsigned long)(&tss) >> 24) & 0xff; 32 | desc->base3 = (unsigned long)(&tss) >> 32; 33 | 34 | // 禁止应用程序对所有端口的访问,会直接终止执行 35 | // memset(tss.io_bitmap, 0xff, IO_BITMAP_BYTES); 36 | 37 | // 装载任务寄存器TR,段索引为6,TI为0,特权级(内核级)为0 38 | __asm__ ("ltr %w0" : : "r"(GDT_TSS_ENTRY << 3)); 39 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/lib/string.c: -------------------------------------------------------------------------------- 1 | // 填充一段内存区 2 | void* memset(void *s, char c, unsigned long n) { 3 | char *tmp = s; 4 | 5 | while (n--) { 6 | *tmp++ = c; 7 | } 8 | 9 | return s; 10 | } 11 | 12 | /* 内存区域复制 13 | * @param dest 目的内存地址 14 | * @param src 源内存地址 15 | * @param n 复制的字节数 16 | */ 17 | void memcpy(void *dest, const void *src, unsigned long n) { 18 | char *tmp = dest; 19 | const char *s = src; 20 | 21 | // 将字节逐个从源内存的内容复制到目的内存 22 | while (n--) { 23 | *tmp++ = *s++; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/main.c: -------------------------------------------------------------------------------- 1 | #include "include/mm.h" 2 | #include "include/print.h" 3 | #include "include/sched.h" 4 | #include "include/tss.h" 5 | #include "include/interrupt.h" 6 | 7 | int main() { 8 | mm_init(); 9 | interrupt_init(); 10 | 11 | // 设置标志寄存器中的IF位,开启中断 12 | __asm__ ("sti"); 13 | sched_init(); 14 | 15 | // 任务状态段初始化 16 | tss_init(); 17 | 18 | // 使能时钟中断 19 | init_8254(); 20 | 21 | // 将寄存器CR3指向进程1的页表 22 | __asm__ ("mov %0, %%cr3": :"r"(current->pml4)); 23 | 24 | // 设置栈指针指向进程1的内核栈的栈顶,并执行ret_from_kernel 25 | __asm__ ("movq %0, %%rsp\n\t" 26 | "jmp ret_from_kernel\n\t" 27 | : 28 | : "m"(current->rsp0) 29 | ); 30 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/makefile: -------------------------------------------------------------------------------- 1 | kernel.bin: build boot16.bin boot32.bin system.bin app/app1.bin 2 | ./build 3 | 4 | boot16.bin: boot16.S 5 | gcc -c boot16.S -o boot16.o 6 | # 实模式运行时的地址是由“段基址<<4+段内偏移”生成,kvmtool将段寄存器初始化为0x1000,段内偏移为0 7 | ld -Ttext=0x0 boot16.o -o boot16.elf 8 | objcopy -O binary boot16.elf boot16.bin 9 | 10 | boot32.bin: boot32.S 11 | gcc -c boot32.S -o boot32.o 12 | # 保护模式的代码段基址为0,段内偏移为0x20000 13 | ld -Ttext=0x20000 boot32.o -o boot32.elf 14 | objcopy -O binary boot32.elf boot32.bin 15 | 16 | # 使用CFLAGS给编译器传参 17 | # -I:从哪些目录搜索头文件 18 | # -fon-pic:位置无关代码可以加载在程序地址空间中的任何位置 19 | # -mcmodel=kernel:指示gcc生成使用64位寻址操作数的汇编代码,基于large模型改进,减少指令长度 20 | # -fno-stack-protector:关闭栈溢出检查 21 | # -fcf-protection=none:关闭gcc检查代码的特性 22 | # -nostdinc:不要搜索宿主系统的系统目录下的头文件 23 | # -fno-builtin:不需要使用内置的memset 24 | CFLAGS = -std=c11 -I. -fno-pic -mcmodel=kernel -fno-stack-protector -fcf-protection=none -nostdinc -fno-builtin 25 | 26 | SRCS = main.c $(wildcard mm/*.c) $(wildcard lib/*.c) $(wildcard kernel/*.c) 27 | # 将SRCS中的每一个.c替换成.o 28 | OBJS = $(SRCS:.c=.o) 29 | 30 | system.bin: head64.o kernel/handler.o $(OBJS) 31 | # 内核映像的虚拟地址起始于0xffffffff8000000,映射物理内存地址0处,64位部分位于物理内存0x100000处 32 | ld -Ttext=0xffffffff80100000 head64.o kernel/handler.o $(OBJS) -o system.elf 33 | objcopy -O binary system.elf $@ 34 | 35 | # 将依赖关系保存到.depend文件中 36 | .depend: $(SRCS) 37 | @rm -f .depend 38 | @$(foreach src,$(SRCS), \ 39 | echo -n $(dir $(src)) >> .depend; \ 40 | gcc -I. -MM $(src) >> .depend; \ 41 | ) 42 | include .depend 43 | 44 | app/app1.bin: app/app1.S 45 | gcc -c app/app1.S -o app/app1.o 46 | ld -Ttext=0x100000 app/app1.o -o app/app1.elf 47 | objcopy -O binary app/app1.elf app/app1.bin 48 | 49 | build: build.c 50 | gcc $< -o $@ 51 | 52 | .PHONY: clean run 53 | 54 | run: kernel.bin 55 | ~/kvmtool/lkvm run -c 1 -k ./kernel.bin 56 | 57 | clean: 58 | find -name "*.o" -o -name "*.elf" -o -name "*.bin" | xargs rm -f 59 | rm -f build .depend messages.log 60 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c10/mm/page_alloc.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/mm.h" 3 | 4 | // 分配页面 5 | unsigned long alloc_page() { 6 | unsigned long addr = 0; 7 | 8 | // 从内存映像占用的物理页面之后的页面开始 9 | for (long i = KERNEL_PAGE_NUM; i < mem_size / PAGE_SIZE; i++) { 10 | // 直到找到空页面 11 | if (pages[i] == 0) { 12 | // 将新分配的页面标记为已占用 13 | pages[i] = 1; 14 | // 计算页面的物理地址 15 | addr = PAGE_SIZE * i; 16 | break; 17 | } 18 | } 19 | 20 | // 返回新分配页面的物理地址 21 | return addr; 22 | } 23 | 24 | // 释放页面 25 | void free_page(uint64_t addr) { 26 | // 计算归还页面在数组page的索引 27 | uint32_t index = addr / PAGE_SIZE; 28 | 29 | // 标记页面为空闲 30 | pages[index] = 0; 31 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/app/app1.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code64 3 | # 无限循环,每次循环向串口输出一个字符 4 | 1: 5 | movb $'F', (0x12345678) 6 | mov (0x12345678), %al 7 | 8 | mov $0x3f8, %dx 9 | out %al, %dx 10 | 11 | jmp 1b -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/app/app2.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code64 3 | 1: 4 | mov $0x3f8, %dx 5 | mov $'B', %ax 6 | out %ax, %dx 7 | jmp 1b 8 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/boot16.S: -------------------------------------------------------------------------------- 1 | #define IO_PIC 0x20 2 | #define IRQ_OFFSET 32 3 | 4 | .text 5 | .code16 6 | start16: 7 | # 清除标志寄存器中的中断位 8 | cli 9 | 10 | # BIOS使用一个称为E820的数据结构实例表示内存地址的每一个段 11 | # 中断0x15的处理函数从寄存器DI中读取这个地址 12 | mov $e820_entry, %di 13 | xor %ebx, %ebx 14 | e820_rd_entry: 15 | # 设置EAX寄存器的功能号 16 | mov $0xe820, %eax 17 | # 告知0x15中断处理函数,复制每条E820记录的前20个字节(第1个8字节表示内存段起始地址,第2个8字节表示段的尺寸,剩下4字节表示段的类型) 18 | mov $20, %ecx 19 | # 发起中断,执行BIOS中0x15中断处理函数 20 | int $0x15 21 | 22 | # 指向下一个E820记录的内存地址 23 | add $20, %di 24 | # 记录读取的E820的条数 25 | incb e820_nr_entry 26 | 27 | # 如果全部读取完毕,0x15中断处理函数会将寄存器EBX设置为0 28 | cmp $0, %ebx 29 | jne e820_rd_entry 30 | 31 | # 初始化8259A 32 | # 初始化ICW1命令字 33 | # D0=0:关闭IC4关联的特性 34 | # D1=0:单片模式 35 | # D3=0:KVM的虚拟8295A不支持电平触发,设置为边沿触发 36 | mov $0x13, %al 37 | # 主8259A的端口地址是0x20 38 | mov $(IO_PIC), %dx 39 | out %al,%dx 40 | # 初始化ICW2命令字 41 | # 外设的中断向量从32开始分配 42 | mov $(IRQ_OFFSET), %al 43 | mov $(IO_PIC+1), %dx 44 | out %al, %dx 45 | # 初始化ICW4命令字 46 | # D0=1:模式是8086及以上系统 47 | # D1=0:关闭自动复位ISR寄存器,由中断处理函数负责向中断芯片发送EOI(中断结束)命令 48 | # D2=0,D3=0:KVM忽略了缓冲模式和主从位 49 | # D4=0:固定优先级策略 50 | mov $0x1, %al 51 | mov $(IO_PIC+1), %dx 52 | out %al, %dx 53 | 54 | # 加载段描述符表地址到寄存器GDTR 55 | lgdt gdtr 56 | 57 | # 开启处理器的保护模式,CR0寄存器的第0位PE用于控制处理器是否开启保护模式 58 | mov %cr0, %eax 59 | # 将CR0的最后一位置为1,即开启保护模式 60 | or $0x1, %eax 61 | mov %eax, %cr0 62 | 63 | # 段选择子是0x8(段索引是1,使用全局段描述符表TI是0,特权级是00,即0000000000001000) 64 | # 保护模式的入口地址是0x20000,段基址为0,所以段内偏移地址为0x20000 65 | # 长跳转指令ljmpl [段选择子] [段内偏移地址] 66 | ljmpl $0x8, $0x20000 67 | 68 | gdt: 69 | # 段描述符表的第0项保留不用 70 | .quad 0x0000000000000000 71 | # 第1项定义内核代码段 72 | .quad 0x00c09a00000007ff 73 | # 第2项定义内核数据段 74 | .quad 0x00c09200000007ff 75 | gdt_end: 76 | 77 | gdtr: 78 | # 低16位对应段描述符表的长度 79 | .word gdt_end - gdt 80 | # 高16位对应段描述符表的地址(0x1000<<4 + gdt) 81 | .word gdt, 0x1 82 | 83 | .org 0x3000 84 | e820_nr_entry: 85 | .long 0 86 | e820_entry: 87 | .fill 1024, 1, 0 88 | 89 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/head64.S: -------------------------------------------------------------------------------- 1 | #include "include/segment.h" 2 | 3 | .text 4 | .code64 5 | .globl gdt 6 | .globl ret_from_kernel 7 | .globl task0_stack 8 | .globl idle_task_entry 9 | 10 | lgdt gdtr 11 | # 将中断描述符表地址写入IDTR寄存器中 12 | lidt idtr 13 | 14 | # 初始化寄存器 15 | mov $KERNEL_DS, %ax 16 | mov %ax, %ds 17 | mov %ax, %ss 18 | mov %ax, %es 19 | mov %ax, %fs 20 | mov %ax, %gs 21 | 22 | # 使用RSP寄存器指向栈底 23 | mov $task0_stack, %rsp 24 | # 跳转到main方法 25 | push $main 26 | ret 27 | 28 | # 所有段的TI为0,内核段的特权级为0,用户段的特权级为3 29 | .align 64 30 | gdt: 31 | # 空描述符(保留不用) 32 | .quad 0x0000000000000000 33 | # 内核代码段描述符 34 | .quad 0x00209a0000000000 35 | # 内核数据段描述符 36 | .quad 0x0000920000000000 37 | # 32位用户代码段描述符 38 | .quad 0x0000000000000000 39 | # 用户数据段描述符 40 | .quad 0x0000f20000000000 41 | # 64位用户代码段描述符 42 | .quad 0x0020fa0000000000 43 | .fill 128, 8, 0 44 | gdt_end: 45 | 46 | gdtr: 47 | .word gdt_end - gdt 48 | .quad gdt 49 | 50 | idtr: 51 | # 中断描述符表长度,256项中断描述符,每个是16字节 52 | .word 16 * 256 53 | # 中断描述符表地址 54 | .quad idt_table 55 | 56 | # 4KB大小的栈空间 57 | .fill 4096, 1, 0 58 | task0_stack: 59 | 60 | # 在执行iret前,初始化其他段寄存器,之后再从栈中弹出断点信息,返回用户空间 61 | ret_from_kernel: 62 | mov $USER_DS, %rax 63 | movw %ax, %ds 64 | movw %ax, %es 65 | movw %ax, %fs 66 | movw %ax, %gs 67 | iretq 68 | 69 | # 空闲进程,当没有就绪任务需要运行时,让处理器运行这个空闲任务 70 | idle_task_entry: 71 | 1: 72 | # 开启处理器响应中断标识 73 | sti 74 | # 让处理器停止运转 75 | hlt 76 | jmp 1b -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/include/interrupt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void init_8254(); 4 | void interrupt_init(); 5 | 6 | void timer_handler(); 7 | void pf_handler(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/include/mm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | #define E820MAX 32 6 | #define E820_RAM 1 7 | 8 | // 定义最大页面数,每个页面大小为4K,一共表示4GB物理内存 9 | #define MAX_PAGES (1024 * 1024) 10 | // 定义内核所需的页面,16K个页面 11 | #define KERNEL_PAGE_NUM (1024 * 16) 12 | 13 | #define PAGE_SIZE 4096 14 | 15 | #define PAGE_OFFSET 0xffff888000000000 16 | #define VA(x) ((void*)((unsigned long)(x) + PAGE_OFFSET)) 17 | #define PA(x) ((unsigned long)(x) - PAGE_OFFSET) 18 | 19 | #define TASK0_PML4 0x30000 20 | 21 | // 内存大小 22 | extern unsigned long mem_size; 23 | 24 | extern uint8_t pages[MAX_PAGES]; 25 | 26 | // 使用packed告知GCC编译器,分配结构体变量时不要进行对齐 27 | struct e820entry { 28 | uint64_t addr; // 内存段的起始地址 29 | uint64_t size; // 内存段的尺寸 30 | uint32_t type; // 内存段的类型 31 | } __attribute__((packed)); 32 | 33 | // 存储E820信息的内存区域 34 | struct e820map { 35 | uint32_t nr_entry; 36 | struct e820entry map[E820MAX]; // 多条E820记录 37 | }; 38 | 39 | // 内存管理初始化 40 | void mm_init(); 41 | // 分配页面 42 | unsigned long alloc_page(); 43 | // 释放页面 44 | void free_page(uint64_t addr); 45 | void* malloc(int size); 46 | void free(void* obj); 47 | // 内存空间映射 48 | void map_range(unsigned long pml4_pa, unsigned long from_va, unsigned long to_pa, char us, long npage); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/include/print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 如果是32位,向串口输出1次,如果是64位,向串口输出2次 4 | #define print(x) \ 5 | do { \ 6 | int size = sizeof(x); \ 7 | if (size <= 4) { \ 8 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 9 | "out %%eax, %%dx\n\t" \ 10 | : \ 11 | : "a"(x) \ 12 | : "dx"); \ 13 | } else if (size == 8) { \ 14 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 15 | "out %%eax, %%dx\n\t" \ 16 | "shr $32, %%rax\n\t" \ 17 | "out %%eax, %%dx\n\t" \ 18 | : \ 19 | : "a"(x) \ 20 | : "dx"); \ 21 | } \ 22 | } while (0) -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/include/sched.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 进程状态 6 | enum task_state { 7 | // 任务正在运行或就绪态 8 | TASK_RUNNING = 0, 9 | // 任务处于可中断的睡眠态 10 | TASK_INTERRUPTIBLE 11 | }; 12 | 13 | // 记录进程的信息和状态 14 | struct task { 15 | unsigned long id; 16 | enum task_state state; // 任务状态 17 | unsigned long rip; // 任务调度切换时,指令的地址 18 | unsigned long rsp0; // 任务调度切换时,内核栈的栈顶 19 | unsigned long kstack; // 内核栈的栈底,用于在内核时,任务状态段中特权级为0的栈指针指向当前任务的内核栈栈底 20 | unsigned long pml4; // 根页表的物理地址,用于更新寄存器CR3指向当前任务的页表 21 | 22 | // 支撑任务链的链表 23 | struct task* next; 24 | struct task* prev; 25 | }; 26 | 27 | extern unsigned long ret_from_kernel; 28 | extern unsigned long idle_task_entry; 29 | extern unsigned long task0_stack; 30 | extern struct task* current; 31 | 32 | void sched_init(); 33 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/include/segment.h: -------------------------------------------------------------------------------- 1 | // 内核代码段选择子 2 | #define KERNEL_CS 0x8 3 | // 内核数据段选择子 4 | #define KERNEL_DS 0x10 5 | // 32位用户代码段选择子 6 | #define USER32_CS 0x1b 7 | // 用户数据段选择子 8 | #define USER_DS 0x23 9 | // 64位用户代码段选择子 10 | #define USER_CS 0x2b 11 | 12 | // 段描述符表的第6项作为任务状态段的段描述符 13 | #define GDT_TSS_ENTRY 6 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/include/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | void* memset(void *s, char c, unsigned long n); 6 | void memcpy(void *dest, const void *src, unsigned long n); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/include/tss.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 可以寻址64K(65536)个端口 6 | #define IO_BITMAP_BYTES (65536 / 8) 7 | 8 | struct tss { 9 | uint32_t reserved1; // 保留 10 | uint64_t rsp0; // 特权级为0的栈指针 11 | uint64_t rsp1; // 特权级为1的栈指针 12 | uint64_t rsp2; // 特权级为2的栈指针 13 | uint64_t reserved2; // 保留 14 | uint64_t ist[7]; // IST的7个专用栈 15 | uint32_t reserved3; // 保留 16 | uint32_t reserved4; // 保留 17 | uint16_t reserved5; // 保留 18 | uint16_t io_bitmap_offset; // 程序I/O权限位图相对于任务状态段基址的16位偏移 19 | uint8_t io_bitmap[IO_BITMAP_BYTES + 1]; // I/O权限位图 20 | } __attribute__((packed)); 21 | 22 | struct tss_desc { 23 | uint16_t limit0; // 段长度 24 | uint16_t base0; // 段基址(0~15) 25 | uint16_t base1 : 8, type : 4, desc_type : 1, dpl : 2, p : 1; 26 | uint16_t limit1 : 4, avl : 1, zero0 : 2, g : 1, base2 : 8; 27 | uint32_t base3; // 段基址(32~63) 28 | uint32_t zero1; // 保留 29 | } __attribute__((packed)); 30 | 31 | extern struct tss tss; 32 | // TSS任务状态段初始化 33 | void tss_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef signed char int8_t; 4 | typedef short int int16_t; 5 | typedef int int32_t; 6 | typedef long int int64_t; 7 | 8 | typedef unsigned char uint8_t; 9 | typedef unsigned short int uint16_t; 10 | typedef unsigned int uint32_t; 11 | typedef unsigned long int uint64_t; 12 | 13 | #define NULL ((void*)0) 14 | 15 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/kernel/handler.S: -------------------------------------------------------------------------------- 1 | # 各种中断处理函数 2 | 3 | .text 4 | .code64 5 | .globl timer_handler 6 | .globl pf_handler 7 | 8 | # 为进程保存完整环境,调用者负责的寄存器:RDI、RSI、RDX、RCX、RAX、R8~R11 9 | .macro SAVE_CONTEXT save_rax = 1 10 | pushq %rdi 11 | pushq %rsi 12 | pushq %rdx 13 | pushq %rcx 14 | .if \save_rax 15 | pushq %rax 16 | .endif 17 | pushq %r8 18 | pushq %r9 19 | pushq %r10 20 | pushq %r11 21 | .endm 22 | 23 | .macro RESTORE_CONTEXT rstore_rax = 1 24 | popq %r11 25 | popq %r10 26 | popq %r9 27 | popq %r8 28 | .if \rstore_rax 29 | popq %rax 30 | .endif 31 | popq %rcx 32 | popq %rdx 33 | popq %rsi 34 | popq %rdi 35 | .endm 36 | 37 | timer_handler: 38 | # 保护上下文 39 | SAVE_CONTEXT 40 | 41 | # 将OCW2写入8259A的端口0x20处,D5=1表示处理器已经处理完中断了 42 | movb $0x20,%al 43 | outb %al,$0x20 44 | 45 | # 调用do_timer函数 46 | call do_timer 47 | 48 | # 恢复上下文 49 | RESTORE_CONTEXT 50 | # IF位自动复位,再次开启中断 51 | iretq 52 | 53 | pf_handler: 54 | SAVE_CONTEXT 55 | 56 | # 发生缺页异常时,处理器会将缺页地址存储到寄存器CR2中 57 | # 需要从寄存器CR2中取出引起缺页的地址 58 | mov %cr2, %rdi 59 | call do_page_fault 60 | 61 | RESTORE_CONTEXT 62 | # 越过错误码,避免弹出到指令指针寄存器RIP中 63 | add $8, %rsp 64 | iretq 65 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/kernel/interrupt.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/interrupt.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | // 产生100Hz的中断,即每秒向8259A发出100次时钟信号,该值表示每秒的中断信号次数 7 | #define COUNTER (1193181 / 100) 8 | // 中断类型 9 | #define GATE_INTERRUPT 0xe 10 | // 异常类型 11 | #define GATE_EXCEPTION 0xf 12 | 13 | // 中断描述符 14 | struct gate_desc { 15 | uint16_t offset_low; // 中断处理函数地址0~15位 16 | uint16_t segment; // 段选择子 17 | uint16_t ist : 3, zero : 5, type : 4, zero2 : 1, dpl : 2, p : 1; // ist:中断栈索引;type:中断类型(中断1110/异常1111);dpl:特权级;p:存在位 18 | uint16_t offset_middle; // 中断处理函数地址16~31位 19 | uint32_t offset_high; // 中断处理函数地址32~63位 20 | uint32_t reserved; // 保留 21 | } __attribute__((packed)); 22 | 23 | // 定义256项中断和异常,中断描述符表 24 | struct gate_desc idt_table[256]; 25 | 26 | // 初始化8254计数器芯片 27 | void init_8254() { 28 | // D7D6=00:SC选择计数器0 29 | // D5D4=11:RW设置读取计数值的方式,11表示首先写入低8位,然后写入高8位 30 | // D3D2D1=011:工作模式为3,使用方波 31 | // D0=0:编码格式为二进制格式 32 | // 将控制字写入0x43端口 33 | __asm__ ("outb %%al, $0x43"::"a"(0x36)); 34 | // 分别将低8位和高8位写入0x40端口 35 | __asm__ ("outb %%al, $0x40"::"a"(COUNTER & 0xff)); 36 | __asm__ ("outb %%al, $0x40"::"a"(COUNTER >> 8)); 37 | } 38 | 39 | // 设置中断描述符 40 | // index:中断向量,对应中断描述符表中的索引号 41 | // addr:中断处理函数地址 42 | // type:描述符类型 43 | static void set_gate(unsigned char index, unsigned long addr, char type) { 44 | struct gate_desc* desc = &idt_table[index]; 45 | 46 | // 中断描述符初始化为0 47 | memset(desc, 0, sizeof(struct gate_desc)); 48 | // 设置为内核代码段 49 | desc->segment = KERNEL_CS; 50 | // 设置中断处理函数地址 51 | desc->offset_low = (uint16_t)addr; 52 | desc->offset_middle = (uint16_t)(addr >> 16); 53 | desc->offset_high = (uint32_t)(addr >> 32); 54 | // 特权级为0,内核空间 55 | desc->dpl = 0; 56 | desc->type = type; 57 | // 存在位设置为1 58 | desc->p = 1; 59 | } 60 | 61 | // 中断系统初始化 62 | void interrupt_init() { 63 | // 添加缺页异常处理函数 64 | set_gate(14, (unsigned long)&pf_handler, GATE_EXCEPTION); 65 | // 设置时钟中断描述符,时钟中断的向量号是32(0x20) 66 | set_gate(0x20, (unsigned long)&timer_handler, GATE_INTERRUPT); 67 | 68 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/kernel/tss.c: -------------------------------------------------------------------------------- 1 | #include "include/tss.h" 2 | #include "include/sched.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | extern unsigned long gdt[64]; 7 | struct tss tss; 8 | 9 | void tss_init() { 10 | // 允许应用程序访问所有I/O端口 11 | memset(&tss, 0, sizeof(tss)); 12 | tss.io_bitmap_offset = __builtin_offsetof(struct tss, io_bitmap); 13 | tss.io_bitmap[IO_BITMAP_BYTES] = ~0; 14 | // 设置任务状态段的RSP0指向当前应用程序的内核栈地址 15 | tss.rsp0 = current->kstack; 16 | 17 | // 得到GDT表的第6项 18 | struct tss_desc* desc = (struct tss_desc*)&gdt[GDT_TSS_ENTRY]; 19 | // 将任务状态段的段描述符清0 20 | memset(desc, 0, sizeof(struct tss_desc)); 21 | // 计算tss结构体的长度的低16位 22 | desc->limit0 = sizeof(tss) & 0xffff; 23 | desc->base0 = (unsigned long)(&tss) & 0xffff; 24 | desc->base1 = ((unsigned long)(&tss) >> 16) & 0xff; 25 | // 段类型为0x9 26 | desc->type = 0x9; 27 | // 存在位为1 28 | desc->p = 1; 29 | // 段长度的第16~19位 30 | desc->limit1 = (sizeof(tss) >> 16) & 0xf; 31 | desc->base2 = ((unsigned long)(&tss) >> 24) & 0xff; 32 | desc->base3 = (unsigned long)(&tss) >> 32; 33 | 34 | // 禁止应用程序对所有端口的访问,会直接终止执行 35 | // memset(tss.io_bitmap, 0xff, IO_BITMAP_BYTES); 36 | 37 | // 装载任务寄存器TR,段索引为6,TI为0,特权级(内核级)为0 38 | __asm__ ("ltr %w0" : : "r"(GDT_TSS_ENTRY << 3)); 39 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/lib/string.c: -------------------------------------------------------------------------------- 1 | // 填充一段内存区 2 | void* memset(void *s, char c, unsigned long n) { 3 | char *tmp = s; 4 | 5 | while (n--) { 6 | *tmp++ = c; 7 | } 8 | 9 | return s; 10 | } 11 | 12 | /* 内存区域复制 13 | * @param dest 目的内存地址 14 | * @param src 源内存地址 15 | * @param n 复制的字节数 16 | */ 17 | void memcpy(void *dest, const void *src, unsigned long n) { 18 | char *tmp = dest; 19 | const char *s = src; 20 | 21 | // 将字节逐个从源内存的内容复制到目的内存 22 | while (n--) { 23 | *tmp++ = *s++; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/main.c: -------------------------------------------------------------------------------- 1 | #include "include/mm.h" 2 | #include "include/print.h" 3 | #include "include/sched.h" 4 | #include "include/tss.h" 5 | #include "include/interrupt.h" 6 | 7 | int main() { 8 | mm_init(); 9 | interrupt_init(); 10 | 11 | // 设置标志寄存器中的IF位,开启中断 12 | __asm__ ("sti"); 13 | sched_init(); 14 | 15 | // 任务状态段初始化 16 | tss_init(); 17 | 18 | // 使能时钟中断 19 | init_8254(); 20 | 21 | // 将寄存器CR3指向进程1的页表 22 | __asm__ ("mov %0, %%cr3": :"r"(current->pml4)); 23 | 24 | // 设置栈指针指向进程1的内核栈的栈顶,并执行ret_from_kernel 25 | __asm__ ("movq %0, %%rsp\n\t" 26 | "jmp ret_from_kernel\n\t" 27 | : 28 | : "m"(current->rsp0) 29 | ); 30 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/makefile: -------------------------------------------------------------------------------- 1 | kernel.bin: build boot16.bin boot32.bin system.bin app/app1.bin app/app2.bin 2 | ./build 3 | 4 | boot16.bin: boot16.S 5 | gcc -c boot16.S -o boot16.o 6 | # 实模式运行时的地址是由“段基址<<4+段内偏移”生成,kvmtool将段寄存器初始化为0x1000,段内偏移为0 7 | ld -Ttext=0x0 boot16.o -o boot16.elf 8 | objcopy -O binary boot16.elf boot16.bin 9 | 10 | boot32.bin: boot32.S 11 | gcc -c boot32.S -o boot32.o 12 | # 保护模式的代码段基址为0,段内偏移为0x20000 13 | ld -Ttext=0x20000 boot32.o -o boot32.elf 14 | objcopy -O binary boot32.elf boot32.bin 15 | 16 | # 使用CFLAGS给编译器传参 17 | # -I:从哪些目录搜索头文件 18 | # -fon-pic:位置无关代码可以加载在程序地址空间中的任何位置 19 | # -mcmodel=kernel:指示gcc生成使用64位寻址操作数的汇编代码,基于large模型改进,减少指令长度 20 | # -fno-stack-protector:关闭栈溢出检查 21 | # -fcf-protection=none:关闭gcc检查代码的特性 22 | # -nostdinc:不要搜索宿主系统的系统目录下的头文件 23 | # -fno-builtin:不需要使用内置的memset 24 | CFLAGS = -std=c11 -I. -fno-pic -mcmodel=kernel -fno-stack-protector -fcf-protection=none -nostdinc -fno-builtin 25 | 26 | SRCS = main.c $(wildcard mm/*.c) $(wildcard lib/*.c) $(wildcard kernel/*.c) 27 | # 将SRCS中的每一个.c替换成.o 28 | OBJS = $(SRCS:.c=.o) 29 | 30 | system.bin: head64.o kernel/handler.o $(OBJS) 31 | # 内核映像的虚拟地址起始于0xffffffff8000000,映射物理内存地址0处,64位部分位于物理内存0x100000处 32 | ld -Ttext=0xffffffff80100000 head64.o kernel/handler.o $(OBJS) -o system.elf 33 | objcopy -O binary system.elf $@ 34 | 35 | # 将依赖关系保存到.depend文件中 36 | .depend: $(SRCS) 37 | @rm -f .depend 38 | @$(foreach src,$(SRCS), \ 39 | echo -n $(dir $(src)) >> .depend; \ 40 | gcc -I. -MM $(src) >> .depend; \ 41 | ) 42 | include .depend 43 | 44 | app/app1.bin: app/app1.S 45 | gcc -c app/app1.S -o app/app1.o 46 | ld -Ttext=0x100000 app/app1.o -o app/app1.elf 47 | objcopy -O binary app/app1.elf app/app1.bin 48 | 49 | app/app2.bin: app/app2.S 50 | gcc -c app/app2.S -o app/app2.o 51 | ld -Ttext=0x100000 app/app2.o -o app/app2.elf 52 | objcopy -O binary app/app2.elf app/app2.bin 53 | 54 | build: build.c 55 | gcc $< -o $@ 56 | 57 | .PHONY: clean run 58 | 59 | run: kernel.bin 60 | ~/kvmtool/lkvm run -c 1 -k ./kernel.bin 61 | 62 | clean: 63 | find -name "*.o" -o -name "*.elf" -o -name "*.bin" | xargs rm -f 64 | rm -f build .depend 65 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c11/mm/page_alloc.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/mm.h" 3 | 4 | // 分配页面 5 | unsigned long alloc_page() { 6 | unsigned long addr = 0; 7 | 8 | // 从内存映像占用的物理页面之后的页面开始 9 | for (long i = KERNEL_PAGE_NUM; i < mem_size / PAGE_SIZE; i++) { 10 | // 直到找到空页面 11 | if (pages[i] == 0) { 12 | // 将新分配的页面标记为已占用 13 | pages[i] = 1; 14 | // 计算页面的物理地址 15 | addr = PAGE_SIZE * i; 16 | break; 17 | } 18 | } 19 | 20 | // 返回新分配页面的物理地址 21 | return addr; 22 | } 23 | 24 | // 释放页面 25 | void free_page(uint64_t addr) { 26 | // 计算归还页面在数组page的索引 27 | uint32_t index = addr / PAGE_SIZE; 28 | 29 | // 标记页面为空闲 30 | pages[index] = 0; 31 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/app/app1.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code64 3 | # 无限循环,每次循环向串口输出一个字符 4 | 1: 5 | mov $0x3f8, %dx 6 | mov $'A', %ax 7 | out %ax, %dx 8 | 9 | # 请求内核将应用挂起1000ms 10 | mov $1000, %rdi 11 | # 从寄存器RAX获取具体的系统调用号,0号为syscall_table中的do_sleep 12 | mov $0, %rax 13 | # 发起系统调用 14 | syscall 15 | 16 | jmp 1b -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/app/app2.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code64 3 | 1: 4 | mov $0x3f8, %dx 5 | mov $'B', %ax 6 | out %ax, %dx 7 | 8 | # 请求内核将应用挂起1000ms 9 | mov $1000, %rdi 10 | # 从寄存器RAX获取具体的系统调用号,0号为syscall_table中的do_sleep 11 | mov $0, %rax 12 | # 发起系统调用 13 | syscall 14 | 15 | jmp 1b 16 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/head64.S: -------------------------------------------------------------------------------- 1 | #include "include/segment.h" 2 | 3 | .text 4 | .code64 5 | .globl gdt 6 | .globl ret_from_kernel 7 | .globl task0_stack 8 | .globl idle_task_entry 9 | 10 | lgdt gdtr 11 | # 将中断描述符表地址写入IDTR寄存器中 12 | lidt idtr 13 | 14 | # 初始化寄存器 15 | mov $KERNEL_DS, %ax 16 | mov %ax, %ds 17 | mov %ax, %ss 18 | mov %ax, %es 19 | mov %ax, %fs 20 | mov %ax, %gs 21 | 22 | # 使用RSP寄存器指向栈底 23 | mov $task0_stack, %rsp 24 | # 跳转到main方法 25 | push $main 26 | ret 27 | 28 | # 所有段的TI为0,内核段的特权级为0,用户段的特权级为3 29 | .align 64 30 | gdt: 31 | # 空描述符(保留不用) 32 | .quad 0x0000000000000000 33 | # 内核代码段描述符 34 | .quad 0x00209a0000000000 35 | # 内核数据段描述符 36 | .quad 0x0000920000000000 37 | # 32位用户代码段描述符 38 | .quad 0x0000000000000000 39 | # 用户数据段描述符 40 | .quad 0x0000f20000000000 41 | # 64位用户代码段描述符 42 | .quad 0x0020fa0000000000 43 | .fill 128, 8, 0 44 | gdt_end: 45 | 46 | gdtr: 47 | .word gdt_end - gdt 48 | .quad gdt 49 | 50 | idtr: 51 | # 中断描述符表长度,256项中断描述符,每个是16字节 52 | .word 16 * 256 53 | # 中断描述符表地址 54 | .quad idt_table 55 | 56 | # 4KB大小的栈空间 57 | .fill 4096, 1, 0 58 | task0_stack: 59 | 60 | # 在执行iret前,初始化其他段寄存器,之后再从栈中弹出断点信息,返回用户空间 61 | ret_from_kernel: 62 | mov $USER_DS, %rax 63 | movw %ax, %ds 64 | movw %ax, %es 65 | movw %ax, %fs 66 | movw %ax, %gs 67 | iretq 68 | 69 | # 空闲进程,当没有就绪任务需要运行时,让处理器运行这个空闲任务 70 | idle_task_entry: 71 | 1: 72 | # 开启处理器响应中断标识 73 | sti 74 | # 让处理器停止运转 75 | hlt 76 | jmp 1b -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/include/interrupt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void init_8254(); 4 | void interrupt_init(); 5 | 6 | void timer_handler(); 7 | void pf_handler(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/include/mm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | #define E820MAX 32 6 | #define E820_RAM 1 7 | 8 | // 定义最大页面数,每个页面大小为4K,一共表示4GB物理内存 9 | #define MAX_PAGES (1024 * 1024) 10 | // 定义内核所需的页面,16K个页面 11 | #define KERNEL_PAGE_NUM (1024 * 16) 12 | 13 | #define PAGE_SIZE 4096 14 | 15 | #define PAGE_OFFSET 0xffff888000000000 16 | #define VA(x) ((void*)((unsigned long)(x) + PAGE_OFFSET)) 17 | #define PA(x) ((unsigned long)(x) - PAGE_OFFSET) 18 | 19 | #define TASK0_PML4 0x30000 20 | 21 | // 内存大小 22 | extern unsigned long mem_size; 23 | 24 | extern uint8_t pages[MAX_PAGES]; 25 | 26 | // 使用packed告知GCC编译器,分配结构体变量时不要进行对齐 27 | struct e820entry { 28 | uint64_t addr; // 内存段的起始地址 29 | uint64_t size; // 内存段的尺寸 30 | uint32_t type; // 内存段的类型 31 | } __attribute__((packed)); 32 | 33 | // 存储E820信息的内存区域 34 | struct e820map { 35 | uint32_t nr_entry; 36 | struct e820entry map[E820MAX]; // 多条E820记录 37 | }; 38 | 39 | // 内存管理初始化 40 | void mm_init(); 41 | // 分配页面 42 | unsigned long alloc_page(); 43 | // 释放页面 44 | void free_page(uint64_t addr); 45 | void* malloc(int size); 46 | void free(void* obj); 47 | // 内存空间映射 48 | void map_range(unsigned long pml4_pa, unsigned long from_va, unsigned long to_pa, char us, long npage); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/include/print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 如果是32位,向串口输出1次,如果是64位,向串口输出2次 4 | #define print(x) \ 5 | do { \ 6 | int size = sizeof(x); \ 7 | if (size <= 4) { \ 8 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 9 | "out %%eax, %%dx\n\t" \ 10 | : \ 11 | : "a"(x) \ 12 | : "dx"); \ 13 | } else if (size == 8) { \ 14 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 15 | "out %%eax, %%dx\n\t" \ 16 | "shr $32, %%rax\n\t" \ 17 | "out %%eax, %%dx\n\t" \ 18 | : \ 19 | : "a"(x) \ 20 | : "dx"); \ 21 | } \ 22 | } while (0) -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/include/sched.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 进程状态 6 | enum task_state { 7 | // 任务正在运行或就绪态 8 | TASK_RUNNING = 0, 9 | // 任务处于可中断的睡眠态 10 | TASK_INTERRUPTIBLE 11 | }; 12 | 13 | // 记录进程的信息和状态 14 | struct task { 15 | unsigned long id; 16 | enum task_state state; // 任务状态 17 | unsigned long rip; // 任务调度切换时,指令的地址 18 | unsigned long rsp0; // 任务调度切换时,内核栈的栈顶 19 | unsigned long kstack; // 内核栈的栈底,用于在内核时,任务状态段中特权级为0的栈指针指向当前任务的内核栈栈底 20 | unsigned long pml4; // 根页表的物理地址,用于更新寄存器CR3指向当前任务的页表 21 | 22 | // 支撑任务链的链表 23 | struct task* next; 24 | struct task* prev; 25 | }; 26 | 27 | struct timer { 28 | unsigned long alarm; // 记录时钟到期时间 29 | struct task* task; 30 | struct timer* next; 31 | struct timer* prev; 32 | }; 33 | 34 | extern unsigned long ret_from_kernel; 35 | extern unsigned long idle_task_entry; 36 | extern unsigned long task0_stack; 37 | extern struct task* current; 38 | 39 | void sched_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/include/segment.h: -------------------------------------------------------------------------------- 1 | // 内核代码段选择子 2 | #define KERNEL_CS 0x8 3 | // 内核数据段选择子 4 | #define KERNEL_DS 0x10 5 | // 32位用户代码段选择子 6 | #define USER32_CS 0x1b 7 | // 用户数据段选择子 8 | #define USER_DS 0x23 9 | // 64位用户代码段选择子 10 | #define USER_CS 0x2b 11 | 12 | // 段描述符表的第6项作为任务状态段的段描述符 13 | #define GDT_TSS_ENTRY 6 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/include/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | void* memset(void *s, char c, unsigned long n); 6 | void memcpy(void *dest, const void *src, unsigned long n); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/include/syscall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void system_call(); 4 | void syscall_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/include/tss.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 可以寻址64K(65536)个端口 6 | #define IO_BITMAP_BYTES (65536 / 8) 7 | 8 | struct tss { 9 | uint32_t reserved1; // 保留 10 | uint64_t rsp0; // 特权级为0的栈指针 11 | uint64_t rsp1; // 特权级为1的栈指针 12 | uint64_t rsp2; // 特权级为2的栈指针 13 | uint64_t reserved2; // 保留 14 | uint64_t ist[7]; // IST的7个专用栈 15 | uint32_t reserved3; // 保留 16 | uint32_t reserved4; // 保留 17 | uint16_t reserved5; // 保留 18 | uint16_t io_bitmap_offset; // 程序I/O权限位图相对于任务状态段基址的16位偏移 19 | uint8_t io_bitmap[IO_BITMAP_BYTES + 1]; // I/O权限位图 20 | } __attribute__((packed)); 21 | 22 | struct tss_desc { 23 | uint16_t limit0; // 段长度 24 | uint16_t base0; // 段基址(0~15) 25 | uint16_t base1 : 8, type : 4, desc_type : 1, dpl : 2, p : 1; 26 | uint16_t limit1 : 4, avl : 1, zero0 : 2, g : 1, base2 : 8; 27 | uint32_t base3; // 段基址(32~63) 28 | uint32_t zero1; // 保留 29 | } __attribute__((packed)); 30 | 31 | extern struct tss tss; 32 | // TSS任务状态段初始化 33 | void tss_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef signed char int8_t; 4 | typedef short int int16_t; 5 | typedef int int32_t; 6 | typedef long int int64_t; 7 | 8 | typedef unsigned char uint8_t; 9 | typedef unsigned short int uint16_t; 10 | typedef unsigned int uint32_t; 11 | typedef unsigned long int uint64_t; 12 | 13 | #define NULL ((void*)0) 14 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/kernel/handler.S: -------------------------------------------------------------------------------- 1 | # 各种中断处理函数 2 | 3 | .text 4 | .code64 5 | .globl timer_handler 6 | .globl pf_handler 7 | .globl system_call 8 | 9 | # 为进程保存完整环境,调用者负责的寄存器:RDI、RSI、RDX、RCX、RAX、R8~R11 10 | .macro SAVE_CONTEXT save_rax = 1 11 | pushq %rdi 12 | pushq %rsi 13 | pushq %rdx 14 | pushq %rcx 15 | .if \save_rax 16 | pushq %rax 17 | .endif 18 | pushq %r8 19 | pushq %r9 20 | pushq %r10 21 | pushq %r11 22 | .endm 23 | 24 | .macro RESTORE_CONTEXT rstore_rax = 1 25 | popq %r11 26 | popq %r10 27 | popq %r9 28 | popq %r8 29 | .if \rstore_rax 30 | popq %rax 31 | .endif 32 | popq %rcx 33 | popq %rdx 34 | popq %rsi 35 | popq %rdi 36 | .endm 37 | 38 | system_call: 39 | # 将当前寄存器RSP中的用户栈顶保存到任务状态段的rsp2,rsp2的基址偏移为20字节 40 | mov %rsp, tss + 20 41 | # 将任务状态段的rsp0保存的进程内核栈加载到寄存器RSP中,rsp0的基址偏移为4字节 42 | mov tss + 4, %rsp 43 | 44 | # 将任务状态段的rsp2的用户栈压入内核栈 45 | pushq tss + 20 46 | # 无需保存RAX寄存器 47 | SAVE_CONTEXT 0 48 | 49 | # 由于位于同一个特权级,使用call调用具体的系统调用 50 | # 间接跳转 51 | call *syscall_table(, %rax, 8) 52 | 53 | RESTORE_CONTEXT 0 54 | # 从内核栈弹出用户栈顶指针RSP,恢复用户栈 55 | pop %rsp 56 | 57 | sysretq 58 | 59 | timer_handler: 60 | # 保护上下文 61 | SAVE_CONTEXT 62 | 63 | # 将OCW2写入8259A的端口0x20处,D5=1表示处理器已经处理完中断了 64 | movb $0x20,%al 65 | outb %al,$0x20 66 | 67 | # 调用do_timer函数 68 | call do_timer 69 | 70 | # 恢复上下文 71 | RESTORE_CONTEXT 72 | # IF位自动复位,再次开启中断 73 | iretq 74 | 75 | pf_handler: 76 | SAVE_CONTEXT 77 | 78 | # 发生缺页异常时,处理器会将缺页地址存储到寄存器CR2中 79 | # 需要从寄存器CR2中取出引起缺页的地址 80 | mov %cr2, %rdi 81 | call do_page_fault 82 | 83 | RESTORE_CONTEXT 84 | # 越过错误码,避免弹出到指令指针寄存器RIP中 85 | add $8, %rsp 86 | iretq 87 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/kernel/interrupt.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/interrupt.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | // 产生100Hz的中断,即每秒向8259A发出100次时钟信号,该值表示每秒的中断信号次数 7 | #define COUNTER (1193181 / 100) 8 | // 中断类型 9 | #define GATE_INTERRUPT 0xe 10 | // 异常类型 11 | #define GATE_EXCEPTION 0xf 12 | 13 | // 中断描述符 14 | struct gate_desc { 15 | uint16_t offset_low; // 中断处理函数地址0~15位 16 | uint16_t segment; // 段选择子 17 | uint16_t ist : 3, zero : 5, type : 4, zero2 : 1, dpl : 2, p : 1; // ist:中断栈索引;type:中断类型(中断1110/异常1111);dpl:特权级;p:存在位 18 | uint16_t offset_middle; // 中断处理函数地址16~31位 19 | uint32_t offset_high; // 中断处理函数地址32~63位 20 | uint32_t reserved; // 保留 21 | } __attribute__((packed)); 22 | 23 | // 定义256项中断和异常,中断描述符表 24 | struct gate_desc idt_table[256]; 25 | 26 | // 初始化8254计数器芯片 27 | void init_8254() { 28 | // D7D6=00:SC选择计数器0 29 | // D5D4=11:RW设置读取计数值的方式,11表示首先写入低8位,然后写入高8位 30 | // D3D2D1=011:工作模式为3,使用方波 31 | // D0=0:编码格式为二进制格式 32 | // 将控制字写入0x43端口 33 | __asm__ ("outb %%al, $0x43"::"a"(0x36)); 34 | // 分别将低8位和高8位写入0x40端口 35 | __asm__ ("outb %%al, $0x40"::"a"(COUNTER & 0xff)); 36 | __asm__ ("outb %%al, $0x40"::"a"(COUNTER >> 8)); 37 | } 38 | 39 | // 设置中断描述符 40 | // index:中断向量,对应中断描述符表中的索引号 41 | // addr:中断处理函数地址 42 | // type:描述符类型 43 | static void set_gate(unsigned char index, unsigned long addr, char type) { 44 | struct gate_desc* desc = &idt_table[index]; 45 | 46 | // 中断描述符初始化为0 47 | memset(desc, 0, sizeof(struct gate_desc)); 48 | // 设置为内核代码段 49 | desc->segment = KERNEL_CS; 50 | // 设置中断处理函数地址 51 | desc->offset_low = (uint16_t)addr; 52 | desc->offset_middle = (uint16_t)(addr >> 16); 53 | desc->offset_high = (uint32_t)(addr >> 32); 54 | // 特权级为0,内核空间 55 | desc->dpl = 0; 56 | desc->type = type; 57 | // 存在位设置为1 58 | desc->p = 1; 59 | } 60 | 61 | // 中断系统初始化 62 | void interrupt_init() { 63 | // 添加缺页异常处理函数 64 | set_gate(14, (unsigned long)&pf_handler, GATE_EXCEPTION); 65 | // 设置时钟中断描述符,时钟中断的向量号是32(0x20) 66 | set_gate(0x20, (unsigned long)&timer_handler, GATE_INTERRUPT); 67 | 68 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/kernel/syscall.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/segment.h" 3 | #include "include/syscall.h" 4 | #include "include/sched.h" 5 | 6 | #define MSR_STAR 0xc0000081 7 | #define MSR_LSTAR 0xc0000082 8 | #define MSR_SYSCALL_MASK 0xc0000084 9 | #define RF_IF 0x00000200 10 | 11 | typedef unsigned long (*fn_ptr)(); 12 | 13 | unsigned long do_sleep(long ms); 14 | 15 | fn_ptr syscall_table[] = { do_sleep }; 16 | 17 | void syscall_init() { 18 | // 设置用户代码段和内核代码段 19 | uint64_t star_val = (uint64_t)USER32_CS << 48 | (uint64_t)KERNEL_CS << 32; 20 | uint64_t syscall_entry = (uint64_t)system_call; 21 | // 初始化标志寄存器的第9位(中断使能位) 22 | uint64_t syscall_mask = RF_IF; 23 | 24 | // 将值写入STAR寄存器 25 | // 指令wrmsr从寄存器ECX读取MSR的ID,分别将寄存器EAX和EDX的值写入MSR寄存器的低32位和高32位 26 | // 编译器将各个值预先装载到ECX、EAX、EDX中 27 | __asm__("wrmsr": : "c" (MSR_STAR), "a" ((uint32_t)star_val), "d" (star_val >> 32)); 28 | // 将值写入LSTAR寄存器 29 | __asm__("wrmsr": : "c" (MSR_LSTAR), "a" ((uint32_t)syscall_entry), "d" (syscall_entry >> 32)); 30 | // 将值写入SFMASK寄存器 31 | __asm__("wrmsr": : "c" (MSR_SYSCALL_MASK), "a" ((uint32_t)syscall_mask), "d" (syscall_mask >> 32)); 32 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/kernel/tss.c: -------------------------------------------------------------------------------- 1 | #include "include/tss.h" 2 | #include "include/sched.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | extern unsigned long gdt[64]; 7 | struct tss tss; 8 | 9 | void tss_init() { 10 | // 允许应用程序访问所有I/O端口 11 | memset(&tss, 0, sizeof(tss)); 12 | tss.io_bitmap_offset = __builtin_offsetof(struct tss, io_bitmap); 13 | tss.io_bitmap[IO_BITMAP_BYTES] = ~0; 14 | // 设置任务状态段的RSP0指向当前应用程序的内核栈地址 15 | tss.rsp0 = current->kstack; 16 | 17 | // 得到GDT表的第6项 18 | struct tss_desc* desc = (struct tss_desc*)&gdt[GDT_TSS_ENTRY]; 19 | // 将任务状态段的段描述符清0 20 | memset(desc, 0, sizeof(struct tss_desc)); 21 | // 计算tss结构体的长度的低16位 22 | desc->limit0 = sizeof(tss) & 0xffff; 23 | desc->base0 = (unsigned long)(&tss) & 0xffff; 24 | desc->base1 = ((unsigned long)(&tss) >> 16) & 0xff; 25 | // 段类型为0x9 26 | desc->type = 0x9; 27 | // 存在位为1 28 | desc->p = 1; 29 | // 段长度的第16~19位 30 | desc->limit1 = (sizeof(tss) >> 16) & 0xf; 31 | desc->base2 = ((unsigned long)(&tss) >> 24) & 0xff; 32 | desc->base3 = (unsigned long)(&tss) >> 32; 33 | 34 | // 禁止应用程序对所有端口的访问,会直接终止执行 35 | // memset(tss.io_bitmap, 0xff, IO_BITMAP_BYTES); 36 | 37 | // 装载任务寄存器TR,段索引为6,TI为0,特权级(内核级)为0 38 | __asm__ ("ltr %w0" : : "r"(GDT_TSS_ENTRY << 3)); 39 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/lib/string.c: -------------------------------------------------------------------------------- 1 | // 填充一段内存区 2 | void* memset(void *s, char c, unsigned long n) { 3 | char *tmp = s; 4 | 5 | while (n--) { 6 | *tmp++ = c; 7 | } 8 | 9 | return s; 10 | } 11 | 12 | /* 内存区域复制 13 | * @param dest 目的内存地址 14 | * @param src 源内存地址 15 | * @param n 复制的字节数 16 | */ 17 | void memcpy(void *dest, const void *src, unsigned long n) { 18 | char *tmp = dest; 19 | const char *s = src; 20 | 21 | // 将字节逐个从源内存的内容复制到目的内存 22 | while (n--) { 23 | *tmp++ = *s++; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/main.c: -------------------------------------------------------------------------------- 1 | #include "include/mm.h" 2 | #include "include/print.h" 3 | #include "include/sched.h" 4 | #include "include/tss.h" 5 | #include "include/interrupt.h" 6 | #include "include/syscall.h" 7 | 8 | int main() { 9 | // 内存初始化 10 | mm_init(); 11 | // 中断初始化 12 | interrupt_init(); 13 | // 系统调用初始化 14 | syscall_init(); 15 | // 设置标志寄存器中的IF位,开启中断 16 | __asm__ ("sti"); 17 | sched_init(); 18 | 19 | // 任务状态段初始化 20 | tss_init(); 21 | 22 | // 使能时钟中断 23 | init_8254(); 24 | 25 | // 将寄存器CR3指向进程1的页表 26 | __asm__ ("mov %0, %%cr3": :"r"(current->pml4)); 27 | 28 | // 设置栈指针指向进程1的内核栈的栈顶,并执行ret_from_kernel 29 | __asm__ ("movq %0, %%rsp\n\t" 30 | "jmp ret_from_kernel\n\t" 31 | : 32 | : "m"(current->rsp0) 33 | ); 34 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/makefile: -------------------------------------------------------------------------------- 1 | kernel.bin: build boot16.bin boot32.bin system.bin app/app1.bin app/app2.bin 2 | ./build 3 | 4 | boot16.bin: boot16.S 5 | gcc -c boot16.S -o boot16.o 6 | # 实模式运行时的地址是由“段基址<<4+段内偏移”生成,kvmtool将段寄存器初始化为0x1000,段内偏移为0 7 | ld -Ttext=0x0 boot16.o -o boot16.elf 8 | objcopy -O binary boot16.elf boot16.bin 9 | 10 | boot32.bin: boot32.S 11 | gcc -c boot32.S -o boot32.o 12 | # 保护模式的代码段基址为0,段内偏移为0x20000 13 | ld -Ttext=0x20000 boot32.o -o boot32.elf 14 | objcopy -O binary boot32.elf boot32.bin 15 | 16 | # 使用CFLAGS给编译器传参 17 | # -I:从哪些目录搜索头文件 18 | # -fon-pic:位置无关代码可以加载在程序地址空间中的任何位置 19 | # -mcmodel=kernel:指示gcc生成使用64位寻址操作数的汇编代码,基于large模型改进,减少指令长度 20 | # -fno-stack-protector:关闭栈溢出检查 21 | # -fcf-protection=none:关闭gcc检查代码的特性 22 | # -nostdinc:不要搜索宿主系统的系统目录下的头文件 23 | # -fno-builtin:不需要使用内置的memset 24 | CFLAGS = -std=c11 -I. -fno-pic -mcmodel=kernel -fno-stack-protector -fcf-protection=none -nostdinc -fno-builtin 25 | 26 | SRCS = main.c $(wildcard mm/*.c) $(wildcard lib/*.c) $(wildcard kernel/*.c) 27 | # 将SRCS中的每一个.c替换成.o 28 | OBJS = $(SRCS:.c=.o) 29 | 30 | system.bin: head64.o kernel/handler.o $(OBJS) 31 | # 内核映像的虚拟地址起始于0xffffffff8000000,映射物理内存地址0处,64位部分位于物理内存0x100000处 32 | ld -Ttext=0xffffffff80100000 head64.o kernel/handler.o $(OBJS) -o system.elf 33 | objcopy -O binary system.elf $@ 34 | 35 | # 将依赖关系保存到.depend文件中 36 | .depend: $(SRCS) 37 | @rm -f .depend 38 | @$(foreach src,$(SRCS), \ 39 | echo -n $(dir $(src)) >> .depend; \ 40 | gcc -I. -MM $(src) >> .depend; \ 41 | ) 42 | include .depend 43 | 44 | app/app1.bin: app/app1.S 45 | gcc -c app/app1.S -o app/app1.o 46 | ld -Ttext=0x100000 app/app1.o -o app/app1.elf 47 | objcopy -O binary app/app1.elf app/app1.bin 48 | 49 | app/app2.bin: app/app2.S 50 | gcc -c app/app2.S -o app/app2.o 51 | ld -Ttext=0x100000 app/app2.o -o app/app2.elf 52 | objcopy -O binary app/app2.elf app/app2.bin 53 | 54 | build: build.c 55 | gcc $< -o $@ 56 | 57 | .PHONY: clean run 58 | 59 | run: kernel.bin 60 | ~/kvmtool/lkvm run -c 1 -k ./kernel.bin 61 | 62 | clean: 63 | find -name "*.o" -o -name "*.elf" -o -name "*.bin" | xargs rm -f 64 | rm -f build .depend 65 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-1/mm/page_alloc.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/mm.h" 3 | 4 | // 分配页面 5 | unsigned long alloc_page() { 6 | unsigned long addr = 0; 7 | 8 | // 从内存映像占用的物理页面之后的页面开始 9 | for (long i = KERNEL_PAGE_NUM; i < mem_size / PAGE_SIZE; i++) { 10 | // 直到找到空页面 11 | if (pages[i] == 0) { 12 | // 将新分配的页面标记为已占用 13 | pages[i] = 1; 14 | // 计算页面的物理地址 15 | addr = PAGE_SIZE * i; 16 | break; 17 | } 18 | } 19 | 20 | // 返回新分配页面的物理地址 21 | return addr; 22 | } 23 | 24 | // 释放页面 25 | void free_page(uint64_t addr) { 26 | // 计算归还页面在数组page的索引 27 | uint32_t index = addr / PAGE_SIZE; 28 | 29 | // 标记页面为空闲 30 | pages[index] = 0; 31 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/app/app1.c: -------------------------------------------------------------------------------- 1 | #include "app/libc/std.h" 2 | #include "include/print.h" 3 | 4 | int main() { 5 | while (1) { 6 | print('A'); 7 | sleep(1000); 8 | } 9 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/app/app2.c: -------------------------------------------------------------------------------- 1 | #include "app/libc/std.h" 2 | #include "include/print.h" 3 | 4 | int main() { 5 | while (1) { 6 | print('B'); 7 | sleep(1000); 8 | } 9 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/app/libc/start.S: -------------------------------------------------------------------------------- 1 | # 启动代码,调用main,自动找到程序的入口 2 | call main -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/app/libc/std.h: -------------------------------------------------------------------------------- 1 | void sleep(long ms); 2 | void* shm_open(const char* name); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/app/libc/syscall.S: -------------------------------------------------------------------------------- 1 | #define SYSCALL_SLEEP 0 2 | 3 | .globl sleep 4 | 5 | sleep: 6 | # 从寄存器RAX获取具体的系统调用号,0号为syscall_table中的do_sleep 7 | mov $SYSCALL_SLEEP, %rax 8 | # 发起系统调用 9 | syscall 10 | ret -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/head64.S: -------------------------------------------------------------------------------- 1 | #include "include/segment.h" 2 | 3 | .text 4 | .code64 5 | .globl gdt 6 | .globl ret_from_kernel 7 | .globl task0_stack 8 | .globl idle_task_entry 9 | 10 | lgdt gdtr 11 | # 将中断描述符表地址写入IDTR寄存器中 12 | lidt idtr 13 | 14 | # 初始化寄存器 15 | mov $KERNEL_DS, %ax 16 | mov %ax, %ds 17 | mov %ax, %ss 18 | mov %ax, %es 19 | mov %ax, %fs 20 | mov %ax, %gs 21 | 22 | # 使用RSP寄存器指向栈底 23 | mov $task0_stack, %rsp 24 | # 跳转到main方法 25 | push $main 26 | ret 27 | 28 | # 所有段的TI为0,内核段的特权级为0,用户段的特权级为3 29 | .align 64 30 | gdt: 31 | # 空描述符(保留不用) 32 | .quad 0x0000000000000000 33 | # 内核代码段描述符 34 | .quad 0x00209a0000000000 35 | # 内核数据段描述符 36 | .quad 0x0000920000000000 37 | # 32位用户代码段描述符 38 | .quad 0x0000000000000000 39 | # 用户数据段描述符 40 | .quad 0x0000f20000000000 41 | # 64位用户代码段描述符 42 | .quad 0x0020fa0000000000 43 | .fill 128, 8, 0 44 | gdt_end: 45 | 46 | gdtr: 47 | .word gdt_end - gdt 48 | .quad gdt 49 | 50 | idtr: 51 | # 中断描述符表长度,256项中断描述符,每个是16字节 52 | .word 16 * 256 53 | # 中断描述符表地址 54 | .quad idt_table 55 | 56 | # 4KB大小的栈空间 57 | .fill 4096, 1, 0 58 | task0_stack: 59 | 60 | # 在执行iret前,初始化其他段寄存器,之后再从栈中弹出断点信息,返回用户空间 61 | ret_from_kernel: 62 | mov $USER_DS, %rax 63 | movw %ax, %ds 64 | movw %ax, %es 65 | movw %ax, %fs 66 | movw %ax, %gs 67 | iretq 68 | 69 | # 空闲进程,当没有就绪任务需要运行时,让处理器运行这个空闲任务 70 | idle_task_entry: 71 | 1: 72 | # 开启处理器响应中断标识 73 | sti 74 | # 让处理器停止运转 75 | hlt 76 | jmp 1b -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/include/interrupt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void init_8254(); 4 | void interrupt_init(); 5 | 6 | void timer_handler(); 7 | void pf_handler(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/include/mm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | #define E820MAX 32 6 | #define E820_RAM 1 7 | 8 | // 定义最大页面数,每个页面大小为4K,一共表示4GB物理内存 9 | #define MAX_PAGES (1024 * 1024) 10 | // 定义内核所需的页面,16K个页面 11 | #define KERNEL_PAGE_NUM (1024 * 16) 12 | 13 | #define PAGE_SIZE 4096 14 | 15 | #define PAGE_OFFSET 0xffff888000000000 16 | #define VA(x) ((void*)((unsigned long)(x) + PAGE_OFFSET)) 17 | #define PA(x) ((unsigned long)(x) - PAGE_OFFSET) 18 | 19 | #define TASK0_PML4 0x30000 20 | 21 | // 内存大小 22 | extern unsigned long mem_size; 23 | 24 | extern uint8_t pages[MAX_PAGES]; 25 | 26 | // 使用packed告知GCC编译器,分配结构体变量时不要进行对齐 27 | struct e820entry { 28 | uint64_t addr; // 内存段的起始地址 29 | uint64_t size; // 内存段的尺寸 30 | uint32_t type; // 内存段的类型 31 | } __attribute__((packed)); 32 | 33 | // 存储E820信息的内存区域 34 | struct e820map { 35 | uint32_t nr_entry; 36 | struct e820entry map[E820MAX]; // 多条E820记录 37 | }; 38 | 39 | // 内存管理初始化 40 | void mm_init(); 41 | // 分配页面 42 | unsigned long alloc_page(); 43 | // 释放页面 44 | void free_page(uint64_t addr); 45 | void* malloc(int size); 46 | void free(void* obj); 47 | // 内存空间映射 48 | void map_range(unsigned long pml4_pa, unsigned long from_va, unsigned long to_pa, char us, long npage); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/include/print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 如果是32位,向串口输出1次,如果是64位,向串口输出2次 4 | #define print(x) \ 5 | do { \ 6 | int size = sizeof(x); \ 7 | if (size <= 4) { \ 8 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 9 | "out %%eax, %%dx\n\t" \ 10 | : \ 11 | : "a"(x) \ 12 | : "dx"); \ 13 | } else if (size == 8) { \ 14 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 15 | "out %%eax, %%dx\n\t" \ 16 | "shr $32, %%rax\n\t" \ 17 | "out %%eax, %%dx\n\t" \ 18 | : \ 19 | : "a"(x) \ 20 | : "dx"); \ 21 | } \ 22 | } while (0) -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/include/sched.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 进程状态 6 | enum task_state { 7 | // 任务正在运行或就绪态 8 | TASK_RUNNING = 0, 9 | // 任务处于可中断的睡眠态 10 | TASK_INTERRUPTIBLE 11 | }; 12 | 13 | // 记录进程的信息和状态 14 | struct task { 15 | unsigned long id; 16 | enum task_state state; // 任务状态 17 | unsigned long rip; // 任务调度切换时,指令的地址 18 | unsigned long rsp0; // 任务调度切换时,内核栈的栈顶 19 | unsigned long kstack; // 内核栈的栈底,用于在内核时,任务状态段中特权级为0的栈指针指向当前任务的内核栈栈底 20 | unsigned long pml4; // 根页表的物理地址,用于更新寄存器CR3指向当前任务的页表 21 | 22 | // 支撑任务链的链表 23 | struct task* next; 24 | struct task* prev; 25 | }; 26 | 27 | struct timer { 28 | unsigned long alarm; // 记录时钟到期时间 29 | struct task* task; 30 | struct timer* next; 31 | struct timer* prev; 32 | }; 33 | 34 | extern unsigned long ret_from_kernel; 35 | extern unsigned long idle_task_entry; 36 | extern unsigned long task0_stack; 37 | extern struct task* current; 38 | 39 | void sched_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/include/segment.h: -------------------------------------------------------------------------------- 1 | // 内核代码段选择子 2 | #define KERNEL_CS 0x8 3 | // 内核数据段选择子 4 | #define KERNEL_DS 0x10 5 | // 32位用户代码段选择子 6 | #define USER32_CS 0x1b 7 | // 用户数据段选择子 8 | #define USER_DS 0x23 9 | // 64位用户代码段选择子 10 | #define USER_CS 0x2b 11 | 12 | // 段描述符表的第6项作为任务状态段的段描述符 13 | #define GDT_TSS_ENTRY 6 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/include/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | void* memset(void *s, char c, unsigned long n); 6 | void memcpy(void *dest, const void *src, unsigned long n); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/include/syscall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void system_call(); 4 | void syscall_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/include/tss.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 可以寻址64K(65536)个端口 6 | #define IO_BITMAP_BYTES (65536 / 8) 7 | 8 | struct tss { 9 | uint32_t reserved1; // 保留 10 | uint64_t rsp0; // 特权级为0的栈指针 11 | uint64_t rsp1; // 特权级为1的栈指针 12 | uint64_t rsp2; // 特权级为2的栈指针 13 | uint64_t reserved2; // 保留 14 | uint64_t ist[7]; // IST的7个专用栈 15 | uint32_t reserved3; // 保留 16 | uint32_t reserved4; // 保留 17 | uint16_t reserved5; // 保留 18 | uint16_t io_bitmap_offset; // 程序I/O权限位图相对于任务状态段基址的16位偏移 19 | uint8_t io_bitmap[IO_BITMAP_BYTES + 1]; // I/O权限位图 20 | } __attribute__((packed)); 21 | 22 | struct tss_desc { 23 | uint16_t limit0; // 段长度 24 | uint16_t base0; // 段基址(0~15) 25 | uint16_t base1 : 8, type : 4, desc_type : 1, dpl : 2, p : 1; 26 | uint16_t limit1 : 4, avl : 1, zero0 : 2, g : 1, base2 : 8; 27 | uint32_t base3; // 段基址(32~63) 28 | uint32_t zero1; // 保留 29 | } __attribute__((packed)); 30 | 31 | extern struct tss tss; 32 | // TSS任务状态段初始化 33 | void tss_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef signed char int8_t; 4 | typedef short int int16_t; 5 | typedef int int32_t; 6 | typedef long int int64_t; 7 | 8 | typedef unsigned char uint8_t; 9 | typedef unsigned short int uint16_t; 10 | typedef unsigned int uint32_t; 11 | typedef unsigned long int uint64_t; 12 | 13 | #define NULL ((void*)0) 14 | 15 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/kernel/handler.S: -------------------------------------------------------------------------------- 1 | # 各种中断处理函数 2 | 3 | .text 4 | .code64 5 | .globl timer_handler 6 | .globl pf_handler 7 | .globl system_call 8 | 9 | # 为进程保存完整环境,调用者负责的寄存器:RDI、RSI、RDX、RCX、RAX、R8~R11 10 | .macro SAVE_CONTEXT save_rax = 1 11 | pushq %rdi 12 | pushq %rsi 13 | pushq %rdx 14 | pushq %rcx 15 | .if \save_rax 16 | pushq %rax 17 | .endif 18 | pushq %r8 19 | pushq %r9 20 | pushq %r10 21 | pushq %r11 22 | .endm 23 | 24 | .macro RESTORE_CONTEXT rstore_rax = 1 25 | popq %r11 26 | popq %r10 27 | popq %r9 28 | popq %r8 29 | .if \rstore_rax 30 | popq %rax 31 | .endif 32 | popq %rcx 33 | popq %rdx 34 | popq %rsi 35 | popq %rdi 36 | .endm 37 | 38 | system_call: 39 | # 将当前寄存器RSP中的用户栈顶保存到任务状态段的rsp2,rsp2的基址偏移为20字节 40 | mov %rsp, tss + 20 41 | # 将任务状态段的rsp0保存的进程内核栈加载到寄存器RSP中,rsp0的基址偏移为4字节 42 | mov tss + 4, %rsp 43 | 44 | # 将任务状态段的rsp2的用户栈压入内核栈 45 | pushq tss + 20 46 | # 无需保存RAX寄存器 47 | SAVE_CONTEXT 0 48 | 49 | # 由于位于同一个特权级,使用call调用具体的系统调用 50 | # 间接跳转 51 | call *syscall_table(, %rax, 8) 52 | 53 | RESTORE_CONTEXT 0 54 | # 从内核栈弹出用户栈顶指针RSP,恢复用户栈 55 | pop %rsp 56 | 57 | sysretq 58 | 59 | timer_handler: 60 | # 保护上下文 61 | SAVE_CONTEXT 62 | 63 | # 将OCW2写入8259A的端口0x20处,D5=1表示处理器已经处理完中断了 64 | movb $0x20,%al 65 | outb %al,$0x20 66 | 67 | # 调用do_timer函数 68 | call do_timer 69 | 70 | # 恢复上下文 71 | RESTORE_CONTEXT 72 | # IF位自动复位,再次开启中断 73 | iretq 74 | 75 | pf_handler: 76 | SAVE_CONTEXT 77 | 78 | # 发生缺页异常时,处理器会将缺页地址存储到寄存器CR2中 79 | # 需要从寄存器CR2中取出引起缺页的地址 80 | mov %cr2, %rdi 81 | call do_page_fault 82 | 83 | RESTORE_CONTEXT 84 | # 越过错误码,避免弹出到指令指针寄存器RIP中 85 | add $8, %rsp 86 | iretq 87 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/kernel/syscall.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/segment.h" 3 | #include "include/syscall.h" 4 | #include "include/sched.h" 5 | 6 | #define MSR_STAR 0xc0000081 7 | #define MSR_LSTAR 0xc0000082 8 | #define MSR_SYSCALL_MASK 0xc0000084 9 | #define RF_IF 0x00000200 10 | 11 | typedef unsigned long (*fn_ptr)(); 12 | 13 | unsigned long do_sleep(long ms); 14 | 15 | fn_ptr syscall_table[] = { do_sleep }; 16 | 17 | void syscall_init() { 18 | // 设置用户代码段和内核代码段 19 | uint64_t star_val = (uint64_t)USER32_CS << 48 | (uint64_t)KERNEL_CS << 32; 20 | uint64_t syscall_entry = (uint64_t)system_call; 21 | // 初始化标志寄存器的第9位(中断使能位) 22 | uint64_t syscall_mask = RF_IF; 23 | 24 | // 将值写入STAR寄存器 25 | // 指令wrmsr从寄存器ECX读取MSR的ID,分别将寄存器EAX和EDX的值写入MSR寄存器的低32位和高32位 26 | // 编译器将各个值预先装载到ECX、EAX、EDX中 27 | __asm__("wrmsr": : "c" (MSR_STAR), "a" ((uint32_t)star_val), "d" (star_val >> 32)); 28 | // 将值写入LSTAR寄存器 29 | __asm__("wrmsr": : "c" (MSR_LSTAR), "a" ((uint32_t)syscall_entry), "d" (syscall_entry >> 32)); 30 | // 将值写入SFMASK寄存器 31 | __asm__("wrmsr": : "c" (MSR_SYSCALL_MASK), "a" ((uint32_t)syscall_mask), "d" (syscall_mask >> 32)); 32 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/kernel/tss.c: -------------------------------------------------------------------------------- 1 | #include "include/tss.h" 2 | #include "include/sched.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | extern unsigned long gdt[64]; 7 | struct tss tss; 8 | 9 | void tss_init() { 10 | // 允许应用程序访问所有I/O端口 11 | memset(&tss, 0, sizeof(tss)); 12 | tss.io_bitmap_offset = __builtin_offsetof(struct tss, io_bitmap); 13 | tss.io_bitmap[IO_BITMAP_BYTES] = ~0; 14 | // 设置任务状态段的RSP0指向当前应用程序的内核栈地址 15 | tss.rsp0 = current->kstack; 16 | 17 | // 得到GDT表的第6项 18 | struct tss_desc* desc = (struct tss_desc*)&gdt[GDT_TSS_ENTRY]; 19 | // 将任务状态段的段描述符清0 20 | memset(desc, 0, sizeof(struct tss_desc)); 21 | // 计算tss结构体的长度的低16位 22 | desc->limit0 = sizeof(tss) & 0xffff; 23 | desc->base0 = (unsigned long)(&tss) & 0xffff; 24 | desc->base1 = ((unsigned long)(&tss) >> 16) & 0xff; 25 | // 段类型为0x9 26 | desc->type = 0x9; 27 | // 存在位为1 28 | desc->p = 1; 29 | // 段长度的第16~19位 30 | desc->limit1 = (sizeof(tss) >> 16) & 0xf; 31 | desc->base2 = ((unsigned long)(&tss) >> 24) & 0xff; 32 | desc->base3 = (unsigned long)(&tss) >> 32; 33 | 34 | // 禁止应用程序对所有端口的访问,会直接终止执行 35 | // memset(tss.io_bitmap, 0xff, IO_BITMAP_BYTES); 36 | 37 | // 装载任务寄存器TR,段索引为6,TI为0,特权级(内核级)为0 38 | __asm__ ("ltr %w0" : : "r"(GDT_TSS_ENTRY << 3)); 39 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/lib/string.c: -------------------------------------------------------------------------------- 1 | // 填充一段内存区 2 | void* memset(void *s, char c, unsigned long n) { 3 | char *tmp = s; 4 | 5 | while (n--) { 6 | *tmp++ = c; 7 | } 8 | 9 | return s; 10 | } 11 | 12 | /* 内存区域复制 13 | * @param dest 目的内存地址 14 | * @param src 源内存地址 15 | * @param n 复制的字节数 16 | */ 17 | void memcpy(void *dest, const void *src, unsigned long n) { 18 | char *tmp = dest; 19 | const char *s = src; 20 | 21 | // 将字节逐个从源内存的内容复制到目的内存 22 | while (n--) { 23 | *tmp++ = *s++; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/main.c: -------------------------------------------------------------------------------- 1 | #include "include/mm.h" 2 | #include "include/print.h" 3 | #include "include/sched.h" 4 | #include "include/tss.h" 5 | #include "include/interrupt.h" 6 | #include "include/syscall.h" 7 | 8 | int main() { 9 | // 内存初始化 10 | mm_init(); 11 | // 中断初始化 12 | interrupt_init(); 13 | // 系统调用初始化 14 | syscall_init(); 15 | // 设置标志寄存器中的IF位,开启中断 16 | __asm__ ("sti"); 17 | sched_init(); 18 | 19 | // 任务状态段初始化 20 | tss_init(); 21 | 22 | // 使能时钟中断 23 | init_8254(); 24 | 25 | // 将寄存器CR3指向进程1的页表 26 | __asm__ ("mov %0, %%cr3": :"r"(current->pml4)); 27 | 28 | // 设置栈指针指向进程1的内核栈的栈顶,并执行ret_from_kernel 29 | __asm__ ("movq %0, %%rsp\n\t" 30 | "jmp ret_from_kernel\n\t" 31 | : 32 | : "m"(current->rsp0) 33 | ); 34 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c12/c12-2/mm/page_alloc.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/mm.h" 3 | 4 | // 分配页面 5 | unsigned long alloc_page() { 6 | unsigned long addr = 0; 7 | 8 | // 从内存映像占用的物理页面之后的页面开始 9 | for (long i = KERNEL_PAGE_NUM; i < mem_size / PAGE_SIZE; i++) { 10 | // 直到找到空页面 11 | if (pages[i] == 0) { 12 | // 将新分配的页面标记为已占用 13 | pages[i] = 1; 14 | // 计算页面的物理地址 15 | addr = PAGE_SIZE * i; 16 | break; 17 | } 18 | } 19 | 20 | // 返回新分配页面的物理地址 21 | return addr; 22 | } 23 | 24 | // 释放页面 25 | void free_page(uint64_t addr) { 26 | // 计算归还页面在数组page的索引 27 | uint32_t index = addr / PAGE_SIZE; 28 | 29 | // 标记页面为空闲 30 | pages[index] = 0; 31 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/app/app1.c: -------------------------------------------------------------------------------- 1 | #include "app/libc/std.h" 2 | #include "include/print.h" 3 | 4 | int main() { 5 | void* m = shm_open("shm-1"); 6 | *(char*)m = 'S'; 7 | 8 | while (1) { 9 | print('A'); 10 | sleep(1000); 11 | } 12 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/app/app2.c: -------------------------------------------------------------------------------- 1 | #include "app/libc/std.h" 2 | #include "include/print.h" 3 | 4 | int main() { 5 | void* m = shm_open("shm-1"); 6 | 7 | while (1) { 8 | print(*(char*)m); 9 | sleep(1000); 10 | } 11 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/app/libc/start.S: -------------------------------------------------------------------------------- 1 | # 启动代码,调用main,自动找到程序的入口 2 | call main -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/app/libc/std.h: -------------------------------------------------------------------------------- 1 | void sleep(long ms); 2 | void* shm_open(const char* name); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/app/libc/syscall.S: -------------------------------------------------------------------------------- 1 | #define SYSCALL_SLEEP 0 2 | #define SYSCALL_SHM 1 3 | .globl sleep 4 | .globl shm_open 5 | 6 | sleep: 7 | # 从寄存器RAX获取具体的系统调用号,0号为syscall_table中的do_sleep 8 | mov $SYSCALL_SLEEP, %rax 9 | # 发起系统调用 10 | syscall 11 | ret 12 | 13 | shm_open: 14 | # 从寄存器RAX获取具体的系统调用号,1号为syscall_table中的do_shm 15 | mov $SYSCALL_SHM, %rax 16 | syscall 17 | ret -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/head64.S: -------------------------------------------------------------------------------- 1 | #include "include/segment.h" 2 | 3 | .text 4 | .code64 5 | .globl gdt 6 | .globl ret_from_kernel 7 | .globl task0_stack 8 | .globl idle_task_entry 9 | 10 | lgdt gdtr 11 | # 将中断描述符表地址写入IDTR寄存器中 12 | lidt idtr 13 | 14 | # 初始化寄存器 15 | mov $KERNEL_DS, %ax 16 | mov %ax, %ds 17 | mov %ax, %ss 18 | mov %ax, %es 19 | mov %ax, %fs 20 | mov %ax, %gs 21 | 22 | # 使用RSP寄存器指向栈底 23 | mov $task0_stack, %rsp 24 | # 跳转到main方法 25 | push $main 26 | ret 27 | 28 | # 所有段的TI为0,内核段的特权级为0,用户段的特权级为3 29 | .align 64 30 | gdt: 31 | # 空描述符(保留不用) 32 | .quad 0x0000000000000000 33 | # 内核代码段描述符 34 | .quad 0x00209a0000000000 35 | # 内核数据段描述符 36 | .quad 0x0000920000000000 37 | # 32位用户代码段描述符 38 | .quad 0x0000000000000000 39 | # 用户数据段描述符 40 | .quad 0x0000f20000000000 41 | # 64位用户代码段描述符 42 | .quad 0x0020fa0000000000 43 | .fill 128, 8, 0 44 | gdt_end: 45 | 46 | gdtr: 47 | .word gdt_end - gdt 48 | .quad gdt 49 | 50 | idtr: 51 | # 中断描述符表长度,256项中断描述符,每个是16字节 52 | .word 16 * 256 53 | # 中断描述符表地址 54 | .quad idt_table 55 | 56 | # 4KB大小的栈空间 57 | .fill 4096, 1, 0 58 | task0_stack: 59 | 60 | # 在执行iret前,初始化其他段寄存器,之后再从栈中弹出断点信息,返回用户空间 61 | ret_from_kernel: 62 | mov $USER_DS, %rax 63 | movw %ax, %ds 64 | movw %ax, %es 65 | movw %ax, %fs 66 | movw %ax, %gs 67 | iretq 68 | 69 | # 空闲进程,当没有就绪任务需要运行时,让处理器运行这个空闲任务 70 | idle_task_entry: 71 | 1: 72 | # 开启处理器响应中断标识 73 | sti 74 | # 让处理器停止运转 75 | hlt 76 | jmp 1b -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/include/interrupt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void init_8254(); 4 | void interrupt_init(); 5 | 6 | void timer_handler(); 7 | void pf_handler(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/include/mm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | #define E820MAX 32 6 | #define E820_RAM 1 7 | 8 | // 定义最大页面数,每个页面大小为4K,一共表示4GB物理内存 9 | #define MAX_PAGES (1024 * 1024) 10 | // 定义内核所需的页面,16K个页面 11 | #define KERNEL_PAGE_NUM (1024 * 16) 12 | 13 | #define PAGE_SIZE 4096 14 | 15 | #define PAGE_OFFSET 0xffff888000000000 16 | #define VA(x) ((void*)((unsigned long)(x) + PAGE_OFFSET)) 17 | #define PA(x) ((unsigned long)(x) - PAGE_OFFSET) 18 | 19 | #define TASK0_PML4 0x30000 20 | 21 | // 内存大小 22 | extern unsigned long mem_size; 23 | 24 | extern uint8_t pages[MAX_PAGES]; 25 | 26 | // 使用packed告知GCC编译器,分配结构体变量时不要进行对齐 27 | struct e820entry { 28 | uint64_t addr; // 内存段的起始地址 29 | uint64_t size; // 内存段的尺寸 30 | uint32_t type; // 内存段的类型 31 | } __attribute__((packed)); 32 | 33 | // 存储E820信息的内存区域 34 | struct e820map { 35 | uint32_t nr_entry; 36 | struct e820entry map[E820MAX]; // 多条E820记录 37 | }; 38 | 39 | // 内存管理初始化 40 | void mm_init(); 41 | // 分配页面 42 | unsigned long alloc_page(); 43 | // 释放页面 44 | void free_page(uint64_t addr); 45 | void* malloc(int size); 46 | void free(void* obj); 47 | // 内存空间映射 48 | void map_range(unsigned long pml4_pa, unsigned long from_va, unsigned long to_pa, char us, long npage); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/include/print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 如果是32位,向串口输出1次,如果是64位,向串口输出2次 4 | #define print(x) \ 5 | do { \ 6 | int size = sizeof(x); \ 7 | if (size <= 4) { \ 8 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 9 | "out %%eax, %%dx\n\t" \ 10 | : \ 11 | : "a"(x) \ 12 | : "dx"); \ 13 | } else if (size == 8) { \ 14 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 15 | "out %%eax, %%dx\n\t" \ 16 | "shr $32, %%rax\n\t" \ 17 | "out %%eax, %%dx\n\t" \ 18 | : \ 19 | : "a"(x) \ 20 | : "dx"); \ 21 | } \ 22 | } while (0) -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/include/sched.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 进程状态 6 | enum task_state { 7 | // 任务正在运行或就绪态 8 | TASK_RUNNING = 0, 9 | // 任务处于可中断的睡眠态 10 | TASK_INTERRUPTIBLE 11 | }; 12 | 13 | // 记录进程的信息和状态 14 | struct task { 15 | unsigned long id; 16 | enum task_state state; // 任务状态 17 | unsigned long rip; // 任务调度切换时,指令的地址 18 | unsigned long rsp0; // 任务调度切换时,内核栈的栈顶 19 | unsigned long kstack; // 内核栈的栈底,用于在内核时,任务状态段中特权级为0的栈指针指向当前任务的内核栈栈底 20 | unsigned long pml4; // 根页表的物理地址,用于更新寄存器CR3指向当前任务的页表 21 | 22 | // 支撑任务链的链表 23 | struct task* next; 24 | struct task* prev; 25 | }; 26 | 27 | struct timer { 28 | unsigned long alarm; // 记录时钟到期时间 29 | struct task* task; 30 | struct timer* next; 31 | struct timer* prev; 32 | }; 33 | 34 | extern unsigned long ret_from_kernel; 35 | extern unsigned long idle_task_entry; 36 | extern unsigned long task0_stack; 37 | extern struct task* current; 38 | 39 | void sched_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/include/segment.h: -------------------------------------------------------------------------------- 1 | // 内核代码段选择子 2 | #define KERNEL_CS 0x8 3 | // 内核数据段选择子 4 | #define KERNEL_DS 0x10 5 | // 32位用户代码段选择子 6 | #define USER32_CS 0x1b 7 | // 用户数据段选择子 8 | #define USER_DS 0x23 9 | // 64位用户代码段选择子 10 | #define USER_CS 0x2b 11 | 12 | // 段描述符表的第6项作为任务状态段的段描述符 13 | #define GDT_TSS_ENTRY 6 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/include/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | void* memset(void *s, char c, unsigned long n); 6 | void memcpy(void *dest, const void *src, unsigned long n); 7 | int strcmp(const char *s1, const char *s2); 8 | int strlen(const char *s); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/include/syscall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void system_call(); 4 | void syscall_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/include/tss.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 可以寻址64K(65536)个端口 6 | #define IO_BITMAP_BYTES (65536 / 8) 7 | 8 | struct tss { 9 | uint32_t reserved1; // 保留 10 | uint64_t rsp0; // 特权级为0的栈指针 11 | uint64_t rsp1; // 特权级为1的栈指针 12 | uint64_t rsp2; // 特权级为2的栈指针 13 | uint64_t reserved2; // 保留 14 | uint64_t ist[7]; // IST的7个专用栈 15 | uint32_t reserved3; // 保留 16 | uint32_t reserved4; // 保留 17 | uint16_t reserved5; // 保留 18 | uint16_t io_bitmap_offset; // 程序I/O权限位图相对于任务状态段基址的16位偏移 19 | uint8_t io_bitmap[IO_BITMAP_BYTES + 1]; // I/O权限位图 20 | } __attribute__((packed)); 21 | 22 | struct tss_desc { 23 | uint16_t limit0; // 段长度 24 | uint16_t base0; // 段基址(0~15) 25 | uint16_t base1 : 8, type : 4, desc_type : 1, dpl : 2, p : 1; 26 | uint16_t limit1 : 4, avl : 1, zero0 : 2, g : 1, base2 : 8; 27 | uint32_t base3; // 段基址(32~63) 28 | uint32_t zero1; // 保留 29 | } __attribute__((packed)); 30 | 31 | extern struct tss tss; 32 | // TSS任务状态段初始化 33 | void tss_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef signed char int8_t; 4 | typedef short int int16_t; 5 | typedef int int32_t; 6 | typedef long int int64_t; 7 | 8 | typedef unsigned char uint8_t; 9 | typedef unsigned short int uint16_t; 10 | typedef unsigned int uint32_t; 11 | typedef unsigned long int uint64_t; 12 | 13 | #define NULL ((void*)0) 14 | 15 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/ipc/shm.c: -------------------------------------------------------------------------------- 1 | #include "include/string.h" 2 | #include "include/mm.h" 3 | #include "include/sched.h" 4 | 5 | // 共享内存块 6 | struct shm { 7 | char* name; // 共享内存块的名字,用于区分不同的共享内存 8 | unsigned long page; // 指向一个用于共享内存的物理页面 9 | 10 | struct shm* next; // 指向下一个共享内存块,用于链接共享内存 11 | }; 12 | 13 | struct shm* shm_head; 14 | struct shm* shm_tail; 15 | 16 | // 实现共享内存系统调用 17 | // name:应用传递进来的共享内存名字 18 | int do_shm(char* name) { 19 | struct shm* shm = NULL; 20 | // 使用一个固定地址作为共享内存映射的虚拟地址 21 | unsigned long va = 0x4000000; 22 | 23 | // 遍历并寻找名字与应用请求相同的共享内存 24 | for (struct shm* s = shm_head; s; s = s->next) { 25 | if (!strcmp(s->name, name)) { 26 | shm = s; 27 | break; 28 | } 29 | } 30 | 31 | if (!shm) { 32 | // 如果没有找到,则创建一个新的共享内存 33 | shm = malloc(sizeof(struct shm)); 34 | // 申请一个内存块 35 | int len = strlen(name); 36 | shm->name = malloc(len + 1); 37 | memcpy(shm->name, name, len); 38 | shm->name[len] = '\0'; 39 | // 分配一个页面作为共享内存页 40 | shm->page = alloc_page(); 41 | shm->next = NULL; 42 | 43 | // 构建共享内存链表 44 | if (shm_head == NULL) { 45 | shm_head = shm; 46 | shm_tail = shm; 47 | } else { 48 | shm_tail->next = shm; 49 | shm_tail = shm; 50 | } 51 | } 52 | 53 | // 将共享内存映射到应用程序的地址空间 54 | // current->pml4 是当前进程页表的根页面 55 | // va 是固定虚拟地址 0x4000000 56 | // shm->page 是共享的物理页面 57 | // us位设置为1,允许允许在特权级为3的用户程序访问这块内存 58 | // 1表示建立1个页面的映射关系 59 | map_range(current->pml4, va, shm->page, 0x4, 1); 60 | 61 | return va; 62 | } 63 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/kernel/handler.S: -------------------------------------------------------------------------------- 1 | # 各种中断处理函数 2 | 3 | .text 4 | .code64 5 | .globl timer_handler 6 | .globl pf_handler 7 | .globl system_call 8 | 9 | # 为进程保存完整环境,调用者负责的寄存器:RDI、RSI、RDX、RCX、RAX、R8~R11 10 | .macro SAVE_CONTEXT save_rax = 1 11 | pushq %rdi 12 | pushq %rsi 13 | pushq %rdx 14 | pushq %rcx 15 | .if \save_rax 16 | pushq %rax 17 | .endif 18 | pushq %r8 19 | pushq %r9 20 | pushq %r10 21 | pushq %r11 22 | .endm 23 | 24 | .macro RESTORE_CONTEXT rstore_rax = 1 25 | popq %r11 26 | popq %r10 27 | popq %r9 28 | popq %r8 29 | .if \rstore_rax 30 | popq %rax 31 | .endif 32 | popq %rcx 33 | popq %rdx 34 | popq %rsi 35 | popq %rdi 36 | .endm 37 | 38 | system_call: 39 | # 将当前寄存器RSP中的用户栈顶保存到任务状态段的rsp2,rsp2的基址偏移为20字节 40 | mov %rsp, tss + 20 41 | # 将任务状态段的rsp0保存的进程内核栈加载到寄存器RSP中,rsp0的基址偏移为4字节 42 | mov tss + 4, %rsp 43 | 44 | # 将任务状态段的rsp2的用户栈压入内核栈 45 | pushq tss + 20 46 | # 无需保存RAX寄存器 47 | SAVE_CONTEXT 0 48 | 49 | # 由于位于同一个特权级,使用call调用具体的系统调用 50 | # 间接跳转 51 | call *syscall_table(, %rax, 8) 52 | 53 | RESTORE_CONTEXT 0 54 | # 从内核栈弹出用户栈顶指针RSP,恢复用户栈 55 | pop %rsp 56 | 57 | sysretq 58 | 59 | timer_handler: 60 | # 保护上下文 61 | SAVE_CONTEXT 62 | 63 | # 将OCW2写入8259A的端口0x20处,D5=1表示处理器已经处理完中断了 64 | movb $0x20,%al 65 | outb %al,$0x20 66 | 67 | # 调用do_timer函数 68 | call do_timer 69 | 70 | # 恢复上下文 71 | RESTORE_CONTEXT 72 | # IF位自动复位,再次开启中断 73 | iretq 74 | 75 | pf_handler: 76 | SAVE_CONTEXT 77 | 78 | # 发生缺页异常时,处理器会将缺页地址存储到寄存器CR2中 79 | # 需要从寄存器CR2中取出引起缺页的地址 80 | mov %cr2, %rdi 81 | call do_page_fault 82 | 83 | RESTORE_CONTEXT 84 | # 越过错误码,避免弹出到指令指针寄存器RIP中 85 | add $8, %rsp 86 | iretq 87 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/kernel/interrupt.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/interrupt.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | // 产生100Hz的中断,即每秒向8259A发出100次时钟信号,该值表示每秒的中断信号次数 7 | #define COUNTER (1193181 / 100) 8 | // 中断类型 9 | #define GATE_INTERRUPT 0xe 10 | // 异常类型 11 | #define GATE_EXCEPTION 0xf 12 | 13 | // 中断描述符 14 | struct gate_desc { 15 | uint16_t offset_low; // 中断处理函数地址0~15位 16 | uint16_t segment; // 段选择子 17 | uint16_t ist : 3, zero : 5, type : 4, zero2 : 1, dpl : 2, p : 1; // ist:中断栈索引;type:中断类型(中断1110/异常1111);dpl:特权级;p:存在位 18 | uint16_t offset_middle; // 中断处理函数地址16~31位 19 | uint32_t offset_high; // 中断处理函数地址32~63位 20 | uint32_t reserved; // 保留 21 | } __attribute__((packed)); 22 | 23 | // 定义256项中断和异常,中断描述符表 24 | struct gate_desc idt_table[256]; 25 | 26 | // 初始化8254计数器芯片 27 | void init_8254() { 28 | // D7D6=00:SC选择计数器0 29 | // D5D4=11:RW设置读取计数值的方式,11表示首先写入低8位,然后写入高8位 30 | // D3D2D1=011:工作模式为3,使用方波 31 | // D0=0:编码格式为二进制格式 32 | // 将控制字写入0x43端口 33 | __asm__ ("outb %%al, $0x43"::"a"(0x36)); 34 | // 分别将低8位和高8位写入0x40端口 35 | __asm__ ("outb %%al, $0x40"::"a"(COUNTER & 0xff)); 36 | __asm__ ("outb %%al, $0x40"::"a"(COUNTER >> 8)); 37 | } 38 | 39 | // 设置中断描述符 40 | // index:中断向量,对应中断描述符表中的索引号 41 | // addr:中断处理函数地址 42 | // type:描述符类型 43 | static void set_gate(unsigned char index, unsigned long addr, char type) { 44 | struct gate_desc* desc = &idt_table[index]; 45 | 46 | // 中断描述符初始化为0 47 | memset(desc, 0, sizeof(struct gate_desc)); 48 | // 设置为内核代码段 49 | desc->segment = KERNEL_CS; 50 | // 设置中断处理函数地址 51 | desc->offset_low = (uint16_t)addr; 52 | desc->offset_middle = (uint16_t)(addr >> 16); 53 | desc->offset_high = (uint32_t)(addr >> 32); 54 | // 特权级为0,内核空间 55 | desc->dpl = 0; 56 | desc->type = type; 57 | // 存在位设置为1 58 | desc->p = 1; 59 | } 60 | 61 | // 中断系统初始化 62 | void interrupt_init() { 63 | // 添加缺页异常处理函数 64 | set_gate(14, (unsigned long)&pf_handler, GATE_EXCEPTION); 65 | // 设置时钟中断描述符,时钟中断的向量号是32(0x20) 66 | set_gate(0x20, (unsigned long)&timer_handler, GATE_INTERRUPT); 67 | 68 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/kernel/syscall.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/segment.h" 3 | #include "include/syscall.h" 4 | #include "include/sched.h" 5 | 6 | #define MSR_STAR 0xc0000081 7 | #define MSR_LSTAR 0xc0000082 8 | #define MSR_SYSCALL_MASK 0xc0000084 9 | #define RF_IF 0x00000200 10 | 11 | typedef unsigned long (*fn_ptr)(); 12 | 13 | unsigned long do_sleep(long ms); 14 | unsigned long do_shm(const char* name); 15 | 16 | fn_ptr syscall_table[] = { do_sleep, do_shm }; 17 | 18 | void syscall_init() { 19 | // 设置用户代码段和内核代码段 20 | uint64_t star_val = (uint64_t)USER32_CS << 48 | (uint64_t)KERNEL_CS << 32; 21 | uint64_t syscall_entry = (uint64_t)system_call; 22 | // 初始化标志寄存器的第9位(中断使能位) 23 | uint64_t syscall_mask = RF_IF; 24 | 25 | // 将值写入STAR寄存器 26 | // 指令wrmsr从寄存器ECX读取MSR的ID,分别将寄存器EAX和EDX的值写入MSR寄存器的低32位和高32位 27 | // 编译器将各个值预先装载到ECX、EAX、EDX中 28 | __asm__("wrmsr": : "c" (MSR_STAR), "a" ((uint32_t)star_val), "d" (star_val >> 32)); 29 | // 将值写入LSTAR寄存器 30 | __asm__("wrmsr": : "c" (MSR_LSTAR), "a" ((uint32_t)syscall_entry), "d" (syscall_entry >> 32)); 31 | // 将值写入SFMASK寄存器 32 | __asm__("wrmsr": : "c" (MSR_SYSCALL_MASK), "a" ((uint32_t)syscall_mask), "d" (syscall_mask >> 32)); 33 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/kernel/tss.c: -------------------------------------------------------------------------------- 1 | #include "include/tss.h" 2 | #include "include/sched.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | extern unsigned long gdt[64]; 7 | struct tss tss; 8 | 9 | void tss_init() { 10 | // 允许应用程序访问所有I/O端口 11 | memset(&tss, 0, sizeof(tss)); 12 | tss.io_bitmap_offset = __builtin_offsetof(struct tss, io_bitmap); 13 | tss.io_bitmap[IO_BITMAP_BYTES] = ~0; 14 | // 设置任务状态段的RSP0指向当前应用程序的内核栈地址 15 | tss.rsp0 = current->kstack; 16 | 17 | // 得到GDT表的第6项 18 | struct tss_desc* desc = (struct tss_desc*)&gdt[GDT_TSS_ENTRY]; 19 | // 将任务状态段的段描述符清0 20 | memset(desc, 0, sizeof(struct tss_desc)); 21 | // 计算tss结构体的长度的低16位 22 | desc->limit0 = sizeof(tss) & 0xffff; 23 | desc->base0 = (unsigned long)(&tss) & 0xffff; 24 | desc->base1 = ((unsigned long)(&tss) >> 16) & 0xff; 25 | // 段类型为0x9 26 | desc->type = 0x9; 27 | // 存在位为1 28 | desc->p = 1; 29 | // 段长度的第16~19位 30 | desc->limit1 = (sizeof(tss) >> 16) & 0xf; 31 | desc->base2 = ((unsigned long)(&tss) >> 24) & 0xff; 32 | desc->base3 = (unsigned long)(&tss) >> 32; 33 | 34 | // 禁止应用程序对所有端口的访问,会直接终止执行 35 | // memset(tss.io_bitmap, 0xff, IO_BITMAP_BYTES); 36 | 37 | // 装载任务寄存器TR,段索引为6,TI为0,特权级(内核级)为0 38 | __asm__ ("ltr %w0" : : "r"(GDT_TSS_ENTRY << 3)); 39 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/lib/string.c: -------------------------------------------------------------------------------- 1 | // 填充一段内存区 2 | void* memset(void *s, char c, unsigned long n) { 3 | char *tmp = s; 4 | 5 | while (n--) { 6 | *tmp++ = c; 7 | } 8 | 9 | return s; 10 | } 11 | 12 | /* 内存区域复制 13 | * @param dest 目的内存地址 14 | * @param src 源内存地址 15 | * @param n 复制的字节数 16 | */ 17 | void memcpy(void *dest, const void *src, unsigned long n) { 18 | char *tmp = dest; 19 | const char *s = src; 20 | 21 | // 将字节逐个从源内存的内容复制到目的内存 22 | while (n--) { 23 | *tmp++ = *s++; 24 | } 25 | } 26 | 27 | /* 字符串比较 28 | * @param s1 字符串1 29 | * @param s2 字符串2 30 | * @return 如果两个字符串的全部字符都相同,返回0。 31 | * 如果字符串1大于字符串2,返回1,否则返回-1。 32 | */ 33 | int strcmp(const char *s1, const char *s2) { 34 | unsigned char c1, c2; 35 | 36 | while (1) { 37 | c1 = *s1++; 38 | c2 = *s2++; 39 | if (c1 != c2) { 40 | return c1 < c2 ? -1 : 1; 41 | } 42 | if (!c1) { 43 | break; 44 | } 45 | } 46 | 47 | return 0; 48 | } 49 | 50 | /* 统计字符串长度 51 | * @param s 字符串 52 | * @return 返回字符串长度 53 | */ 54 | int strlen(const char *s) { 55 | const char *sc; 56 | 57 | for (sc = s; *sc != '\0'; ++sc); 58 | 59 | return sc - s; 60 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/main.c: -------------------------------------------------------------------------------- 1 | #include "include/mm.h" 2 | #include "include/print.h" 3 | #include "include/sched.h" 4 | #include "include/tss.h" 5 | #include "include/interrupt.h" 6 | #include "include/syscall.h" 7 | 8 | int main() { 9 | // 内存初始化 10 | mm_init(); 11 | // 中断初始化 12 | interrupt_init(); 13 | // 系统调用初始化 14 | syscall_init(); 15 | // 设置标志寄存器中的IF位,开启中断 16 | __asm__ ("sti"); 17 | sched_init(); 18 | 19 | // 任务状态段初始化 20 | tss_init(); 21 | 22 | // 使能时钟中断 23 | init_8254(); 24 | 25 | // 将寄存器CR3指向进程1的页表 26 | __asm__ ("mov %0, %%cr3": :"r"(current->pml4)); 27 | 28 | // 设置栈指针指向进程1的内核栈的栈顶,并执行ret_from_kernel 29 | __asm__ ("movq %0, %%rsp\n\t" 30 | "jmp ret_from_kernel\n\t" 31 | : 32 | : "m"(current->rsp0) 33 | ); 34 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c13/mm/page_alloc.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/mm.h" 3 | 4 | // 分配页面 5 | unsigned long alloc_page() { 6 | unsigned long addr = 0; 7 | 8 | // 从内存映像占用的物理页面之后的页面开始 9 | for (long i = KERNEL_PAGE_NUM; i < mem_size / PAGE_SIZE; i++) { 10 | // 直到找到空页面 11 | if (pages[i] == 0) { 12 | // 将新分配的页面标记为已占用 13 | pages[i] = 1; 14 | // 计算页面的物理地址 15 | addr = PAGE_SIZE * i; 16 | break; 17 | } 18 | } 19 | 20 | // 返回新分配页面的物理地址 21 | return addr; 22 | } 23 | 24 | // 释放页面 25 | void free_page(uint64_t addr) { 26 | // 计算归还页面在数组page的索引 27 | uint32_t index = addr / PAGE_SIZE; 28 | 29 | // 标记页面为空闲 30 | pages[index] = 0; 31 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/app/app1.c: -------------------------------------------------------------------------------- 1 | #include "app/libc/std.h" 2 | #include "include/print.h" 3 | #include "app/libdraw/draw.h" 4 | 5 | int main() { 6 | void* m = shm_open("shm-1"); 7 | *(char*)m = 'S'; 8 | 9 | struct mode_info mode_info; 10 | get_mode_info(&mode_info); 11 | 12 | unsigned long fbbase = fbmap(); 13 | 14 | // 每次间隔1秒,变换矩形的颜色 15 | while (1) { 16 | draw_rect(10, 100, 150, 100, RED, fbbase, &mode_info); 17 | sleep(1000); 18 | draw_rect(10, 100, 150, 100, GREEN, fbbase, &mode_info); 19 | sleep(1000); 20 | draw_rect(10, 100, 150, 100, BLUE, fbbase, &mode_info); 21 | sleep(1000); 22 | } 23 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/app/app2.c: -------------------------------------------------------------------------------- 1 | #include "app/libc/std.h" 2 | #include "include/print.h" 3 | #include "app/libdraw/draw.h" 4 | 5 | void draw_char(uint64_t fbaddr, struct mode_info* mode_info, int color) { 6 | // H 7 | draw_en(10, 2, 0, color, fbaddr, mode_info); 8 | // e 9 | draw_en(20, 2, 1, color, fbaddr, mode_info); 10 | // l 11 | draw_en(30, 2, 2, color, fbaddr, mode_info); 12 | // l 13 | draw_en(40, 2, 2, color, fbaddr, mode_info); 14 | // o 15 | draw_en(50, 2, 3, color, fbaddr, mode_info); 16 | // , 17 | draw_en(60, 2, 4, color, fbaddr, mode_info); 18 | 19 | // 中 20 | draw_zh(70, 2, 0, color, fbaddr, mode_info); 21 | // 国 22 | draw_zh(90, 2, 1, color, fbaddr, mode_info); 23 | // . 24 | draw_en(110, 2, 5, color, fbaddr, mode_info); 25 | } 26 | 27 | int main() { 28 | void* m = shm_open("shm-1"); 29 | 30 | struct mode_info mode_info; 31 | get_mode_info(&mode_info); 32 | 33 | unsigned long fbbase = fbmap(); 34 | // 显示黄色 35 | draw_char(fbbase, &mode_info, RED | GREEN); 36 | 37 | unsigned long i = 0; 38 | while (++i) { 39 | if (i % 2) { 40 | // 显示黄色 41 | draw_char(fbbase, &mode_info, RED | GREEN); 42 | } else { 43 | // 显示紫色 44 | draw_char(fbbase, &mode_info, RED | BLUE); 45 | } 46 | print(*(char*)m); 47 | // 每次间隔1秒变换一次颜色 48 | sleep(1000); 49 | } 50 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/app/libc/start.S: -------------------------------------------------------------------------------- 1 | # 启动代码,调用main,自动找到程序的入口 2 | call main -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/app/libc/std.h: -------------------------------------------------------------------------------- 1 | #include "include/vesa.h" 2 | 3 | void sleep(long ms); 4 | void* shm_open(const char* name); 5 | unsigned long fbmap(); 6 | void get_mode_info(struct mode_info* mode_info); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/app/libc/syscall.S: -------------------------------------------------------------------------------- 1 | #define SYSCALL_SLEEP 0 2 | #define SYSCALL_SHM 1 3 | #define SYSCALL_FBMAP 2 4 | #define SYSCALL_GETMODEINFO 3 5 | 6 | .globl sleep 7 | .globl shm_open 8 | .globl fbmap 9 | .globl get_mode_info 10 | 11 | sleep: 12 | # 从寄存器RAX获取具体的系统调用号,0号为syscall_table中的do_sleep 13 | mov $SYSCALL_SLEEP, %rax 14 | # 发起系统调用 15 | syscall 16 | ret 17 | 18 | shm_open: 19 | # 从寄存器RAX获取具体的系统调用号,1号为syscall_table中的do_shm 20 | mov $SYSCALL_SHM, %rax 21 | syscall 22 | ret 23 | 24 | fbmap: 25 | # 从寄存器RAX获取具体的系统调用号,2号为syscall_table中的do_fbmap 26 | mov $SYSCALL_FBMAP, %rax 27 | syscall 28 | ret 29 | 30 | get_mode_info: 31 | # 从寄存器RAX获取具体的系统调用号,3号为syscall_table中的do_get_mode_info 32 | mov $SYSCALL_GETMODEINFO, %rax 33 | syscall 34 | ret -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/app/libdraw/draw.h: -------------------------------------------------------------------------------- 1 | #include "include/vesa.h" 2 | #include "include/types.h" 3 | 4 | #define BLUE 0xff0000 5 | #define GREEN 0xff00 6 | #define RED 0xff 7 | 8 | void draw_zh(int origin_x, int origin_y, int index, int color, unsigned long fbbase, struct mode_info* mode_info); 9 | void draw_en(int origin_x, int origin_y, int index, int color, unsigned long fbbase, struct mode_info* mode_info); 10 | void draw_rect(int origin_x, int origin_y, int l, int w, int color, unsigned long fbbase, struct mode_info* mode_info); 11 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/drivers/atkbd.c: -------------------------------------------------------------------------------- 1 | #include "include/print.h" 2 | 3 | // 使用键码集2 4 | unsigned char keymap[256] = { 5 | [0x1c] = 'a', 6 | [0x32] = 'b', 7 | [0x21] = 'c', 8 | [0x23] = 'd', 9 | [0x24] = 'e', 10 | [0x2b] = 'f', 11 | [0x34] = 'g', 12 | [0x33] = 'h', 13 | [0x43] = 'i', 14 | [0x3b] = 'j', 15 | [0x42] = 'k', 16 | [0x4b] = 'l', 17 | [0x3a] = 'm', 18 | [0x31] = 'n', 19 | [0x44] = 'o', 20 | [0x4d] = 'p', 21 | [0x15] = 'q', 22 | [0x2d] = 'r', 23 | [0x1b] = 's', 24 | [0x2c] = 't', 25 | [0x3c] = 'u', 26 | [0x2a] = 'v', 27 | [0x1d] = 'w', 28 | [0x22] = 'x', 29 | [0x35] = 'y', 30 | [0x1a] = 'z', 31 | [0x45] = '0', 32 | [0x16] = '1', 33 | [0x1e] = '2', 34 | [0x26] = '3', 35 | [0x25] = '4', 36 | [0x2e] = '5', 37 | [0x36] = '6', 38 | [0x3d] = '7', 39 | [0x3e] = '8', 40 | [0x46] = '9' 41 | }; 42 | 43 | // 键盘中断处理函数 44 | void process_kb() { 45 | // 当前按键 46 | unsigned char scancode; 47 | // 前置修饰符 48 | static unsigned char prevcode; 49 | 50 | // 从端口0x60读取键盘控制器扫描码 51 | __asm__ ("inb $0x60, %%al" : "=a"(scancode)); 52 | 53 | // 由于不适用扩展键位,可以使用0xE0进行判断 54 | // 记录前置修饰符 55 | if (scancode == 0xe0 || scancode == 0xf0) { 56 | prevcode = scancode; 57 | return; 58 | } 59 | 60 | // 如果前置修饰符是F0,则表示释放操作 61 | if (prevcode == 0xe0 || prevcode == 0xf0) { 62 | prevcode = 0; 63 | return; 64 | } 65 | 66 | // 如果前置修饰符不是F0,则表示按键操作,打印键值 67 | print(keymap[scancode]); 68 | } 69 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/drivers/vesa.c: -------------------------------------------------------------------------------- 1 | #include "include/vesa.h" 2 | #include "include/string.h" 3 | #include "include/mm.h" 4 | #include "include/sched.h" 5 | 6 | struct vesa_mode_info* vesa_mode_info; 7 | 8 | void vesa_init() { 9 | // 分配内存 10 | vesa_mode_info = malloc(sizeof(struct vesa_mode_info)); 11 | // 由于kvmtool加载在内核0x10000处,实模式中读取的模式信息偏移为0x4000处,故从0x14000处复制模式信息 12 | memcpy(vesa_mode_info, (char*)0x14000, sizeof(struct vesa_mode_info)); 13 | } 14 | 15 | // 为应用传递模式信息 16 | unsigned long do_get_mode_info(struct mode_info *mode_info) { 17 | mode_info->fbbase = vesa_mode_info->fbbase; 18 | mode_info->hres = vesa_mode_info->hres; 19 | mode_info->vres = vesa_mode_info->vres; 20 | mode_info->bpp = vesa_mode_info->bpp; 21 | 22 | return 0; 23 | } 24 | 25 | // 将framebuffer映射到用户空间 26 | unsigned long do_fbmap() { 27 | // 固定一个地址作为framebuffer映射在进程的用户空间的起始地址 28 | unsigned long va = 0xe000000; 29 | // 物理地址 30 | unsigned long pa = vesa_mode_info->fbbase; 31 | // 计算映射长度 32 | int size = vesa_mode_info->hres * vesa_mode_info->vres * vesa_mode_info->bpp / 8; 33 | int npage = 0; 34 | // 进行地址映射,US位设置为1,表示允许运行在特权级为3的用户访问这块内存 35 | map_range(current->pml4, va, pa, 0x4, (size + PAGE_SIZE - 1) / PAGE_SIZE); 36 | 37 | return va; 38 | } 39 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/head64.S: -------------------------------------------------------------------------------- 1 | #include "include/segment.h" 2 | 3 | .text 4 | .code64 5 | .globl gdt 6 | .globl ret_from_kernel 7 | .globl task0_stack 8 | .globl idle_task_entry 9 | 10 | lgdt gdtr 11 | # 将中断描述符表地址写入IDTR寄存器中 12 | lidt idtr 13 | 14 | # 初始化寄存器 15 | mov $KERNEL_DS, %ax 16 | mov %ax, %ds 17 | mov %ax, %ss 18 | mov %ax, %es 19 | mov %ax, %fs 20 | mov %ax, %gs 21 | 22 | # 使用RSP寄存器指向栈底 23 | mov $task0_stack, %rsp 24 | # 跳转到main方法 25 | push $main 26 | ret 27 | 28 | # 所有段的TI为0,内核段的特权级为0,用户段的特权级为3 29 | .align 64 30 | gdt: 31 | # 空描述符(保留不用) 32 | .quad 0x0000000000000000 33 | # 内核代码段描述符 34 | .quad 0x00209a0000000000 35 | # 内核数据段描述符 36 | .quad 0x0000920000000000 37 | # 32位用户代码段描述符 38 | .quad 0x0000000000000000 39 | # 用户数据段描述符 40 | .quad 0x0000f20000000000 41 | # 64位用户代码段描述符 42 | .quad 0x0020fa0000000000 43 | .fill 128, 8, 0 44 | gdt_end: 45 | 46 | gdtr: 47 | .word gdt_end - gdt 48 | .quad gdt 49 | 50 | idtr: 51 | # 中断描述符表长度,256项中断描述符,每个是16字节 52 | .word 16 * 256 53 | # 中断描述符表地址 54 | .quad idt_table 55 | 56 | # 4KB大小的栈空间 57 | .fill 4096, 1, 0 58 | task0_stack: 59 | 60 | # 在执行iret前,初始化其他段寄存器,之后再从栈中弹出断点信息,返回用户空间 61 | ret_from_kernel: 62 | mov $USER_DS, %rax 63 | movw %ax, %ds 64 | movw %ax, %es 65 | movw %ax, %fs 66 | movw %ax, %gs 67 | iretq 68 | 69 | # 空闲进程,当没有就绪任务需要运行时,让处理器运行这个空闲任务 70 | idle_task_entry: 71 | 1: 72 | # 开启处理器响应中断标识 73 | sti 74 | # 让处理器停止运转 75 | hlt 76 | jmp 1b -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/include/interrupt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void init_8254(); 4 | void interrupt_init(); 5 | 6 | void timer_handler(); 7 | void pf_handler(); 8 | void kb_handler(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/include/mm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | #define E820MAX 32 6 | #define E820_RAM 1 7 | 8 | // 定义最大页面数,每个页面大小为4K,一共表示4GB物理内存 9 | #define MAX_PAGES (1024 * 1024) 10 | // 定义内核所需的页面,16K个页面 11 | #define KERNEL_PAGE_NUM (1024 * 16) 12 | 13 | #define PAGE_SIZE 4096 14 | 15 | #define PAGE_OFFSET 0xffff888000000000 16 | #define VA(x) ((void*)((unsigned long)(x) + PAGE_OFFSET)) 17 | #define PA(x) ((unsigned long)(x) - PAGE_OFFSET) 18 | 19 | #define TASK0_PML4 0x30000 20 | 21 | // 内存大小 22 | extern unsigned long mem_size; 23 | 24 | extern uint8_t pages[MAX_PAGES]; 25 | 26 | // 使用packed告知GCC编译器,分配结构体变量时不要进行对齐 27 | struct e820entry { 28 | uint64_t addr; // 内存段的起始地址 29 | uint64_t size; // 内存段的尺寸 30 | uint32_t type; // 内存段的类型 31 | } __attribute__((packed)); 32 | 33 | // 存储E820信息的内存区域 34 | struct e820map { 35 | uint32_t nr_entry; 36 | struct e820entry map[E820MAX]; // 多条E820记录 37 | }; 38 | 39 | // 内存管理初始化 40 | void mm_init(); 41 | // 分配页面 42 | unsigned long alloc_page(); 43 | // 释放页面 44 | void free_page(uint64_t addr); 45 | void* malloc(int size); 46 | void free(void* obj); 47 | // 内存空间映射 48 | void map_range(unsigned long pml4_pa, unsigned long from_va, unsigned long to_pa, char us, long npage); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/include/print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 如果是32位,向串口输出1次,如果是64位,向串口输出2次 4 | #define print(x) \ 5 | do { \ 6 | int size = sizeof(x); \ 7 | if (size <= 4) { \ 8 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 9 | "out %%eax, %%dx\n\t" \ 10 | : \ 11 | : "a"(x) \ 12 | : "dx"); \ 13 | } else if (size == 8) { \ 14 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 15 | "out %%eax, %%dx\n\t" \ 16 | "shr $32, %%rax\n\t" \ 17 | "out %%eax, %%dx\n\t" \ 18 | : \ 19 | : "a"(x) \ 20 | : "dx"); \ 21 | } \ 22 | } while (0) -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/include/sched.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 进程状态 6 | enum task_state { 7 | // 任务正在运行或就绪态 8 | TASK_RUNNING = 0, 9 | // 任务处于可中断的睡眠态 10 | TASK_INTERRUPTIBLE 11 | }; 12 | 13 | // 记录进程的信息和状态 14 | struct task { 15 | unsigned long id; 16 | enum task_state state; // 任务状态 17 | unsigned long rip; // 任务调度切换时,指令的地址 18 | unsigned long rsp0; // 任务调度切换时,内核栈的栈顶 19 | unsigned long kstack; // 内核栈的栈底,用于在内核时,任务状态段中特权级为0的栈指针指向当前任务的内核栈栈底 20 | unsigned long pml4; // 根页表的物理地址,用于更新寄存器CR3指向当前任务的页表 21 | 22 | // 支撑任务链的链表 23 | struct task* next; 24 | struct task* prev; 25 | }; 26 | 27 | struct timer { 28 | unsigned long alarm; // 记录时钟到期时间 29 | struct task* task; 30 | struct timer* next; 31 | struct timer* prev; 32 | }; 33 | 34 | extern unsigned long ret_from_kernel; 35 | extern unsigned long idle_task_entry; 36 | extern unsigned long task0_stack; 37 | extern struct task* current; 38 | 39 | void sched_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/include/segment.h: -------------------------------------------------------------------------------- 1 | // 内核代码段选择子 2 | #define KERNEL_CS 0x8 3 | // 内核数据段选择子 4 | #define KERNEL_DS 0x10 5 | // 32位用户代码段选择子 6 | #define USER32_CS 0x1b 7 | // 用户数据段选择子 8 | #define USER_DS 0x23 9 | // 64位用户代码段选择子 10 | #define USER_CS 0x2b 11 | 12 | // 段描述符表的第6项作为任务状态段的段描述符 13 | #define GDT_TSS_ENTRY 6 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/include/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | void* memset(void *s, char c, unsigned long n); 6 | void memcpy(void *dest, const void *src, unsigned long n); 7 | int strcmp(const char *s1, const char *s2); 8 | int strlen(const char *s); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/include/syscall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void system_call(); 4 | void syscall_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/include/tss.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 可以寻址64K(65536)个端口 6 | #define IO_BITMAP_BYTES (65536 / 8) 7 | 8 | struct tss { 9 | uint32_t reserved1; // 保留 10 | uint64_t rsp0; // 特权级为0的栈指针 11 | uint64_t rsp1; // 特权级为1的栈指针 12 | uint64_t rsp2; // 特权级为2的栈指针 13 | uint64_t reserved2; // 保留 14 | uint64_t ist[7]; // IST的7个专用栈 15 | uint32_t reserved3; // 保留 16 | uint32_t reserved4; // 保留 17 | uint16_t reserved5; // 保留 18 | uint16_t io_bitmap_offset; // 程序I/O权限位图相对于任务状态段基址的16位偏移 19 | uint8_t io_bitmap[IO_BITMAP_BYTES + 1]; // I/O权限位图 20 | } __attribute__((packed)); 21 | 22 | struct tss_desc { 23 | uint16_t limit0; // 段长度 24 | uint16_t base0; // 段基址(0~15) 25 | uint16_t base1 : 8, type : 4, desc_type : 1, dpl : 2, p : 1; 26 | uint16_t limit1 : 4, avl : 1, zero0 : 2, g : 1, base2 : 8; 27 | uint32_t base3; // 段基址(32~63) 28 | uint32_t zero1; // 保留 29 | } __attribute__((packed)); 30 | 31 | extern struct tss tss; 32 | // TSS任务状态段初始化 33 | void tss_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef signed char int8_t; 4 | typedef short int int16_t; 5 | typedef int int32_t; 6 | typedef long int int64_t; 7 | 8 | typedef unsigned char uint8_t; 9 | typedef unsigned short int uint16_t; 10 | typedef unsigned int uint32_t; 11 | typedef unsigned long int uint64_t; 12 | 13 | #define NULL ((void*)0) 14 | 15 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/include/vesa.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | struct vesa_mode_info { 6 | uint8_t pad0[18]; 7 | uint16_t hres; // 以像素为单位的水平分辨率 8 | uint16_t vres; // 以像素为单位的垂直分辨率 9 | 10 | uint8_t pad1[3]; 11 | uint8_t bpp; // 每个像素使用的字节数 12 | uint8_t pad2[14]; 13 | 14 | uint32_t fbbase; // framebuffer占据的地址空间的起始地址 15 | 16 | uint8_t pad3[212]; 17 | } __attribute__ ((packed)); 18 | 19 | // 精简的结构体,用于应用程序获取模式信息 20 | struct mode_info { 21 | uint32_t fbbase; // framebuffer占据的地址空间的起始地址 22 | uint16_t hres; // 以像素为单位的水平分辨率 23 | uint16_t vres; // 以像素为单位的垂直分辨率 24 | uint8_t bpp; // 每个像素使用的字节数 25 | }; 26 | 27 | void vesa_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/ipc/shm.c: -------------------------------------------------------------------------------- 1 | #include "include/string.h" 2 | #include "include/mm.h" 3 | #include "include/sched.h" 4 | 5 | // 共享内存块 6 | struct shm { 7 | char* name; // 共享内存块的名字,用于区分不同的共享内存 8 | unsigned long page; // 指向一个用于共享内存的物理页面 9 | 10 | struct shm* next; // 指向下一个共享内存块,用于链接共享内存 11 | }; 12 | 13 | struct shm* shm_head; 14 | struct shm* shm_tail; 15 | 16 | // 实现共享内存系统调用 17 | // name:应用传递进来的共享内存名字 18 | int do_shm(char* name) { 19 | struct shm* shm = NULL; 20 | // 使用一个固定地址作为共享内存映射的虚拟地址 21 | unsigned long va = 0x4000000; 22 | 23 | // 遍历并寻找名字与应用请求相同的共享内存 24 | for (struct shm* s = shm_head; s; s = s->next) { 25 | if (!strcmp(s->name, name)) { 26 | shm = s; 27 | break; 28 | } 29 | } 30 | 31 | if (!shm) { 32 | // 如果没有找到,则创建一个新的共享内存 33 | shm = malloc(sizeof(struct shm)); 34 | // 申请一个内存块 35 | int len = strlen(name); 36 | shm->name = malloc(len + 1); 37 | memcpy(shm->name, name, len); 38 | shm->name[len] = '\0'; 39 | // 分配一个页面作为共享内存页 40 | shm->page = alloc_page(); 41 | shm->next = NULL; 42 | 43 | // 构建共享内存链表 44 | if (shm_head == NULL) { 45 | shm_head = shm; 46 | shm_tail = shm; 47 | } else { 48 | shm_tail->next = shm; 49 | shm_tail = shm; 50 | } 51 | } 52 | 53 | // 将共享内存映射到应用程序的地址空间 54 | // current->pml4 是当前进程页表的根页面 55 | // va 是固定虚拟地址 0x4000000 56 | // shm->page 是共享的物理页面 57 | // us位设置为1,允许允许在特权级为3的用户程序访问这块内存 58 | // 1表示建立1个页面的映射关系 59 | map_range(current->pml4, va, shm->page, 0x4, 1); 60 | 61 | return va; 62 | } 63 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/kernel/syscall.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/segment.h" 3 | #include "include/syscall.h" 4 | #include "include/sched.h" 5 | #include "include/vesa.h" 6 | 7 | #define MSR_STAR 0xc0000081 8 | #define MSR_LSTAR 0xc0000082 9 | #define MSR_SYSCALL_MASK 0xc0000084 10 | #define RF_IF 0x00000200 11 | 12 | typedef unsigned long (*fn_ptr)(); 13 | 14 | unsigned long do_sleep(long ms); 15 | unsigned long do_shm(const char* name); 16 | unsigned long do_fbmap(); 17 | unsigned long do_get_mode_info(struct mode_info *mode_info); 18 | 19 | fn_ptr syscall_table[] = { do_sleep, do_shm, do_fbmap, do_get_mode_info }; 20 | 21 | void syscall_init() { 22 | // 设置用户代码段和内核代码段 23 | uint64_t star_val = (uint64_t)USER32_CS << 48 | (uint64_t)KERNEL_CS << 32; 24 | uint64_t syscall_entry = (uint64_t)system_call; 25 | // 初始化标志寄存器的第9位(中断使能位) 26 | uint64_t syscall_mask = RF_IF; 27 | 28 | // 将值写入STAR寄存器 29 | // 指令wrmsr从寄存器ECX读取MSR的ID,分别将寄存器EAX和EDX的值写入MSR寄存器的低32位和高32位 30 | // 编译器将各个值预先装载到ECX、EAX、EDX中 31 | __asm__("wrmsr": : "c" (MSR_STAR), "a" ((uint32_t)star_val), "d" (star_val >> 32)); 32 | // 将值写入LSTAR寄存器 33 | __asm__("wrmsr": : "c" (MSR_LSTAR), "a" ((uint32_t)syscall_entry), "d" (syscall_entry >> 32)); 34 | // 将值写入SFMASK寄存器 35 | __asm__("wrmsr": : "c" (MSR_SYSCALL_MASK), "a" ((uint32_t)syscall_mask), "d" (syscall_mask >> 32)); 36 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/kernel/tss.c: -------------------------------------------------------------------------------- 1 | #include "include/tss.h" 2 | #include "include/sched.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | extern unsigned long gdt[64]; 7 | struct tss tss; 8 | 9 | void tss_init() { 10 | // 允许应用程序访问所有I/O端口 11 | memset(&tss, 0, sizeof(tss)); 12 | tss.io_bitmap_offset = __builtin_offsetof(struct tss, io_bitmap); 13 | tss.io_bitmap[IO_BITMAP_BYTES] = ~0; 14 | // 设置任务状态段的RSP0指向当前应用程序的内核栈地址 15 | tss.rsp0 = current->kstack; 16 | 17 | // 得到GDT表的第6项 18 | struct tss_desc* desc = (struct tss_desc*)&gdt[GDT_TSS_ENTRY]; 19 | // 将任务状态段的段描述符清0 20 | memset(desc, 0, sizeof(struct tss_desc)); 21 | // 计算tss结构体的长度的低16位 22 | desc->limit0 = sizeof(tss) & 0xffff; 23 | desc->base0 = (unsigned long)(&tss) & 0xffff; 24 | desc->base1 = ((unsigned long)(&tss) >> 16) & 0xff; 25 | // 段类型为0x9 26 | desc->type = 0x9; 27 | // 存在位为1 28 | desc->p = 1; 29 | // 段长度的第16~19位 30 | desc->limit1 = (sizeof(tss) >> 16) & 0xf; 31 | desc->base2 = ((unsigned long)(&tss) >> 24) & 0xff; 32 | desc->base3 = (unsigned long)(&tss) >> 32; 33 | 34 | // 禁止应用程序对所有端口的访问,会直接终止执行 35 | // memset(tss.io_bitmap, 0xff, IO_BITMAP_BYTES); 36 | 37 | // 装载任务寄存器TR,段索引为6,TI为0,特权级(内核级)为0 38 | __asm__ ("ltr %w0" : : "r"(GDT_TSS_ENTRY << 3)); 39 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/lib/string.c: -------------------------------------------------------------------------------- 1 | // 填充一段内存区 2 | void* memset(void *s, char c, unsigned long n) { 3 | char *tmp = s; 4 | 5 | while (n--) { 6 | *tmp++ = c; 7 | } 8 | 9 | return s; 10 | } 11 | 12 | /* 内存区域复制 13 | * @param dest 目的内存地址 14 | * @param src 源内存地址 15 | * @param n 复制的字节数 16 | */ 17 | void memcpy(void *dest, const void *src, unsigned long n) { 18 | char *tmp = dest; 19 | const char *s = src; 20 | 21 | // 将字节逐个从源内存的内容复制到目的内存 22 | while (n--) { 23 | *tmp++ = *s++; 24 | } 25 | } 26 | 27 | /* 字符串比较 28 | * @param s1 字符串1 29 | * @param s2 字符串2 30 | * @return 如果两个字符串的全部字符都相同,返回0。 31 | * 如果字符串1大于字符串2,返回1,否则返回-1。 32 | */ 33 | int strcmp(const char *s1, const char *s2) { 34 | unsigned char c1, c2; 35 | 36 | while (1) { 37 | c1 = *s1++; 38 | c2 = *s2++; 39 | if (c1 != c2) { 40 | return c1 < c2 ? -1 : 1; 41 | } 42 | if (!c1) { 43 | break; 44 | } 45 | } 46 | 47 | return 0; 48 | } 49 | 50 | /* 统计字符串长度 51 | * @param s 字符串 52 | * @return 返回字符串长度 53 | */ 54 | int strlen(const char *s) { 55 | const char *sc; 56 | 57 | for (sc = s; *sc != '\0'; ++sc); 58 | 59 | return sc - s; 60 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/main.c: -------------------------------------------------------------------------------- 1 | #include "include/mm.h" 2 | #include "include/print.h" 3 | #include "include/sched.h" 4 | #include "include/tss.h" 5 | #include "include/interrupt.h" 6 | #include "include/syscall.h" 7 | #include "include/vesa.h" 8 | 9 | int main() { 10 | // 内存初始化 11 | mm_init(); 12 | // 中断初始化 13 | interrupt_init(); 14 | // 系统调用初始化 15 | syscall_init(); 16 | // 初始化VESA显示驱动 17 | vesa_init(); 18 | 19 | // 设置标志寄存器中的IF位,开启中断 20 | __asm__ ("sti"); 21 | sched_init(); 22 | 23 | // 任务状态段初始化 24 | tss_init(); 25 | 26 | // 将寄存器CR3指向进程1的页表 27 | __asm__ ("mov %0, %%cr3": :"r"(current->pml4)); 28 | 29 | // 使能时钟中断 30 | init_8254(); 31 | 32 | // 设置栈指针指向进程1的内核栈的栈顶,并执行ret_from_kernel 33 | __asm__ ("movq %0, %%rsp\n\t" 34 | "jmp ret_from_kernel\n\t" 35 | : 36 | : "m"(current->rsp0) 37 | ); 38 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c14/mm/page_alloc.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/mm.h" 3 | 4 | // 分配页面 5 | unsigned long alloc_page() { 6 | unsigned long addr = 0; 7 | 8 | // 从内存映像占用的物理页面之后的页面开始 9 | for (long i = KERNEL_PAGE_NUM; i < mem_size / PAGE_SIZE; i++) { 10 | // 直到找到空页面 11 | if (pages[i] == 0) { 12 | // 将新分配的页面标记为已占用 13 | pages[i] = 1; 14 | // 计算页面的物理地址 15 | addr = PAGE_SIZE * i; 16 | break; 17 | } 18 | } 19 | 20 | // 返回新分配页面的物理地址 21 | return addr; 22 | } 23 | 24 | // 释放页面 25 | void free_page(uint64_t addr) { 26 | // 计算归还页面在数组page的索引 27 | uint32_t index = addr / PAGE_SIZE; 28 | 29 | // 标记页面为空闲 30 | pages[index] = 0; 31 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c3/kernel.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/codes/implement-an-os-from-scratch/c3/kernel.bin -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s01/hello.s: -------------------------------------------------------------------------------- 1 | mov $'A', %al -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s07-1/hello.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/codes/implement-an-os-from-scratch/c4/s07-1/hello.elf -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s07-1/hello.s: -------------------------------------------------------------------------------- 1 | .text 2 | .code16 3 | start: 4 | # 向串口输出一个字符A 5 | mov $'A', %al 6 | # 准备out指令的目的操作数 7 | mov $0x3f8, %dx 8 | # 向串口发起写操作 9 | out %al, %dx 10 | # 告诉处理器停止执行 11 | hlt -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s07-3/hello.s: -------------------------------------------------------------------------------- 1 | .text 2 | .code16 3 | start: 4 | mov $0x1010, %bx 5 | mov %bx, %es 6 | 7 | mov $0x3f8, %dx 8 | 9 | # 将0x10100处存储的值0x42,存入AL寄存器中 10 | mov 0x100, %al 11 | out %al, %dx 12 | 13 | # 将0x10200(= 0x10100 + 0x100)处存储的值0x43,存入AL寄存器中 14 | mov %es:0x100, %al 15 | out %al, %dx 16 | 17 | hlt 18 | 19 | .org 0x100 20 | .byte 0x42 21 | .org 0x200 22 | .byte 0x43 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s09-1/hello.s: -------------------------------------------------------------------------------- 1 | # 立即数寻址 2 | .text 3 | .code16 4 | start: 5 | mov $0x41, %al 6 | jmp 1f 7 | mov $0x42, %al 8 | 1: 9 | # 将0x41写到串口 10 | mov $0x3f8, %dx 11 | out %al, %dx 12 | hlt -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s09-2/hello.s: -------------------------------------------------------------------------------- 1 | # 直接寻址 2 | .text 3 | .code16 4 | start: 5 | mov var, %al 6 | mov $0x3f8, %dx 7 | out %al, %dx 8 | hlt 9 | 10 | .org 0x20 11 | var: 12 | .byte 0x41 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s09-3/hello.s: -------------------------------------------------------------------------------- 1 | # ModR/M寻址方式 2 | .text 3 | .code16 4 | start: 5 | mov $0x3f8, %dx 6 | mov $var1, %bx 7 | 8 | # 以寄存器BX的值作为内存地址,将该地址处的内容存入AL寄存器中 9 | mov (%bx), %al 10 | out %al, %dx 11 | 12 | # 将0x60(= 0x50 + 0x10)地址处的值,存入AL寄存器中 13 | mov 0x10(%bx), %al 14 | out %al, %dx 15 | 16 | # 操作数在寄存器中的寻址模式 17 | mov %bl, %al 18 | 19 | hlt 20 | 21 | .org 0x50 22 | var1: 23 | .byte 0x41 24 | 25 | .org 0x60 26 | var2: 27 | .byte 0x42 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s09-4/hello.s: -------------------------------------------------------------------------------- 1 | # SIB寻址 2 | .text 3 | .code16 4 | start: 5 | # EBX寄存器存储数组基址 6 | mov $var, %ebx 7 | # ESI寄存器存储数组的索引 8 | mov $0, %esi 9 | mov $0, %ax 10 | 11 | 1: 12 | # 将数组的元素累加到AX寄存器中 13 | add (%ebx, %esi, 2), %ax 14 | # 指向数组的下一个元素 15 | add $1, %esi 16 | # 循环5个数 17 | cmp $5, %esi 18 | jb 1b 19 | hlt 20 | 21 | var: 22 | .word 1 23 | .word 2 24 | .word 3 25 | .word 4 26 | .word 5 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s09-5/hello.s: -------------------------------------------------------------------------------- 1 | # SIB + disp寻址 2 | .text 3 | .code16 4 | start: 5 | # 使用EBX寄存器存储行基址 6 | mov $0, %ebx 7 | # 使用ESI存储数组的索引 8 | mov $1, %esi 9 | mov $0, %ax 10 | 11 | 1: 12 | # 将数组元素累加到AX寄存器中 13 | add var(%ebx, %esi, 2), %ax 14 | 15 | # 指向数组的下一行基址 16 | add $6, %ebx 17 | cmp $12, %ebx 18 | jbe 1b 19 | hlt 20 | 21 | var: 22 | .word 1 23 | .word 2 24 | .word 3 25 | 26 | .word 1 27 | .word 2 28 | .word 3 29 | 30 | .word 1 31 | .word 2 32 | .word 3 33 | 34 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s11/hello.s: -------------------------------------------------------------------------------- 1 | # 栈 2 | .text 3 | .code16 4 | start: 5 | mov $stack, %sp 6 | mov $0x3f8, %dx 7 | 8 | # 向栈中压入0x41和0x42 9 | push $0x41 10 | push $0x42 11 | 12 | # 打印输出栈的值 13 | mov 0x1000 - 2, %ax 14 | out %ax, %dx 15 | mov 0x1000 - 4, %ax 16 | out %ax, %dx 17 | 18 | # 弹出栈 19 | pop %ax 20 | out %ax, %dx 21 | pop %ax 22 | out %ax, %dx 23 | 24 | hlt 25 | 26 | .ort 0x1000 27 | stack: -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s12/hello.s: -------------------------------------------------------------------------------- 1 | .text 2 | .code16 3 | start: 4 | mov $stack, %sp 5 | # 发起函数调用 6 | call sum 7 | 8 | mov $0x3f8, %dx 9 | mov $0x41, %al 10 | out %al, %dx 11 | hlt 12 | 13 | .org 0x1000 14 | stack: 15 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s12/makefile: -------------------------------------------------------------------------------- 1 | build: 2 | as -o hello.o hello.s 3 | as -o sum.o sum.s 4 | ld -Ttext=0 hello.o sum.o -o hello.elf 5 | objcopy -O binary hello.elf hello.bin 6 | 7 | dump: build 8 | objdump -d -m i8086 hello.elf 9 | 10 | run: build 11 | ~/kvmtool/lkvm run -c l -k hello.bin 12 | 13 | clean: 14 | rm -f *.o *.bin *.elf -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c4/s12/sum.s: -------------------------------------------------------------------------------- 1 | .text 2 | .code16 3 | # 全局可见 4 | .global sum 5 | sum: 6 | push %bp 7 | mov %sp, %bp 8 | 9 | mov $1, %bx 10 | mov $0, %cx 11 | sub $8, %sp 12 | 1: 13 | add %bx, %cx 14 | inc %bx 15 | cmp $10, %bx 16 | jbe 1b 17 | 18 | mov $0x3f8, %dx 19 | mov %cx, %ax 20 | out %ax, %dx 21 | 22 | mov %bp, %sp 23 | pop %bp 24 | ret -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c5/global-var/hello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/codes/implement-an-os-from-scratch/c5/global-var/hello -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c5/global-var/hello.c: -------------------------------------------------------------------------------- 1 | // 全局变量 2 | extern int sum; 3 | 4 | int main() { 5 | sum = 3; 6 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c5/inline-asm/hello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/codes/implement-an-os-from-scratch/c5/inline-asm/hello -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c5/inline-asm/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int x = 2; 5 | int y = 3; 6 | int sum = 0; 7 | 8 | // 内联汇编 9 | asm ( "mov %1, %%eax\n\t" 10 | "mov %[addend], %%ebx\n\t" 11 | "add %%ebx, %%eax\n\t" 12 | "mov %%eax, %0" 13 | : "=m"(sum) 14 | : "m"(x), [addend]"m"(y) 15 | : "eax", "ebx", "r11", "r12" 16 | ); 17 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c5/local-var-static/hello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/codes/implement-an-os-from-scratch/c5/local-var-static/hello -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c5/local-var-static/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void f() { 4 | // 静态局部变量 5 | static int count = 0; 6 | 7 | count++; 8 | printf("%d\n", count); 9 | } 10 | 11 | int main() { 12 | f(); 13 | f(); 14 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c5/local-var/hello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/codes/implement-an-os-from-scratch/c5/local-var/hello -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c5/local-var/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | // 局部变量 5 | char x = 1; 6 | short y = 2; 7 | int z = 3; 8 | long m = 4; 9 | 10 | printf("Hello world.\n"); 11 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c5/s03/hello: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/codes/implement-an-os-from-scratch/c5/s03/hello -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c5/s03/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | int student_number = 10; 6 | printf("student number: %d\n", student_number); 7 | 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c6/boot16.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code16 3 | start16: 4 | # 清除标志寄存器中的中断位 5 | cli 6 | 7 | # 加载段描述符表地址到寄存器GDTR 8 | lgdt gdtr 9 | 10 | # 开启处理器的保护模式,CR0寄存器的第0位PE用于控制处理器是否开启保护模式 11 | mov %cr0, %eax 12 | # 将CR0的最后一位置为1,即开启保护模式 13 | or $0x1, %eax 14 | mov %eax, %cr0 15 | 16 | # 段选择子是0x8(段索引是1,使用全局段描述符表TI是0,特权级是00,即0000000000001000) 17 | # 保护模式的入口地址是0x20000,段基址为0,所以段内偏移地址为0x20000 18 | # 长跳转指令ljmpl [段选择子] [段内偏移地址] 19 | ljmpl $0x8, $0x20000 20 | 21 | gdt: 22 | # 段描述符表的第0项保留不用 23 | .quad 0x0000000000000000 24 | # 第1项定义内核代码段 25 | .quad 0x00c09a00000007ff 26 | # 第2项定义内核数据段 27 | .quad 0x00c09200000007ff 28 | gdt_end: 29 | 30 | gdtr: 31 | # 低16位对应段描述符表的长度 32 | .word gdt_end - gdt 33 | # 高16位对应段描述符表的地址(0x1000<<4 + gdt) 34 | .word gdt, 0x1 35 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c6/boot32.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code32 3 | start32: 4 | # 向串口输出一个字符P 5 | mov $0x3f8, %dx 6 | mov $'P', %al 7 | out %al, %dx 8 | 9 | hlt 10 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c6/build.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | * 将多个文件成为一个kernel.bin文件 8 | */ 9 | int main() { 10 | int fd, fd_kernel; 11 | int c; 12 | char buf[512]; 13 | 14 | // 创建kernel.bin文件并以读写方式打开 15 | fd_kernel = open("kernel.bin", O_WRONLY | O_CREAT, 0664); 16 | 17 | // 将boot16.bin文件的数据写入kernel.bin文件中 18 | fd = open("boot16.bin", O_RDONLY); 19 | while (1) { 20 | c = read(fd, buf, 512); 21 | if (c > 0) { 22 | write(fd_kernel, buf, c); 23 | } else { 24 | break; 25 | } 26 | } 27 | close(fd); 28 | 29 | // 将内核保护模式部分加载到内存0x20000处 30 | lseek(fd_kernel, 0x20000 - 0x10000, SEEK_SET); 31 | 32 | // 将boot32.bin文件的数据写入kernel.bin文件中 33 | fd = open("boot32.bin", O_RDONLY); 34 | while (1) { 35 | c = read(fd, buf, 512); 36 | if (c > 0) { 37 | write(fd_kernel, buf, c); 38 | } else { 39 | break; 40 | } 41 | } 42 | close(fd); 43 | 44 | close(fd_kernel); 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c6/makefile: -------------------------------------------------------------------------------- 1 | kernel.bin: build boot16.bin boot32.bin 2 | ./build 3 | 4 | boot16.bin: boot16.S 5 | gcc -c boot16.S -o boot16.o 6 | # 实模式运行时的地址是由“段基址<<4+段内偏移”生成,kvmtool将段寄存器初始化为0x1000,段内偏移为0 7 | ld -Ttext=0x0 boot16.o -o boot16.elf 8 | objcopy -O binary boot16.elf boot16.bin 9 | 10 | boot32.bin: boot32.S 11 | gcc -c boot32.S -o boot32.o 12 | # 保护模式的代码段基址为0,段内偏移为0x20000 13 | ld -Ttext=0x20000 boot32.o -o boot32.elf 14 | objcopy -O binary boot32.elf boot32.bin 15 | 16 | build: build.c 17 | gcc $< -o $@ 18 | 19 | .PHONY: clean run 20 | 21 | run: kernel.bin 22 | ~/kvmtool/lkvm run -c 1 -k ./kernel.bin 23 | 24 | clean: 25 | rm -f *.bin *.elf *.o build 26 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c7/boot16.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code16 3 | start16: 4 | # 清除标志寄存器中的中断位 5 | cli 6 | 7 | # 加载段描述符表地址到寄存器GDTR 8 | lgdt gdtr 9 | 10 | # 开启处理器的保护模式,CR0寄存器的第0位PE用于控制处理器是否开启保护模式 11 | mov %cr0, %eax 12 | # 将CR0的最后一位置为1,即开启保护模式 13 | or $0x1, %eax 14 | mov %eax, %cr0 15 | 16 | # 段选择子是0x8(段索引是1,使用全局段描述符表TI是0,特权级是00,即0000000000001000) 17 | # 保护模式的入口地址是0x20000,段基址为0,所以段内偏移地址为0x20000 18 | # 长跳转指令ljmpl [段选择子] [段内偏移地址] 19 | ljmpl $0x8, $0x20000 20 | 21 | gdt: 22 | # 段描述符表的第0项保留不用 23 | .quad 0x0000000000000000 24 | # 第1项定义内核代码段 25 | .quad 0x00c09a00000007ff 26 | # 第2项定义内核数据段 27 | .quad 0x00c09200000007ff 28 | gdt_end: 29 | 30 | gdtr: 31 | # 低16位对应段描述符表的长度 32 | .word gdt_end - gdt 33 | # 高16位对应段描述符表的地址(0x1000<<4 + gdt) 34 | .word gdt, 0x1 35 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c7/build.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | * 将多个文件成为一个kernel.bin文件 8 | */ 9 | int main() { 10 | int fd, fd_kernel; 11 | int c; 12 | char buf[512]; 13 | 14 | // 创建kernel.bin文件并以读写方式打开 15 | fd_kernel = open("kernel.bin", O_WRONLY | O_CREAT, 0664); 16 | 17 | // 将boot16.bin文件的数据写入kernel.bin文件中 18 | fd = open("boot16.bin", O_RDONLY); 19 | while (1) { 20 | c = read(fd, buf, 512); 21 | if (c > 0) { 22 | write(fd_kernel, buf, c); 23 | } else { 24 | break; 25 | } 26 | }; 27 | close(fd); 28 | 29 | // 将内核保护模式部分加载到内存0x20000处 30 | lseek(fd_kernel, 0x20000 - 0x10000, SEEK_SET); 31 | 32 | // 将boot32.bin文件的数据写入kernel.bin文件中 33 | fd = open("boot32.bin", O_RDONLY); 34 | while (1) { 35 | c = read(fd, buf, 512); 36 | if (c > 0) { 37 | write(fd_kernel, buf, c); 38 | } else { 39 | break; 40 | } 41 | }; 42 | close(fd); 43 | 44 | // 将内核64位部分加载到内存0x100000处 45 | lseek(fd_kernel, 0x100000 - 0x10000, SEEK_SET); 46 | 47 | // 将system.bin文件的数据写入kernel.bin文件中 48 | fd = open("system.bin", O_RDONLY); 49 | while (1) { 50 | c = read(fd, buf, 512); 51 | if (c > 0) { 52 | write(fd_kernel, buf, c); 53 | } else { 54 | break; 55 | } 56 | }; 57 | close(fd); 58 | 59 | close(fd_kernel); 60 | 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c7/head64.S: -------------------------------------------------------------------------------- 1 | #include "include/segment.h" 2 | 3 | .text 4 | .code64 5 | 6 | lgdt gdtr 7 | 8 | # 初始化寄存器 9 | mov $KERNEL_DS, %ax 10 | mov %ax, %ds 11 | mov %ax, %ss 12 | mov %ax, %es 13 | mov %ax, %fs 14 | mov %ax, %gs 15 | 16 | # 使用RSP寄存器指向栈底 17 | mov $task0_stack, %rsp 18 | # 跳转到main方法 19 | push $main 20 | ret 21 | 22 | # 所有段的TI为0,内核段的特权级为0,用户段的特权级为3 23 | .align 64 24 | gdt: 25 | # 空描述符(保留不用) 26 | .quad 0x0000000000000000 27 | # 内核代码段描述符 28 | .quad 0x00209a0000000000 29 | # 内核数据段描述符 30 | .quad 0x0000920000000000 31 | # 32位用户代码段描述符 32 | .quad 0x0000000000000000 33 | # 用户数据段描述符 34 | .quad 0x0000f20000000000 35 | # 64位用户代码段描述符 36 | .quad 0x0020fa0000000000 37 | .fill 128, 8, 0 38 | gdt_end: 39 | 40 | gdtr: 41 | .word gdt_end - gdt 42 | .quad gdt 43 | 44 | # 4KB大小的栈空间 45 | .fill 4096, 1, 0 46 | task0_stack: 47 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c7/include/segment.h: -------------------------------------------------------------------------------- 1 | // 内核代码段选择子 2 | #define KERNEL_CS 0x8 3 | // 内核数据段选择子 4 | #define KERNEL_DS 0x10 5 | // 32位用户代码段选择子 6 | #define USER32_CS 0x1b 7 | // 用户数据段选择子 8 | #define USER_DS 0x23 9 | // 64位用户代码段选择子 10 | #define USER_CS 0x2b 11 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c7/main.c: -------------------------------------------------------------------------------- 1 | int main() { 2 | // 输出字符M 3 | __asm__ ("mov $0x3f8, %dx\n\t" 4 | "mov $'M', %ax\n\t" 5 | "out %ax, %dx\n" 6 | ); 7 | __asm__ ("hlt"); 8 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c7/makefile: -------------------------------------------------------------------------------- 1 | kernel.bin: build boot16.bin boot32.bin system.bin 2 | ./build 3 | 4 | boot16.bin: boot16.S 5 | gcc -c boot16.S -o boot16.o 6 | # 实模式运行时的地址是由“段基址<<4+段内偏移”生成,kvmtool将段寄存器初始化为0x1000,段内偏移为0 7 | ld -Ttext=0x0 boot16.o -o boot16.elf 8 | objcopy -O binary boot16.elf boot16.bin 9 | 10 | boot32.bin: boot32.S 11 | gcc -c boot32.S -o boot32.o 12 | # 保护模式的代码段基址为0,段内偏移为0x20000 13 | ld -Ttext=0x20000 boot32.o -o boot32.elf 14 | objcopy -O binary boot32.elf boot32.bin 15 | 16 | # 使用CFLAGS给编译器传参 17 | # -I:从哪些目录搜索头文件 18 | # -fon-pic:位置无关代码可以加载在程序地址空间中的任何位置 19 | # -mcmodel=kernel:指示gcc生成使用64位寻址操作数的汇编代码,基于large模型改进,减少指令长度 20 | # -fno-stack-protector:关闭栈溢出检查 21 | # -fcf-protection=none:关闭gcc检查代码的特性 22 | CFLAGS = -std=c11 -I. -fno-pic -mcmodel=kernel -fno-stack-protector -fcf-protection=none 23 | 24 | system.bin: head64.o main.o 25 | # 内核映像的虚拟地址起始于0xffffffff8000000,映射物理内存地址0处,64位部分位于物理内存0x100000处 26 | ld -Ttext=0xffffffff80100000 $^ -o system.elf 27 | objcopy -O binary system.elf $@ 28 | 29 | build: build.c 30 | gcc $< -o $@ 31 | 32 | .PHONY: clean run 33 | 34 | run: kernel.bin 35 | ~/kvmtool/lkvm run -c 1 -k ./kernel.bin 36 | 37 | clean: 38 | rm -f *.bin *.elf *.o build 39 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/boot16.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code16 3 | start16: 4 | # 清除标志寄存器中的中断位 5 | cli 6 | 7 | # BIOS使用一个称为E820的数据结构实例表示内存地址的每一个段 8 | # 中断0x15的处理函数从寄存器DI中读取这个地址 9 | mov $e820_entry, %di 10 | xor %ebx, %ebx 11 | e820_rd_entry: 12 | # 设置EAX寄存器的功能号 13 | mov $0xe820, %eax 14 | # 告知0x15中断处理函数,复制每条E820记录的前20个字节(第1个8字节表示内存段起始地址,第2个8字节表示段的尺寸,剩下4字节表示段的类型) 15 | mov $20, %ecx 16 | # 发起中断,执行BIOS中0x15中断处理函数 17 | int $0x15 18 | 19 | # 记录读取的E820的条数 20 | incb e820_nr_entry 21 | # 指向下一个E820记录的内存地址 22 | add $20, %di 23 | 24 | # 如果全部读取完毕,0x15中断处理函数会将寄存器EBX设置为0 25 | cmp $0, %ebx 26 | jne e820_rd_entry 27 | 28 | # 加载段描述符表地址到寄存器GDTR 29 | lgdt gdtr 30 | 31 | # 开启处理器的保护模式,CR0寄存器的第0位PE用于控制处理器是否开启保护模式 32 | mov %cr0, %eax 33 | # 将CR0的最后一位置为1,即开启保护模式 34 | or $0x1, %eax 35 | mov %eax, %cr0 36 | 37 | # 段选择子是0x8(段索引是1,使用全局段描述符表TI是0,特权级是00,即0000000000001000) 38 | # 保护模式的入口地址是0x20000,段基址为0,所以段内偏移地址为0x20000 39 | # 长跳转指令ljmpl [段选择子] [段内偏移地址] 40 | ljmpl $0x8, $0x20000 41 | 42 | gdt: 43 | # 段描述符表的第0项保留不用 44 | .quad 0x0000000000000000 45 | # 第1项定义内核代码段 46 | .quad 0x00c09a00000007ff 47 | # 第2项定义内核数据段 48 | .quad 0x00c09200000007ff 49 | gdt_end: 50 | 51 | gdtr: 52 | # 低16位对应段描述符表的长度 53 | .word gdt_end - gdt 54 | # 高16位对应段描述符表的地址(0x1000<<4 + gdt) 55 | .word gdt, 0x1 56 | 57 | .org 0x3000 58 | e820_nr_entry: 59 | .long 0 60 | # 存储E820记录的地址 61 | e820_entry: 62 | .fill 1024, 1, 0 63 | 64 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/build.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | * 将多个文件成为一个kernel.bin文件 8 | */ 9 | int main() { 10 | int fd, fd_kernel; 11 | int c; 12 | char buf[512]; 13 | 14 | // 创建kernel.bin文件并以读写方式打开 15 | fd_kernel = open("kernel.bin", O_WRONLY | O_CREAT, 0664); 16 | 17 | // 将boot16.bin文件的数据写入kernel.bin文件中 18 | fd = open("boot16.bin", O_RDONLY); 19 | while (1) { 20 | c = read(fd, buf, 512); 21 | if (c > 0) { 22 | write(fd_kernel, buf, c); 23 | } else { 24 | break; 25 | } 26 | }; 27 | close(fd); 28 | 29 | // 将内核保护模式部分加载到内存0x20000处 30 | lseek(fd_kernel, 0x20000 - 0x10000, SEEK_SET); 31 | 32 | // 将boot32.bin文件的数据写入kernel.bin文件中 33 | fd = open("boot32.bin", O_RDONLY); 34 | while (1) { 35 | c = read(fd, buf, 512); 36 | if (c > 0) { 37 | write(fd_kernel, buf, c); 38 | } else { 39 | break; 40 | } 41 | }; 42 | close(fd); 43 | 44 | // 将内核64位部分加载到内存0x100000处 45 | lseek(fd_kernel, 0x100000 - 0x10000, SEEK_SET); 46 | 47 | // 将system.bin文件的数据写入kernel.bin文件中 48 | fd = open("system.bin", O_RDONLY); 49 | while (1) { 50 | c = read(fd, buf, 512); 51 | if (c > 0) { 52 | write(fd_kernel, buf, c); 53 | } else { 54 | break; 55 | } 56 | }; 57 | close(fd); 58 | 59 | close(fd_kernel); 60 | 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/head64.S: -------------------------------------------------------------------------------- 1 | #include "include/segment.h" 2 | 3 | .text 4 | .code64 5 | 6 | lgdt gdtr 7 | 8 | # 初始化寄存器 9 | mov $KERNEL_DS, %ax 10 | mov %ax, %ds 11 | mov %ax, %ss 12 | mov %ax, %es 13 | mov %ax, %fs 14 | mov %ax, %gs 15 | 16 | # 使用RSP寄存器指向栈底 17 | mov $task0_stack, %rsp 18 | # 跳转到main方法 19 | push $main 20 | ret 21 | 22 | # 所有段的TI为0,内核段的特权级为0,用户段的特权级为3 23 | .align 64 24 | gdt: 25 | # 空描述符(保留不用) 26 | .quad 0x0000000000000000 27 | # 内核代码段描述符 28 | .quad 0x00209a0000000000 29 | # 内核数据段描述符 30 | .quad 0x0000920000000000 31 | # 32位用户代码段描述符 32 | .quad 0x0000000000000000 33 | # 用户数据段描述符 34 | .quad 0x0000f20000000000 35 | # 64位用户代码段描述符 36 | .quad 0x0020fa0000000000 37 | .fill 128, 8, 0 38 | gdt_end: 39 | 40 | gdtr: 41 | .word gdt_end - gdt 42 | .quad gdt 43 | 44 | # 4KB大小的栈空间 45 | .fill 4096, 1, 0 46 | task0_stack: 47 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/include/mm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | #define E820MAX 32 6 | #define E820_RAM 1 7 | 8 | // 定义最大页面数,每个页面大小为4K,一共表示4GB物理内存 9 | #define MAX_PAGES (1024 * 1024) 10 | // 定义内核所需的页面,16K个页面 11 | #define KERNEL_PAGE_NUM (1024 * 16) 12 | 13 | #define PAGE_SIZE 4096 14 | 15 | #define PAGE_OFFSET 0xffff888000000000 16 | #define VA(x) ((void*)((unsigned long)(x) + PAGE_OFFSET)) 17 | #define PA(x) ((unsigned long)(x) - PAGE_OFFSET) 18 | 19 | #define TASK0_PML4 0x30000 20 | 21 | // 内存大小 22 | extern unsigned long mem_size; 23 | 24 | extern uint8_t pages[MAX_PAGES]; 25 | 26 | // 使用packed告知GCC编译器,分配结构体变量时不要进行对齐 27 | struct e820entry { 28 | uint64_t addr; // 内存段的起始地址 29 | uint64_t size; // 内存段的尺寸 30 | uint32_t type; // 内存段的类型 31 | } __attribute__((packed)); 32 | 33 | // 存储E820信息的内存区域 34 | struct e820map { 35 | uint32_t nr_entry; 36 | struct e820entry map[E820MAX]; // 多条E820记录 37 | }; 38 | 39 | // 内存管理初始化 40 | void mm_init(); 41 | // 分配页面 42 | unsigned long alloc_page(); 43 | // 释放页面 44 | void free_page(uint64_t addr); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/include/print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 如果是32位,向串口输出1次,如果是64位,向串口输出2次 4 | #define print(x) \ 5 | do { \ 6 | int size = sizeof(x); \ 7 | if (size <= 4) { \ 8 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 9 | "out %%eax, %%dx\n\t" \ 10 | : \ 11 | : "a"(x) \ 12 | : "dx"); \ 13 | } else if (size == 8) { \ 14 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 15 | "out %%eax, %%dx\n\t" \ 16 | "shr $32, %%rax\n\t" \ 17 | "out %%eax, %%dx\n\t" \ 18 | : \ 19 | : "a"(x) \ 20 | : "dx"); \ 21 | } \ 22 | } while (0) -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/include/segment.h: -------------------------------------------------------------------------------- 1 | // 内核代码段选择子 2 | #define KERNEL_CS 0x8 3 | // 内核数据段选择子 4 | #define KERNEL_DS 0x10 5 | // 32位用户代码段选择子 6 | #define USER32_CS 0x1b 7 | // 用户数据段选择子 8 | #define USER_DS 0x23 9 | // 64位用户代码段选择子 10 | #define USER_CS 0x2b 11 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/include/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | void* memset(void *s, char c, unsigned long n); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef signed char int8_t; 4 | typedef short int int16_t; 5 | typedef int int32_t; 6 | typedef long int int64_t; 7 | 8 | typedef unsigned char uint8_t; 9 | typedef unsigned short int uint16_t; 10 | typedef unsigned int uint32_t; 11 | typedef unsigned long int uint64_t; 12 | 13 | #define NULL ((void*)0) 14 | 15 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/lib/string.c: -------------------------------------------------------------------------------- 1 | // 填充一段内存区 2 | void* memset(void *s, char c, unsigned long n) { 3 | char *tmp = s; 4 | 5 | while (n--) { 6 | *tmp++ = c; 7 | } 8 | 9 | return s; 10 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/main.c: -------------------------------------------------------------------------------- 1 | #include "include/mm.h" 2 | #include "include/print.h" 3 | 4 | int main() { 5 | mm_init(); 6 | 7 | unsigned long *x = VA(200 * 1024 * 1024); 8 | 9 | *x = 5; 10 | print(*x); 11 | 12 | __asm__("hlt"); 13 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/makefile: -------------------------------------------------------------------------------- 1 | kernel.bin: build boot16.bin boot32.bin system.bin 2 | ./build 3 | 4 | boot16.bin: boot16.S 5 | gcc -c boot16.S -o boot16.o 6 | # 实模式运行时的地址是由“段基址<<4+段内偏移”生成,kvmtool将段寄存器初始化为0x1000,段内偏移为0 7 | ld -Ttext=0x0 boot16.o -o boot16.elf 8 | objcopy -O binary boot16.elf boot16.bin 9 | 10 | boot32.bin: boot32.S 11 | gcc -c boot32.S -o boot32.o 12 | # 保护模式的代码段基址为0,段内偏移为0x20000 13 | ld -Ttext=0x20000 boot32.o -o boot32.elf 14 | objcopy -O binary boot32.elf boot32.bin 15 | 16 | # 使用CFLAGS给编译器传参 17 | # -I:从哪些目录搜索头文件 18 | # -fon-pic:位置无关代码可以加载在程序地址空间中的任何位置 19 | # -mcmodel=kernel:指示gcc生成使用64位寻址操作数的汇编代码,基于large模型改进,减少指令长度 20 | # -fno-stack-protector:关闭栈溢出检查 21 | # -fcf-protection=none:关闭gcc检查代码的特性 22 | # -nostdinc:不要搜索宿主系统的系统目录下的头文件 23 | # -fno-builtin:不需要使用内置的memset 24 | CFLAGS = -std=c11 -I. -fno-pic -mcmodel=kernel -fno-stack-protector -fcf-protection=none -nostdinc -fno-builtin 25 | 26 | SRCS = main.c $(wildcard mm/*.c) $(wildcard lib/*.c) 27 | # 将SRCS中的每一个.c替换成.o 28 | OBJS = $(SRCS:.c=.o) 29 | 30 | system.bin: head64.o $(OBJS) 31 | # 内核映像的虚拟地址起始于0xffffffff8000000,映射物理内存地址0处,64位部分位于物理内存0x100000处 32 | ld -Ttext=0xffffffff80100000 $^ -o system.elf 33 | objcopy -O binary system.elf $@ 34 | 35 | # 将依赖关系保存到.depend文件中 36 | .depend: $(SRCS) 37 | @rm -f .depend 38 | @$(foreach src,$(SRCS), \ 39 | echo -n $(dir $(src)) >> .depend; \ 40 | gcc -I. -MM $(src) >> .depend; \ 41 | ) 42 | include .depend 43 | 44 | build: build.c 45 | gcc $< -o $@ 46 | 47 | .PHONY: clean run 48 | 49 | run: kernel.bin 50 | ~/kvmtool/lkvm run -c 1 -k ./kernel.bin 51 | 52 | clean: 53 | find -name "*.o" -o -name "*.elf" -o -name "*.bin" | xargs rm -f 54 | rm -f build .depend 55 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c8/mm/page_alloc.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/mm.h" 3 | 4 | // 分配页面 5 | unsigned long alloc_page() { 6 | unsigned long addr = 0; 7 | 8 | // 从内存映像占用的物理页面之后的页面开始 9 | for (long i = KERNEL_PAGE_NUM; i < mem_size / PAGE_SIZE; i++) { 10 | // 直到找到空页面 11 | if (pages[i] == 0) { 12 | // 将新分配的页面标记为已占用 13 | pages[i] = 1; 14 | // 计算页面的物理地址 15 | addr = PAGE_SIZE * i; 16 | break; 17 | } 18 | } 19 | 20 | // 返回新分配页面的物理地址 21 | return addr; 22 | } 23 | 24 | // 释放页面 25 | void free_page(uint64_t addr) { 26 | // 计算归还页面在数组page的索引 27 | uint32_t index = addr / PAGE_SIZE; 28 | 29 | // 标记页面为空闲 30 | pages[index] = 0; 31 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/app/app1.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code64 3 | # 无限循环,每次循环向串口输出一个字符 4 | 1: 5 | mov $0x3f8, %dx 6 | mov $'A', %ax 7 | out %ax, %dx 8 | jmp 1b -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/boot16.S: -------------------------------------------------------------------------------- 1 | .text 2 | .code16 3 | start16: 4 | # 清除标志寄存器中的中断位 5 | cli 6 | 7 | # BIOS使用一个称为E820的数据结构实例表示内存地址的每一个段 8 | # 中断0x15的处理函数从寄存器DI中读取这个地址 9 | mov $e820_entry, %di 10 | xor %ebx, %ebx 11 | e820_rd_entry: 12 | # 设置EAX寄存器的功能号 13 | mov $0xe820, %eax 14 | # 告知0x15中断处理函数,复制每条E820记录的前20个字节(第1个8字节表示内存段起始地址,第2个8字节表示段的尺寸,剩下4字节表示段的类型) 15 | mov $20, %ecx 16 | # 发起中断,执行BIOS中0x15中断处理函数 17 | int $0x15 18 | 19 | # 记录读取的E820的条数 20 | incb e820_nr_entry 21 | # 指向下一个E820记录的内存地址 22 | add $20, %di 23 | 24 | # 如果全部读取完毕,0x15中断处理函数会将寄存器EBX设置为0 25 | cmp $0, %ebx 26 | jne e820_rd_entry 27 | 28 | # 加载段描述符表地址到寄存器GDTR 29 | lgdt gdtr 30 | 31 | # 开启处理器的保护模式,CR0寄存器的第0位PE用于控制处理器是否开启保护模式 32 | mov %cr0, %eax 33 | # 将CR0的最后一位置为1,即开启保护模式 34 | or $0x1, %eax 35 | mov %eax, %cr0 36 | 37 | # 段选择子是0x8(段索引是1,使用全局段描述符表TI是0,特权级是00,即0000000000001000) 38 | # 保护模式的入口地址是0x20000,段基址为0,所以段内偏移地址为0x20000 39 | # 长跳转指令ljmpl [段选择子] [段内偏移地址] 40 | ljmpl $0x8, $0x20000 41 | 42 | gdt: 43 | # 段描述符表的第0项保留不用 44 | .quad 0x0000000000000000 45 | # 第1项定义内核代码段 46 | .quad 0x00c09a00000007ff 47 | # 第2项定义内核数据段 48 | .quad 0x00c09200000007ff 49 | gdt_end: 50 | 51 | gdtr: 52 | # 低16位对应段描述符表的长度 53 | .word gdt_end - gdt 54 | # 高16位对应段描述符表的地址(0x1000<<4 + gdt) 55 | .word gdt, 0x1 56 | 57 | .org 0x3000 58 | e820_nr_entry: 59 | .long 0 60 | e820_entry: 61 | .fill 1024, 1, 0 62 | 63 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/build.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | * 将多个文件成为一个kernel.bin文件 8 | */ 9 | int main() { 10 | int fd, fd_kernel; 11 | int c; 12 | char buf[512]; 13 | 14 | // 创建kernel.bin文件并以读写方式打开 15 | fd_kernel = open("kernel.bin", O_WRONLY | O_CREAT, 0664); 16 | 17 | // 将boot16.bin文件的数据写入kernel.bin文件中 18 | fd = open("boot16.bin", O_RDONLY); 19 | while (1) { 20 | c = read(fd, buf, 512); 21 | if (c > 0) { 22 | write(fd_kernel, buf, c); 23 | } else { 24 | break; 25 | } 26 | }; 27 | close(fd); 28 | 29 | // 将内核保护模式部分加载到内存0x20000处 30 | lseek(fd_kernel, 0x20000 - 0x10000, SEEK_SET); 31 | 32 | // 将boot32.bin文件的数据写入kernel.bin文件中 33 | fd = open("boot32.bin", O_RDONLY); 34 | while (1) { 35 | c = read(fd, buf, 512); 36 | if (c > 0) { 37 | write(fd_kernel, buf, c); 38 | } else { 39 | break; 40 | } 41 | }; 42 | close(fd); 43 | 44 | // 将内核64位部分加载到内存0x100000处 45 | lseek(fd_kernel, 0x100000 - 0x10000, SEEK_SET); 46 | 47 | // 将system.bin文件的数据写入kernel.bin文件中 48 | fd = open("system.bin", O_RDONLY); 49 | while (1) { 50 | c = read(fd, buf, 512); 51 | if (c > 0) { 52 | write(fd_kernel, buf, c); 53 | } else { 54 | break; 55 | } 56 | }; 57 | close(fd); 58 | 59 | // 将app1程序映像加载到内存0xc800000处 60 | lseek(fd_kernel, 0xc800000 - 0x10000, SEEK_SET); 61 | fd = open("app/app1.bin", O_RDONLY); 62 | while (1) { 63 | c = read(fd, buf, 512); 64 | if (c > 0) { 65 | write(fd_kernel, buf, c); 66 | } else { 67 | break; 68 | } 69 | }; 70 | close(fd); 71 | 72 | close(fd_kernel); 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/head64.S: -------------------------------------------------------------------------------- 1 | #include "include/segment.h" 2 | 3 | .text 4 | .code64 5 | .globl gdt 6 | .globl ret_from_kernel 7 | 8 | lgdt gdtr 9 | 10 | # 初始化寄存器 11 | mov $KERNEL_DS, %ax 12 | mov %ax, %ds 13 | mov %ax, %ss 14 | mov %ax, %es 15 | mov %ax, %fs 16 | mov %ax, %gs 17 | 18 | # 使用RSP寄存器指向栈底 19 | mov $task0_stack, %rsp 20 | # 跳转到main方法 21 | push $main 22 | ret 23 | 24 | # 所有段的TI为0,内核段的特权级为0,用户段的特权级为3 25 | .align 64 26 | gdt: 27 | # 空描述符(保留不用) 28 | .quad 0x0000000000000000 29 | # 内核代码段描述符 30 | .quad 0x00209a0000000000 31 | # 内核数据段描述符 32 | .quad 0x0000920000000000 33 | # 32位用户代码段描述符 34 | .quad 0x0000000000000000 35 | # 用户数据段描述符 36 | .quad 0x0000f20000000000 37 | # 64位用户代码段描述符 38 | .quad 0x0020fa0000000000 39 | .fill 128, 8, 0 40 | gdt_end: 41 | 42 | gdtr: 43 | .word gdt_end - gdt 44 | .quad gdt 45 | 46 | # 4KB大小的栈空间 47 | .fill 4096, 1, 0 48 | task0_stack: 49 | 50 | # 在执行iret前,初始化其他段寄存器,之后再从栈中弹出断点信息,返回用户空间 51 | ret_from_kernel: 52 | mov $USER_DS, %rax 53 | movw %ax, %ds 54 | movw %ax, %es 55 | movw %ax, %fs 56 | movw %ax, %gs 57 | iretq -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/include/mm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | #define E820MAX 32 6 | #define E820_RAM 1 7 | 8 | // 定义最大页面数,每个页面大小为4K,一共表示4GB物理内存 9 | #define MAX_PAGES (1024 * 1024) 10 | // 定义内核所需的页面,16K个页面 11 | #define KERNEL_PAGE_NUM (1024 * 16) 12 | 13 | #define PAGE_SIZE 4096 14 | 15 | #define PAGE_OFFSET 0xffff888000000000 16 | #define VA(x) ((void*)((unsigned long)(x) + PAGE_OFFSET)) 17 | #define PA(x) ((unsigned long)(x) - PAGE_OFFSET) 18 | 19 | #define TASK0_PML4 0x30000 20 | 21 | // 内存大小 22 | extern unsigned long mem_size; 23 | 24 | extern uint8_t pages[MAX_PAGES]; 25 | 26 | // 使用packed告知GCC编译器,分配结构体变量时不要进行对齐 27 | struct e820entry { 28 | uint64_t addr; // 内存段的起始地址 29 | uint64_t size; // 内存段的尺寸 30 | uint32_t type; // 内存段的类型 31 | } __attribute__((packed)); 32 | 33 | // 存储E820信息的内存区域 34 | struct e820map { 35 | uint32_t nr_entry; 36 | struct e820entry map[E820MAX]; // 多条E820记录 37 | }; 38 | 39 | // 内存管理初始化 40 | void mm_init(); 41 | // 分配页面 42 | unsigned long alloc_page(); 43 | // 释放页面 44 | void free_page(uint64_t addr); 45 | void* malloc(int size); 46 | void free(void* obj); 47 | // 内存空间映射 48 | void map_range(unsigned long pml4_pa, unsigned long from_va, unsigned long to_pa, char us, long npage); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/include/print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 如果是32位,向串口输出1次,如果是64位,向串口输出2次 4 | #define print(x) \ 5 | do { \ 6 | int size = sizeof(x); \ 7 | if (size <= 4) { \ 8 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 9 | "out %%eax, %%dx\n\t" \ 10 | : \ 11 | : "a"(x) \ 12 | : "dx"); \ 13 | } else if (size == 8) { \ 14 | __asm__ ("mov $0x3f8, %%dx\n\t" \ 15 | "out %%eax, %%dx\n\t" \ 16 | "shr $32, %%rax\n\t" \ 17 | "out %%eax, %%dx\n\t" \ 18 | : \ 19 | : "a"(x) \ 20 | : "dx"); \ 21 | } \ 22 | } while (0) -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/include/sched.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 记录进程的信息和状态 6 | struct task { 7 | unsigned long id; 8 | unsigned long rip; // 任务调度切换时,指令的地址 9 | unsigned long rsp0; // 任务调度切换时,内核栈的栈顶 10 | unsigned long kstack; // 内核栈的栈底,用于在内核时,任务状态段中特权级为0的栈指针指向当前任务的内核栈栈底 11 | unsigned long pml4; // 根页表的物理地址,用于更新寄存器CR3指向当前任务的页表 12 | 13 | // 支撑任务链的链表 14 | struct task* next; 15 | struct task* prev; 16 | }; 17 | 18 | extern struct task* current; 19 | void sched_init(); 20 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/include/segment.h: -------------------------------------------------------------------------------- 1 | // 内核代码段选择子 2 | #define KERNEL_CS 0x8 3 | // 内核数据段选择子 4 | #define KERNEL_DS 0x10 5 | // 32位用户代码段选择子 6 | #define USER32_CS 0x1b 7 | // 用户数据段选择子 8 | #define USER_DS 0x23 9 | // 64位用户代码段选择子 10 | #define USER_CS 0x2b 11 | 12 | // 段描述符表的第6项作为任务状态段的段描述符 13 | #define GDT_TSS_ENTRY 6 -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/include/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | void* memset(void *s, char c, unsigned long n); 6 | void memcpy(void *dest, const void *src, unsigned long n); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/include/tss.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/types.h" 4 | 5 | // 可以寻址64K(65536)个端口 6 | #define IO_BITMAP_BYTES (65536 / 8) 7 | 8 | struct tss { 9 | uint32_t reserved1; // 保留 10 | uint64_t rsp0; // 特权级为0的栈指针 11 | uint64_t rsp1; // 特权级为1的栈指针 12 | uint64_t rsp2; // 特权级为2的栈指针 13 | uint64_t reserved2; // 保留 14 | uint64_t ist[7]; // IST的7个专用栈 15 | uint32_t reserved3; // 保留 16 | uint32_t reserved4; // 保留 17 | uint16_t reserved5; // 保留 18 | uint16_t io_bitmap_offset; // 程序I/O权限位图相对于任务状态段基址的16位偏移 19 | uint8_t io_bitmap[IO_BITMAP_BYTES + 1]; // I/O权限位图 20 | } __attribute__((packed)); 21 | 22 | struct tss_desc { 23 | uint16_t limit0; // 段长度 24 | uint16_t base0; // 段基址(0~15) 25 | uint16_t base1 : 8, type : 4, desc_type : 1, dpl : 2, p : 1; 26 | uint16_t limit1 : 4, avl : 1, zero0 : 2, g : 1, base2 : 8; 27 | uint32_t base3; // 段基址(32~63) 28 | uint32_t zero1; // 保留 29 | } __attribute__((packed)); 30 | 31 | extern struct tss tss; 32 | // TSS任务状态段初始化 33 | void tss_init(); -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/include/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef signed char int8_t; 4 | typedef short int int16_t; 5 | typedef int int32_t; 6 | typedef long int int64_t; 7 | 8 | typedef unsigned char uint8_t; 9 | typedef unsigned short int uint16_t; 10 | typedef unsigned int uint32_t; 11 | typedef unsigned long int uint64_t; 12 | 13 | #define NULL ((void*)0) 14 | 15 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/kernel/tss.c: -------------------------------------------------------------------------------- 1 | #include "include/tss.h" 2 | #include "include/sched.h" 3 | #include "include/string.h" 4 | #include "include/segment.h" 5 | 6 | extern unsigned long gdt[64]; 7 | struct tss tss; 8 | 9 | void tss_init() { 10 | // 允许应用程序访问所有I/O端口 11 | memset(&tss, 0, sizeof(tss)); 12 | tss.io_bitmap_offset = __builtin_offsetof(struct tss, io_bitmap); 13 | tss.io_bitmap[IO_BITMAP_BYTES] = ~0; 14 | // 设置任务状态段的RSP0指向当前应用程序的内核栈地址 15 | tss.rsp0 = current->kstack; 16 | 17 | // 得到GDT表的第6项 18 | struct tss_desc* desc = (struct tss_desc*)&gdt[GDT_TSS_ENTRY]; 19 | // 将任务状态段的段描述符清0 20 | memset(desc, 0, sizeof(struct tss_desc)); 21 | // 计算tss结构体的长度的低16位 22 | desc->limit0 = sizeof(tss) & 0xffff; 23 | desc->base0 = (unsigned long)(&tss) & 0xffff; 24 | desc->base1 = ((unsigned long)(&tss) >> 16) & 0xff; 25 | // 段类型为0x9 26 | desc->type = 0x9; 27 | // 存在位为1 28 | desc->p = 1; 29 | // 段长度的第16~19位 30 | desc->limit1 = (sizeof(tss) >> 16) & 0xf; 31 | desc->base2 = ((unsigned long)(&tss) >> 24) & 0xff; 32 | desc->base3 = (unsigned long)(&tss) >> 32; 33 | 34 | // 禁止应用程序对所有端口的访问,会直接终止执行 35 | // memset(tss.io_bitmap, 0xff, IO_BITMAP_BYTES); 36 | 37 | // 装载任务寄存器TR,段索引为6,TI为0,特权级(内核级)为0 38 | __asm__ ("ltr %w0" : : "r"(GDT_TSS_ENTRY << 3)); 39 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/lib/string.c: -------------------------------------------------------------------------------- 1 | // 填充一段内存区 2 | void* memset(void *s, char c, unsigned long n) { 3 | char *tmp = s; 4 | 5 | while (n--) { 6 | *tmp++ = c; 7 | } 8 | 9 | return s; 10 | } 11 | 12 | /* 内存区域复制 13 | * @param dest 目的内存地址 14 | * @param src 源内存地址 15 | * @param n 复制的字节数 16 | */ 17 | void memcpy(void *dest, const void *src, unsigned long n) { 18 | char *tmp = dest; 19 | const char *s = src; 20 | 21 | // 将字节逐个从源内存的内容复制到目的内存 22 | while (n--) { 23 | *tmp++ = *s++; 24 | } 25 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/main.c: -------------------------------------------------------------------------------- 1 | #include "include/mm.h" 2 | #include "include/print.h" 3 | #include "include/sched.h" 4 | #include "include/tss.h" 5 | 6 | int main() { 7 | mm_init(); 8 | 9 | sched_init(); 10 | tss_init(); 11 | 12 | // 将寄存器CR3指向进程1的页表 13 | __asm__ ("mov %0, %%cr3": :"r"(current->pml4)); 14 | 15 | // 设置栈指针指向进程1的内核栈的栈顶,并执行ret_from_kernel 16 | __asm__ ("movq %0, %%rsp\n\t" 17 | "jmp ret_from_kernel\n\t" 18 | : 19 | : "m"(current->rsp0) 20 | ); 21 | } -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/makefile: -------------------------------------------------------------------------------- 1 | kernel.bin: build boot16.bin boot32.bin system.bin app/app1.bin 2 | ./build 3 | 4 | boot16.bin: boot16.S 5 | gcc -c boot16.S -o boot16.o 6 | # 实模式运行时的地址是由“段基址<<4+段内偏移”生成,kvmtool将段寄存器初始化为0x1000,段内偏移为0 7 | ld -Ttext=0x0 boot16.o -o boot16.elf 8 | objcopy -O binary boot16.elf boot16.bin 9 | 10 | boot32.bin: boot32.S 11 | gcc -c boot32.S -o boot32.o 12 | # 保护模式的代码段基址为0,段内偏移为0x20000 13 | ld -Ttext=0x20000 boot32.o -o boot32.elf 14 | objcopy -O binary boot32.elf boot32.bin 15 | 16 | # 使用CFLAGS给编译器传参 17 | # -I:从哪些目录搜索头文件 18 | # -fon-pic:位置无关代码可以加载在程序地址空间中的任何位置 19 | # -mcmodel=kernel:指示gcc生成使用64位寻址操作数的汇编代码,基于large模型改进,减少指令长度 20 | # -fno-stack-protector:关闭栈溢出检查 21 | # -fcf-protection=none:关闭gcc检查代码的特性 22 | # -nostdinc:不要搜索宿主系统的系统目录下的头文件 23 | # -fno-builtin:不需要使用内置的memset 24 | CFLAGS = -std=c11 -I. -fno-pic -mcmodel=kernel -fno-stack-protector -fcf-protection=none -nostdinc -fno-builtin 25 | 26 | SRCS = main.c $(wildcard mm/*.c) $(wildcard lib/*.c) $(wildcard kernel/*.c) 27 | # 将SRCS中的每一个.c替换成.o 28 | OBJS = $(SRCS:.c=.o) 29 | 30 | system.bin: head64.o $(OBJS) 31 | # 内核映像的虚拟地址起始于0xffffffff8000000,映射物理内存地址0处,64位部分位于物理内存0x100000处 32 | ld -Ttext=0xffffffff80100000 $^ -o system.elf 33 | objcopy -O binary system.elf $@ 34 | 35 | # 将依赖关系保存到.depend文件中 36 | .depend: $(SRCS) 37 | @rm -f .depend 38 | @$(foreach src,$(SRCS), \ 39 | echo -n $(dir $(src)) >> .depend; \ 40 | gcc -I. -MM $(src) >> .depend; \ 41 | ) 42 | include .depend 43 | 44 | app/app1.bin: app/app1.S 45 | gcc -c app/app1.S -o app/app1.o 46 | ld -Ttext=0x100000 app/app1.o -o app/app1.elf 47 | objcopy -O binary app/app1.elf app/app1.bin 48 | 49 | build: build.c 50 | gcc $< -o $@ 51 | 52 | .PHONY: clean run 53 | 54 | run: kernel.bin 55 | ~/kvmtool/lkvm run -c 1 -k ./kernel.bin 56 | 57 | clean: 58 | find -name "*.o" -o -name "*.elf" -o -name "*.bin" | xargs rm -f 59 | rm -f build .depend 60 | -------------------------------------------------------------------------------- /codes/implement-an-os-from-scratch/c9/mm/page_alloc.c: -------------------------------------------------------------------------------- 1 | #include "include/types.h" 2 | #include "include/mm.h" 3 | 4 | // 分配页面 5 | unsigned long alloc_page() { 6 | unsigned long addr = 0; 7 | 8 | // 从内存映像占用的物理页面之后的页面开始 9 | for (long i = KERNEL_PAGE_NUM; i < mem_size / PAGE_SIZE; i++) { 10 | // 直到找到空页面 11 | if (pages[i] == 0) { 12 | // 将新分配的页面标记为已占用 13 | pages[i] = 1; 14 | // 计算页面的物理地址 15 | addr = PAGE_SIZE * i; 16 | break; 17 | } 18 | } 19 | 20 | // 返回新分配页面的物理地址 21 | return addr; 22 | } 23 | 24 | // 释放页面 25 | void free_page(uint64_t addr) { 26 | // 计算归还页面在数组page的索引 27 | uint32_t index = addr / PAGE_SIZE; 28 | 29 | // 标记页面为空闲 30 | pages[index] = 0; 31 | } -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Linux操作系统学习笔记 {docsify-ignore-all} 2 | 3 | 本项目主要是对Linux操作系统的学习,包括源码解读和动手实现,主要阅读以下书籍: 4 | - 《Linux源码趣读》(推荐学时:19天):本书主要通过小说的讲解方式,从内核启动、系统初始化、进程创建、shell程序执行和命令执行等方面,讲解内核源码的相关知识。最喜欢本书的知识回顾,每一回中遇到了其他的知识点,都能简要回顾执行过程,并且给下一部分做了概览性的介绍,从全局角度上了解执行的全貌。 5 | - 《穿越操作系统迷雾》(推荐学时:21天):本书从计算机的软硬件开始讲起,从电路设计到机器语言、汇编语言、C语言的知识介绍,之后开始从0到1实现操作系统,并详细介绍引导区、内存管理、进程、中断和异常、进程调度、系统调用、进程间通信、图形输出等内容。 6 | 7 | ## 在线阅读地址 8 | 9 | 在线阅读地址:https://relph1119.github.io/linux-learning-notes/#/ 10 | 11 | ## 项目结构 12 | 13 |
14 | codes--------------------------------------书中的实现代码
15 | +---implement-an-os-from-scratch---------------《穿越操作系统迷雾》的操作系统实现代码
16 | docs---------------------------------------学习笔记
17 | +---linux-source-code-reading------------------Linux源码趣读
18 | +---implement-an-os-from-scratch---------------穿越操作系统迷雾
19 | references---------------------------------参考资料
20 | 
-------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/ch11.md: -------------------------------------------------------------------------------- 1 | # 第11章 进程调度 2 | 3 | ## 1 任务状态 4 | 5 | 新增进程任务状态: 6 | - `TASK_RUNNING`:任务正在运行或就绪态 7 | - `TASK_INTERRUPTIBLE`:任务处于可中断的睡眠态 8 | 9 | ## 2 创建进程 10 | 11 | - 应用程序app2的主要功能:循环向串口输出字符`B`。 12 | - 加载地址:将应用程序app2加载到内核映像文件中偏移`0xd000000`\~`0x10000`处。 13 | 14 | ## 3 空闲任务 15 | 16 | - 空闲任务(idle):当没有就绪任务需要运行时,让处理器运行这个任务。 17 | - 初始化空闲任务: 18 | 19 | ```c 20 | // 创建空闲任务 21 | static void make_idle_task() { 22 | idle_task = malloc(sizeof(struct task)); 23 | idle_task->id = 0; 24 | idle_task->state = TASK_RUNNING; 25 | idle_task->pml4 = TASK0_PML4; 26 | idle_task->kstack = (unsigned long)&task0_stack; 27 | idle_task->rsp0 = (unsigned long)&task0_stack; 28 | idle_task->rip = (unsigned long)&idle_task_entry; 29 | } 30 | ``` 31 | 32 | ## 4 任务调度 33 | 34 | 代码详见`codes/implement-an-os-from-scratch/c11/kernel/sched.c`的`do_timer`方法。 35 | 36 | - 任务调度算法:简单的时间片轮转算法,以时钟两次中断的间隔作为一个时间片,每个时钟中断到来时,当前任务的时间片耗尽,内核将停止运行当前任务,挑选下一个就绪状态的任务执行,如果没有就绪任务,就执行空闲任务。 37 | - 特别注意:在调用`do_timer`之前,将中断处理完成的命令EOI发送给中断芯片。 38 | 39 | ## 5 任务切换 40 | 41 | - 目标:当发生任务切换时,为了可以恢复运行时被切换走的任务,需要保存任务的上下文,然后将即将投入运行的任务的上下文装载到处理器中。 42 | - 当前任务内核态上下文的保存: 43 | - 页表:无需保存,任务中已经记录了根页表的物理地址(`pml4`) 44 | - 内核栈顶:将寄存器RSP的值保存到当前任务的`rsp0` 45 | - 指令地址:将切换指令后的语句地址保存到当前任务的`rip` 46 | - 恢复即将投入运行任务的内核态的上下文: 47 | - 页表:将任务的根页表物理地址保存到寄存器CR3中 48 | - 内核栈顶:将下一个任务的`rsp0`保存到处理器的栈指针寄存器RSP 49 | - 指令地址:将下一个任务的`rip`压入栈顶,通过`ret`指令弹出到寄存器RIP 50 | 51 | ## 6 运行结果 52 | 53 | 运行`make run > linux_msg.log 2>&1`命令,查看`linux_msg.log`,可观察到循环打印应用程序App1中的字符`F`,之后循环打印应用程序App2中的字符`B`。 54 | 55 | ![第11章运行结果1](images/ch11-01.png) 56 | 57 | ![第11章运行结果2](images/ch11-02.png) 58 | 59 | -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/ch13.md: -------------------------------------------------------------------------------- 1 | # 第13章 进程间通信 2 | 3 | ## 1 共享内存原理 4 | 5 | - 定义:共享内存是一种常用的进程间通信方式,不同进程通过访问同一块内存区域实现数据共享和交互。 6 | - 目的:通过共享内存,避免数据的复制。 7 | 8 | 当一个应用程序申请访问某个共享内存时,具体步骤: 9 | 1. 应用程序通过内核提供的系统调用,请求内核创建或者返回已有共享内存的虚拟地址,并将共享内存的名字传给内核。 10 | 2. 内核将共享内存名作为关键字,查找共享内存链表,如果共享内存尚未创建,则创建一个`shm`实例,并链接到共享内存链表中。 11 | 3. 如果是新创建的实例,还要通过内存页管理分配一个空闲物理页面作为共享内存页,记录到`shm`的`page`字段。 12 | 4. 内核在进程的地址空间中的用户空间部分,分配一个页面大小的虚拟内存区域,用于映射共享内存页。 13 | 5. 内核在页表中建立虚拟内存区域到共享内存页的映射关系。 14 | 6. 内核将虚拟地址返回给应用程序,之后,应用程序可以直接访问共享内存。 15 | 16 | ## 2 实现内核共享内存 17 | 18 | 详见代码`codes/implement-an-os-from-scratch/c13/ipc/shm.c` 19 | 20 | ## 3 运行结果 21 | 22 | 运行项目`codes/implement-an-os-from-scratch/c13`,执行`make run > linux_msg.log 2>&1`命令,查看`linux_msg.log`,可观察到每隔1秒循环打印应用程序App1中的字符`A`,再间隔1秒打印应用程序App2从App1中读出共享内存的字符`S`。 23 | 24 | ![第13章运行结果](images/ch13.png) -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/content.md: -------------------------------------------------------------------------------- 1 | # 《穿越操作系统迷雾》学习笔记 2 | 3 | ## 本书介绍 4 | 5 | 本书从计算机的软硬件开始讲起,从电路设计到机器语言、汇编语言、C语言的知识介绍,之后开始从0到1实现操作系统,并详细介绍引导区、内存管理、进程、中断和异常、进程调度、系统调用、进程间通信、图形输出等内容。 6 | 7 | ## 阅读体会 8 | 9 | 本书的前5章主要讲解计算机软硬件基础和原理,对于已经学过408的我,这一块的知识点还是很熟悉的,尤其是学习过电子信息课程。 10 | 11 | 对于本书的第6章开始就是重头戏了,真正从0到1实现操作系统,在阅读过程中,王柏生老师给了我很大的帮助,尤其是在代码解释和系统调试方面,再次感谢王老师。 12 | 13 | - 第6章:主要介绍了实模式和保护模式、内核映像的设计,开始真正从实模式(16位)到保护模式(32位)的跳转,完成第一次操作系统内核的跳跃。 14 | - 第7章:主要介绍了64位的引导设计,完成内存映像的虚拟地址到物理地址的映射,建立从一级到四级的页表映射,完整构建页表管理,并从保护模式跳转到内核64位模式。 15 | - 第8章:主要讲解了内存管理,设计E820的结构体,并实现`print`打印函数,完成页面管理,实现页面划分、分配页面和归还页面的功能,并进行两阶段的物理内存映射,对更细粒度的内存块进行管理。 16 | - 第9章:主要介绍了进程任务的实现,设计任务的结构体,建立进程地址映射,创建进程任务状态段及内核栈,并通过伪造中断现场,进入用户空间。 17 | - 第10章:主要讲解了中断的主要机制,初始化8259A,实现中断描述符表,设置中断寄存器,并实现时钟中断,并解决缺页异常的情况。 18 | - 第11章:主要介绍了进程调度,当没有就绪任务需要运行时,让处理器运行空闲任务,通过模拟应用程序App1和App2,实现任务切换功能,主要保存当前任务内核态的上下文和恢复即将投入运行任务的内核态的上下文。 19 | - 第12章:主要讲解了系统调用的工作机制,并实现了第一个系统调用`do_sleep`,控制应用程序每隔1秒打印一次。 20 | - 第13章:主要介绍了使用共享内存方式完成进程间通信。 21 | - 第14章:主要讲解了显示及键盘输入,根据图形处理器的原理,基于`framebuffer`存储区域实现文本和图形的展示;实现键盘中断处理程序,完成键盘输入功能。 22 | 23 | ## 本书勘误 24 | 25 | 勘误地址:https://book.douban.com/subject/36560814/discussion/637560822/ -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch06.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch07.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch09.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch10-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch10-01.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch10-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch10-02.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch11-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch11-01.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch11-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch11-02.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch12-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch12-01.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch12-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch12-02.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch13.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch14-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch14-01.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch14-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch14-02.png -------------------------------------------------------------------------------- /docs/implement-an-os-from-scratch/images/ch14-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/implement-an-os-from-scratch/images/ch14-03.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/ch01.md: -------------------------------------------------------------------------------- 1 | # 第1回 Linux开机初始化 2 | 3 | - 开机后初始化指向BIOS:开机后,CPU初始化PC寄存器为`0xFFFF0`,然后按照PC寄存器的值到内存中对应的地址寻找这条指令执行。 4 | - 读取硬盘启动区(第一扇区):硬盘第一扇区的512字节的最后两个字节分别是`0x55`和`0xaa`,将512字节的二进制数据从硬盘搬运到内存。该数据是`boot/bootsect.s`的二进制文件,存放在启动区的第一个扇区。 5 | - 加载到内存`0x7c00`位置,并跳转到这里:如下代码表示把`0x07c0`复制到ax寄存器,再复制到ds寄存器。为了CPU在16位实模式下能访问20位地址线,左移4位得到`0x7c00`。 6 | 7 | ```nasm 8 | BOOTSEG = 0x07c0 9 | start: 10 | mov ax,#BOOTSEG 11 | mov ds,ax 12 | ``` 13 | 14 | ![第一扇区的内存图](images/ch01-memory-after-1st-sector-loaded.png) -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/ch02.md: -------------------------------------------------------------------------------- 1 | # 第2回 从0x7c00到0x90000 2 | 3 | ## 2.1 寄存器初始化 4 | 5 | 代码路径:`boot/bootsect.s` 6 | 7 | ```nasm 8 | BOOTSEG = 0x07c0 ; original address of boot-sector 9 | INITSEG = 0x9000 ; we move boot here - out of the way 10 | 11 | start: 12 | mov ax,#BOOTSEG 13 | mov ds,ax ; ds=0x07c0 14 | mov ax,#INITSEG ; ax=0x9000 15 | mov es,ax ; es=0x9000 16 | mov cx,#256 ; cx=256 17 | sub si,si ; si=0 18 | sub di,di ; di=0 19 | ``` 20 | 21 | 执行上述代码后,各寄存器的值为: 22 | - ds数据段:`0x07c0` 23 | - es附加段:`0x9000` 24 | - cx寄存器:256 25 | - si原变址:0 26 | - di目的变址:0 27 | 28 | ## 2.2 从0x7c00到0x90000 29 | 30 | ```nasm 31 | start: 32 | rep movw 33 | ``` 34 | 35 | `rep`表示重复执行后面的指令,`movw`表示复制一个字(2个字节),重复执行的次数为256次(cx寄存器的值),从`ds:si`处复制到`es:di`处(即从`0x7c00`到`0x90000`),一共复制2*256=512个字节。 36 | 37 | 该操作表示将内存地址`0x7c00`处开始往后的512个字节的数据,复制到`0x90000`处开始的后面512字节的地方。 38 | 39 | ## 2.3 跳转到0x9000:go处执行 40 | 41 | ```nasm 42 | start: 43 | jmpi go,INITSEG ; cs=0x9000 44 | ``` 45 | 46 | ![从0x7c00到0x90000内存图](images/ch02-from-0x7c00-to-0x90000.png) -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/ch03.md: -------------------------------------------------------------------------------- 1 | # 第3回 做好访问内存的基础准备工作 2 | 3 | ## 3.1 寄存器的作用 4 | 5 | - cs寄存器:表示代码段寄存器,CPU即将要执行的代码在内存中的位置,就是由`cs:ip`配合指向的。 6 | - ds寄存器:表示数据段寄存器,作为访问内存数据时的基地址。 7 | - es寄存器:表示扩展段寄存器。 8 | - ss寄存器:表示栈段寄存器,配合栈指针寄存器sp表示此时的栈顶地址,由`ss:sp`配合指向的。 9 | 10 | ## 3.2 CPU访问内存的三种途径 11 | 12 | - `cs:ip`访问代码 13 | - `ds:xxx`访问数据 14 | - `ss:sp`访问栈 15 | 16 | ## 3.3 寄存器初始化 17 | 18 | 代码路径:`boot/bootsect.s` 19 | 20 | ```nasm 21 | go: 22 | mov ax,cs ; ax=0x9000 23 | mov ds,ax ; ds=0x9000 24 | mov es,ax ; es=0x9000 25 | ; put stack at 0x9ff00. 26 | mov ss,ax ; ss=0x9000 27 | mov sp,#0xFF00 ; arbitrary value >>512 28 | ``` 29 | 30 | ## 3.4 内容总结 31 | 32 | 1. 代码从硬盘移动到内存,在内存中从`0x7c00`复制到`0x90000`处。 33 | 2. 数据段寄存器ds和代码段寄存器es设置为`0x9000`。 34 | 3. 栈顶地址设置为`0x9FF00`,其中栈段寄存器ss为`0x9000`,栈基址寄存器sp为`0xFF00`。 -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/ch04.md: -------------------------------------------------------------------------------- 1 | # 第4回 把全部的操作系统代码从硬盘搬到内存 2 | 3 | 代码路径:`boot/bootsect.s` 4 | 5 | ## 4.1 把剩下的操作系统代码从硬盘搬到内存 6 | 7 | ```nasm 8 | SETUPLEN = 4 ; nr of setup-sectors 9 | 10 | load_setup: 11 | mov dx,#0x0000 ; drive 0, head 0 12 | mov cx,#0x0002 ; sector 2, track 0 13 | mov bx,#0x0200 ; address = 512, in INITSEG 14 | mov ax,#0x0200+SETUPLEN ; service 2, nr of sectors 15 | int 0x13 ; read it 16 | ``` 17 | 18 | 上述代码主要作用:从硬盘的第2个扇区开始,把数据加载到内存的`0x90200`处,共加载4个扇区。 19 | 20 | ```nasm 21 | load_setup: 22 | jnc ok_load_setup ; ok - continue 23 | mov dx,#0x0000 24 | mov ax,#0x0000 ; reset the diskette 25 | int 0x13 26 | j load_setup 27 | ``` 28 | 29 | 上述代码主要作用:如果成功,就跳转到`ok_load_setup`标签的代码,如果失败,就重复执行本段代码。 30 | 31 | ```nasm 32 | SYSSEG = 0x1000 ; system loaded at 0x10000 (65536). 33 | SETUPSEG = 0x9020 ; setup starts here 34 | 35 | ok_load_setup: 36 | ... 37 | mov ax,#SYSSEG 38 | mov es,ax ; segment of 0x010000 39 | call read_it 40 | ... 41 | jmpi 0,SETUPSEG 42 | ``` 43 | 44 | 上述代码主要作用:把从硬盘第6个扇区开始往后的240个扇区,加载到内存`0x10000`处,再跳转到`0x90200`处(硬盘第2个扇区开始的位置)。 45 | 46 | ## 4.2 操作系统的编译过程 47 | 48 | 操作系统的编译过程是通过`Makefile`和`build.c`完成,过程如下: 49 | 1. 把`bootsect.s`编译成`bootsect`放在硬盘的第1扇区。 50 | 2. 把`setup.s`编译成`setup`放在硬盘的第2\~5扇区。 51 | 3. 把剩下的全部代码(`head.s`作为开头,与各种`.c`和其他`.s`等文件一起)编译并链接成`system`放在硬盘随后的240个扇区中。 52 | 53 | ![操作系统编译及内存图](images/ch04-memory-after-os-compiled.png) -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/ch06.md: -------------------------------------------------------------------------------- 1 | # 第6回 解决段寄存器的历史包袱问题 2 | 3 | 代码路径:`boot/setup.s` 4 | 5 | ## 6.1 保护模式下的物理地址计算方式 6 | 7 | - 实模式下的物理地址转换:段寄存器+偏移地址`[ds:x]`,将段寄存器左移4位,再加上偏移地址。 8 | - 保护模式下的物理地址转换:段寄存器(比如ds、ss、cs)里面存储的是段选择子,段选择子去全局描述符中寻找段描述符,从中取出段基址,再加上偏移地址。 9 | 10 | ## 6.2 全局描述符 11 | 12 | ```nasm 13 | lidt idt_48 ; load idt with 0,0 14 | lgdt gdt_48 ; load gdt with whatever appropriate 15 | 16 | gdt_48: 17 | .word 0x800 ; gdt limit=2048, 256 GDT entries 18 | .word 512+gdt,0x9 ; gdt base = 0X9xxxx 19 | ``` 20 | 21 | 上述代码解析: 22 | - `lgdt`指令表示将后面的操作数放到gdtr寄存器中。 23 | - `gdt_48`返回的是两个2字的数据: 24 | - 0\~15位:`0x800`,表示GDT界限是256个 25 | - 16\~47位:`0x90200+gdt`,表示全局描述符的内存起始地址。 26 | 27 | ```nasm 28 | gdt: 29 | .word 0,0,0,0 ; dummy 30 | 31 | .word 0x07FF ; 8Mb - limit=2047 (2048*4096=8Mb) 32 | .word 0x0000 ; base address=0 33 | .word 0x9A00 ; code read/exec 34 | .word 0x00C0 ; granularity=4096, 386 35 | 36 | .word 0x07FF ; 8Mb - limit=2047 (2048*4096=8Mb) 37 | .word 0x0000 ; base address=0 38 | .word 0x9200 ; data read/write 39 | .word 0x00C0 ; granularity=4096, 386 40 | ``` 41 | 42 | 上述代码是对GDT初始化,GDT的前三个段描述符分别为: 43 | 1. 空 44 | 2. 代码段描述符(type=code):段基址为0 45 | 3. 数据段描述符(type=data):段基址为0 46 | 47 | ![加载IDT和GDT之后的内存图](images/ch06-memory-after-setup-idt-and-gdt.png) -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/ch07.md: -------------------------------------------------------------------------------- 1 | # 第7回 六行代码进入保护模式 2 | 3 | 代码路径:`boot/setup.s` 4 | 5 | ## 7.1 开启A20地址线 6 | 7 | ```nasm 8 | call empty_8042 9 | mov al,#0xD1 ; command write 10 | out #0x64,al 11 | call empty_8042 12 | mov al,#0xDF ; A20 on 13 | out #0x60,al 14 | call empty_8042 15 | ``` 16 | 17 | 上述代码表示打开A20地址线,目的是为了突破地址信号线20位宽度,变成32位可用。 18 | 19 | ## 7.2 对可编程中断控制器8259A芯片进行编程 20 | 21 | - 编程原因:由于中断号是不能冲突的,Intel把`0~0x19`中断都作为保留中断,但是IBM原始计算机中发生了冲突。 22 | - 重新编程结果: 23 | 24 | | PIC请求号 | 中断号 | 用途 | 25 | |:------:|:----:|:------:| 26 | | IRQ0 | 0x20 | 时钟中断 | 27 | | IRQ1 | 0x21 | 键盘中断 | 28 | | IRQ2 | 0x22 | 连接从芯片 | 29 | | IRQ3 | 0x23 | 串口2 | 30 | | IRQ4 | 0x24 | 串口1 | 31 | | IRQ5 | 0x25 | 并口2 | 32 | | IRQ6 | 0x26 | 软盘驱动器 | 33 | | IRQ7 | 0x27 | 并口1 | 34 | | IRQ8 | 0x28 | 实时钟中断 | 35 | | IRQ9 | 0x29 | 保留 | 36 | | IRQ10 | 0x2a | 保留 | 37 | | IRQ11 | 0x2b | 保留 | 38 | | IRQ12 | 0x2c | 鼠标中断 | 39 | | IRQ13 | 0x2d | 数字协处理器 | 40 | | IRQ14 | 0x2e | 硬盘中断 | 41 | | IRQ15 | 0x2f | 保留 | 42 | 43 | ## 7.3 模式切换 44 | 45 | ```nasm 46 | mov ax,#0x0001 ; protected mode (PE) bit 47 | lmsw ax ; This is it; 48 | jmpi 0,8 ; jmp offset 0 of segment 8 (cs) 49 | ``` 50 | 51 | 上述代码解释: 52 | - 将cr0寄存器的第0位设置为1,从实模式切换到保护模式。 53 | - 当切换到保护模式,内存寻址方式改变,段寄存器里的值当作段选择子,描述符索引为1 54 | 55 | | 描述符索引
第15~3位 | TI
第2位 | RPL
第1~0位 | 56 | |:----------------:|:----------:|:-------------:| 57 | | 0x01 | 0 | 00 | 58 | 59 | - 到全局描述符表中查找索引为1的描述符,得到代码段描述符,可得段基址为0,偏移是0。 60 | - `jmpi`指令将跳转到零地址位置。 -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/ch10.md: -------------------------------------------------------------------------------- 1 | # 第10回 进入main函数前的最后一跃 2 | 3 | ## 10.1 跳转main函数 4 | 5 | 代码路径:`boot/head.s` 6 | 7 | ```nasm 8 | after_page_tables: 9 | pushl $0 # These are the parameters to main :-) 10 | pushl $0 11 | pushl $0 12 | pushl $L6 # return address for main, if it decides to. 13 | pushl $_main 14 | jmp setup_paging 15 | L6: 16 | jmp L6 # main should never return here, but 17 | # just in case, we know what happens. 18 | ``` 19 | 20 | 当遇到`ret`指令时,CPU将栈顶的值当作返回地址并跳转执行,把ESP寄存器(栈顶地址)所指向的内存处的值赋给EIP寄存器,所以CS:EIP就是CPU要执行的下一条指令的地址。 21 | 22 | ## 10.2 跳转main之前的工作 23 | 24 | ![跳转main之前的流程图](images/ch10-processes-before-main.png) 25 | 26 | ## 10.3 当前内存布局图 27 | 28 | ![跳转main之前的内存图](images/ch10-memory-before-main.png) -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/images/ch01-memory-after-1st-sector-loaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part01/images/ch01-memory-after-1st-sector-loaded.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/images/ch02-from-0x7c00-to-0x90000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part01/images/ch02-from-0x7c00-to-0x90000.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/images/ch04-memory-after-os-compiled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part01/images/ch04-memory-after-os-compiled.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/images/ch05-current-memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part01/images/ch05-current-memory.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/images/ch06-memory-after-setup-idt-and-gdt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part01/images/ch06-memory-after-setup-idt-and-gdt.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/images/ch08-copy-idt-and-gdt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part01/images/ch08-copy-idt-and-gdt.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/images/ch09-pagination.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part01/images/ch09-pagination.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/images/ch09-setup-paging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part01/images/ch09-setup-paging.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/images/ch10-memory-before-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part01/images/ch10-memory-before-main.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part01/images/ch10-processes-before-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part01/images/ch10-processes-before-main.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part02/ch11.md: -------------------------------------------------------------------------------- 1 | # 第11回 整个操作系统就二十几行 2 | 3 | 代码路径:`init/main.c` 4 | 5 | ## 11.1 参数的取值和计算 6 | 7 | ```c 8 | void main(void) /* This really IS void, no error here. */ 9 | { /* The startup routine assumes (well, ...) this */ 10 | /* 11 | * Interrupts are still disabled. Do necessary setups, then 12 | * enable them 13 | */ 14 | ROOT_DEV = ORIG_ROOT_DEV; 15 | drive_info = DRIVE_INFO; 16 | memory_end = (1<<20) + (EXT_MEM_K<<10); 17 | memory_end &= 0xfffff000; 18 | if (memory_end > 16*1024*1024) 19 | memory_end = 16*1024*1024; 20 | if (memory_end > 12*1024*1024) 21 | buffer_memory_end = 4*1024*1024; 22 | else if (memory_end > 6*1024*1024) 23 | buffer_memory_end = 2*1024*1024; 24 | else 25 | buffer_memory_end = 1*1024*1024; 26 | main_memory_start = buffer_memory_end; 27 | #ifdef RAMDISK 28 | main_memory_start += rd_init(main_memory_start, RAMDISK*1024); 29 | #endif 30 | ... 31 | ``` 32 | 33 | 包括根设备`ROOT_DEV`,之前在`setup.s`中获取的各个设备的参数信息`drive_info`,以及通过计算得到的表示内存边界的值。 34 | 35 | ## 11.2 进行各种初始化的一系列`init`函数 36 | 37 | ```c 38 | void main(void) 39 | { 40 | ... 41 | mem_init(main_memory_start,memory_end); 42 | trap_init(); 43 | blk_dev_init(); 44 | chr_dev_init(); 45 | tty_init(); 46 | time_init(); 47 | sched_init(); 48 | buffer_init(buffer_memory_end); 49 | hd_init(); 50 | floppy_init(); 51 | ... 52 | } 53 | ``` 54 | 55 | 包括内存初始化`mem_init`、中断初始化`trap_init`、进程调度初始化`sched_init`等。 56 | 57 | ## 11.3 切换到用户态模式,在一个新的进程中做一个最终的初始化 58 | 59 | ```c 60 | void main(void) 61 | { 62 | ... 63 | sti(); 64 | move_to_user_mode(); 65 | if (!fork()) { /* we count on this going ok */ 66 | init(); 67 | } 68 | ... 69 | } 70 | ``` 71 | 72 | - `fork`函数启动的进程是进程1。 73 | - `init`函数会设置终端的标准IO,创建出一个执行shell程序的进程,用于接收用户的命令,该进程是进程2。 74 | 75 | ## 11.4 死循环 76 | 77 | ```c 78 | void main(void) 79 | { 80 | ... 81 | for(;;) pause(); 82 | } 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part02/ch12.md: -------------------------------------------------------------------------------- 1 | # 第12回 管理内存前先划分出三个边界值 2 | 3 | 代码路径:`init/main.c` 4 | 5 | ```c 6 | #define EXT_MEM_K (*(unsigned short *)0x90002) 7 | #define DRIVE_INFO (*(struct drive_info *)0x90080) 8 | #define ORIG_ROOT_DEV (*(unsigned short *)0x901FC) 9 | 10 | void main(void) /* This really IS void, no error here. */ 11 | { /* The startup routine assumes (well, ...) this */ 12 | /* 13 | * Interrupts are still disabled. Do necessary setups, then 14 | * enable them 15 | */ 16 | ROOT_DEV = ORIG_ROOT_DEV; 17 | drive_info = DRIVE_INFO; 18 | memory_end = (1<<20) + (EXT_MEM_K<<10); 19 | memory_end &= 0xfffff000; 20 | if (memory_end > 16*1024*1024) 21 | memory_end = 16*1024*1024; 22 | if (memory_end > 12*1024*1024) 23 | buffer_memory_end = 4*1024*1024; 24 | else if (memory_end > 6*1024*1024) 25 | buffer_memory_end = 2*1024*1024; 26 | else 27 | buffer_memory_end = 1*1024*1024; 28 | main_memory_start = buffer_memory_end; 29 | #ifdef RAMDISK 30 | main_memory_start += rd_init(main_memory_start, RAMDISK*1024); 31 | #endif 32 | ... 33 | } 34 | ``` 35 | 36 | - 如果内存大于等于16MB,缓冲区为4MB。 37 | - 如果内存大于6MB,缓冲区为2MB。 38 | - 如果内存小于等于6MB,缓冲区为1MB。 39 | 40 | 该段代码确定了主内存的起始地址`main_memory_start`、内存的结束地址`memory_end`、缓冲区的结束地址`buffer_memory_end`。 41 | 42 | ![内存的划分](images/ch12-calc-memory.png) -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part02/ch13.md: -------------------------------------------------------------------------------- 1 | # 第13回 主内存初始化`mem_init` 2 | 3 | 代码路径:`mm/memory.c` 4 | 5 | ```c 6 | #define LOW_MEM 0x100000 7 | #define PAGING_MEMORY (15*1024*1024) 8 | #define PAGING_PAGES (PAGING_MEMORY>>12) 9 | #define USED 100 10 | 11 | static long HIGH_MEMORY = 0; 12 | static unsigned char mem_map [ PAGING_PAGES ] = {0,}; 13 | 14 | // start_mem = 2 * 1024 * 1024 15 | // end_mem = 8 * 1024 * 1024 16 | void mem_init(long start_mem, long end_mem) 17 | { 18 | int i; 19 | 20 | HIGH_MEMORY = end_mem; 21 | for (i=0 ; i>= 12; 26 | while (end_mem-->0) 27 | mem_map[i++]=0; 28 | } 29 | ``` 30 | 31 | 代码详细解释: 32 | - 主要功能:给`mem_map`数组的各个元素赋值,先全部赋值为`USED`,再对其中一部分赋值为0。 33 | - 分页管理:`mem_map`中的每个元素都代表一个4KB的内存是否空闲,该内存表示一页。 34 | - 内存分配: 35 | - 1MB以下的内存无需管理,受到保护,无权申请和释放,该区域是内核代码区。 36 | - 1MB\~2MB是缓冲区,直接标记`USED`,无法被分配。 37 | - 2MB以上空间是主内存,可以申请和释放。 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part02/ch14.md: -------------------------------------------------------------------------------- 1 | # 第14回 中断初始化`trap_init` 2 | 3 | ## 14.1 代码解读 4 | 5 | 代码路径:`kernel/traps.c` 6 | 7 | ```c 8 | void trap_init(void) 9 | { 10 | int i; 11 | 12 | // 设置trap_gate 13 | set_trap_gate(0,÷_error); 14 | set_trap_gate(1,&debug); 15 | set_trap_gate(2,&nmi); 16 | // 设置system_gate 17 | set_system_gate(3,&int3); /* int3-5 can be called from all */ 18 | set_system_gate(4,&overflow); 19 | set_system_gate(5,&bounds); 20 | set_trap_gate(6,&invalid_op); 21 | set_trap_gate(7,&device_not_available); 22 | set_trap_gate(8,&double_fault); 23 | set_trap_gate(9,&coprocessor_segment_overrun); 24 | set_trap_gate(10,&invalid_TSS); 25 | set_trap_gate(11,&segment_not_present); 26 | set_trap_gate(12,&stack_segment); 27 | set_trap_gate(13,&general_protection); 28 | set_trap_gate(14,&page_fault); 29 | set_trap_gate(15,&reserved); 30 | set_trap_gate(16,&coprocessor_error); 31 | // 设置17~48中断号 32 | for (i=17;i<48;i++) 33 | set_trap_gate(i,&reserved); 34 | set_trap_gate(45,&irq13); 35 | outb_p(inb_p(0x21)&0xfb,0x21); 36 | outb(inb_p(0xA1)&0xdf,0xA1); 37 | set_trap_gate(39,¶llel_interrupt); 38 | } 39 | ``` 40 | 41 | 上述代码主要功能: 42 | - 在中断描述符表中初始化一个个中断描述符。 43 | - 当CPU执行一条指令时,从硬件层面发起一个中断,然后执行由操作系统定义的异常处理函数。 44 | - 第17~48号中断被暂时设置为`reserved`中断处理程序。 45 | 46 | ![中断初始化之后的内存](images/ch14-mem-after-trap-init.png) 47 | 48 | ## 14.2 键盘响应 49 | 50 | ```c 51 | // init/main.c 52 | void main(void) { 53 | ... 54 | trap_init(); 55 | ... 56 | tty_init(); 57 | ... 58 | } 59 | 60 | // kernel/chr_drv/tty_io.c 61 | void tty_init(void) { 62 | rs_init(); 63 | con_init(); 64 | } 65 | 66 | // kernel/chr_drv/console.c 67 | void con_init(void) { 68 | ... 69 | set_trap_gate(0x21,&keyboard_interrupt); 70 | ... 71 | } 72 | ``` 73 | 74 | 上述代码添加了键盘中断处理程序的21号中断到中断描述符表中。 -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part02/ch15.md: -------------------------------------------------------------------------------- 1 | # 第15回 块设备请求项初始化`blk_dev_init` 2 | 3 | ## 15.1 代码解读 4 | 5 | 代码路径:`kernel/blk_drv/ll_rw_blk.c` 6 | 7 | ```c 8 | #define NR_REQUEST 32 9 | 10 | void blk_dev_init(void) { 11 | int i; 12 | 13 | for (i=0 ; i>4)*10) 33 | 34 | static void time_init(void) 35 | { 36 | struct tm time; 37 | 38 | do { 39 | time.tm_sec = CMOS_READ(0); 40 | time.tm_min = CMOS_READ(2); 41 | time.tm_hour = CMOS_READ(4); 42 | time.tm_mday = CMOS_READ(7); 43 | time.tm_mon = CMOS_READ(8); 44 | time.tm_year = CMOS_READ(9); 45 | } while (time.tm_sec != CMOS_READ(0)); 46 | BCD_TO_BIN(time.tm_sec); 47 | BCD_TO_BIN(time.tm_min); 48 | BCD_TO_BIN(time.tm_hour); 49 | BCD_TO_BIN(time.tm_mday); 50 | BCD_TO_BIN(time.tm_mon); 51 | BCD_TO_BIN(time.tm_year); 52 | time.tm_mon--; 53 | startup_time = kernel_mktime(&time); 54 | } 55 | ``` 56 | 57 | - `CMOS_READ`:从CMOS外设中读取数据。 58 | - `BCD_TO_BIN`:将BCD码转换为二进制码(BIN)。 -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part02/ch19.md: -------------------------------------------------------------------------------- 1 | # 第19回 缓冲区初始化`buffer_init` 2 | 3 | ## 19.1 `start_buffer`的计算 4 | 5 | ```c 6 | // fs/buffer.c 7 | extern int end; 8 | struct buffer_head * start_buffer = (struct buffer_head *) &end; 9 | 10 | void buffer_init(long buffer_end) { 11 | struct buffer_head * h = start_buffer; 12 | ... 13 | } 14 | ``` 15 | 16 | - `end`是一个外部变量,由链接器ld在链接整个程序时设置的。 17 | - 在程序编译链接时,由链接器程序计算内核程序末端的地址。 18 | 19 | ## 19.2 给缓冲头和缓存块分配空间 20 | 21 | ```c 22 | // fs/buffer.c 23 | void buffer_init(long buffer_end) { 24 | struct buffer_head * h = start_buffer; 25 | void * b; 26 | int i; 27 | 28 | if (buffer_end == 1<<20) { 29 | b = (void *) (640*1024); 30 | } else { 31 | b = (void *) buffer_end; 32 | } 33 | while ( (b -= BLOCK_SIZE) >= ((void *) (h+1)) ) { 34 | h->b_dev = 0; 35 | h->b_dirt = 0; 36 | h->b_count = 0; 37 | h->b_lock = 0; 38 | h->b_uptodate = 0; 39 | h->b_wait = NULL; 40 | h->b_next = NULL; 41 | h->b_prev = NULL; 42 | h->b_data = (char *) b; 43 | h->b_prev_free = h-1; 44 | h->b_next_free = h+1; 45 | h++; 46 | NR_BUFFERS++; 47 | if (b == (void *) 0x100000) 48 | b = (void *) 0xA0000; 49 | } 50 | h--; 51 | free_list = start_buffer; 52 | free_list->b_prev_free = h; 53 | h->b_next_free = free_list; 54 | ... 55 | } 56 | ``` 57 | 58 | - 缓冲头`h`的地址为`start_buffer`指向的地址。 59 | - 缓冲块`b`的地址为`buffer_end`指向的地址。 60 | - 遍历所有缓冲块,每个缓冲块1024B,并分配一个缓冲头;缓冲头用于寻找缓冲块的。 61 | - 建立双向空闲链表:缓冲头就是具体缓冲块的管理结构,而`free_list`开头的双向链表优势缓冲头的管理结构。 62 | - 双向链表的作用:从`free_list`指向的第一个结构中,可以在链表中遍历到任何一个缓冲头结构,并且找到缓冲头对应的缓冲块。 63 | 64 | ![缓冲区的缓冲头和缓冲块分配](images/ch19-buffer-memory.png) 65 | 66 | ## 19.3 初始化哈希表`hash_table` 67 | 68 | ```c 69 | // fs/buffer.c 70 | void buffer_init(long buffer_end) { 71 | ... 72 | for (i=0;icounter) > 0) return; 55 | // 如果没有剩余时间片,则执行调度 56 | schedule(); 57 | } 58 | ``` 59 | 60 | ## 23.4 优先级 61 | 62 | ```c 63 | struct task_struct { 64 | ... 65 | long counter; 66 | long priority; 67 | ... 68 | struct tss_struct tss; 69 | }; 70 | ``` 71 | 72 | - 使用`priority`设置优先级,用于衡量该进程在CPU中运行的时间量。 73 | - 每次初始化一个进程时,都把`counter`赋值为`priority`的值,当`counter`值减到0时,下一次分配时间片,继续赋值为`priority`的值。 74 | 75 | ## 23.5 进程状态 76 | 77 | ```c 78 | // include/linux/sched.h 79 | #define TASK_RUNNING 0 80 | #define TASK_INTERRUPTIBLE 1 81 | #define TASK_UNINTERRUPTIBLE 2 82 | #define TASK_ZOMBIE 3 83 | #define TASK_STOPPED 4 84 | ``` 85 | 86 | 用于判断当前进程的状态,用于CPU的调度执行。 -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part03/ch30.md: -------------------------------------------------------------------------------- 1 | # 第30回 番外篇:你管这破玩意叫文件系统 2 | 3 | **案例:** 如果手上有一块硬盘,大小只有1MB,准备把文件存储在硬盘上,要如何设计才能更方便地在硬盘中读写这些文件? 4 | 5 | **设计方案:** 6 | - 基本操作(分块):将硬盘按逻辑分成一个个块,每个块定义为两个物理扇区大小(1KB),这块硬盘有1024个块。 7 | - 解决记录问题(位图):建立一个位图,每一位表示其中一个块的使用情况,0表示未使用,1表示已使用,块位图放于第0号块中。 8 | - 解决文件查找问题(inode):建立一个固定大小(128B)的空间用于存储文件大小、文件创建时间、文件权限等信息。 9 | - `inode`位图用于管理`inode`的使用情况,放于第1号块中 10 | - 后续块存放`inode`表,主要存放`inode`内容,一个块可以存放8个`inode`,组成一个`inode`表。 11 | - 解决文件存储留下的空洞问题(间接索引):使用间接索引扩大`inode`记录块号的信息,使得一个`inode`可以记录多个块号。 12 | - 解决块和`inode`的额外信息(超级块和块描述符): 13 | - 超级块:用于存储`inode`数量、空闲`inode`数量、块数量、空闲块数量。 14 | - 块描述符:用于存储块位图、`inode`位图、`inode`表所在的块号信息。 15 | - 解决文件分类问题:使用一个目录类型的`inode`指向的数据块记录文件的`inode`信息。 16 | - 解决目录下的所有文件名和文件类型展示:将一个目录类型的`inode`指向的数据块中的记录文件的`inode`,同时记录文件名和文件类型,删除`inode`表中的文件名和文件类型的内容。 17 | - 解决所有文件的根目录问题:规定`inode`表中的0号`inode`表示根目录。 18 | 19 | ![文件系统的块设计](images/ch30-disk-blocks.png) 20 | 21 | Ext2文件系统: 22 | - 超级块前面是启动块,由PC联盟给硬盘规定的1KB专属空间,任何文件系统都不能用它。 23 | - `inode`表中用15个块来定位文件,其中第13~15个块分别表示一级、二级、三级间接索引。 24 | - 文件类型很多,常见的例如块设备文件、字符设备文件、管道文件、socket文件等。 25 | - 超级块、块描述符、`inode`表中记录的信息更多。 26 | - 2号`inode`表为根目录。 -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part03/images/ch25-fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part03/images/ch25-fork.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part03/images/ch27-pde-or-pte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part03/images/ch27-pde-or-pte.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part03/images/ch27-segment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part03/images/ch27-segment.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part03/images/ch29-intel-pagination.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part03/images/ch29-intel-pagination.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part03/images/ch30-disk-blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part03/images/ch30-disk-blocks.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part04/ch34.md: -------------------------------------------------------------------------------- 1 | # 第34回 进程2的创建 2 | 3 | ```c 4 | // init/main.c 5 | void init(void) { 6 | ... 7 | if (!(pid=fork())) { 8 | close(0); 9 | if (open("/etc/rc",O_RDONLY,0)) 10 | _exit(1); 11 | execve("/bin/sh",argv_rc,envp_rc); 12 | _exit(2); 13 | } 14 | ... 15 | } 16 | ``` 17 | 18 | 主要流程: 19 | 1. `fork`一个新的子进程,进程2被创建出来了。 20 | 2. 在进程2里关闭0号文件描述符。 21 | 3. 以只读形式打开`/etc/rc`文件。 22 | 4. 执行`/bin/sh`程序。 23 | 24 | ## 34.1 `fork`进程2 25 | 26 | 根据[第27回](../part03/ch27.md)中介绍的写时复制机制,将进程1复制出进程2,与进程0复制出进程1有以下区别: 27 | 1. 进程1打开了3个文件描述符并指向`/dev/tty0`,也同样复制到了进程2中,进程结构`task_struct`里的`filp[]`数组被复制了一份。 28 | 2. 进程0复制进程1时,页表的复制只有160项(640KB),之后的进程复制是1024项(4MB) 29 | 30 | ![fork进程2](images/ch34-fork-process2.png) 31 | 32 | ## 34.2 清空0号文件描述符 33 | 34 | ```c 35 | // fs/open.c 36 | int sys_close(unsigned int fd) 37 | { 38 | struct file * filp; 39 | 40 | if (fd >= NR_OPEN) 41 | return -EINVAL; 42 | current->close_on_exec &= ~(1<filp[fd])) 44 | return -EINVAL; 45 | current->filp[fd] = NULL; 46 | if (filp->f_count == 0) 47 | panic("Close: file count is 0"); 48 | if (--filp->f_count) 49 | return (0); 50 | iput(filp->f_inode); 51 | return (0); 52 | } 53 | ``` 54 | 55 | 将0号文件描述符清空,赋值为`NULL`,即清除从进程1复制过来的标准输入的文件描述符。 56 | 57 | ## 34.3 打开`/etc/rc`配置文件 58 | 59 | ```c 60 | // init/main.c 61 | void init(void) { 62 | ... 63 | if (!(pid=fork())) { 64 | close(0); 65 | if (open("/etc/rc",O_RDONLY,0)) 66 | _exit(1); 67 | execve("/bin/sh",argv_rc,envp_rc); 68 | _exit(2); 69 | } 70 | ... 71 | } 72 | ``` 73 | 74 | 结合[第33回](ch33.md)介绍的,将指向标准输入的0号文件描述符重新指向了`/etc/rc`文件。 75 | -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part04/images/ch32-loading-root-system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part04/images/ch32-loading-root-system.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part04/images/ch33-dev-tty0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part04/images/ch33-dev-tty0.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part04/images/ch34-fork-process2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part04/images/ch34-fork-process2.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part04/images/ch35-create-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part04/images/ch35-create-table.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part05/images/ch42-command-display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part05/images/ch42-command-display.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part05/images/ch42-termios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part05/images/ch42-termios.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part05/images/ch43-shell-read-cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part05/images/ch43-shell-read-cmd.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part05/images/ch44-proc-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part05/images/ch44-proc-list.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part05/images/ch45-create-pipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part05/images/ch45-create-pipe.png -------------------------------------------------------------------------------- /docs/linux-source-code-reading/part05/images/ch47-ll_rw_block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/docs/linux-source-code-reading/part05/images/ch47-ll_rw_block.png -------------------------------------------------------------------------------- /references/BIOS Interrupts and Functions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relph1119/linux-learning-notes/e58e7f698a7e38becf8c86118ff02458993d0bd1/references/BIOS Interrupts and Functions.pdf --------------------------------------------------------------------------------