• 分析概览 文章管理 草稿管理 图片管理 站点管理 主站 关于 登出 手写操作系统项目----进程


    大家好,我叫徐锦桐,个人博客地址为www.xujintong.com。平时记录一下学习计算机过程中获取的知识,还有日常折腾的经验,欢迎大家来访。

    这里记录了,手写操作系统项目中关于进程的部分。

    进程四要素

    首先进程有四要素。

    • 有一段程序代其执行
    • 有进程专用的系统堆栈空间
    • 在内核有task_struct数据结构
    • 进程有独立的存储空间,拥有专有的用户空间

    如果具备前三条缺少第四条,那就称为线程。如果完全没有用户空间,就称为 内核线程 。如果共享用户空间就称为用户线程

    进程初始化

    手写操作系统项目的进程初始化的源代码如下:

    ```cpp
    /**
     * @brief 初始化任务
     */
    int task_init (task_t * task, const char * name, int flag ,uint32_t entry, uint32_t esp) {
        ASSERT(task != (task_t *)0);
    
        int err = tss_init(task, flag, entry, esp);
        if (err < 0) {
            log_printf("init task failed.\n");
            return err;
        }
    
        // 任务字段初始化
        kernel_strncpy(task->name, name, TASK_NAME_SIZE);
        task->state = TASK_CREATED;
        task->sleep_ticks = 0;
        task->parent = (task_t *)0;
        task->heap_start = 0;
        task->heap_end = 0;
        task->time_ticks = TASK_TIME_SLICE_DEFAULT;
        task->slice_ticks = task->time_ticks;
        task->state = 0;
        list_node_init(&task->all_node);
        list_node_init(&task->run_node);
        list_node_init(&task->wait_node);
    
        // 文件相关
        kernel_memset(task->file_table, 0, sizeof(task->file_table));
    
        // 插入就绪队列中和所有的任务队列中
        irq_state_t state = irq_enter_protection();
        task->pid = (uint32_t)task;
        list_insert_last(&task_manager.task_list, &task->all_node);
        irq_leave_protection(state);
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    接下来,我为你讲解这个进程初始化函数的各个部分。

    初始化TSS

    TSS前置知识

    TSS是x86系统上的一个结构,保存了当前任务的状态信息,比如运行到了哪,当前任务的寄存器,CPU用来进行任务调度。
    当进行任务切换的时候,就把TSS取出来然后恢复要切换的任务的状态。TR寄存器中存储着当前运行进程的TSS结构。进程的task_struct结构中存储着该进程的tss结构,源码如下:

    typedef struct _task_t {
        ...
        
        tss_t tss;                  // 任务的TSS段
        int tss_sel;                // tss选择子
    }task_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    TSS过程.webp


    下面是TSS的具体结构,具体的各个结构内容我们就不过多叙述了,如果真想要了解各个位所代表的是什么,大家可以看IA-32手册。
    TSS结构.webp


    注: TSS是x86系统的特性,在当前64位操作系统已经被抛弃。

    项目中TSS初始化

    我们的项目中并没有用到这么多的,只用到了一部分。具体项目源码如下:

    /**
     * tss描述符
     */
    typedef struct _tss_t {
        uint32_t pre_link;  // 没用到
        uint32_t esp0, ss0, esp1, ss1, esp2, ss2;
        uint32_t cr3;
        uint32_t eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;
        uint32_t es, cs, ss, ds, fs, gs;
        uint32_t idt;   // 没用到
        uint32_t iomap;     // 没用到
    }tss_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    首先给出项目中tss初始化的源码。

    static int tss_init (task_t * task, int flag ,uint32_t entry, uint32_t esp) {
        // 为TSS分配GDT
        int tss_sel = gdt_alloc_desc();
        if (tss_sel < 0) {
            log_printf("alloc tss failed.\n");
            return -1;
        }
    
        segment_desc_set(tss_sel, (uint32_t)&task->tss, sizeof(tss_t),
            SEG_P_PRESENT | SEG_DPL0 | SEG_TYPE_TSS
        );
        
        
        // tss段初始化
        kernel_memset(&task->tss, 0, sizeof(tss_t));
    
        // 分配内核栈,得到的是物理地址
        uint32_t kernel_stack = memory_alloc_page();   // 分配一页内存    用于中断、系统异常、系统调用
        if (kernel_stack == 0) {
            goto tss_init_failed;
        }
    
    
        // 根据不同的权限选择不同的访问选择子
        int code_sel, data_sel;
        if (flag & TASK_FLAGS_SYSTEM) {
            code_sel = KERNEL_SELECTOR_CS;
            data_sel = KERNEL_SELECTOR_DS;
        } else {
            // 注意加了RP3,不然将产生段保护错误
            code_sel = task_manager.app_code_sel | SEG_CPL3;
            data_sel = task_manager.app_data_sel | SEG_CPL3;
        }
        
        task->tss.eip = entry;
        task->tss.esp = esp ? esp : kernel_stack + MEM_PAGE_SIZE;
        task->tss.esp0 = kernel_stack + MEM_PAGE_SIZE;
        // task->tss.ss = data_sel;
        task->tss.ss0 = KERNEL_SELECTOR_DS;
         task->tss.eip = entry;
        task->tss.es = task->tss.ds = task->tss.ss = task->tss.fs = task->tss.gs = data_sel;    // 全部采用同一数据段s
        task->tss.cs = code_sel;
        task->tss.eflags = EFLGAGS_IF | EFLGAGS_DEFAULT;
        task->tss.iomap = 0;
        
        // 页表初始化
        uint32_t page_dir = memory_create_uvm();
        if (page_dir == 0) {
            goto tss_init_failed;
        }
        task->tss.cr3 = page_dir;
        
        task->tss_sel = tss_sel;
        return 0;
    tss_init_failed:
        // 如果创建页表失败
        gdt_free_sel(tss_sel);
        if (kernel_stack) {
            memory_free_page(kernel_stack);
        }
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    首先通过int tss_sel = gdt_alloc_desc()代码分配一个GDT(GDT表是从第一项开始的,第0项不分配)。gdt_alloc_desc()源码如下:

    /**
     * 分配一个GDT推荐表符
     */
    int gdt_alloc_desc() {
        mutex_lock(&mutex);
        // 跳过第0项
        for (int i = 1; i < GDT_TABLE_SIZE; i ++ ) {
            segment_desc_t * desc = gdt_table + i;
            if (desc->attr == 0) {
                mutex_unlock(&mutex);
                return i * sizeof(segment_desc_t);
            }
        }
        mutex_unlock(&mutex);
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    可以看到就是遍历一下GDT表,看哪个表项没有被分出去,然后分配给当前TSS。

    初始化task_struct结构

    操作系统为每个进程分配一个task_struct结构,用以描述该进程,也就相当于一个进程的简历,写了进程的信息,进程的状态、父进程、进程的pid,进程的名字等等。

    /**
     * @brief 任务控制块结构
     */
    typedef struct _task_t {
        // uint32_t * stack;
        // 这是个枚举数据类型,递增的宏定义,默认第一个为0,每次加1.
        enum {
            TASK_CREATED,
            TASK_RUNNING,
            TASK_SLEEP,
            TASK_READY,
            TASK_WAITTING,   // 等待时间
            TASK_ZOMBIE,    // 将死状态
        }state;
    
        int pid;                        // 进程的pid
        struct _task_t * parent;        // 父进程
        uint32_t heap_start;            // 堆的顶层地址
        uint32_t heap_end;              // 堆结束地址
        int status;                     // 进程执行结果
    
        int sleep_ticks;        // 睡眠时间
        int time_ticks;         // 设置计数器   时间片
        int slice_ticks;        // 递减时间片计数
    
        file_t * file_table[TASK_OFILE_NR];      // 记录进程打开了哪些文件  任务最多打开的文件数量
    
        char name[TASK_NAME_SIZE];      // 任务名字
    
        list_node_t run_node;           // 运行相关结点
        list_node_t wait_node;          // 等待队列
        list_node_t all_node;           // 所有队列结点
    
        tss_t tss;                  // 任务的TSS段
        int tss_sel;                // tss选择子
    }task_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    linux源码中的task_struct描述符,里面包含很多的变量。我这个操作系统知识demo级别的,所以用到的并不多。


    可以看到我们是用pid来区分不同的进程,task_struct里面还有该进程的名字,该进程的堆栈空间地址。


    进程初始化有一部分就是初始化task_struct这个结构中的信息。

    将当前任务插入到所有任务队列中

    关于这个代码list_insert_last(&task_manager.task_list, &task->all_node);,这个是将当前任务加入所有任务队列中去。
    task_manager是个task_manager_t的结构类型。结构的源码如下:

    typedef struct _task_manager_t {
        task_t * curr_task;     // 当前运行的任务
    
        list_t ready_list;      // 就绪队列
        list_t task_list;       // 保存所有已经创建好的进程 所有已创建任务的队列
        list_t sleep_list;      // 睡眠队列 延时队列
    
        task_t first_task;      // 内核任务
        task_t idle_task;       // 空闲任务
    
        int app_code_sel;       // 任务代码段选择子
        int app_data_sel;       // 应用任务的数据段选择子
    }task_manager_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这里面记录了当前运行的任务,就绪队列,已创建好的所有进程队列等等关于进程的队列。

    进程切换

    进程切换,项目中就只切换了两个进程,一个init_task和一个first_task。就两个进程就好说了,切换的时候传入两个task_struct的地址,然后利用长跳指令跳到新的进程的TSS结构,这个TSS结构包含了新进程的上下文信息,硬件(硬件根据选择子判断是不是TSS结构)会自动将这些信息加载到各个寄存器中。


    任务切换中,cpu会把当前寄存器的数据保存到当前(旧的)tr寄存器所指向的tss数据结构里,然后把新的tss数据复制到当前寄存器里。这些操作是通过cpu的硬件实现的

        task_init(&init_task, (uint32_t)init_task_entry, (uint32_t)&init_task_stack[1024]);
        task_init(&first_task, 0, 0);  // 后面两个参数为0:first_task跑起来后已经运行,不需要从tss中加载初始化的值,因此里面的值无所谓,后面切换的时候也会保存状态。
        write_tr(first_task.tss_sel);  // 对任务寄存器tr进行初始化
    
        int count = 0;
        for (;;) {
            log_printf("int main %d", count++);
            task_switch_from_to(&first_task, &init_task);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    先初始化的两个任务,然后将当前任务的tss选择子存到TR寄存器中。task_switch_from_to(&first_task, &init_task);就是从first_task切换到init_task。

    void switch_to_tss (int tss_sel) {
        far_jump(tss_sel, 0);
    }
    
    static inline void far_jump (uint32_t selector, uint32_t offset) {
        uint32_t addr[] = {offset, selector};
    
        __asm__ __volatile__("ljmpl *(%[a])"::[a]"r"(addr));
    }
    
    void task_switch_from_to (task_t * from, task_t * to) {
        // 简单的用jmp到对应的tss选择子进行任务切换
        switch_to_tss(to->tss_sel);
        // simple_switch(&from->stack, to->stack);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    就是长跳到另一个进程的tss位置,然后硬件会自动将当前TSS中的信息加载到各个寄存器,将tss位置存到TR寄存器中。

  • 相关阅读:
    2022年10月27日下午工作日志
    边缘计算的AI小板——OrangePi AI Pro
    [附源码]Python计算机毕业设计Django校园代取快递系统
    HTML网页设计——轮滑运动体育类人物介绍主题12页面毕业设计网页
    猿创征文|『单片机原理』程序存储器的结构
    一天吃透MySQL面试八股文
    JAVA计算机毕业设计毕业生信息管理系统Mybatis+源码+数据库+lw文档+系统+调试部署
    【Designing ML Systems】第 8 章 :数据分布转移和监控
    2.0、C语言——分支、循环语句
    【Unity】自定义Untiy天空
  • 原文地址:https://blog.csdn.net/Roger_Spencer/article/details/133972901