操作系统真象还原_线程调度原理

本文详细解析了操作系统的四个核心概念:CPU控制器、内存管理、程序控制块(PCB)及线程调度流程。通过深入探讨线程创建与调度机制,揭示了线程内部结构及其在内存中的布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

重要概念

学习操作系统或是学习计算机编程得明白四个很重要的概念:

  1. CPU中的控制器无处不在,尽管程序中从来没有出现过控制器,但每条指令都是控制器处理的。
  2. 内存是一个大仓库,编译后代码放在一边,由CPU执行代码产生的中间结果和最终结果放在另一边。代码主要是ELF文件包括代码段数据段;中间结果主要是堆栈。
  3. CPU控制器只会到寄存器eip指向的内存地址处取指令。
  4. CPU执行压栈指令时只会压到寄存器esp指向的地址处,执行出栈指令只会从esp指向的地址处取数据。

这一节说明:

  这一节找错改错经历了差不多一个星期,期间复习了线程进程的创建的整个过程,之前的已经忘得差不多了。线程的创建与如何运行搞了三四天才从新搞明白,明白了程序控制块PCB结构,画了结构图搞了很长时间,后来又发现不对,然后在thread.h文件中又从新用文字画了一遍。
  搞明白了intr_stack中ss字段在pcb页的最高地址,PCB_struct中的字段*self_kstack在pcb页的最低地址,结构体intr_stack、thread_stack、PCB_struct在pcb页的中都是倒序排列的。字段*self_kstack是个指针,指向thread_stack中的ebp字段,pcb页ebp字段到stack_magic字段中间是为栈预留的空白的区域。
  然后弄明白了线程创建到调度的过程,线程和进程的创建与调度大体一致又有所不同。当我弄明白线程的创建与
调度之后非常感慨,设计的如此巧妙真想认识一下设计的人。下面是我从schedule()函数复制过来的说明注释。

thread\thread.h

/***********   中断栈intr_stack   ***********/
struct intr_stack
{
    uint32_t vec_no; // kernel.S 宏VECTOR中push %1压入的中断号
    uint32_t edi;
    uint32_t esi;
    uint32_t ebp;
    uint32_t esp_dummy; // 虽然pushad把esp也压入,但esp是不断变化的,所以会被popad忽略
    uint32_t ebx;
    uint32_t edx;
    uint32_t ecx;
    uint32_t eax;
    uint32_t gs;
    uint32_t fs;
    uint32_t es;
    uint32_t ds;

    /* 以下由cpu从低特权级进入高特权级时压入 */
    uint32_t err_code; // err_code会被压入在eip之后
    void (*eip)(void);
    uint32_t cs;
    uint32_t eflags;
    void *esp;
    uint32_t ss;
};

// 进程使用的栈也属于PCB的一部分,不过此栈是进程所使用的0特权级下内核栈(并不是3特权级下的用户栈)。
struct thread_stack
{
    uint32_t ebp; // 0-3
    uint32_t ebx; // 4-7
    uint32_t edi; // 8-11
    uint32_t esi; // 12-15

    // 线程第一次执行时,eip指向待调用的函数kernel_thread,其它时候eip是指向switch_to的返回地址
    void (*eip)(thread_func *func, void *func_arg); // 16-19

    /*****   以下仅供第一次被调度上cpu时使用   ****/
    /* 参数unused_retaddr只为占位置充数为返回地址 */
    void(*unused_retaddr); //充当返回地址,在返回地址所在的栈帧占个位置
    thread_func *function; // 由函数Kernel_thread所调用的函数名
    void *func_arg;        // 由函数Kernel_thread所调用的函数所需的参数
};

/* 定义的PCB,进程或线程的pcb,程序控制块Process Control Block */
struct PCB_struct
{
    uint32_t *self_kstack;                  // 线程的内核栈顶指针,指向thread_stack结构体

    pid_t pid;

    // 用于记录线程状态,其类型便是前面定义的枚举结构enum task_status。
    enum task_status status;

    // 用于记录任务(线程或进程)的名字,长度是16,即任务名最长不过16个字符。
    char name[TASK_NAME_LEN];

    uint8_t priority;

    uint8_t ticks; // 每次在处理器上执行的时间嘀嗒数,每次时钟中断都会将当前任务的ticks减1

