Linux 系统调用是通过中断门来实现的,通过软中断指令 int 来主动发送中断信号。但由于系统调用的功能不止一个两个,所以不可能一个系统调用对应一个中断门描述符,即不可能一个系统调用对应一个中断向量。
因此 Linux 使用了一个中断向量号 0x80
,通过子功能号的形式去调用不同的功能。
查看系统调用帮助信息:
$ man syscall
函数 syscall 并不是由操作系统提供的,它由运行库 glibc 提供的,因此实际上 syscall 是库函数。
查看直接调用帮助信息:
$ man _syscall
Linux 中的系统调用是用寄存器来传递参数,这些参数从左到右的顺序依次存入不同的通用寄存器(除了 ESP)。
eax
:保存子功能号ebx
:保存第 1 个参数ecx
:保存第 2 个参数edx
:保存第 3 个参数esi
:保存第 4 个参数edi
:保存第 5 个参数还有个传入 6 个参数的,略过了。
lib/user/syscall.h:
#ifndef __LIB_USER_SYSCALL_H
#define __LIB_USER_SYSCALL_H
#include "stdint.h"
enum SYSCALL_NR {
SYS_GETPID,
SYS_WRITE,
SYS_MALLOC,
SYS_FREE
};
uint32_t getpid(void);
uint32_t write(char* str);
void* malloc(uint32_t size);
void free(void* ptr);
#endif
lib/user/syscall.c:
#include "syscall.h"
// 无参数的系统调用
#define _syscall0(NUMBER) ({ \
int retval; \
asm volatile ("int $0x80" : "=a"(retval) : "a"(NUMBER) : "memory"); \
retval; \
})
// 一个参数的系统调用
#define _syscall1(NUMBER, ARG1) ({ \
int retval; \
asm volatile ("int $0x80" : "=a"(retval) : "a"(NUMBER), "b"(ARG1) : "memory"); \
retval; \
})
// 两个参数的系统调用
#define _syscall2(NUMBER, ARG1, ARG2) ({ \
int retval; \
asm volatile ("int $0x80" : "=a"(retval) : "a"(NUMBER), "b"(ARG1), "c"(ARG2) : "memory"); \
retval; \
})
// 三个参数的系统调用
#define _syscall3(NUMBER, ARG1, ARG2, ARG3) ({ \
int retval; \
asm volatile ("int $0x80" : "=a"(retval) : "a"(NUMBER), "b"(ARG1), "c"(ARG2), "d"(ARG3) : "memory"); \
retval; \
})
// 返回当前任务的 PID
uint32_t getpid() {
return _syscall0(SYS_GETPID);
}
// 打印字符串
uint32_t write(char* str) {
return _syscall1(SYS_WRITE, str);
}
// 申请 size 字节大小的内存,并返回结果
void* malloc(uint32_t size) {
return (void*) _syscall1(SYS_MALLOC, size);
}
// 释放 ptr 指向的内存
void free(void* ptr) {
_syscall1(SYS_FREE, ptr);
}
kernel/interrupt.c:
// 初始化中断描述符表
static void idt_desc_init(void) {
int i, lastindex = IDT_DESC_CNT - 1;
for(i = 0; i < IDT_DESC_CNT; i++) make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]);
// 单独处理系统调用,系统调用对应的中断门 DPL 为 3
// 注:此描述符的 DPL 必须为 3,若为 0,则在 3 特权级环境下执行 int 指令会出现 GP 异常
make_idt_desc(&idt[lastindex], IDT_DESC_ATTR_DPL3, syscall_handler);
put_str(" idt_desc_init done\n");
}
kernel/kernel.S:
;;;;;;;;;;;;;;;; 0x80号中断 ;;;;;;;;;;;;;;;;
[bits 32]
extern syscall_table
section .text
global syscall_handler
syscall_handler:
; 保护上下文环境 BEGIN
push 0 ; 压入 0 假装一下中断错误码,使其格式统一
push ds
push es
push fs
push gs
pushad
push 0x80
; 压入参数
push edx ; 第三个参数
push ecx ; 第二个参数
push ebx ; 第一个参数
; 调用子功能处理函数
call [syscall_table + eax * 4] ; 编译器会在栈中传入的参数去找到正确的重载函数
add esp, 12 ; 跨过上面传入的三个参数
; 将 call 调用后的返回值存入到 intr_exit 准备要恢复现场的对应 EAX 位置
; 这是为了避免 intr_exit 后 EAX 的值被旧的 EAX 值所还原、覆盖
mov [esp + 8 * 4], eax
jmp intr_exit ; 执行 intr_exit 恢复上下文环境
userprog/syscall-init.h:
#ifndef __USERPROG_SYSCALLINIT_H
#define __USERPROG_SYSCALLINIT_H
#include "stdint.h"
void syscall_init(void);
uint32_t sys_getpid(void);
#endif
userprog/syscall-init.c:
#define syscall_nr 32
typedef void* syscall;
syscall syscall_table[syscall_nr]; // 系统调用表
// 返回当前任务的 PID
uint32_t sys_getpid(void) {
return running_thread() -> pid;
}
// 打印字符串(未实现文件系统前的版本)
uint32_t sys_write(char* str) {
console_put_str(str);
return strlen(str);
}
// 初始化系统调用
void syscall_init(void) {
put_str("syscall_init start.\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
put_str("syscall_init done.\n");
}
上面说了堆用于动态内存分配,而函数用的是栈空间,因此编译器必须要要提前知道函数所需要占用的内存大小,但可变参数的参数却可以有很多个,看上去很动态的样子。
可变参数本质上是静态的,这得益于编译器采用 C 调用约定来处理函数的传参方式。
C 调用约定:由调用者把参数从右向左的顺序压入栈中,并且由调用者清理堆栈中的参数。
既然参数由调用者压入,那调用者自然知道有几个参数,以及所占用多少空间,因此采用 C 调用约定,无论有多少参数,调用者都可以确定个数以及所占用空间大小,从而完好的回收栈空间。
也就是说,传入的参数的个数是编译器在编译阶段就以及确定下来的了。
查看 stdarg.h:
man stdarg
va_start、va_end、va_arg
这三个表示可变参数。
查看 stdarg.h 文件:
...
#define va_start(v,l) __builtin_va_start(v,l)
#define va_end(v) __builtin_va_end(v)
#define va_arg(v,l) __builtin_va_arg(v,l)
...
我的路径:/usr/lib/gcc/x86_64-linux-gnu/9/include/stdarg.h
__builtin_va_*
表示内建符号,内建 builtins 表示这个是 gcc 内部实现的功能。
参数:
ap(argument pointer)
:是个指针变量,它指向 format 在栈中的地址。三个宏:
#define va_start(ap, v) ap = (va_list)&v // 把ap指向第一个固定参数v
#define va_arg(ap, t) *((t*)(ap += 4)) // ap指向下一个参数并返回其值
#define va_end(ap) ap = NULL // 清除a
printf 输出靠内部的 write 系统调用,其内容的格式化又靠 vsprintf 函数,因此 printf 只是个壳子。
lib/user/syscall.h:
enum SYSCALL_NR {
SYS_WRITE,
};
uint32_t write(char* str);
lib/user/syscall.c:
// 打印字符串
uint32_t write(char* str) {
return _syscall1(SYS_WRITE, str);
}
userprog/syscall-init.c:
// 初始化系统调用
void syscall_init(void) {
syscall_table[SYS_WRITE] = sys_write;
}
lib/stdio.c:
#define va_start(ap, v) ap = (va_list)&v // 把ap指向第一个固定参数v
#define va_arg(ap, t) *((t*)(ap += 4)) // ap指向下一个参数并返回其值
#define va_end(ap) ap = NULL // 清除ap
// 将整形转换成字符
static void itoa(uint32_t value, char** buf_ptr_addr, uint8_t base) {
uint32_t m = value % base; // 取模
uint32_t i = value / base; // 取整
if(i) itoa(i, buf_ptr_addr, base); // 若为0表示已经除到头了、转换完了
if(m < 10) *((*buf_ptr_addr)++) = m + '0'; // 若取模得到的余数 < 10,则转换为 '0'~'9' 字符
else *((*buf_ptr_addr)++) = m - 10 + 'A'; // 若取模得到的余数 >= 10,则转换为 'A'~'F' 字符
}
// 将参数 ap 按照格式 format 输出到字符串 str,并返回替换后的 str 长度
uint32_t vsprintf(char* str, const char* format, va_list ap) {
char* buf_ptr = str;
const char* index_ptr = format;
char index_char = *index_ptr; // 指向 format 字符串某个字符
int32_t arg_int;
char* arg_str;
while(index_char) {
if(index_char != '%') { // 处理当前字符不是 % 的情况
*(buf_ptr++) = index_char; // 先把字符输出到 str,然后++
index_char = *(++index_ptr);// 得到 format 下一个字符,赋值给 index_char
continue;
}
index_char = *(++index_ptr); // 得到 % 后的字符,它表示数据类型
switch(index_char) {
case 's':
arg_str = va_arg(ap, char*);
strcpy(buf_ptr, arg_str);
buf_ptr += strlen(arg_str);
index_char = *(++index_ptr);
break;
case 'c':
*(buf_ptr++) = va_arg(ap, char);
index_char = *(++index_ptr);
break;
case 'd':
arg_int = va_arg(ap, int);
if(arg_int < 0) { // 若为负数,将其转为正数后,在前面添加一个负号'-'
arg_int = 0 - arg_int;
*buf_ptr++ = '-';
}
itoa(arg_int, &buf_ptr, 10);
index_char = *(++index_ptr);
break;
case 'x':
arg_int = va_arg(ap, int);
itoa(arg_int, &buf_ptr, 16);
index_char = *(++index_ptr); // 指向 format 的下一个字符
break;
}
}
return strlen(str);
}
/* 同printf不同的地方就是字符串不是写到终端,而是写到buf中 */
uint32_t sprintf(char* buf, const char* format, ...) {
va_list args;
uint32_t retval;
va_start(args, format);
retval = vsprintf(buf, format, args);
va_end(args);
return retval;
}
// 格式化输出字符串 format
uint32_t printf(const char* format, ...) {
va_list args;
va_start(args, format); // 将 args 指向 format
char buf[1024] = {0}; // 用于存储拼接后的字符串
vsprintf(buf, format, args);
va_end(args);
return write(buf);
}
lib/stdio.h:
#ifndef __LIB_STDIO_H
#define __LIB_STDIO_H
#include "stdint.h"
typedef char* va_list;
uint32_t printf(const char* str, ...);
uint32_t vsprintf(char* str, const char* format, va_list ap);
#endif
arena 是很多开源项目中都会用到的内存管理概念,将内存划分为多个大内存块,每个大内存块之间互不干涉,可以分别管理,这个大内存块称为 arena。
而大内存块,即 arena 中包含两部分,其中一部分用来划分小内存块,这个小内存块我们称为 mem_block。
本书中一个大内存块为 4KB 粒度,即为一页,当然,大内存块也可以是一页或多页,根据大内存块的容量平均划分小内存块的大小。
arena 是个提供内存分配的数据结构,它分为两部分,一部分是元信息,用来描述自己内存池中空闲内存块的数量,该部分占用 12 字节,另一部分就是内存池区域,里面有很多被划分好的小内存块。每个内存块命名为 mem_block,它们是内存分配粒度更细的资源。
图示:
当一个 arena 中的小内存块,即 mem_block 都分配完了后,若还需要申请空间,则此时需要再次创建一个同一规格的 arena,若同一规格的 arena 越来也多,为了跟踪这些 arena,也就是为了跟踪这些 arena 中的 mem_block,我们需要为每个 arena 的元信息中建立一个内存块描述符,即 mem_block_desc,在其中记录内存块规格大小,以及所有同一规格 arena 中的空闲块(是个链表)。
本书支持了 7 种规格,分别是:16、32、64、128、256、512、1024 字节。超过 1024 字节的直接分配一页空间。
arena 中 mem_block 数量的计算方式:(内存粒度 - 12) / 规格。
例如:(4096 - 12) / 128
arena 是个内存仓库,并不直接对外提供内存分配,只有内存块描述符才对外提供内存块,内存块描述符将同类 arena 中的空闲块汇聚到一起,作为某个同一规格内存块的分配入口。
kernel/memory.c:
// 内存仓库 arena 的元信息
struct arena {
struct mem_block_desc* desc; // 此 arena 锁关联的 mem_block_desc
uint32_t cnt; // 若 large = true,则 cnt 表示页宽数
// 若 large = false,则 cnt 表示空闲 mem_block 的数量
bool large; // 标识该 arena 到底是管理 1024 字节内的内存(true),如何超过 1024 字节的内存(false)
};
struct mem_block_desc k_block_descs[DESC_CNT]; // 内核内存块描述符数组
kernel/memory.h:
// 内存块
struct mem_block {
struct list_elem free_elem;
};
// 内存块描述符
struct mem_block_desc {
uint32_t block_size; // 内存块大小
uint32_t blocks_per_arena; // 该 arena 中可容纳多少 block_size 大小的块
struct list free_list; // 目前可用的 mem_block 链表
};
#define DESC_CNT 7 // 内存块描述符的个数
kernel/menory.c:
// 为 malloc 做准备
void block_desc_init(struct mem_block_desc* desc_array) {
uint16_t desc_idx, block_size = 16;
for(desc_idx = 0; desc_idx < DESC_CNT; desc_idx++) {
desc_array[desc_idx].block_size = block_size;
// 初始化 arena 中的内存块数量
desc_array[desc_idx].blocks_per_arena = (PG_SIZE - sizeof(struct arena)) / block_size;
list_init(&desc_array[desc_idx].free_list);
block_size *= 2; // 更新为下一个规格的内存块
}
}
// 内存管理部分的初始化入口
void mem_init() {
put_str("mem_init start\n");
// 0xb00 是 loader.S 中定义的 mem_bytes_total 存储总内存容量
uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
mem_pool_init(mem_bytes_total); // 初始化内存池
block_desc_init(k_block_descs);
put_str("mem_init done\n");
}
thread/thread.h:
// 进程或线程的 PCB,即程序控制块
struct task_struct {
...
struct mem_block_desc u_block_desc[DESC_CNT]; // 用户进程内存块的描述符
...
};
userprog/process.c:
void process_execute(void* filename, char* name) {
...
thread -> pgdir = create_page_dir();
block_desc_init(thread -> u_block_desc);
...
}
kernel/memory.c:
// 返回 arena 中第 idx 个内存块的地址
static struct mem_block* arena2block(struct arena* a, uint32_t idx) {
return (struct mem_block*) ((uint32_t)a + sizeof(struct arena) + idx * a -> desc -> block_size);
}
// 返回内存块 b 所在的 arena 地址
static struct arena* block2arena(struct mem_block* b) {
return (struct arena*)((uint32_t)b & 0xFFFFF000);
}
// 在堆中申请 size 字节内存
void* sys_malloc(uint32_t size) {
enum pool_flags PF;
struct pool* mem_pool;
uint32_t pool_size;
struct mem_block_desc* descs;
struct task_struct* cur_thread = running_thread();
// 判断使用哪个内存池
if(cur_thread -> pgdir == NULL) { // 内核线程
PF = PF_KERNEL;
pool_size = kernel_pool.pool_size;
mem_pool = &kernel_pool;
descs = k_block_descs;
} else { // 用户线程
PF = PF_USER;
pool_size = user_pool.pool_size;
mem_pool = &user_pool;
descs = cur_thread -> u_block_desc;
}
// 若申请的内存不在内存池容量范围内,则直接返回 NULL
if(!(size > 0 && size < pool_size)) return NULL;
struct arena* a; // 这就是划分的大内存块
struct mem_block* b; // 这就是在大内存块内又划分的小内存块
lock_acquire(&mem_pool -> lock);
// 超过最大内存块 1024 字节,就分配页框
if(size > 1024) {
uint32_t page_cnt = DIV_ROUND_UP(size + sizeof(struct arena), PG_SIZE);
a = malloc_page(PF, page_cnt);
if(a == NULL) {
lock_release(&mem_pool -> lock);
return NULL;
}
memset(a, 0, page_cnt * PG_SIZE);
// 对于分配的大块页框,要初始化如下:
a -> desc = NULL; // 没有关联的内存块描述符
a -> cnt = page_cnt; // 页框的数量
a -> large = true; // 该 arena 表示为页框
lock_release(&mem_pool -> lock);
return (void*) (a + 1); // 跨过 arena 大小,把剩下的内存将其首地址返回
} else {
uint8_t desc_idx;
// 从内存块描述符中匹配合适的内存块规格
for(desc_idx = 0; desc_idx < DESC_CNT; desc_idx++) {
if(size <= descs[desc_idx].block_size) break;
}
// 判断该规格的空闲块是否还有剩余,若无,则进行生产
if(list_empty(&descs[desc_idx].free_list)) {
a = malloc_page(PF, 1);
if(a == NULL) {
lock_release(&mem_pool -> lock);
return NULL;
}
memset(a, 0, PG_SIZE);
a -> desc = &descs[desc_idx];
a -> cnt = descs[desc_idx].blocks_per_arena;
a -> large = false;
uint32_t block_idx;
enum intr_status old_status = intr_disable();
// 开始将 arena 拆分成内存块,并添加到内存块描述符的 free_list 中
for(block_idx = 0; block_idx < descs[desc_idx].blocks_per_arena; block_idx++) {
b = arena2block(a, block_idx);
ASSERT(!elem_find(&a -> desc -> free_list, &b -> free_elem));
list_append(&a -> desc -> free_list, &b -> free_elem);
}
intr_set_status(old_status);
}
// 开始分配内存块
b = elem2entry(struct mem_block, free_elem, list_pop(&(descs[desc_idx].free_list)));
memset(b, 0, descs[desc_idx].block_size);
a = block2arena(b); // 获取内存块 b 所在的 arena
a -> cnt--; // 将此 arena 中的空闲内存块减一
lock_release(&mem_pool -> lock);
return (void*) b;
}
}
内存的使用情况是通过位图来管理的,因此,无论内存的分配或释放,本质上都是在设置相关位图中的相应位。
分配内存:
在虚拟地址内存池中寻找可用虚拟地址,相关函数为 vaddr_get。
操作的位图:kernel_vaddr.vaddr_bitmap 或 pcb->userprog_vaddr.vaddr_bitmap。
在物理内存池中寻找可分配的物理地址,相关函数为 palloc。
操作的位图:kernel_pool->pool_bitmap 或 user_pool->pool_bitmap。
在页表中完成虚拟地址到物理地址的映射,相关函数为 page_table_add。
以上步骤封装到了 malloc_page 中。
释放内存:
// 将物理地址 pg_phy_addr 回收到物理内存池
void pfree(uint32_t pg_phy_addr) {
struct pool* mem_pool;
uint32_t bit_idx = 0;
if(pg_phy_addr >= user_pool.phy_addr_start) { // 用户物理内存池
mem_pool = &user_pool;
bit_idx = (pg_phy_addr - user_pool.phy_addr_start) / PG_SIZE;
} else { // 内核物理内存池
mem_pool = &kernel_pool;
bit_idx = (pg_phy_addr - kernel_pool.phy_addr_start) / PG_SIZE;
}
bitmap_set(&mem_pool->pool_bitmap, bit_idx, 0);
}
// 去掉页表中虚拟地址 vaddr 的映射,只去掉 vaddr 对应的 PTE
static void page_table_pte_remove(uint32_t vaddr) {
uint32_t* pte = pte_ptr(vaddr);
*pte &= ~PG_P_1; // 将页表项 PTE 的 P 位置为 0
asm volatile("invlpg %0" : : "m"(vaddr) : "memory"); // 更新 TLB
}
// 在虚拟地址内存池中释放以 _vaddr 起始的连续 pg_cnt 个虚拟页地址
static void vaddr_remove(enum pool_flags pf, void* _vaddr, uint32_t pg_cnt) {
uint32_t bit_idx_start = 0, vaddr = (uint32_t) _vaddr, cnt = 0;
if(pf == PF_KERNEL) { // 内核虚拟内存池
bit_idx_start = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE; // 计算 vaddr 在虚拟内存池中的偏移量
while(cnt < pg_cnt)
bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 0);
} else { // 用户虚拟内存池
struct task_struct* cur_thread = running_thread();
bit_idx_start = (vaddr - cur_thread -> userprog_vaddr.vaddr_start) / PG_SIZE;
while(cnt < pg_cnt)
bitmap_set(&cur_thread -> userprog_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 0);
}
}
// 释放以虚拟地址 vaddr 为起始的 cnt 个物理页信息
void mfree_page(enum pool_flags pf, void* _vaddr, uint32_t pg_cnt) {
uint32_t pg_phy_addr;
uint32_t vaddr = (int32_t) _vaddr, page_cnt = 0;
ASSERT(pg_cnt >= 1 && vaddr % PG_SIZE == 0);
pg_phy_addr = addr_v2p(vaddr);
// 确保待释放的物理内存 存在低端 1M + 1k 大小的页目录 + 1k 大小的页表地址范围外
ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= 0x102000);
// 判断 pg_phy_addr 属于哪个区域
if(pg_phy_addr >= user_pool.phy_addr_start) { // 用户物理内存池
vaddr -= PG_SIZE;
while(page_cnt < pg_cnt) {
vaddr += PG_SIZE;
pg_phy_addr = addr_v2p(vaddr);
// 确保物理地址属于用户内存池
ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= user_pool.phy_addr_start);
// 先将对应的物理页框规划到内存池
pfree(pg_phy_addr);
// 再从页表中清除此虚拟地址所在页表项 PTE
page_table_pte_remove(vaddr);
//pg_cnt++;
page_cnt++;
}
// 清空虚拟地址位图中相应的位
vaddr_remove(pf, _vaddr, pg_cnt);
} else { // 内核物理内存池
vaddr -= PG_SIZE;
while(page_cnt < pg_cnt) {
vaddr += PG_SIZE;
pg_phy_addr = addr_v2p(vaddr);
// 确保待释放的物理内存只属于内核物理内存池
ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= kernel_pool.phy_addr_start && pg_phy_addr < user_pool.phy_addr_start);
// 线将对应的物理页框归还到内存池
pfree(pg_phy_addr);
// 再从页表中清除此虚拟地址所在的页表项 PTE
page_table_pte_remove(vaddr);
page_cnt++;
}
// 清空虚拟地址位图中的相应位
vaddr_remove(pf, _vaddr, pg_cnt);
}
}
// 回收内存 ptr
void sys_free(void* ptr) {
ASSERT(ptr != NULL);
if(ptr != NULL) {
enum pool_flags PF;
struct pool* mem_pool;
// 判断线程还是进程
if(running_thread() -> pgdir == NULL) { // 线程
ASSERT((uint32_t) ptr >= K_HEAP_START);
PF = PF_KERNEL;
mem_pool = &kernel_pool;
} else { // 进程
PF = PF_USER;
mem_pool = &user_pool;
}
lock_acquire(&mem_pool -> lock);
struct mem_block* b = ptr; // 转为 mem_block 内存块类型指针
struct arena* a = block2arena(b); // mem_block 转换到 arena
ASSERT(a -> large == 0 || a -> large == 1);
if(a -> desc == NULL && a -> large == true) // 要释放的内存大于 1024 字节
mfree_page(PF, a, a -> cnt);
else { // 要释放的内存小于或等于 1024 字节
// 先将内存块回收到 free_list
list_append(&a -> desc -> free_list, &b -> free_elem);
// 再判断此 arena 中的内存块是否都是空闲块,若是,则释放 arena
if(++a -> cnt == a -> desc -> blocks_per_arena) {
uint32_t block_idx;
for(block_idx = 0; block_idx < a -> desc -> blocks_per_arena; block_idx++) {
struct mem_block* b = arena2block(a, block_idx);
ASSERT(elem_find(&a -> desc -> free_list, &b -> free_elem));
list_remove(&b -> free_elem);
}
mfree_page(PF, a, 1); // 释放该 arena
}
}
lock_release(&mem_pool -> lock);
}
}
lib/user/syscall.h:
enum SYSCALL_NR {
SYS_GETPID,
SYS_WRITE,
SYS_MALLOC,
SYS_FREE
};
uint32_t getpid(void);
uint32_t write(char* str);
void* malloc(uint32_t size);
void free(void* ptr);
lib/user/syscall.c:
// 申请 size 字节大小的内存,并返回结果
void* malloc(uint32_t size) {
return (void*) _syscall1(SYS_MALLOC, size);
}
// 释放 ptr 指向的内存
void free(void* ptr) {
_syscall1(SYS_FREE, ptr);
}
userprog/syscall-init.c:
// 初始化系统调用
void syscall_init(void) {
put_str("syscall_init start.\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
put_str("syscall_init done.\n");
}