文章目录
前言
上章博客链接:《操作系统真象还原》第十一章——用户进程-CSDN博客
本章可分为3部分,实现系统调用框架,实现printf,实现堆内存管理。
吸取11章的教训,我们每章还是分成好几篇博客来完成。这篇博客完成第一部分。
分析Linux系统调用
这一节通过参考linux的系统调用结构,给我们的系统调用作参考。
以下是总的实现原理:
Linux 系统调用是用中断门来实现的,通过软中 断指令int来主动发起中断信号。由于要支持的系统功 能很多,总不能一个系统功能调用就占用一个中断向 量,真要是这样的话整个中断描述符表都不够用呢。 Linux 只占用一个中断向量号,即0x80,处理器执行指 令int 0x80 时便触发了系统调用。为了让用户程序可以 通过这一个中断门调用多种系统功能,在系统调用之 前,Linux 在寄存器eax中写入子功能号,例如系统调 用open和close 都是不同的子功能号,当用户程序通过 int 0x80 进行系统调用时,对应的中断处理例程会根据 eax 的值来判断用户进程申请哪种系统调用。
具体实现有两种方式:
一种是更完善的库函数方式syscall。它是定义在glibc库内的,接受子功能号和参数,进行具体的系统调用。这是一种间接的系统调用。优点是平台无关、完善、安全。缺点是复杂。
另一种是更直接更简单的宏方式_syscall。linux定义了7个宏 _syscall[0-6]
,通过内联汇编直接把相关参数(最多支持6个参数)输入到寄存器里,然后走中断门。
其中,寄存器 eax用来保存子功能号,ebx保存第1个参 数,ecx 保存第2个参数,edx保存第3个参数,esi保存第4个参数,edi保存第5个参数。第
我们来看一下宏定义代码:
#define _syscall3(type, name, type1, arg1, type2, arg2, type3, arg3) \
type name(type1 arg1, type2 arg2, type3 arg3) { \
long __res; \
__asm__ volatile ("push %%ebx; movl %2,%%ebx; int $0x80; pop %%ebx" \
: "=a" (__res) \
: "0" (__NR_##name),"ri" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3)) : "memory"); \
__syscall_return(type,__res); \
}
#define _syscall6(type,name, type1,arg1, type2,arg2, type3,arg3,
type4,arg4, type5,arg5, type6,arg6) \
type name (type1 arg1,type2 arg2,type3 arg3,\
type4 arg4,type5 arg5,type6 arg6) { \
long __res; \
struct { long __a1; long __a6; } __s = { (long)arg1, (long)arg6 }; \
__asm__ volatile ("push %%ebp ; push %%ebx ; movl 4(%2),%%ebp ; " \
"movl 0(%2),%%ebx ; movl %1,%%eax ; int $0x80 ; " \
"pop %%ebx ; pop %%ebp" \
: "=a" (__res) \
: "i" (__NR_##name),"0" ((long)(&__s)),"c" ((long)(arg2)), \
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5)) \
: "memory"); \
__syscall_return(type,__res); \
}
#define __syscall_return(type, res) \
\
do{
if ((unsigned long)(res) >= (unsigned long)(-125)) { \
errno = -(res); \
res = -1; \
} \
return (type) (res); \
} while (0)
我们的操作系统最终效仿更简单的宏_syscall,实现系统调用。
系统调用的实现
实现框架
这是我们的实现思路:
- 用中断门实现系统调用,效仿Linux 用 0x80 号中断作为系统调用 的入口。
- 在IDT 中安装0x80 号中断对应的描述符,在该描述符中注册系统调 用对应的中断处理例程。
- 建立系统调用子功能表syscall_table,利用eax寄存器中的子功能号 在该表中索引相应的处理函数。
- 用宏实现用户空间系统调用接口 _syscall,最大支持 3 个参数的系统 调用,故只需要完成 _syscall[0-3]。寄存器传递参数,eax 为子功能号,ebx 保存第1个参数,ecx保存第2个参数,edx保存第3个参数。
这是书中的流程图
现在我们对系统调用有了一个整体上的把握,后续就是逐步实现了。
增加0x80号中断描述符,修改interrupt.c
总共修改了2部分,一个是支持的最大中断号变为0x81,引入外部函数——系统调用中断处理函数。还有就是在中断描述符表初始化的时候单独加了一行。
给出完整的interrupt.c。
#include "./interrupt.h"
#include "../lib/kernel/stdint.h"
#include "./global.h"
#include "./io.h"
#include "../lib/kernel/print.h"
#define PIC_M_CTRL 0x20 // 这里用的可编程中断控制器是8259A,主片的控制端口是0x20
#define PIC_M_DATA 0x21 // 主片的数据端口是0x21
#define PIC_S_CTRL 0xa0 // 从片的控制端口是0xa0
#define PIC_S_DATA 0xa1 // 从片的数据端口是0xa1
#define IDT_DESC_CNT 0x81 // 目前总共支持的中断数
#define EFLAGS_IF 0x00000200 // 中断标志位IF,在EFLAGS寄存器中
#define GET_EFLAGS_IF(EFLAGS_VAR) asm volatile("pushfl ; popl %0" : "=g"(EFLAGS_VAR)) // 获取中断标志位IF
extern uint32_t syscall_handler(void); // 系统调用中断处理函数
/*中断门描述符结构体*/
struct gate_desc
{
uint16_t func_offset_low_word;
uint16_t selector;
uint8_t dcount; // 此项为双字计数字段,是门描述符中的第4字节。此项固定值,不用考虑
uint8_t attribute;
uint16_t func_offset_high_word;
};
static struct gate_desc idt[IDT_DESC_CNT]; // idt是中断描述符表,本质上就是个中断门描述符数组
char *intr_name[IDT_DESC_CNT]; // 用于保存异常的名字
/******** 定义中断处理程序数组 ********
* 在kernel.S中定义的intrXXentry只是中断处理程序的入口,
* 最终调用的是ide_table中的处理程序*/
intr_handler idt_table[IDT_DESC_CNT];
/********************************************/
extern intr_handler intr_entry_table[IDT_DESC_CNT]; // 声明引用定义在kernel.S中的中断处理函数入口数组
/* 初始化可编程中断控制器8259A */
static void pic_init(void)
{
/* 初始化主片 */
outb(PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.
outb(PIC_M_DATA, 0x20); // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.
outb(PIC_M_DATA, 0x04); // ICW3: IR2接从片.
outb(PIC_M_DATA, 0x01); // ICW4: 8086模式, 正常EOI
/* 初始化从片 */
outb(PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.
outb(PIC_S_DATA, 0x28); // ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.
outb(PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的IR2引脚
outb(PIC_S_DATA, 0x01); // ICW4: 8086模式, 正常EOI
outb(PIC_M_DATA, 0xfc); // 打开主片的键盘中断和时钟中断
outb(PIC_S_DATA, 0xff); // 屏蔽从盘所有中断
put_str(" pic_init done\n");
}
/* 创建中断门描述符 */
static void make_idt_desc(struct gate_desc *p_gdesc, uint8_t attr, intr_handler function)
{
p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF;
p_gdesc->selector = SELECTOR_K_CODE;
p_gdesc->dcount = 0;
p_gdesc->attribute = attr;
p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}
/*初始化中断描述符表*/
static void idt_desc_init(void)
{
int i, last_index = 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]);
}
// 以下是单独的0x80系统调用中断处理函数的初始化,特权级是3用户级
make_idt_desc(&idt[last_index], IDT_DESC_ATTR_DPL3, syscall_handler);
put_str(" idt_desc_init done\n");
}
/* 通用的中断处理函数,一般用在异常出现时的处理 */
static void general_intr_handler(uint8_t vec_nr)
{
if (vec_nr == 0x27 || vec_nr == 0x2f)
{ // 0x2f是从片8259A上的最后一个irq引脚,保留
return; // IRQ7和IRQ15会产生伪中断(spurious interrupt),无须处理。
}
// put_str("test general_intr_handler\n");
/* 将光标置为0,从屏幕左上角清出一片打印异常信息的区域,方便阅读 */
set_cursor(0); // 设置光标位置为0
int cursor_pos = 0; // 光标位置
while (cursor_pos++ < 320)
{ // 清除屏幕
put_char(' '); // 打印空格
}
set_cursor(0); // 设置光标位置为0
put_str("!!!!!! excetion message begin !!!!!!\n"); // 打印异常信息
set_cursor(88);
put_str(intr_name[vec_nr]); // 打印异常名称
if (vec_nr == 14)
{ // 如果是页面错误异常
int page_fault_vaddr = 0; // 页面错误地址
// cr2寄存器存放造成page_fault的地址
asm volatile("movl %%cr2, %0" : "=r"(page_fault_vaddr)); // 获取页面错误地址
put_str("\npage fault addr is "); // 打印页面错误地址
put_int(page_fault_vaddr); // 打印页面错误地址
}
put_str("!!!!!! excetion message end !!!!!!\n"); // 打印异常信息结束
// 目前处在关中断状态,不再调度进程,下面的死循环不再被中断覆盖
while (1)
; // 进入死循环
}
/* 完成一般中断处理函数注册及异常名称注册 */
static void exception_init(void)
{ // 完成一般中断处理函数注册及异常名称注册
int i;
for (i = 0; i < IDT_DESC_CNT; i++)
{
/* idt_table数组中的函数是在进入中断后根据中断向量号调用的,
* 见kernel/kernel.S的call [idt_table + %1*4] */
idt_table[i] = general_intr_handler; // 默认为general_intr_handler。以后会由register_handler来注册具体处理函数。
intr_name[i] = "unknown"; // 先统一赋值为unknown
}
intr_name[0] = "#DE Divide Error";
intr_name[1] = "#DB Debug Exception";
intr_name[2] = "NMI Interrupt";
intr_name[3] = "#BP Breakpoint Exception";
intr_name[4] = "#OF Overflow Exception";
intr_name[5] = "#BR BOUND Range Exceeded Exception";
intr_name[6] = "#UD Invalid Opcode Exception";
intr_name[7] = "#NM Device Not Available Exception";
intr_name[8] = "#DF Double Fault Exception";
intr_name[9] = "Coprocessor Segment Overrun";
intr_name[10] = "#TS Invalid TSS Exception";
intr_name[11] = "#NP Segment Not Present";
intr_name[12] = "#SS Stack Fault Exception";
intr_name[13] = "#GP General Protection Exception";
intr_name[14] = "#PF Page-Fault Exception";
// intr_name[15] 第15项是intel保留项,未使用
intr_name[16] = "#MF x87 FPU Floating-Point Error";
intr_name[17] = "#AC Alignment Check Exception";
intr_name[18] = "#MC Machine-Check Exception";
intr_name[19] = "#XF SIMD Floating-Point Exception";
}
/*完成有关中断的所有初始化工作*/
void idt_init()
{
put_str("idt_init start\n");
idt_desc_init(); // 初始化中断描述符表
exception_init(); // 异常名初始化并注册通常的中断处理函数
pic_init(); // 初始化8259A
/* 加载idt */
uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));
asm volatile("lidt %0" : : "m"(idt_operand));
put_str("idt_init done\n");
}
/* 在中断处理程序数组第vector_no个元素中注册安装中断处理程序function */
void register_handler(uint8_t vector_no, intr_handler function)
{
idt_table[vector_no] = function; // 注册中断处理函数
}
/* 开启中断并返回开启中断之前的状态 */
enum intr_status intr_enable(void)
{
if (intr_get_status() == INTR_ON)
{
return INTR_ON; // 如果当前中断已经打开,则直接返回
}
else
{
asm volatile("sti" : : : "memory"); // 开中断
return INTR_OFF; // 返回关中断
}
}
/* 关闭中断并返回在关闭中断之前的状态 */
enum intr_status intr_disable(void)
{
if (intr_get_status() == INTR_OFF)
{
return INTR_OFF; // 如果当前中断已经关闭,则直接返回
}
else
{
asm volatile("cli" : : : "memory"); // 关中断
return INTR_ON; // 返回开中断
}
}
/* 将中断状态设置为status */
enum intr_status intr_set_status(enum intr_status status)
{
return (status == INTR_ON) ? intr_enable() : intr_disable();
}
/* 获取当前中断状态 */
enum intr_status intr_get_status(void)
{
uint32_t eflags = 0;
GET_EFLAGS_IF(eflags);
return (eflags & EFLAGS_IF) ? INTR_ON : INTR_OFF;
}
要注意的是记得给此描述符的 dpl 指定为用户级 IDT_DESC_ ATTR_DPL3,若指定为0级,则在3级环境下执行int指令会产生GP异常。
实现系统调用接口,创建syscall.c
目录是lib/user/syscall.c .h,我们的版本允许接受0-3个参数,所以我们弄4个宏定义即可。
#include "./syscall.h"
/*从上到下,分别是0、1、2、3参数的系统调用,结构基本一致
*eax是子程序号,剩下三个存在ebx、ecx、edx中*/
/*({ ... })是gcc扩展
*将一组语句封装为一个表达式,返回最后一个语句的值*/
#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; \
})
注释说的很清楚了,就不多嘴了。
对应的头文件我们稍后会给出。
增加0x80 号中断处理例程,修改kernel.S
这部分就是实现上面提到的系统调用中断处理程序syscall_handler。
只给出新增部分,注释同样很详细
;;;;;;;;;;;;;;;; 0x80中断 ;;;;;;;;;;;;;;;;
bits[32]
extern syscall_table
section .text
global syscall_handler
syscall_handler:
;1.保存上下文环境
;因为我们要复用上面的intr_exit
;所以要按上面intr_entry_table的格式压入数据,让栈中格式统一
push 0
push ds
push es
push fs
push gs
pushad
push 0x80
;2.为系统调用子功能传入参数
;不管我们需要几个参数,一律压入三个
;编译器会根据我们的函数声明正确处理
push EDX
push ECX
push EBX
;3.调用子功能处理函数
call [syscall_table+EAX*4]
add esp ,12 ;跨过ebx等三个参数
;4.保存eax中的返回值
;eax内有call后的返回值,我们把它保存到内存中内核栈中eax变量的位置
;8=0x80+pushad后七个,这样esp+4*8就指向目前内存内核栈uint32_t eax变量,然后把eax寄存器中的数据存入内存
mov [esp+4*8],EAX
jmp intr_exit
有两个要点,我粘贴以下书上的原文把,在529页。
关于2.为系统调用子功能传入参数:
第117~119 行是为子功能函数准备参数,由于只支持3个参数的系统调用,故只压入了三个参数,按照C 调用约定,最右边的参数先入栈,因此先把edx中的第3个参数入栈,其次是ecx中的第2个参数、ebx中的第 1 个参数。注意,这里我们不管具体系统调用中的参数是几个,一律压入3个参数,也许您对此感到奇怪。是 这样的,子功能处理函数都有自己的原型声明,声明中包括参数个数及类型,编译时编译器会根据函数声明在 栈中匹配出正确数量的参数,进入函数体后,根据C调用约定,栈顶的4字节(32位系统,下同)是函数的返 回地址,往上(高地址的栈底方向)的4字节是第1个参数,再往上的4字节便是第2个参数,依此类推。在 函数体中,编译器生成的取参数指令是从栈顶往上(跨过栈顶的返回地址,向高地址方向)获取参数的,参数 个数是通过函数声明事先确定好的,因此并不会获取到错误的参数,从而保证了多余的参数用不上,因此,尽 管我们压入了3个参数,但对于那些参数少于3个的函数也不会出错,而我们也只是浪费了一点点栈空间。
关于4.保存eax中的返回值:
根据二进制编程接口abi约定,寄存器eax用来存储返回值。经过上面第122行的call函数调用,如 果有返回值的话,eax 的值已经变成了返回值(如果没有返回值也没关系,编译器会保证函数返回后 eax 的值不变),此时我们要把返回值传给用户进程,但是从内核态退出时,要从内核栈中恢复寄存器上下文, 这会将当前eax的返回值覆盖,那如何将返回值传给用户进程呢?聪明的您一定想到了,就是把寄存器eax 的值回写到内核栈中用于保存eax的内存处,这样从内核返回时,popd指令也只是用该返回值重新覆盖一 次eax寄存器,返回到用户态时,用户进程便获取到了系统调用函数的返回值。 以上的思路是在第126行通过“mov [esp + 8x4], eax”实现的,此行代码就是将返回值写到了栈(此 时是内核栈)中保存eax的那个内存空间。这里解释一下[esp+8x4],这是寄存器相对寻址,esp就是当前 栈顶,8x4 就是相对栈顶,往栈中高地址方向的偏移量,其实把8x4拆分成(1+7)x4更好,其中的1是指 上面的push 0x80 所占的4字节,另外的7是指pushad指令会将eax最先压入,故要跨过7个4字节,总 共是8个4字节,即[esp+8*4]是对应栈中eax的“藏身之所”。
初始化系统调用和实现sys_getpid
上面的汇编用到了一个数据结构:syscall_table。很好理解,相当于把我们的宏定义的那四个宏封装好。但是,目前我们还没有能往数组里放的东西,所以我们先定义一个子功能处理函数吧。
第一个系统调用是getpid,功能是让任务获取自己的pid。目前我们的pcb还没有pid这个属性,先完善相关内容。
修改thread.h
定义了一个类型,另外就是在pcb结构体中加入了pid属性
...
typedef int16_t pid_t;
...
/* 线程或进程的pcb程序控制块 */
struct task_struct
{
uint32_t *self_kstack; // 线程自己的栈的栈顶指针
pid_t pid; // 线程的pid,系统调用部分对它进行操作
enum thread_status status; // 线程的状态
uint8_t priority; // 线程的优先级
uint8_t ticks; // 线程的时间片,在处理器上运行的时间滴答数
uint32_t elapsed_ticks; // 线程的运行时间,也就是这个线程已经执行了多久
char name[16]; // 线程的名字
struct list_elem general_tag; // 用于线程在一般队列中的节点
struct list_elem all_list_tag; // 用于线程在thread_all_list队列中的节点
uint32_t *pgdir; // 如果是进程,这是进程的页表结构中页目录表的虚拟地址,线程则置为NULL
struct virtual_addr userprog_vaddr; // 用户进程的虚拟地址,后续转化为物理地址后存入cr3寄存器
uint32_t stack_magic; // 线程栈的魔数,边界标记,用来检测栈溢出
};
...
修改thread.c
代码很简单,注释也比较详细,不再赘述。还是只给出修改部分的全文。
...
struct lock pid_lock; // 分配pid锁,此锁用来在分配pid时实现互斥,避免为不同的任务分配重复的pid
...
/* 分配pid */
static pid_t allocate_pid(void){
static pid_t next_pid = 0; // 用static延长生命周期,确保pid唯一
lock_acquire(&pid_lock);
next_pid++;
lock_release(&pid_lock);
return next_pid;
}
...
/* 初始化线程基本信息 */
void init_thread(struct task_struct *pthread, char *name, int prio)
{
memset(pthread, 0, sizeof(*pthread)); // 清空线程pcb
pthread->pid = allocate_pid(); // 获取唯一的pid
strcpy(pthread->name, name); // 线程名字
if (pthread == main_thread) // 线程状态
pthread->status = TASK_RUNNING;
else
pthread->status = TASK_READY;
/* self_kstack 是线程自己在内核态下使用的栈顶地址 */
pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PAGE_SIZE); // 线程内核栈
pthread->priority = prio; // 线程优先级
pthread->ticks = prio; // 线程时间片
pthread->elapsed_ticks = 0; // 线程运行时间
pthread->pgdir = NULL; // 线程页表
pthread->stack_magic = 0x19870916; // 线程栈的魔数,边界标记,用来检测栈溢出
}
...
/* 初始化线程环境 */
void thread_init(void)
{
put_str("thread_init start\n");
list_init(&thread_ready_list); // 初始化就绪线程队列
list_init(&thread_all_list); // 初始化所有线程队列
lock_init(&pid_lock); // 初始化pid锁
make_main_thread(); // 创建主线程
put_str("thread_init done\n");
}
...
编写syscall-init.c
上两部分写完,写这部分的新增代码,路径是userprog/syscall-init.c。
#include "./syscall-init.h"
#include "../lib/kernel/stdint.h"
#include "../lib/user/syscall.h"
#include "../thread/thread.h"
#include "../lib/kernel/print.h"
#define syscall_nr 32 // 最大支持的子功能个数
typedef void *syscall;
syscall syscall_table[syscall_nr];
/*返回当前任务的pid*/
uint32_t sys_getpid(void)
{
return running_thread()->pid;
}
/*初始化系统调用*/
void syscall_init(void){
put_str("syscall_init start!\n");
syscall_table[SYS_GETPID] = sys_getpid;
put_str("syscall_init done!\n");
}
对应的syscall-init.h
#ifndef __USERPROG_SYSCALL_INIT_H
#define __USERPROG_SYSCALL_INIT_H
#include "../lib/kernel/stdint.h"
uint32_t sys_getpid(void);
void syscall_init(void);
#endif
当然这两部分后面会不断完善。
添加系统调用getpid
准备工作已经做好,重新返回到我们的syscall.c部分。
先给出之前没有写的头文件吧,路径lib/user/syscall.h
#ifndef __LIB_USER_SYSCALL_H
#define __LIB_USER_SYSCALL_H
#include "../kernel/stdint.h"
enum SYSCALL_NR
{
SYS_GETPID
};
uint32_t getpid(void);
#endif
然后完善syscall.c,新增getpid函数
...
/*返回当前任务的pid*/
uint32_t getpid()
{
return _syscall0(SYS_GETPID);
}
测试系统调用
修改init.c
// 完成所有的初始化工作
#include "./init.h"
#include "../lib/kernel/stdint.h"
#include "../lib/kernel/print.h"
#include "./interrupt.h"
#include "../device/timer.h"
#include "./memory.h"
#include "../thread/thread.h"
#include "../device/console.h"
#include "../device/keyboard.h"
#include "../userprog/tss.h"
#include "../userprog/syscall-init.h"
/*负责初始化所有模块 */
void init_all()
{
put_str("init_all\n");
idt_init(); // 中断初始化
mem_init(); // 内存初始化
timer_init(); // 定时器初始化
thread_init(); // 线程初始化
console_init(); // 控制台初始化,最好放到开中断之前
keyboard_init(); // 键盘初始化
tss_init(); // TSS和GDT初始化
syscall_init(); // 系统调用初始化
}
修改main.c
// 内核的入口函数
#include "../lib/kernel/print.h"
#include "./init.h"
#include "../thread/thread.h"
#include "../device/console.h"
#include "./interrupt.h"
#include "../userprog/process.h"
// 本章测试头文件
#include "../lib/user/syscall.h"
#include "../userprog/syscall-init.h"
void k_thread_a(void *);
void k_thread_b(void *);
void u_prog_a(void);
void u_prog_b(void);
int a_pid = 0, b_pid = 0;
int main(void)
{
put_str("HongBai's OS kernel\n");
init_all(); // 初始化所有模块
process_execute(u_prog_a, "user_prog_a");
process_execute(u_prog_b, "user_prog_b");
intr_enable();
console_put_str(" main_pid:0x");
console_put_int(sys_getpid());
console_put_char('\n');
thread_start("k_thread_a", 31, k_thread_a, "argA: ");
thread_start("k_thread_b", 31, k_thread_b, "argB: ");
while (1)
{
};
}
void k_thread_a(void *arg)
{
char *para = arg;
console_put_str(" thread_a_pid:0x");
console_put_int(sys_getpid());
console_put_char('\n');
console_put_str(" prog_a_pid:0x");
console_put_int(a_pid);
console_put_char('\n');
while (1)
{
};
}
void k_thread_b(void *arg)
{
char *para = arg;
console_put_str(" thread_b_pid:0x");
console_put_int(sys_getpid());
console_put_char('\n');
console_put_str(" prog_b_pid:0x");
console_put_int(a_pid);
console_put_char('\n');
while (1)
{
};
}
void u_prog_a(void)
{
a_pid = getpid();
while (1)
{
};
}
void u_prog_b(void)
{
b_pid = getpid();
while (1)
{
};
}
编写makefile
BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
$(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \
$(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \
$(BUILD_DIR)/switch.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o \
$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o \
$(BUILD_DIR)/process.o $(BUILD_DIR)/syscall-init.o $(BUILD_DIR)/syscall.o
################ c代码编译 ##################
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
lib/kernel/stdint.h kernel/init.h kernel/debug.h \
kernel/memory.h thread/thread.h kernel/interrupt.h \
device/console.h userprog/process.h lib/user/syscall.h \
userprog/syscall-init.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
lib/kernel/stdint.h kernel/interrupt.h device/timer.h \
kernel/memory.h thread/thread.h device/console.h \
device/keyboard.h userprog/tss.h userprog/syscall-init.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
lib/kernel/stdint.h kernel/global.h kernel/io.h \
lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/stdint.h \
kernel/io.h lib/kernel/print.h kernel/interrupt.h \
thread/thread.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
lib/kernel/print.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/string.o: lib/string.c lib/string.h \
kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h \
lib/string.h thread/sync.h thread/thread.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \
lib/string.h kernel/interrupt.h lib/kernel/print.h \
kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
lib/kernel/stdint.h lib/kernel/list.h lib/string.h \
kernel/memory.h kernel/interrupt.h kernel/debug.h \
lib/kernel/print.h userprog/process.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \
lib/kernel/stdint.h kernel/interrupt.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
lib/kernel/stdint.h thread/thread.h kernel/debug.h \
kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/console.o: device/console.c device/console.h \
lib/kernel/print.h thread/sync.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
lib/kernel/print.h kernel/interrupt.h kernel/io.h \
lib/kernel/stdint.h device/ioqueue.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \
lib/kernel/stdint.h thread/thread.h thread/sync.h \
kernel/interrupt.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \
lib/kernel/stdint.h thread/thread.h kernel/global.h \
lib/kernel/print.h lib/string.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \
kernel/global.h lib/kernel/stdint.h thread/thread.h \
kernel/debug.h userprog/tss.h device/console.h \
lib/string.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \
lib/kernel/stdint.h lib/user/syscall.h thread/thread.h \
lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
############## 汇编代码编译 ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/print.o: lib/kernel/print.S
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/switch.o: thread/switch.S
$(AS) $(ASFLAGS) $< -o $@
############## 连接所有目标文件 #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
$(LD) $(LDFLAGS) $^ -o $@
.PHONY : mk_dir hd clean all
mk_dir:
if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
hd:
dd if=$(BUILD_DIR)/kernel.bin \
of=/home/hongbai/bochs/bin/c.img \
bs=512 count=200 seek=10 conv=notrunc
clean:
cd $(BUILD_DIR) && rm -f ./*
build: $(BUILD_DIR)/kernel.bin
all: mk_dir build hd
结果截图
一切顺利。
结语
这是第12章的开头,算是搭建好了系统调用的框架,我们后续在这个框架上实现printf和堆内存管理。
这一部分还是比较顺利的,代码比较简单,在我11章痛苦的调试后,这部分算是缓冲了,今天下午就继续冲刺12章剩下两部分吧。