    //用于记录任务在处理器上运行的时钟嘀嗒数,从开始执行,到运行结束所经历的总时钟数。
    uint32_t elapsed_ticks;

    // general_tag的作用是用于线程在一般的队列中的结点
    struct list_elem general_tag;

    // all_list_tag的作用是用于线程队列thread_all_list中的结点
    struct list_elem all_list_tag;

    // 用于存放进程页目录表的虚拟地址,这将在为进程创建页表时为其赋值。如果该任务为线程,pgdir则为NULL
    uint32_t *pgdir;

    struct virtual_addr userprog_vaddr;   // 用户进程的虚拟地址
    // struct mem_block_desc u_block_desc[DESC_CNT]; // 用户进程内存块描述符
    // int32_t fd_table[MAX_FILES_OPEN_PER_PROC];    // 已打开文件数组
    // uint32_t cwd_inode_nr;                        // 进程所在的工作目录的inode编号
    // pid_t parent_pid;                             // 父进程pid
    // int8_t exit_status;                           // 进程结束时自己调用exit传入的参数

    uint32_t stack_magic;  // 用这串数字做栈的边界标记,用于检测栈的溢出
};
PCB页中的栈内存顺序        

在这里插入图片描述

三个线程的调度

  为了简单说明问题只创建有3个线程M、A、B没有线程。其中M是主线程也就是调度模块所在的线程,调度模块还未初始化时就已存在。线程调用顺序是M -> A -> B -> M采用顺寻调度模式,其中A、B线程为第一次调用。

  调度模块共四个函数,调用顺序是:intr32entry -> intr_timer_handler() -> schedule() -> switch_to()。函数intr32entry在数据段.data中,是中断入口函数用于保存当前线程上下文环境。

  1. 当发生时钟中断时,A将第一次执行。cpu把中断号0x20作为下标到中断门描述符数组idt中找到对应的元素(元素中包含地址),获取高16位地址和低16位地址,将地址合成为一个32为地址,然后跳到该地址指向的内存处执行。这个地址就是中断入口函数intr32entry的起始地址。
section .data
global intr_entry_table
intr_entry_table:         

section .text
intr32entry:		 
    push 0			 ; %2代表nop或push 0,中断若有错误码会压在eip后面 
; 以下是保存上下文环境
    push ds
    push es
    push fs
    push gs
    pushad			 ; PUSHAD指令压入32位寄存器,其入栈顺序是: EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI
    
    ; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI 
    mov al,0x20                   ; 中断结束命令EOI
    out 0xa0,al                   ; 向从片发送
    out 0x20,al                   ; 向主片发送
 
    ;0x20,即中断向量号压入栈中作为idt_table数组中某个元素所指向的中断处理程序的参数。
    push 0x20			  ; 不管idt_table中的目标程序是否需要参数,都一律压入中断向量号,调试时很方便
    call [idt_table + 32 * 4]     ; 对该地址通过中括号[]取值,调用idt_table中的C版本中断处理函数
    jmp intr_exit

section .data
    dd    intr32entry	          ; 存储各个中断入口程序的地址,形成intr_entry_table数组
  1. 在函数intr32entry中保存上下文,之后esp便指向了thread_stack中的func_arg字段,为什么这是寄存器esp指向func_arg字段?因为栈是向下增长的,压入esi、edi、vec_no之后就是func_arg字段了。又根据中断号0x20到中断处理程序数组idt_table中找到一个地址,该地址就是intr_timer_handler()函数的地址。然后调用intr_timer_handler()函数。
  2. 在调用intr_timer_handler()之前,call指令会将其下方的指令jmp intr_exit的地址压入thread_stack中的func_arg字段中,也就是此时寄存器esp指向栈顶,栈顶存放的是指令jmp intr_exit的地址。
  3. 在intr_timer_handler()函数中调用schedule(),在schedule()中调用switch_to(cur, next)时栈还会增长。这三个函数都是属于中断处理程序。注意与上图不同,此时eip字段的值不是kernel_thread()函数的地址,因为函数intr32entry中,执行call [idt_table + %1 * 4]时call指令就将其自身下一条指令jmp intr_exit的地址压入到了字段func_arg处,之后调用intr_timer_handler()函数再调用switch_to()函数还要往栈中压入数据,会将下面的function, unused_retaddr, eip, esi .....等字段全部覆盖掉。

thread\switch.S

