《操作系统真象还原》chapter12 笔记与思考
Last Update:
本章内容只有一个:系统调用
实现的调用包括:sys_malloc、sys_free、sys_write、sys_getpid
前言
可惜的是,本书本章使用的是目前Linux已经弃用的_syscallX方式。在原版的Linux中,这种方式最多只支持6个参数,限制诸多且据本书作者说还存在安全问题(不过我查了一圈不知道具体是指什么样的安全事件)。目前记笔记时姑且这样继续,事后自己尝试的时候再试试能不能实现更加现代化一点的操作。
另外本书这节也实现了malloc和free,但其实现方式和我一直以来认知的堆管理似乎有很大的差别……考虑到实际的工程量问题,事后再尝试能否也做一些现代化的改造吧,当下先以笔记优先,姑且认同其实现方式。
系统调用
仿造Linux的操作,只使用软中断0x80来实现系统调用,过程如下:
维护一张系统调用表syscall_table,该表用于储存每个系统调用函数的地址
在IDT中注册0x80中断号对应的处理程序(称之为syscall_handler)
1 |
|
该处理函数根据中断调用号在系统调用表中寻址对应函数,这些函数是sys_funcname族函数,属于具体的实现函数
例如调用常规的getpid函数将引发如下操作:
- 调用_syscall0函数传入getpid的调用号
- 在_syscall0中向内核通过eax传参(即调用号),然后触发0x80中断
- 中断调用处理函数syscall_handler通过调用号在调用表中寻址得到对应的函数(sys_getpid),该函数负责具体操作并返回结果
注:pid是加在task_struct也就是PCB中的
其实也没什么,单纯就是在触发中断以后进入处理函数,此时已经陷入内核,属于R0权级了,所有操作都能够正常进行了。特地为用户加一个进入内核方法罢了,只是所有方法的操作都被限制在固定范围。
1 |
|
printf和变长参数
1 |
|
printf中通过vsprintf将输入的参数和format适配并转换成新的字符串通过write函数输出
变长参数的实现主要是出于c调用规则规定由调用者清理参数,所以调用printf函数push多少参数入栈,事后也要自己清理这些堆栈,所以不用担心这些参数淤积在栈里。
所以需要关心的问题是,如何准确的适配所有参数?答案是提供了参数模板,也就是这里的格式化字符串。
format中提供占位符来识别参数数量,有多少占位符就会用多少个参数,多的参数不会被用到,也不会留在栈里。至于少参数的情况……似乎没有高效的解决办法,即便现在2022年了,直接在C语言里直接这样写也会导致内存泄露:
1 |
|
(当然,真要解决也不是没办法,只是需要付出更多的开销)
至于vsprintf函数则只需要根据format来识别函数就行了:
1 |
|
对于常规字符直接拷贝即可,遇到‘%’时根据下一个字符决定拷贝内容。
堆管理
首先简述一下本书所实现的堆管理逻辑吧:
sys_malloc:
- 在每个任务的PCB中加入一个arena数组用于管理不同大小的chunk(其实就是GLIBC实现的Bins结构的简化版)
- 初始化时将每个Bin中的free_list清空,然后初始化该Bin中能够存放的chunk数
- 然后在用户实际调用malloc时,根据其所需要开辟的size选择对应的arena,然后从内核分配一个内存页,将该页按照该arena所管理的size切割成一块块chunk然后全都挂到其free_list里,最后从该链表里取出一块chunk分配给用户
sys_free:
- 对于一些小的需求,将该chunk重新挂回free_list即可
- 对于需要返还内存页的情况,将用户虚拟地址位图中对应位置0,然后将自己PTE中对应的页的P位置0表示其不在内存中
- 最后把物理内存池的位图中对应内存页的flag置0,表示该页可用
- 另外还需要刷新TLS(用于缓存页表的硬件设备)
逻辑和GLIBC有点像,不过是精简版的,个人认为这种方式虽然可行,但效率并没有GLIBC那样高。
最后,为用户提供malloc和free函数,两个函数调用sys_malloc和sys_free就算完成了。
嘛,如果到时候有机会的话可以试着实现一个看看,不过目前先就这样放着吧。本书最终的操作系统毕竟只是一个用于理解原理的精简版,所以知道真是情况以后还是省察着看吧。
插画ID:90781328