自制操作系统日志——第十六天

news/2024/5/3 22:41:01/文章来源:https://blog.csdn.net/qq_43696276/article/details/126084991

自制操作系统日志——第十六天

今天我们再接再厉,继续进一步的完成我们的多任务机制。


文章目录

  • 自制操作系统日志——第十六天
  • 一、多任务自动管理化
  • 二、任务休眠
  • 设定任务优先级
  • 总结


一、多任务自动管理化

在昨天我们的多任务都是通过自己手动添加,然后进行运行的,这样子当我们任务过多时,总不好再一个个给他们进行设置吧?

因此呢,我们效仿前面的定时器与图层的自动管理,来进行对多任务的管理:
首先添加并修改结构体部分:

/* mtask.c */
#define	MAX_TASKS		1000 //最大的任务数量
#define	TASK_GDT0		3    //指从第几号的GDT开始
struct TSS32//共26个int成员,104字节。摘自cpu技术资料里的设定
{int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;//记录与任务设置的相关内容int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;//寄存器的值会被写入内存int es, cs, ss, ds, fs, gs;//段寄存器的值。段寄存器16位,但是预先设定32,以防止为来说不定是32了int ldtr, iomap;//任务设置部分。先设置ldtr=0 ,iomap=0x40000000
};
struct TASK
{int sel, flags; //sel用于存放GDT编号struct TSS32 tss;
};
struct TASKCTL
{int running;//正在运行的数量int now;    //记录当前正在运行的是哪一个任务struct TASK *tasks[MAX_TASKS];struct TASK tasks0[MAX_TASKS];
};
extern struct TIMER *task_timer;
struct TASK *task_init(struct MEMMAN *memman);
struct TASK *task_alloc(void);
void task_run(struct TASK *task);
void task_switch(void);

然后mtask.c进行修改:

  • 首先,进行多任务自动化管理的初始化,并将调用该函数的程序,作为第一个任务进行运行:
struct TASKCTL *taskctl;
struct TIMER *task_timer;
//返回一个内存地址,将当前运行的调用该函数的程序作为一个任务
struct TASK *task_init(struct MEMMAN *memman)
{int i;struct TASK *task;struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;taskctl = (struct TASKCTL *) memman_alloc_4k(memman, sizeof (struct TASKCTL));for(i = 0; i < MAX_TASKS; i++){taskctl->tasks0[i].flags = 0;taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;set_segmdesc(gdt + TASK_GDT0 +i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);}task = task_alloc();//接收到akkoc返回的第一个任务task->flags = 2;//活动中的标志taskctl->running = 1;taskctl->now = 0;taskctl->tasks[0] = task;load_tr(task->sel);task_timer = timer_alloc();timer_settime(task_timer, 2);return task;
}
  • 分配并初始化一个任务:
struct TASK *task_alloc(void)
{int i;struct TASK *task;for(i = 0; i < MAX_TASKS; i++){if(taskctl->tasks0[i].flags == 0){task = &taskctl->tasks0[i];task->flags = 1;//正在使用的标志task->tss.eflags = 0x00000202; //IF = 1task->tss.eax = 0;//先都设置为0task->tss.ecx = 0;task->tss.edx = 0;task->tss.ebx = 0;task->tss.ebp = 0;task->tss.esi = 0;task->tss.edi = 0;task->tss.es = 0;task->tss.ds = 0;task->tss.fs = 0;task->tss.gs = 0;task->tss.ldtr = 0;task->tss.iomap = 0x40000000;return task;//找到就返回一个!}}return 0;//全部都在使用当中
}
  • 运行与切换任务:
//将活动的task添加到tasks的末尾
void task_run(struct TASK *task)
{task->flags = 2;//活动中的标志taskctl->tasks[taskctl->running] = task;taskctl->running++;return;
}void task_switch(void)
{timer_settime(task_timer, 2);if(taskctl->running >= 2) {//只有两个任务以上才进行任务切换taskctl->now++;//先把now++,然后把now所代表的任务切换成当前任务if(taskctl->now == taskctl->running){//当前正在运行的任务是最后一个的话,则切换回第一个任务taskctl->now = 0;}farjmp(0, taskctl->tasks[taskctl->now]->sel);}return;
}
  • 最后,将原本的mt_switch mt_timer等等都进行替换即可!(在timer.c中) ; 然后还要对主程序进行修改一下:
struct TASK *task_b;task_init(memman);task_b = task_alloc();task_b->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;task_b->tss.eip = (int) &task_b_main;task_b->tss.es = 1 * 8;task_b->tss.cs = 2 * 8;task_b->tss.ss = 1 * 8;task_b->tss.ds = 1 * 8;task_b->tss.fs = 1 * 8;task_b->tss.gs = 1 * 8;*((int *) (task_b->tss.esp + 4)) = (int) sht_back;task_run(task_b);

然后,make run 发现确实可以正常运行!非常好,我们已经实现自动化的管理了。。

二、任务休眠

截至前一节为止,我们的多任务系统都是给每个任务大约相同的运行时间。但在实际中,并非每个任务都需要一样的运行时间。就比如,我们的任务a,如果没有发生鼠标,键盘中断的话那么其大多时间都是空闲的,这样子就会损耗cpu的运行效率。

因此,我们需要让处于空闲状态的任务进行休眠,以此来提高cpu的运行效率。那么要如何进行休眠呢?当然这很简单,由于我们之前已经建立了任务管理表,因此我们只需将休眠的任务从tasks中剔除就可以达到休眠的效果了。

创建sleep:

void task_sleep(struct TASK *task)
{int i;char ts = 0;if(task->flags == 2){//如果指定任务处于唤醒中if(task == taskctl->tasks[taskctl->now]){ts = 1;//当前任务让自己休眠,那么稍后需要进行任务切换;如果是任务A让任务B休眠则不用切换}//寻找task的位置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 ){//任务切换if(taskctl->now >= taskctl->running ){//如果now出现异常则修正taskctl->now = 0;}farjmp(0, taskctl->tasks[taskctl->now]->sel);}}return;
}

然后,为了防止我们休眠任务a后,若键盘中断发生像缓冲区写入数据,但无法响应的情况。我们要在fifo中进行一下修改,使得其可以唤醒任务:
首先更改fifo32的结构体:

struct FIFO32{int *buf;//缓冲区地址int p, q, size, free, flags;//p 下一写入地址;q 下一读出地址; size是总字节数,free是缓冲区中没有数据的字节数;flag判断溢出struct TASK *task;
};

然后,修改fifo的初始化与写入的函数:

void fifo32_init(struct FIFO32 *fifo, int size, int *buf, struct TASK *task)
//初始化fifo地址
{fifo->size = size;fifo->buf = buf;fifo->free = size; //空fifo->flags = 0;fifo->p = 0 ;   //写入位置fifo->q = 0 ;   //读取位置fifo->task = task; //有数据读入时需要唤醒的任务!!!如果赋予0则代表禁用自动唤醒return;
}int fifo32_put(struct FIFO32 *fifo, int data) 
//向fifo传递数据并保存
{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;
}

最后,进行改写主函数!

  • 先初始化fifo缓冲区,将fifo的自动唤醒设置为0,表示禁止中断;
  • task_init返回一个地址,赋予task_a;
  • 当fifo为空时,将任务a进行休眠。不过这里,我们要先休眠,再开发中断,否则先开放中断的话,在处理休眠过程中如果发生了可能会使得自动唤醒功能出错!!
	struct TASK *task_a, *task_b;fifo32_init(&fifo, 128, fifobuf, 0);略task_a =  task_init(memman);fifo.task = task_a;for(;;)   {io_cli(); //IF=0if (fifo32_status(&fifo) == 0){task_sleep(task_a);io_sti();void task_b_main(struct SHEET *sht_back)
{fifo32_init(&fifo, 128, fifobuf, 0);

然后,make run一下!

哇哦,这里我们会发现任务b的速度快了两倍不止!!
这是昨天的测速:
在这里插入图片描述

这是,今天添加了休眠后的测速:
在这里插入图片描述

解释一下原因: 假设我们处理器的能力为每秒200次:

  • 那么由于昨天的任务a与任务b所占的时间相同,因此任务b的处理能力为100;
  • 其中10用于显示count,90用于计算count;
  • 而今天的休眠后,由于a已经休眠了,因此b的处理能力为198(这是因为休眠的任务a也不是完全不消耗处理能力的)
  • 那么显示count的还是10,则就会有188用于计算count。
  • 因此,大约就是2倍多吧!!

然后,我们设置多个任务同时进行吧!!!这里请查看源代码了,太长了就不放进来了,就看看效果图:
在这里插入图片描述

设定任务优先级

这里,我们利用任务的优先级,来确定哪一个任务应该获得的运行时间最长!以确保比较重要的任务的优先级足够,不至于运行时会有所卡顿。

我们拟设置共用n个taskctl,给每一个taskctl设置不同的级别。级别越低的taskctl里的任务越优先执行。例如,在level 0 里只要一任务,就可以忽略level 1 里的所有任务!!!

除此之外,我们还要设计一个同层内的优先级,利用priority给同一级别里的任务分配不同的运行时长,当priority值越大,则代表该任务在本层里的得到的运行时长越长。。

首先,我们设置Bootpack.h:

/* mtask.c */
#define	MAX_TASKS		1000 //最大的任务数量
#define	TASK_GDT0		3    //指从第几号的GDT开始
#define	MAX_TASKS_LV	100  //每个level最多100个任务
#define	MAX_TASKLEVELS	10   //共十个level
struct TSS32//共26个int成员,104字节。摘自cpu技术资料里的设定
{int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;//记录与任务设置的相关内容int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;//寄存器的值会被写入内存int es, cs, ss, ds, fs, gs;//段寄存器的值。段寄存器16位,但是预先设定32,以防止为来说不定是32了int ldtr, iomap;//任务设置部分。先设置ldtr=0 ,iomap=0x40000000
};
struct TASK
{int sel, flags; //sel用于存放GDT编号int level, priority; //设置优先级struct TSS32 tss;
};
struct TASKLEVEL
{int running; //正在运行的任务数量int now;    //记录当前正在运行的是哪一个任务struct TASK *tasks[MAX_TASKS_LV];
};struct TASKCTL
{int now_lv; //现在活动中的levelchar lv_change;//任务切换时是否需要改变levelstruct TASKLEVEL level[MAX_TASKLEVELS];struct TASK tasks0[MAX_TASKS];
};

在这个声明里,TASKCTL 变成了管理level以及每一个单独的task的。tasklevel就是管理本leve中的每一个任务。然后,我们需要新创建几个用于操作tasklevel的函数:

//返回现在活动中的struct task的地址
struct TASK *task_now(void)
{struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];return tl->tasks[tl->now];
}//向tasklevel中增加一个任务
void task_add(struct TASK *task)
{struct TASKLEVEL *tl = &taskctl->level[task->level];tl->tasks[tl->running] = task;tl->running++;task->flags = 2;//活动中return;
}//从tasklevel中删除一个任务
void task_remove(struct TASK *task)
{int i;struct TASKLEVEL *tl = &taskctl->level[task->level];//寻找task的位置for(i = 0; i < tl->running; i++){if(tl->tasks[i] == task){break;//找到了}}tl->running--;if(i < tl->now){tl->now--;//需要移动成员,因此做对应的处理}if(tl->now >= tl->running){tl->now = 0; //now出现异常,进行修正}task->flags = 1;//休眠中//移动for(; i < tl->running; i++){tl->tasks[i] = tl->tasks[i + 1];}return;
}//决定切换到哪一个level中
void task_switchsub(void)
{int i;//寻找最上层的levefor(i = 0; i < MAX_TASKLEVELS; i++){if(taskctl->level[i].running > 0){break;//找到了}}taskctl->now_lv = i;taskctl->lv_change = 0;return;
}

然后,继续修改一下前面的task任务部分:

//返回一个内存地址,将当前运行的调用该函数的程序作为一个任务
struct TASK *task_init(struct MEMMAN *memman)
{int i;struct TASK *task;struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;taskctl = (struct TASKCTL *) memman_alloc_4k(memman, sizeof (struct TASKCTL));for(i = 0; i < MAX_TASKS; i++){taskctl->tasks0[i].flags = 0;taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;set_segmdesc(gdt + TASK_GDT0 +i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);}for (i = 0; i < MAX_TASKLEVELS; i++) {taskctl->level[i].running = 0;taskctl->level[i].now = 0;}task = task_alloc();//接收到akkoc返回的第一个任务task->flags = 2;//活动中的标志task->priority = 2;//0.02stask->level = 0; //先将当前调用程序的任务暂时设置为0,即最高task_add(task);task_switchsub();//level 设置load_tr(task->sel);task_timer = timer_alloc();timer_settime(task_timer, task->priority);return task;
}
//将活动的task添加到tasks的末尾
void task_run(struct TASK *task, int level, int priority)
{if(level < 0){level = task->level; //不改变level}if(priority > 0){task->priority = priority;}if(task->flags == 2 && task->level != level){//改变活动区中的leveltask_remove(task);//这里执行后flag会变为1,因此下面也会执行的}if(task->flags != 2){//从休眠下唤醒的情况task->level = level;task_add(task);}taskctl->lv_change = 1 ;//下次切换任务时检查levelreturn;
}void task_switch(void)
{struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];struct TASK *new_task, *now_task = tl->tasks[tl->now];tl->now++;if (tl->now == tl->running) {tl->now = 0;}if (taskctl->lv_change != 0) {task_switchsub();tl = &taskctl->level[taskctl->now_lv];}new_task = tl->tasks[tl->now];timer_settime(task_timer, new_task->priority);if (new_task != now_task) {farjmp(0, new_task->sel);}return;
}void task_sleep(struct TASK *task)
{struct TASK *now_task;if(task->flags == 2){//如果处于活动中now_task = task_now();task_remove(task); //执行此语句flags变为1if(task == now_task){//如果是让自己休眠,则需要进行任务切换task_switchsub();now_task = task_now();//在设定完成后取得当前值farjmp(0, now_task->sel);}}return;
}

然后,我们修改一下fifo的放数据的函数:

int fifo32_put(struct FIFO32 *fifo, int data) 
//向fifo传递数据并保存
{if(fifo->task != 0){if(fifo->task->flags != 2 ){//如果任务处于休眠状态task_run(fifo->task, -1, 0);//唤醒任务}}

然后继续,进一步的修改主函数:

	task_run(task_a, 1, 0);/* sht_win_b */for (i = 0; i < 3; i++) {task_run(task_b[i], 2, i + 1);}

然后,我们运行一下,可以放下若不动鼠标键盘的话,其他三个计数更快;若动力鼠标或者键盘则计数会慢(因为我们给鼠标数据这个任务的优先级较高):

不动鼠标键盘:
在这里插入图片描述

发生鼠标键盘的中断后的:
在这里插入图片描述

可以看出计数确实有明显的差异!


总结

至此,我们多任务的基础部分已经算大致完成了!!感觉越来越有范了,嘿嘿嘿。加油加油!

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工业4.0 资产管理壳学习笔记(1)

何谓资产&#xff1f; 工业4.0的观点下&#xff0c;资产是任何“对组织具有价值的对象&#xff08;object which has a value for an organization&#xff09;”。因此&#xff0c;工业4.0中的资产几乎可以采取任何形式&#xff0c;例如生产系统&#xff0c;产品&#xff0c;软…

Vue基础案例-成绩显示

成绩排序应该按照降序排 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthde…

msm8953 LCD移植详解

一、简介 本文是基于高通msm8953的LCD模块移植说明。LCD移植主要是涉及到LK和kernel部分的修改。 二、实操准备 1、专业术语 HSYNC:行同步信号&#xff0c;表示扫描1行的开始。 VSYNC:帧同步信号,表示扫描1帧的开始&#xff0c;一帧也就是LCD显示的一个画面。 HFP:Horizon …

Linux学习笔记——网络管理

主要涉及Linux网络管理的几个简单指令&#xff0c;主要包括ifconfig、ping、nslookup 获取网络接口的配置信息 可以使用 ifconfig 命令查看网络接口的配置信息 语法 ifconfig [网络设备][down up -allmulti -arp -promisc][add<地址>][del<地址>][<hw<网络…

认识我们的团队:Ed Hertzog

认识我们的团队:Ed HertzogOur team works hard, but they believe in the work they do— hear more from one of our devs below. 了解背后的人 茴香梗 ——在本系列中,我们与 Fennel Labs 团队的成员坐下来了解更多关于他们是谁以及他们做什么的信息。今天,我们一起出去玩…

网络编程-流

流一、概貌1.消息类型二、getmsg和putmsg函数三、getpmsg和putpmsg函数四、ioctl函数五、TPI&#xff1a;传输提供者接口1.TPI时间获取客户程序一、概貌 流在进程和驱动程序(driver)之间提供全双工的连接,如下所示&#xff0c;虽然我们称底部那个方框为驱动程序&#xff0c;它…

02 最优化模型建立方法

1 什么是数学模型 数学模型是关于部分现实世界和为一种特殊目的而作地一个抽象地.简化地结构。 具体就是为了某种目的&#xff0c;用字母.数字及其他数学符号建立起来地等式或不等式以及图表。图像。框图等描述客观事物的特征及内在联系的数学结构表达式。 2.建立数学模型的方…

【Linux---02】CentOS操作系统的说明「简单使用 | 文件目录 | 常用命令」

文章目录1. CentOS的简单使用2. CentOS的文件目录2.1 目录结构2.2 各个目录的含义3. CentOS的常用命令1. CentOS的简单使用 鼠标进入虚拟机的OS&#xff1a;直接点击鼠标左键 鼠标退出虚拟机的OS&#xff1a;ctrl alt 在linux系统上&#xff0c;不使用图形化终端&#xff0c…

蓝牙BLE调试关于NRF connect相关信息分析

简介 nRF Connect是一个强大的通用工具&#xff0c;它允许你扫描和探索你的蓝牙低功耗(以后的蓝牙LE&#xff0c;也称为蓝牙4.0版本的蓝牙规范)设备&#xff0c;并与它们通信。 nRF连接还允许您的iOS设备广告作为一个外围设备&#xff0c;充分支持许多蓝牙SIG采用的配置文件。…

微信小程序在线考试项目开发-用户信息注册登录功能

⭐️⭐️⭐️ 作者&#xff1a;船长在船上 &#x1f6a9;&#x1f6a9;&#x1f6a9; 主页&#xff1a;来访地址船长在船上的博客 &#x1f528;&#x1f528;&#x1f528; 简介&#xff1a;CSDN前端领域优质创作者&#xff0c;资深前端开发工程师&#xff0c;专注前端开发…

ERAT读和写指令(eratre和eratwe)

ERAT管理指令 为了使hypervisor&#xff08;或 “bare-metal” operating system&#xff09;软件可以直接操作ERAT的entries&#xff0c;在A2 core中实现一组nonarchitected的ERAT管理指令。为了防止user和guest模式下的程序影响TLB地址转换和访问控制机制&#xff0c;所有的E…

乐高广告创意50例——创意无砖

乐高的创意不仅仅局限于建造令人惊叹的建筑,或是拍摄定格电影,甚至是重新制作音乐专辑封面和电影海报,它甚至延伸到了广告领域。 与塑料砖一样,乐高可以采用最简单的概念,做出强大、智能且通常诙谐的声明。 从本图库中的精彩平面广告中,你可以看到,典型的乐高广告所采用…

《Python3 网络爬虫开发实战》:灵巧好用的 正则表达式

灵巧好用的 正则表达式 在上一节中&#xff0c;我们已经可以用 requests 来获取网页的源代码&#xff0c;得到 HTML 代码。但我们真正想要的数据是包含在 HTML 代码之中的&#xff0c;怎么才能从 HTML 代码中获取我们想要的信息呢&#xff1f;正则表达式就是其中一个有效的方法…

基于VC++的WEB浏览器的实现

目 录 摘 要 2 1设计题目与要求 2 2系统设计 2 2.1总体设计 2 2.2详细设计 2 2.2.1用户界面设计 3 2.2.2多标签模块设计 6 2.2.3浏览模块设计 6 2.2.4操作按钮模块设计 9 2.2.5页面缩放模块设计 10 2.2.6状态栏模块设计 11 2.2.7收藏夹模块设计 13 2.2.8窗体关闭模块设计 13 2.…

网页设计中蒸汽朋克的美丽例子

即使你不熟悉蒸汽朋克这个词,你无疑已经通过流行文化被介绍到了这种设计现象。蒸汽朋克指的是一种异想天开的风格,这种风格基于对19世纪可能的反乌托邦世界的想象。这听起来可能有点复杂,但这个折衷的类别将H.G.威尔斯的想法与维多利亚时代的设计、工业主义主题、后世界末日…

第3章 基础项目的搭建

3.1 后端项目搭建 3.1.1 gitee下载脚手架 下载地址&#xff1a;https://gitee.com/77jubao2015/springbootdemo 打开浏览器输入以上地址&#xff0c;点击下载即可&#xff0c;如图所示&#xff1a; 3.1.2 把脚手架导入到idea开发工具 步骤01 把下载后的脚手架放到指定位置并解…

数据库基本概念

目录 一、数据库概念 1、数据库的组成 &#xff08;数据为行&#xff0c;字段为列&#xff09; 2、数据库的管理系统&#xff08;DBMS) 二、数据库系统发展史 1、第一代数据库&#xff08;人工管理&#xff09; 2、第二代数据库&#xff08;文件管理&#xff09; 3、第三…

大学SQLServer2012 安装流程+启动+登录+用户的操作

这里写目录标题第一步下载解压的文件第二步骤安装软件第三步执行安装选项执行安装选项1执行安装选项2执行安装选项3 同意条款执行安装选项4配置检测执行安装选项5 下载需求组件执行安装选项6 上面安装完成后执行安装选项需求---关闭防火墙执行安装选项7--重新检测执行安装选项8…

SpringMVC基础:AJAX发送请求

AJAX请求 前面我们讲解了如何向浏览器发送一个JSON格式的数据&#xff0c;那么我们现在来看看如何向服务器请求数据。 Ajax即Asynchronous Javascript And XML&#xff08;异步JavaScript和XML&#xff09;&#xff0c;它的目标就是实现页面中的数据动态更新&#xff0c;而不是…

微服务--数据一致性

本篇文章讲解微服务数据一致性相关的知识 一、案例 在使用微服务时&#xff0c;存在跨多个服务更新数据库数据的情况。那么这就会出现一个问题&#xff0c;比如我们有三个服务&#xff08;如下图&#xff09;&#xff0c;正常情况下&#xff0c;当一个请求进来时&#xff0c;…