switch_to:
   push esi
   push edi
   push ebx
   push ebp ; 栈顶指针,此时ebp的值是schedule()函数栈帧的ebp吗?是的

   mov eax, [esp + 20]
   mov [eax], esp            ;将当前栈顶指针esp保存到当前线程cur的PCB中的self_kstack成员中
;------------------  以上是备份当前线程cur的环境,下面是恢复下一个线程next的环境  ----------------
   ;获取栈中的next的值,也就是next线程的PCB地址
   mov eax, [esp + 24]		 ; 得到栈中的参数next, next = [esp+24]
   mov esp, [eax]		     ;将next线程的栈指针恢复到esp中

   pop ebp                   ; 每pop一次esp值加4
   pop ebx
   pop edi
   pop esi

   ret	;ret指令将寄存器esp中的地址当中的值送上eip执行

thread\thread.c

/* 实现任务调度 */
void schedule()
{
    ASSERT(intr_get_status() == INTR_OFF);

    // 通过running_thread()获取了当前运行线程的PCB,将其存入PCB指针cur中
    struct PCB_struct *cur = running_thread();

    // 若此线程只是cpu时间片到了,将其加入到就绪队列尾
    if (cur->status == TASK_RUNNING)
    {
            ASSERT(!elem_find(&thread_ready_list, &cur->general_tag));
            list_append(&thread_ready_list, &cur->general_tag);
            cur->ticks = cur->priority; // 重新将当前线程的ticks再重置为其priority;
            cur->status = TASK_READY;
    }else{} // 若此线程需要某事件发生后才能继续上cpu运行,不需要将其加入队列,因为当前线程不在就绪队列中。

    ASSERT(!list_empty(&thread_ready_list));
    thread_tag = NULL; // thread_tag清空

    //  将thread_ready_list队列中的第一个就绪线程弹出,准备将其调度上cpu. 
    thread_tag = list_pop(&thread_ready_list);

    // 要获得线程的信息,必须将其转换成PCB才行,因此用到了宏elem2entry,通过elem2entry获得新线程的PCB地址
    struct PCB_struct *next = elem2entry(struct PCB_struct, general_tag, thread_tag);

    next->status = TASK_RUNNING;

    // 击活任务页表等
    process_activate(next);

    switch_to(cur, next);
}
  1. 调用switch_to()

    1. 在schedule()调用switch_to(cur, next)进入到switch_to()函数中时esp指向M线程的PCB页中的栈顶。根据ABI规则执行完push esi, push edi, push ebx, push ebp, push ebp之后执行mov eax, [esp + 20]指令得到栈中的参数cur,[esp + 20]的位置是形参cur所在的位置。该地址指向cur线程的pcb页的地址也就是页的最低地址,也就是字段self_kstack所在的位置。指令mov [eax], esp将当前M线程栈顶指针esp保存到当前线程cur(也就是M线程)的PCB中的self_kstack字段中。[esp + 16]的位置是返回地址,[esp + 24]的位置是switch_to(cur, next)中的next参数的值。

    2. 然后执行mov eax, [esp + 24]和mov esp, [eax]指令将esi的上方8字节的位置也就是参数next的位置的值放入寄存器esp中。A线程的PCB页的地址放入寄存器eax中,因为PCB页的第一个字段是self_kstack,而self_kstack字段是个指针存放的是A线程的PCB页中thread_stack的esp字段的地址,所以执行mov esp, [eax]指令将字段esp的地址存入寄存器esp中。至此寄存器esp指向A线程的PCB页中的栈thread_stack的esp字段。

  2. 然后执行一系列pop之后esp指向eip字段,此时eip字段的值是kernel_thread()函数的地址,然后执行ret指令,ret指令会将esp指向的值弹出到eip中,然后cpu就跳到kernel_thread()函数所在的地址。在kernel_thread()函数中cpu会从esp+4处获取kernel_thread(thread_func *function, void *func_arg)函数的function的值,也就是A线程的PCB页中thread_stack的thread_func *function字段的值。从esp+8处获取func_arg参数的值,也就是A线程的PCB页中thread_stack的void *func_arg字段的值。

  3. 之后调用function()函数。

  4. 当A线程的时间片到了以后,B线程将会第一次执行,过程如上。

  5. 当B线程时间片到了,cpu根据中断号0x20到中断门描述符数组中找到对应的元素(元素中包含地址),获取高16位地址和低16位地址,将地址合成为一个32为地址,然后跳到该地址指向的内存处执行。这个地址就是函数intr32entry的起始地址。

  6. 在函数intr32entry中保存上下文,之后esp便指向thread_stack中的func_arg字段,之后又根据中断号0x20到中断处理
    程序数组idt_table中找到一个地址,该地址就是intr_timer_handler()函数的地址。然后intr_timer_handler()函数。

  7. 在调用intr_timer_handler()之前,call指令会将其下方的指令jmp intr_exit的地址压入thread_stack中的func_arg字
    段中,也就是此时寄存器esp指向栈顶,栈顶存放的是指令jmp intr_exit的地址。

  8. 然后在intr_timer_handler()函数中调用schedule(),在schedule()调用switch_to(cur, next)栈还会增长,也就是esp还会变化。这三个函数都是属于中断处理程序。

  9. 执行到switch_to()中时寄存器esp指向B线程的PCB页中的栈顶,执行push esi, push edi, push ebx, push ebp之后将esp的值存入B线程的PCB页存放esi的上方4字节的位置,也就是参数cur的位置。然后将esi的上方8字节的位置也就是参数next的位置的值也就是M线程的PCB页的地址放入寄存器esp中,因为M线程的PCB页的第一个字段是self_kstack所以存入esp中的地址也是self_kstack字段的地址,而self_kstack字段是个指针存放的是M线程的PCB页中thread_stack的栈顶的地址。接着执行mov esp, [eax]指令,esp就指向A线程的PCB页中thread_stack的栈顶的地址了。

  10. 恢复M线程的执行

    1. 在switch_to中执行一系列pop之后esp指向M线程的PCB页中thread_stack的栈中的一个位置,该位置存放的是M线程将要恢复执行的指令的地址,也就是在schedule()函数中调用switch_to(cur, next)函数的下一条指令,该条指令返回到schedule()函数的上一级函数intr_timer_handler()中,intr_timer_handler()函数返回到函数intr32entry中。
    2. 与此同时esp指向的栈中的值是指令jmp intr_exit,随即cpu控制器将esp指向的地址赋给cs:ip,cpu执行jmp intr_exit指令进入到恢复M线程的intr_exit函数中,将M线程PCB中的中断栈intr_stack的值一一pop到对应的寄存器中,最后执行iretd指令恢复M线程的执行。
