《操作系统真象还原》第十二章(1)——进一步完善内核

前言

上章博客链接:《操作系统真象还原》第十一章——用户进程-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,实现系统调用。


系统调用的实现

实现框架

这是我们的实现思路:

  1. 用中断门实现系统调用,效仿Linux 用 0x80 号中断作为系统调用 的入口。
  2. 在IDT 中安装0x80 号中断对应的描述符,在该描述符中注册系统调 用对应的中断处理例程。
  3. 建立系统调用子功能表syscall_table,利用eax寄存器中的子功能号 在该表中索引相应的处理函数。
  4. 用宏实现用户空间系统调用接口 _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章剩下两部分吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值