X86_64 Linux下:
make
nasm
gcc
ld
bochs ,配置文件可用./bochsrc
用bximage创建好虚拟硬盘(可自行变更)
bximage
,注意修改makefile中的创建好的虚拟硬盘名称
head | ||
---|---|---|
| | ||
master | ||
| | ||
[实现系统调用]-> ... | master分支 | |
[实现用户进程]--> | ||
[vga]-->[desktop]->[mouse] | graphics分支 |
make all
,得到elf格式的kernel.bin
bochs
运行.(配置文件在当前目录下)
make clean
:清除编译产生的中间文件
- IDT中安装0x80的中断描述符,注册0x80的中断处理例程(DPL=3)
- 在内核中实现每一个系统调用,入口用syscall_table数组组织,ring 3通过eax的值作为syscall_table的索引进入具体的系统调用
- ring 3的用户空间用宏实现(封装)进入0x80中断的指令和传递的参数
- 向
syscall.h
中enum syscall_nr结构中添加新的子功能号,和用户空间函数声明 - 在
syscall.c
中实现用户函数接口(调用_syscallx()宏) - 在
syscall-init.c
中实现调用函数,并且向syscall_table
中注册
测试用户进程(ring 3)下都是靠内核线程来帮助打印,现在添加ring 3的打印功能
- 添加了简单的write系统调用
- 添加vsprintf()和itoa()函数
- 添加printf()
使用arena结构管理小的内存块分配,支持7种大小的内存块,16 32 64 128 256 412 1024
内核空间 内存块描述符数组 定义在memory.c
用户空间 内存块描述符数组 定义在pcb中
修改thread.h的pcb结构
在process.c中添加u_block_descs[]的初始化
添加2个地址转换函数:arena2block(),block2arena().
最后从mem_block_desc.free_list中找到了mem_block,就会将mem_block填充为0,
回收内存指的是将bitmap清零,并不是真正的将memory的字节清零
内存分配实际调用函数1,内存释放按照malloc_page()注释中的相反步骤来做.
mfree_page(粒度:页)流程:
- pfree()释放物理页
- page_table_pte_remove():在page_table中去掉映射关系
- vaddr_remove():在虚拟内存池中释放虚拟地址对应的页(清除bitmap)
添加sys_free()后测试时,在page_table_add()处产生错误,来源于pfree()等函数中变量写错,目前sys_free()测试成功
- 打开irq2+硬盘中断
- 添加printk()
...
-
5-12完成创建文件系统,
-
5-18实现sys_open()中的文件创建
-
5-18实现sys_open()文件的打开,和文件的关闭
-
5-19实现文件写入sys_write()
-
5-19实现文件读取sys_read()
-
5-20实现文件定位sys_lseek()
-
5-20实现文件删除sys_unlink()
-
5-20实现创建目录sys_mkdir()
-
5-20实现遍历目录-1.打开和关闭目录 sys_opendir(),sys_closedir()-2.sys_readdir()和sys_rewinddir()
fs/fs.c sys_opendir(): line 653 判断应该有问题(待修改)
-
5-21实现删除目录sys_rmdir()
-
5-21实现获得当前工作目录sys_getcwd(),和切换工作目录sys_chdir()
-
5-21实现读取文件属性sys_stat()
- 5-22 sys_fork()的实现,添加fork()系统调用
- 实现init进程,由loader->init进程
- 5-22是sys_read()添加处理键盘输入(stdin_no)
- 5-22添加putchar(),cls_screen()系统调用
- 5-22实现 简单shell (5-23完成)
- 5-22(keyboard.c中有特殊处理),在shell.c添加ctrl+u/ctrl+l快捷键
- 5-23 解析键入的字符
- 5-23 添加fs.h中未添加的系统调用,增加ps系统调用
- 5-23 实现路径解析转换,在shell.c中测试(将参数(路径)->转化)
- 5-23 实现 ls,cd,mkdir,rmdir,rm,pwd,ps,clear命令
- 5-25 实现exec (修改了lib/string.c的assert)
- 6-2 用户进程(自己编译失败,待在centos 6.3上编译(可能当前gcc版本过高)),但是
https://github.com/zhangwenxiao/os-core/tree/master/command
将他编译好的prog_arg和prog_no_arg写入,并且加载执行成功. - 6-3 实现wait(),exit()系统调用
- 6-5 完善wait(),exit(),并且实现了cat命令.(结束了main内核线程,并且while()都修改为exit(),避免消耗cpu)
沿用当前在X64的-m 32等选项,编译链接出来的用户进程在bochs中加载失败,可能是gcc版本太高了 gcc 9.6
于是开了个i686的虚拟机来编译,gcc版本是4.5左右,编译链接出来的程序放到bochs中加载成功,
i686下编译的Makefile以及一些*.sh上传到
command/tools
目录中
-
6-5实现管道sys_pipe(),添加系统调用pipe(),用管道来进行父子进程间通信测试
-
6-6 fs.c添加sys_help(),pipe.c添加sys_fd_redirect(),
然后添加help()和fd_redirect()系统调用,
修改shell.c/my_shell()的处理逻辑,增加对shell的管道支持
修改cat.c(无参数则echo),测试多管道成功
C:表示一致性代码段,也称为依从代码段,Conforming,一致性代码段是指如果自己是转移的目标段,并且自己是一致性代码段,自己的特权级一定要高于当前特权级,转移后的特权级不与自己的DPL为主,而是与转移前的低特权级一致,(依从)
处理器微架构表示:
mov eax,[0x1234]
push eax ;sub esp,4
mov [esp],eax
call function
0x7c00(mbr)->(0xb00=0x900+0x300)(loader)->0x1500(kernel_entry_point)
elf内核加载到
0x70000
处,解析elf内核时,因为编译指定了kernel的起始地址为0xc0001500,所以将data段加载到物理地址0x1500处
- 内存管理的bitmap放在
0x9a000
处(包括vaddr,和physical addr) loader.s
:进入内核前设置前内核栈顶为0x9f000
,所以内核线程pcb
地址是0x9e000
0x100000处页目录的第769开始的页目录项目指向从0x102000开始的页表,这对应着1GB虚拟内存空间,
- 所以1mb上2+254个页表占用的1mb的物理内存空间,真正可用物理内存从0x200000开始分配
- 准备好页目录和页表
- 将页表地址写入cr3,(cr3:页目录基址寄存器)
- cr0的pg = 1(paging = 1),控制寄存器开启分页
填写了3个页目录项目,0指向1mb,0xc00指向高3gb的内核的虚拟地址空间,4092指向页目录自己
开启分页后gdt表虚拟地址和video描述符的基地址都是在kernel部分 3gb后,实际在1mb下
真实物理地址: = 0x80000
0000 0000 00
0010 0000 00
0000 0000 0000
分页机制下:
0=第0个页目录项
2^7=128
128*4k = 0x80000
开启分页机制后刚好映射到低1mb的地址空间
- 0xfffffxxx,前20bit索引到最后是页目录的基地址,xxx作为偏移则是 项的索引*4
- 高10bit: 0x3ff,索引到页目录自身,中间10bit定位具体页表,12bit偏移=页表项索引值*4
用来存放虚拟地址页框与物理地址页框的映射关系,
当页表中的内容修改后,要人为的更新tlb,保证内存访问的正确
- 重新加载cr3寄存器,
- invlpg指令: 格式invlpg x (invalidate page),x是虚拟地址,用来在tlb检索
elf内核放在高地址(0x70000),等解析后的内核镜像在低地址运行起来后就可以覆盖高地址elf格式内核(没用处了)
在加载内核后和开启分页后,要分析加载elf格式的内核,从Loader正式进入kernel中,**loader中,在进入kernel_entry_point前将内核的栈esp
设置为1mb下的高地址0x9f000
- loader使用的栈是0x900以下,loader在0x900处
- loader开启分页跳到kernel时重新设置的栈是
0xc009f000
,物理地址也就是0x9f000
,接近1mb的最高处
pcb大小是4kb,并且只能单独占用一个1页框,pcb的开始,地址0xabcde000
开始存放的进程的信息状态等,pcb的最高处0xabcdefff
以下的内存作为进程或者线程在0特权级下所使用的栈,所以安排内核的栈顶是0xc009f000
,则pcb的起始地址是0xc009e000
2021/4/15: 修改了部分memory.c
- 调用门返回到ring3
- 中断返回到ring3
- 提前准备好用户进程所用的栈结构,填好用户进程的上下文信息
cs.rpl
等于当前cpu的特权级,所以cs.rpl=3
- 栈中段寄存器的选择子必须指向
dpl=3
的描述符(内存段) - 退出中断后,继续允许中断
eflags.if = 1
- 用户进程不能直接io,访问硬件设备,
eflags.iopl = 0
大概3种属性的section:
- 可读可写的数据,如 数据节
.data
和未初始化节.bss
- 只读可执行的代码,如 代码节
.text
和 初始化代码节.init
- 只读数据,如
.rodata
- 进程自己的页目录表的高1GB和内核页目录表相同,
- 并且这些页目录项和页表项的权限=PG_US_U,即所有特权级的代码都可以访问,反之,如果页目录项/页表项此bit为PG_US_S,则ring 3用户进程访问不了内核空间的数据
- 0扇区mbr
- 1扇区没用
- loader写入第2个扇区,并且占用4个扇区大小 (2-5)
- elf格式的内核文件写入第10个扇区(扇区9)
- -Ttext指定链接生成的文件中代码段起始的虚拟地址空间
- -e 指定进程的入口符号(函数)
- ld: -m elf_i386指定链接成x86架构
- gcc:-m32在x64下编译成32bit代码
gcc编译默认开启了栈保护,会调用__stack_chk_fail
函数,而我们没有链接C标准库,所以报错
-fno-stack-protector
关闭gcc栈保护,就行
-
call调用时的栈的变化
当提前通过栈传递参数时,call调用会把pc下一条地址push入栈,所以注意此时栈顶是call指令的下一条地址,