struct thread_stack
{
        uint32_t ebp; // 0-3
        uint32_t ebx; // 4-7
        uint32_t edi; // 8-11
        uint32_t esi; // 12-15

        void (*eip)(thread_func *func, void *func_arg); // 16-19
        void(*unused_retaddr); //充当返回地址,在返回地址所在的栈帧占个位置
        thread_func *function; // 由函数Kernel_thread所调用的函数名
        void *func_arg;        // 由函数Kernel_thread所调用的函数所需的参数
};
// 在PCB中的栈thread_stack中的顺序
|—————————————————————————————————————————————————————————————————————————————————|
|                        void *func_arg;                                          |\
|—————————————————————————————————————————————————————————————————————————————————| \
|                        thread_func *function;                                   |  \
|—————————————————————————————————————————————————————————————————————————————————|   \
|                        void(*unused_retaddr);                                   |    \
|—————————————————————————————————————————————————————————————————————————————————|     |
|                        void (*eip)(thread_func *func, void *func_arg);          | thread_stack
|—————————————————————————————————————————————————————————————————————————————————|     |
|                        uint32_t esi;                                            |     |
|—————————————————————————————————————————————————————————————————————————————————|     /
|                        uint32_t edi;                                            |    /
|—————————————————————————————————————————————————————————————————————————————————|   /
|                        uint32_t ebx;                                            |  /
|—————————————————————————————————————————————————————————————————————————————————| /
|                        uint32_t ebp;                                            |/
|—————————————————————————————————————————————————————————————————————————————————|  

