MacOS环境-手写操作系统-32-进程挂起和恢复

简介: MacOS环境-手写操作系统-32-进程挂起和恢复

进程挂起和恢复

1.简介

有了进程的自动调度后 接下来的任务在于


如何将空闲进程挂起 空闲进程往往是那些没有具体任务需要处理的进程


因此 如果继续让其运行的话 那么必然会耗费宝贵的CPU资源


如果能让它先挂起 等到它需要执行具体任务时 再把它调度到前台 那才是一种合理的进程管理机制


我们实现的进程调度 是依赖于进程控制器


也就是taskctl中的任务数组来实现的 当我们想要启动某个进程时


在该数组中找到对应的任务对象 然后把它加载到CPU那就可以了


2.代码

修改multi_task.c 增加一个task_sleep函数

void task_sleep(struct TASK *task) {
   int  i;
   char ts = 0;
   if (task->flags == 2) {
        if (task == taskctl->tasks[taskctl->now]) {
            ts = 1;
        }

    for (i = 0; i < taskctl->running; i++) {
        //在任务数组中找到要挂起的进程对象
        if (taskctl->tasks[i] == task) {
            break;
        }
    }

    taskctl->running--;
    if (i < taskctl->now) {
        taskctl->now--;
    }

    for(; i < taskctl->running; i++) {
        //通过把后面的任务往前覆盖,实现将当前任务从任务列表中移除的目的
        taskctl->tasks[i] = taskctl->tasks[i+1];
    }

    task->flags = 1;
    if (ts != 0) {
        //如果当前挂起的任务正好是当前正在前台运行的任务,那么将第0个任务调度到前台
        if (taskctl->now >= taskctl->running) {
            taskctl->now = 0;
        }

       farjmp(0, taskctl->tasks[taskctl->now]->sel);
    }

   }

   return;
}

该函数的逻辑是 根据要挂起的任务 在整个任务数组中查找 找到其对应的数组下标


然后把后面的任务向前覆盖 这样的话 要移除的任务就在数组中就会被覆盖掉


从而实现将任务从数组中移除的目的


需要注意的是 如果要挂起的任务 正好是当前正在前台运行的进程 那么ts==1


我们就把下标为0的任务调度到前台 并且把任务的数量也就是running的值减一


这样 处于数组最后的那个任务将不会有机会被调度


任务挂起是实现了 那么当我们想重新把任务调度到前台时


我们可以利用现有的队列机制 一旦鼠标 键盘的事件发生时


我们会把硬件产生的数据加入到他们对应的队列中 然后在CMain主循环中 将队列中的数据取出来处理


同理 当我们挂起一个任务时 我们把挂起的任务对象放入到一个队列中 当想要重新调度这个对象时


我们往队列里发送一个数据 然后在主循环中对该队列进行检查


一旦发现队列中含有数据的话 那么就把队列中寄存的任务重新加入调度数组


代码修改如下,在global_define.c中


void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf, 
    struct TASK *task) {
    fifo->size = size;
    fifo->buf = buf;
    fifo->free = size;
    fifo->flags = 0;
    fifo->p = 0;
    fifo->q = 0;
    fifo->task = task;
    return ;
}


在初始化一个队列时 把一个任务对象添加进去


如果队列不需要寄存任务对象 那么把task设置为0就可以

int fifo8_put(struct FIFO8 *fifo, unsigned char data) {
    if (fifo == 0) {
        return -1;
    }    

    if (fifo->free ==0) {
        fifo->flags |= FLAGS_OVERRUN;
        return -1;
    }

    fifo->buf[fifo->p] = data;
    fifo->p++;
    if (fifo->p == fifo->size) {
        fifo->p = 0;
    }

    fifo->free--;

    if (fifo->task != 0) {
        if (fifo->task->flags != 2) {
            task_run(fifo->task);
        }
    }

    return 0;
}

当队列中有数据加入时 顺便查看该队列是否寄存着一个任务对象


如果是 那么把该任务对象加入调度数组


由于timer.c中 对计时器的运行需要使用到队列 既然队列的数据结构有变动


因此timer.c中 需要做一点小改动

static struct TIMERCTL timerctl;
extern struct TIMER *task_timer;

void  init_pit(void) {
    io_out8(PIT_CTRL, 0x34);
    io_out8(PIT_CNT0, 0x9c);
    io_out8(PIT_CNT0, 0x2e);

    timerctl.count = 0;
    int i;
    for (i = 0; i < MAX_TIMER; i++) {
        timerctl.timer[i].flags = 0; //not used
        timerctl.timer[i].fifo = 0;
    }
}


上面的改动在于 把每个timer对象的fifo队列成员设置为0


接下来的改动主要在主入口CMain函数中


void CMain(void) {
    ....
    fifo8_init(&timerinfo, 8, timerbuf, 0);
    ....
    fifo8_init(&keyinfo, 32, keybuf, 0);
    ....
    task_a = task_init(memman);
    keyinfo.task = task_a;
    ....
}


上面代码的逻辑是 先通过task_init得到CMain函数所对应的任务对象


并把该任务对象寄存在键盘事件列表中 也就是


keyinfo.task = task_a;

