手写操作系统项目----进程切换

讲解了项目中是如何进程进程切换的。\
项目地址:https://github.com/xjintong/simpleOS

一、宏观进程切换

task.h中定义了一个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;

这里定义了当前运行的任务,就绪队列、睡眠队列等等,进行任务切换的时候,要操作这个结构。\

进程切换的具体源码如下:

int sys_sched_yield() {
    // 进入中断保护--保存当前寄存器状态,然后关闭中断。
    irq_state_t state = irq_enter_protection();

    if (list_count(&task_manager.ready_list) > 1) {
        task_t * curr_task = task_current();

        // 如果队列中还有其它任务,则将当前任务移入到队列尾部
        task_set_block(curr_task);
        task_set_ready(curr_task);    // 再加入的时候,是加入队列的尾部

        // 切换至下一个任务,在切换完成前要保护,不然可能下一任务
        // 由于某些原因运行后阻塞或删除,再回到这里切换将发生问题
        task_dispatch();
    }

    // 退出中断保护,恢复中断前的寄存器的值
    irq_leave_protection(state);
    // 如果就绪队列里面就1个进程。
    return 0;
}
  • 将中断关闭irq_enter_protection()(防止进程切换过程中,被中断打断造成一些错误)。
  • 然后判断当前就绪队列中有多少个任务,如果只有这个任务就不切换了;如果有多个任务,就先将当前任务放到就绪队列的尾部(先将任务从就绪队列中移除--task_set_block(curr_task);,然后将任务放到就绪队列尾部--task_set_ready(curr_task);)。
  • 最后切换到下一个进程。

二、细扣切换的函数

task_dispatch();源码如下:

void task_dispatch (void) {

    irq_state_t state = irq_enter_protection();

    task_t * to = task_next_run();
    if (to != task_manager.curr_task) {
        task_t * from = task_manager.curr_task;
        task_manager.curr_task = to;
        to->state = TASK_RUNNING;

        task_switch_from_to(from, to);
    }

    irq_leave_protection(state);

}
  • 进入中断保护
  • 从就绪队列中取出下一个任务。(task_t * to = task_next_run();
  • 通过task_manager_t结构体获取当前任务(from = task_manager.curr_task;
  • task_manager_t中当前运行的任务改为to任务。
  • 将to任务的状态改为TASK_RUNNING。
  • 通过task_switch_from_to(from, to)进行进程上下文的切换。

task_switch_from_to(from, to)的过程

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

任务切换中,cpu会把当前寄存器的数据保存到当前(旧的)tr寄存器所指向的tss数据结构里,然后把新的tss数据复制到当前寄存器里。这些操作是通过cpu的硬件实现的\
先初始化的两个任务,然后将当前任务的tss选择子存到TR寄存器中。\

注意: 下面函数的顺序并不是实际项目中的顺序,这里只是按执行的顺序放在这的,方便分析。

/**
 * @brief 切换至指定任务
 */
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,即跳转实现任务切换
 */
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));
}

就是通过tss的选择子在GDT中找到下一个任务的tss地址,然后长跳到另一个进程的tss位置,然后硬件会自动将当前TSS中的信息加载到各个寄存器,将tss位置存到TR寄存器中。

三、总结整个流程

  • 1、如果就绪队列中有多个任务才进行切换。\
    如果有多个任务,就先将当前任务放到就绪队列的尾部(先将任务从就绪队列中移除--task_set_block(curr_task);,然后将任务放到就绪队列尾部--task_set_ready(curr_task);)。
  • 2、从就绪队列中取出下一个任务。(task_t * to = task_next_run();
  • 3、将task_manager_t中当前运行的任务改为 to 任务。
  • 4、将 to 任务的状态改为 TASK_RUNNING。
  • 5、进行TSS结构的切换。\
    就是通过tss的选择子在GDT中找到下一个任务的tss地址,然后长跳到另一个进程的tss位置,然后硬件会自动将当前TSS中的信息加载到各个寄存器,将tss位置存到TR寄存器中。
THE END