总之,线程在第一次调用时就会将PCB中的线程栈thread_stack的字段覆盖掉了,已不是线程第一次运行前的样子了。

  1. 注意因为之前M线程已经执行过了,所以此时eip字段的值不是kernel_thread()函数的地址,在第一次调用时就覆盖了。当A B线程第一次执行时 M -> A,A -> B的过程中在switch_to(cur, next)函数中指令ret是将kernel_thread()函数的地址送上cpu执行,往后每次在switch_to(cur, next)函数中指令ret是将switch_to(cur, next)函数的返回地址送上cpu执行的,也就是在schedule()函数中call switch_to指令的下面一条指令的地址送上cs:ip。该指令也是一条返回指令所以返回到上一级函数intr_timer_handler()中,intr_timer_handler()函数返回到函数intr32entry中。因此AB线程的第一次调用和后面的每次调用是不同的。
section .text
global intr_exit
intr_exit:	     
; 以下是恢复上下文环境
    add esp, 4			   ; 跳过中断号
    popad
    pop gs
    pop fs
    pop es
    pop ds
    add esp, 4			   ; 跳过error_code
    iretd
  1. 在函数intr_exit中执行add esp, 4跳过中断号让esp寄存器执行字段edi。将M线程PCB中的栈thread_stack中的内容恢复到各个寄存器中。
|—————————————————————————————————————————————————————————————————————————————————|
|                         uint32_t esi;                                           |
|—————————————————————————————————————————————————————————————————————————————————|
|                         uint32_t edi;                                           |
|—————————————————————————————————————————————————————————————————————————————————|
|                         uint32_t vec_no;                                        |
|—————————————————————————————————————————————————————————————————————————————————|
|                         void *func_arg;                                         |
|—————————————————————————————————————————————————————————————————————————————————|

至此M线程恢复执行。

问题

  这里有个问题,esp的值得改变并不会把PCB中PCB_struct中字段覆盖掉。因为PCB_struct在PCB页的最低端地址,intr_stack在PCB页的最高端地址,thread_stack在intr_stack下面。thread_stack与PCB_struct中间还有一大段内存区域,因此调度程序产生的栈增加不会覆盖掉PCB_struct所在的内存。除非调度程序很大,调度程序要调用的函数非常多往线程PCB的栈thread_stack中压入太多数据将魔数覆盖掉了。

总结

  1. 在intr32entry函数中要做的事情是把即将要中断的线程也就是M线程的上下文环境保存到M线程PCB的栈intr_stack中,然后调用调度器程序。intr32entry -> intr_timer_handler() -> schedule() -> switch_to()。
  2. 在switch_to()中做的事情是将调度程序的上下文环境保存到M线程PCB的栈thread_stack中。然后恢复A线程PCB的栈thread_stack中的上下文。

kernel.s文件的作用是保存和恢复线程的上下文环境,switch.S文件的作用使用保存和恢复调度程序的的上下文环境。

排错心得记录

一、

  复习了ELF文件的结构,加载应用文件的过程,以及此系统中内存地址是如何分配的。此系统中的虚拟地址是从0x08048000开始分配的,虚拟地址位图的起始地址是0x08048000,位图中的1位映射1页,每分配1页对应的位图中的位置为1。
  悲剧的是之前在测试的时候在sys_getcwd()函数中将一条语句sys_free()注释掉了,导致分配的0x08048000未能释放,于是在file_read()函数中调用sys_malloc()分配内存块所在的页是0x08049000。而应用程序需要复制4792个字节到起始地址0x08048000处,将前2页覆盖掉了,导致第2页0x08049000到0x08049fff释放的时候在sys_free()函数中报错。

二、

  问题到此差不多解决了,但运行的时候出现不断fork新线程的的情况。昨天9点多钟感觉太累了,上床睡觉到凌晨1点多起床5点多才睡期间无所事事都在看手机。今天突然想起来之前的compile.sh文件是直接从15_5节复制过来的,没有将该路径于是将这一节的应用程序写到了15_5节的disk.img硬盘中,15_5_4节(上一节)的disk.img硬盘中什么都没有,而15_5_4节main函数中是在disk80.img硬盘创建一个文件并将disk.img硬盘的第300个扇区中的内容复制到disk80.img硬盘的文件系统中。15_5_4节的disk.img中什么都没有,复制了个寂寞,当时找错找了好久。
  由于之前在15_5节中测试的时候,将disk80.img硬盘的文件系统中的prog_no_arg应用程序删掉了,之后又运行了一次。于是在disk80.img硬盘中的文件系统中创建了一个名为prog_no_arg的文件,却将prog_arg应用程序中的内容复制了进去。
  15_5_4节的disk80.img硬盘是从15_5节复制过来的,于是15_5_4节中的disk80.img中的文件系统中就有了一个名为prog_no_arg实为prog_arg的应用程序。
  15_5_4节程序设计的初衷是prog_arg将prog_no_arg作为参数,fork一个新线程作为prog_arg线程,原来的线程变成prog_no_arg线程。但是prog_no_arg线程中的内容实际上是prog_arg的内容。所以出现prog_arg将prog_no_arg作为参数,prog_no_arg又将prog_arg作为参数互相调用,所以出现了不断fork新线程的情况!(╥﹏╥) 真不容易!