void CMain(void) {
    ....
    task_run(task_b);
    ...
    int pos = 0;
    int stop_task_A = 0;
     for(;;) {

       io_cli();
       ....
       else if (fifo8_status(&timerinfo) != 0) {
           io_sti();
           int i = fifo8_get(&timerinfo);
           if (i == 10) {
                showString(shtctl, sht_back, pos, 144, COL8_FFFFFF,
                 "A"); 

                timer_settime(timer, 100);
                pos += 8;
                if (pos > 40 && stop_task_A == 0) {
                    io_cli();
                    task_sleep(task_a);
                    io_sti();
                 }
           }
       ....
    }
}

上面代码的逻辑时 当CMain函数在主循环中


连续打印字符”A”,当打印的字符超过5个时


通过task_sleep(task_a)把CMain进程挂起


这样的话 系统运行时 我们会发现原来是字符A和B 是同时打印到桌面上的


此时便只剩下字符B在继续打印了


由于我们把task_A寄存到键盘队列 那么当我们点击键盘


键盘数据就会存储到键盘队列中


由于键盘队列存储了任务Ad的任务对象


那么此时他会把对应任务对象重新加入到调度队列中


由此字符A会从恢复打印状态 也就是说 打印字符A的进程重新获得了被调度的机会


3.编译运行

目录
相关文章
|
1月前
|
算法 调度 UED
深入理解操作系统:进程调度与优先级队列
【10月更文挑战第31天】在计算机科学的广阔天地中,操作系统扮演着枢纽的角色,它不仅管理着硬件资源,还为应用程序提供了运行的环境。本文将深入浅出地探讨操作系统的核心概念之一——进程调度,以及如何通过优先级队列来优化资源分配。我们将从基础理论出发,逐步过渡到实际应用,最终以代码示例巩固知识点,旨在为读者揭开操作系统高效管理的神秘面纱。
|
25天前
|
消息中间件 安全 算法
深入理解操作系统:进程管理的艺术
【10月更文挑战第38天】在数字世界的心脏,操作系统扮演着至关重要的角色。它不仅是硬件与软件的桥梁,更是维持计算机运行秩序的守夜人。本文将带你走进操作系统的核心——进程管理,探索它是如何协调和优化资源的使用,确保系统的稳定与高效。我们将从进程的基本概念出发,逐步深入到进程调度、同步与通信,最后探讨进程安全的重要性。通过这篇文章,你将获得对操作系统进程管理的全新认识,为你的计算机科学之旅增添一份深刻的理解。
|
29天前
|
算法 调度 UED
深入理解操作系统:进程管理与调度策略
【10月更文挑战第34天】本文旨在探讨操作系统中至关重要的一环——进程管理及其调度策略。我们将从基础概念入手,逐步揭示进程的生命周期、状态转换以及调度算法的核心原理。文章将通过浅显易懂的语言和具体实例,引导读者理解操作系统如何高效地管理和调度进程,保证系统资源的合理分配和利用。无论你是初学者还是有一定经验的开发者,这篇文章都能为你提供新的视角和深入的理解。
42 3
|
1月前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
1月前
|
算法 调度 UED
深入理解操作系统的进程调度机制
本文旨在探讨操作系统中至关重要的组成部分之一——进程调度机制。通过详细解析进程调度的概念、目的、类型以及实现方式,本文为读者提供了一个全面了解操作系统如何高效管理进程资源的视角。此外,文章还简要介绍了几种常见的进程调度算法,并分析了它们的优缺点,旨在帮助读者更好地理解操作系统内部的复杂性及其对系统性能的影响。
|
1月前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
40 2
|
1月前
|
消息中间件 算法 Linux
深入理解操作系统之进程管理
【10月更文挑战第30天】在数字时代的浪潮中,操作系统作为计算机系统的核心,扮演着至关重要的角色。本文将深入浅出地探讨操作系统中的进程管理机制,从进程的概念入手,逐步解析进程的创建、调度、同步与通信等关键过程,并通过实际代码示例,揭示这些理论在Linux系统中的应用。文章旨在为读者提供一扇窥探操作系统深层工作机制的窗口,同时激发对计算科学深层次理解的兴趣和思考。
|
1月前
|
消息中间件 算法 调度
深入理解操作系统:进程管理与调度策略
【10月更文挑战第29天】本文将带领读者深入探讨操作系统中的核心组件之一——进程,并分析进程管理的重要性。我们将从进程的生命周期入手,逐步揭示进程状态转换、进程调度算法以及优先级调度等关键概念。通过理论讲解与代码演示相结合的方式,本文旨在为读者提供对进程调度机制的全面理解,从而帮助读者更好地掌握操作系统的精髓。
42 1
|
1月前
|
消息中间件 算法 调度
深入理解操作系统:进程管理的艺术
【10月更文挑战第33天】本文旨在揭示操作系统中进程管理的神秘面纱,带领读者从理论到实践,探索进程调度、同步以及通信的精妙之处。通过深入浅出的解释和直观的代码示例,我们将一起踏上这场技术之旅,解锁进程管理的秘密。
27 0
|
1月前
|
算法 Linux 调度
深入理解操作系统之进程调度
【10月更文挑战第31天】在操作系统的心脏跳动中,进程调度扮演着关键角色。本文将深入浅出地探讨进程调度的机制和策略,通过比喻和实例让读者轻松理解这一复杂主题。我们将一起探索不同类型的调度算法,并了解它们如何影响系统性能和用户体验。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇理解操作系统深层工作机制的大门。
34 0