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

news2024/12/25 13:52:15

大家好,我叫徐锦桐,个人博客地址为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;
}

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

初始化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;

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;

首先给出项目中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;
}

首先通过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;
}

可以看到就是遍历一下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;

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;

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

进程切换

进程切换,项目中就只切换了两个进程,一个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);
    }

先初始化的两个任务,然后将当前任务的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);
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1120798.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

golang 八股文整理

目录 进程、线程、协程Go 的垃圾回收机制GC 的触发条件GC 的调优GMP 调度和 CSP 模型Goroutine 的调度原理Goroutine 的切换时机Context 结构原理Context 工作原理Context 使用场景Golang 的内存分配机制竞态问题内存逃逸golang 内存对齐机制golang 中 new 和 make 的区别&…

数二思维导图

高数上 第一章&#xff1a;函数、极限、连续 函数 函数的单调性、周期性、奇偶性复合函数 极限 求直接代入型的极限求∞∞型的极限用等价无穷小代换求00型的极限用洛必达法则求00型或∞∞型的极限求∞•0型的极限求幂指函数的极限函数的左右极限及需要求左右极限的情形极限的…

还不知道光场相机吗?

1.什么是光场&#xff1f; 光场&#xff08;light field&#xff09;&#xff1a;就是指光在每一个方向通过每一个点的光量。 从概念里&#xff0c;你至少可以得到两点信息&#xff1a; 光场包含光的方向光场包含一个点的光量 2.什么是光场相机 我们知道普通的相机拍照成像…

Parallels Client for Mac:改变您远程控制体验的革命性软件

在当今数字化的世界中&#xff0c;远程控制软件已经成为我们日常生活和工作中不可或缺的一部分。在众多远程控制软件中&#xff0c;Parallels Client for Mac以其独特的功能和出色的性能脱颖而出&#xff0c;让远程控制变得更加简单、高效和灵活。 Parallels Client for Mac是…

Redis的五种常用(基本)数据类型

目录 1、Redis简介 2、五种常用&#xff08;基本&#xff09;数据类型 2.1 String 数据结构 ⭐常用用法 举例&#xff08;Linux版本&#xff09; 2.2 List 数据结构 ⭐常用用法 举例&#xff08;Linux版本&#xff09; 2.3 Set 数据结构 ⭐常用用法 举例&#xf…

【未完待续】计算机组成与体系结构第三次试验:微程序控制器实验

计算机组成与体系结构第三次试验&#xff1a;微程序控制器实验 前言一、实验目的二、实验内容三、实验器件四、实验原理五、 实验步骤六、 实验结果七、思考题 前言 为了帮助同学们完成痛苦的实验课程设计&#xff0c;本作者将其作出的实验结果及代码贴至CSDN中&#xff0c;供…

机器学习中的核方法

一、说明 线性模型很棒&#xff0c;因为它们易于理解且易于优化。他们受苦是因为他们只能学习非常简单的决策边界。神经网络可以学习更复杂的决策边界&#xff0c;但失去了线性模型良好的凸性特性。 使线性模型表现出非线性的一种方法是转换输入。例如&#xff0c;通过添加特征…

BetaFlight飞控AOCODAF435V2MPU6500固件编译

BetaFlight飞控AOCODAF435V2MPU6500固件编译 1. 源由2. 准备2.1 板子2.2 代码2.3 工具 3. 配置修改4. 编译4.1 获取代码4.2 获取配置4.3 编译固件4.4 DFU烧录4.5 版本核对 5. 总结 1. 源由 刚拿到一块Aocoda F405V2 (MPU6500) AT32F435飞控板(替换主控芯片)。 Aocoda-RC F40…

unity中方向的两种表示:欧拉角和四元数

欧拉角&#xff1a;简单来说就是你可以选择 0度~360度 的范围 四元数&#xff1a;在计算机图像学中&#xff0c;四元数用于物体的旋转&#xff0c;是一种复杂&#xff0c;但效率较高的旋转方式 Quaternion结构体代表一个四元数&#xff0c;包含一个标量和一个三维向量&#x…

02、Python 字符串

目录 字符串的基础用法字符串包含引号字符串拼接获取用户输入长字符串原始字符串字节串字符串与字节串转换 字符串的基础用法 列字符串的内容几乎可以包含任何字符&#xff0c;英文字符也行&#xff0c;中文字符也行。 既可用单引号&#xff0c;也可用双引号 字符串包含引号…

函数和执行上下文

一.变量提升与函数提升 变量提升&#xff1a;通过var关键字定义&#xff08;声明&#xff09;的变量&#xff0c;在定义语句之前就可以访问到&#xff0c;只不过其值是undefined 函数提升&#xff1a;通过function声明的函数&#xff0c;在之前就可以调用&#xff0c;值是函数…

onehot-词嵌入-图嵌入

目录 一、为什么要有词嵌入&#xff1f; 二、one-hot编码&#xff1a; 三、什么是词嵌入&#xff08;word embedding&#xff09; 1、什么是嵌入矩阵&#xff1f; 2、为什么要设置维数&#xff1f; 3、相比one-hot编码的优点 4、什么是word2vec和GLove&#xff1f; 四、…

【计算机毕设案例推荐】高校学术研讨信息管理系统小程序SpringBoot+Vue+小程序

前言&#xff1a;我是IT源码社&#xff0c;从事计算机开发行业数年&#xff0c;专注Java领域&#xff0c;专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务 项目名 基于SpringBoot的高校学术研讨信息管理系统小程序 技术栈 SpringBoot小程序VueMySQLMaven 文…

珠宝行业软件,虽简约但不简单

作者&#xff1a;永远的新手 从学习猫框以来&#xff0c;一直向猫老师请教如何学习猫框和VFP的基础知识&#xff0c;猫老师不厌其烦传授知识于我。因为我是一位纯业余VFP爱好者&#xff0c;我的VFP几乎是零基础&#xff0c;接触猫框后&#xff0c;虽说时间很短&#xff0c;但其…

快来跟我一起抢先看看未来世界的出行,体验未来城市吧~

体验平台&#xff1a;Pony Robotaxi&#xff0c;Apollo Robotaxi&#xff0c;如棋Robotaxi 本文关键词Apollo&#xff0c;自动驾驶&#xff0c;智能出行&#xff0c;无人公交&#xff0c;无人清扫车等 感受未来世界的出行&#xff0c;体验未来城市&#xff01; 一、未来智能出行…

C++ vector 的模拟实现

目录 1. vector 类的成员变量 2. 无参构造 3. 析构函数 4. size_t capacity() 5. size_t size() 6. void reserve(size_t n) 7. 迭代器 8. void push_back(const T& x) 9. T& operator[](size_t pos) 10. iterator insert(iterator pos, const T& val…

【哈希表完整代码】模拟实现哈希表和unordered_set与unordered_map

目录 HashTable.h: Test.cpp: MyUnorderedSet.h: HashTable.h: #pragma once #include<iostream> #include<vector> #include<utility>//pair头文件 #include<assert.h> #include<string>using namespace std;namespace CLOSEHASH {enum Sta…

mk语法示例

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

【会员管理系统】篇一之项目预热

一、技术架构 vue.js vueCLI 3.x Axios babel EcmaScript6 Eslint Mock.js Easy-Mock Element UI Vuex 二、RESTful风格 三、Mock.js简单使用 1、简单使用 &#xff08;1&#xff09;新建文件夹&#xff0c;在vscode中打开&#xff0c;使用终端…