/**    		               程序控制块PCB结构(4K大小)
****************************************************************************************
*
*   |—————————————————————————————————————————————————————————————————————————————————|高地址
*   |                           uint32_t ss;                                          |\
*   |—————————————————————————————————————————————————————————————————————————————————| \
*   |                           void *esp;                                            |  \
*   |—————————————————————————————————————————————————————————————————————————————————|   \
*   |                         uint32_t eflags;                                        |    \
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                           uint32_t cs;                                          |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                      void (*eip)(void);                                         |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                    uint32_t err_code;                                           |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                        uint32_t ds;                                             |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                       uint32_t es;                                              |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                       uint32_t fs;                                              |     |
*   |—————————————————————————————————————————————————————————————————————————————————|  intr_stack
*   |                        uint32_t gs;                                             |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                         uint32_t eax;                                           |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                         uint32_t ecx;                                           |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                         uint32_t edx;                                           |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                          uint32_t ebx;                                          |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                         uint32_t esp_dummy;                                     |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                        uint32_t ebp;                                            |     |
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                         uint32_t esi;                                           |    /
*   |—————————————————————————————————————————————————————————————————————————————————|   /
*   |                         uint32_t edi;                                           |  /
*   |—————————————————————————————————————————————————————————————————————————————————| /
*   |                         uint32_t vec_no;                                        |/
*   |—————————————————————————————————————————————————————————————————————————————————|
*   |                         void *func_arg;                                         |\
*   |—————————————————————————————————————————————————————————————————————————————————| \
*   |                        thread_func *function;                                   |  \
*   |—————————————————————————————————————————————————————————————————————————————————|   \
*   |                         void(*unused_retaddr);                                  |    \
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                        void (*eip)(thread_func *func, void *func_arg);          | thread_stack
*   |—————————————————————————————————————————————————————————————————————————————————|     |
*   |                      uint32_t esi;                                              |     |
*   |—————————————————————————————————————————————————————————————————————————————————|    /
*   |                       uint32_t edi;                                             |   /
*   |—————————————————————————————————————————————————————————————————————————————————|  /
*   |                         uint32_t ebx;                                           | /
*   |—————————————————————————————————————————————————————————————————————————————————|/
*   |                        uint32_t ebp;                                            |<-----.
*   |—————————————————————————————————————————————————————————————————————————————————|       \
*   |                                :                                                |        \
*   |                                :                                                |         \
*   |                                :                                                |         |
*   |                                :                                                |         |
*   |                                :                                                |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                          uint32_t stack_magic;                                  |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                         int8_t exit_status;                                     |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                          pid_t parent_pid;                                      |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                         uint32_t cwd_inode_nr;                                  |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                          int32_t fd_table[MAX_FILES_OPEN_PER_PROC];             |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                     struct mem_block_desc u_block_desc[DESC_CNT];               |         |
*   |—————————————————————————————————————————————————————————————————————————————————|       指|
*   |                  struct virtual_addr userprog_vaddr;                            |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                       uint32_t *pgdir;                                          |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                      struct list_elem all_list_tag;                             |       向|
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                      struct list_elem general_tag;                              |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                       uint32_t elapsed_ticks;                                   |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                       uint8_t ticks;                                            |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                        uint8_t priority;                                        |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                          char name[TASK_NAME_LEN];                              |         |
*   |—————————————————————————————————————————————————————————————————————————————————|         |
*   |                         enum task_status status;                                |         /
*   |—————————————————————————————————————————————————————————————————————————————————|        /
*   |                         pid_t pid;                                              |       /
*   |—————————————————————————————————————————————————————————————————————————————————|      /
*   |                      uint32_t *self_kstack;                                     |-----'
*   |—————————————————————————————————————————————————————————————————————————————————|低地址
*****************************************************************************************/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值