【读书笔记-《30天自制操作系统》-15】Day16

news2024/11/13 9:34:56

本篇内容继续多任务的讲解。上一篇中实现了两个任务之间的自动切换,但还不够通用,这里将其优化为多个任务之间的切换。接着引入了任务休眠的概念与休眠的程序实现。最后介绍了任务的优先级,一种用切换时间的长短来衡量,一种用Task Level来衡量。

在这里插入图片描述

1. 多任务管理程序优化

对多任务管理程序进行一些优化,首先修改结构体定义。

#define MAX_TASKS		1000	/* 最大任务数量 */
#define TASK_GDT0		3		/* 定义GDT从3号开始分配给TSS */
struct TSS32 {
	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;
	int ldtr, iomap;
};
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];
};

仿照前面的SHEETCTL修改了当前的TASKCTL。tasks用于存放当前运行的任务指针,初始化函数task_init如下:

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();
	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;
}

初始化函数中给每个任务分配了GDT编号,有了自己的TSS。task_init函数最终会返回一个内存地址,表示当前运行的程序已经作为一个任务了。

接下来还需要初始化一个任务结构的函数task_alloc,这里随意地设置了变量的初始值:

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 = 1; */
			task->tss.eax = 0; /* 先设置为0 */
			task->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_run函数,将task添加到tasks的末尾,并将running变量增加1。这样就将任务添加到了任务列表中,在任务切换时可以被切换到。

void task_run(struct TASK *task)
{
	task->flags = 2; /* 
	task->flags = 2; /* 任务活动中标志 */
	taskctl->tasks[taskctl->running] = task;
	taskctl->running++;
	return;
}

然后是重点,任务切换函数task_switch,此函数实现对任务列表中的任务进行依次切换:

void task_switch(void)
{
	timer_settime(task_timer, 2);
	if (taskctl->running >= 2) {
		taskctl->now++;
		if (taskctl->now == taskctl->running) {
			taskctl->now = 0;
		}
		farjmp(0, taskctl->tasks[taskctl->now]->sel);
	}
	return;
}

此函数中设置了0.02s的超时时间。当任务里列表中有2个以上任务时,在达到超时时间时就依次进行切换。如果当前是列表中最后一个任务在运行,则下一次切换又会切换到列表的首个任务。

切换相关的函数完成,相应地修改主程序:

……
	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);
	……

超时中断函数inthandler20,在超时处理中调用了task_switch函数实现切换:

void inthandler20(int *esp)
{
	struct TIMER *timer;
	char ts = 0;
	io_out8(PIC0_OCW2, 0x60);	
	timerctl.count++;
	if (timerctl.next > timerctl.count) {
		return;
	}
	timer = timerctl.t0; 
	for (;;) {
		if (timer->timeout > timerctl.count) {
			break;
		}
		timer->flags = TIMER_FLAGS_ALLOC;
		if (timer != task_timer) {
			fifo32_put(timer->fifo, timer->data);
		} else {
			ts = 1;
		}
		timer = timer->next; 
	}
	timerctl.t0 = timer;
	timerctl.next = timer->timeout;
	if (ts != 0) {
		task_switch();
	}
	return;
}

2. 任务休眠

有些任务实际运行的时间可能并不长,但是需要及时地响应,比如移动鼠标,如果长时间不同鼠标,分配给鼠标监控任务的时间里,该任务基本都处于无事可做的状态,这样就浪费了本可以分配给其他任务的时间,降低了CPU的使用效率。为了改善这种情况,引入了任务休眠的概念。

实现起来也比较简单,只要将该任务从任务列表中删除即可。但同时如果触发了鼠标事件,又要及时将任务唤醒。
首先创建函数task_sleep:

{
	int i;
	char ts = 0;
	if (task->flags == 2) {		/* 如果指定任务处于唤醒状态 */
		if (task == taskctl->tasks[taskctl->now]) {
			ts = 1; /* 让当前运行中的任务休眠,稍后需要进行任务切换 */
		}
		/* 寻找task所在位置 */
		for (i = 0; i < taskctl->running; i++) {
			if (taskctl->tasks[i] == task) {
				break;
			}
		}
		taskctl->running--;
		if (i < taskctl->now) {
			taskctl->now--; /* 需要移动tasks数组成员 */
		}
		/* 移动成员 */
		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;
}

如果需要休眠的任务不是当前运行的任务,使该任务休眠只需要将该任务从任务列表中删除,将tasks列表中后面的任务移位即可;如果需要休眠的任务是当前运行的任务,则处理结束后还必须马上切换到下一个任务。

还要实现在FIFO中写入数据时将任务唤醒的功能。修改FIFO结构体定义,加入用于记录要唤醒任务的信息:

struct FIFO32 {
	int *buf;
	int p, q, size, free, flags;
	struct TASK *task;
};

改写fifo32_init,使其可以在参数中指定一个任务。在不使用唤醒功能时,该参数被设置为了0。

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; /* 有数据写入时需要唤醒的任务 */
	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); /* 通过task_run让任务重新回到任务列表 */
		}
	}
	return 0;
}

相比任务A未休眠的情况,B任务得到了更多运行时间,提高了运行效率。

下面通过增加多个窗口来直观感受多任务的运行情况。

……
	init_palette();
	shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny);
	task_a = task_init(memman);
	fifo.task = task_a;

	/* sht_back */
	sht_back  = sheet_alloc(shtctl);
	buf_back  = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);
	sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1);
	init_screen8(buf_back, binfo->scrnx, binfo->scrny);

	/* sht_win_b */
	//创建3个窗口
	for (i = 0; i < 3; i++) {
		sht_win_b[i] = sheet_alloc(shtctl);
		buf_win_b = (unsigned char *) memman_alloc_4k(memman, 144 * 52);
		sheet_setbuf(sht_win_b[i], buf_win_b, 144, 52, -1); /* �����F�Ȃ� */
		sprintf(s, "task_b%d", i);
		make_window8(buf_win_b, 144, 52, s, 0);
		task_b[i] = task_alloc();
		task_b[i]->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
		task_b[i]->tss.eip = (int) &task_b_main;
		task_b[i]->tss.es = 1 * 8;
		task_b[i]->tss.cs = 2 * 8;
		task_b[i]->tss.ss = 1 * 8;
		task_b[i]->tss.ds = 1 * 8;
		task_b[i]->tss.fs = 1 * 8;
		task_b[i]->tss.gs = 1 * 8;
		*((int *) (task_b[i]->tss.esp + 4)) = (int) sht_win_b[i];
		task_run(task_b[i]);
	}

	/* sht_win */
	sht_win   = sheet_alloc(shtctl);
	buf_win   = (unsigned char *) memman_alloc_4k(memman, 160 * 52);
	sheet_setbuf(sht_win, buf_win, 144, 52, -1); /* �����F�Ȃ� */
	make_window8(buf_win, 144, 52, "task_a", 1);
	make_textbox8(sht_win, 8, 28, 128, 16, COL8_FFFFFF);
	cursor_x = 8;
	cursor_c = COL8_FFFFFF;
	timer = timer_alloc();
	timer_init(timer, &fifo, 1);
	timer_settime(timer, 50);

	/* sht_mouse */
	sht_mouse = sheet_alloc(shtctl);
	sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99);
	init_mouse_cursor8(buf_mouse, 99);
	mx = (binfo->scrnx - 16) / 2; 
	my = (binfo->scrny - 28 - 16) / 2;

	//固定各个窗口的位置
	sheet_slide(sht_back, 0, 0);
	sheet_slide(sht_win_b[0], 168,  56);
	sheet_slide(sht_win_b[1],   8, 116);
	sheet_slide(sht_win_b[2], 168, 116);
	sheet_slide(sht_win,        8,  56);
	sheet_slide(sht_mouse, mx, my);
	sheet_updown(sht_back,     0);
	sheet_updown(sht_win_b[0], 1);
	sheet_updown(sht_win_b[1], 2);
	sheet_updown(sht_win_b[2], 3);
	sheet_updown(sht_win,      4);
	sheet_updown(sht_mouse,    5);
	sprintf(s, "(%3d, %3d)", mx, my);
	putfonts8_asc_sht(sht_back, 0, 0, COL8_FFFFFF, COL8_008484, s, 10);
	sprintf(s, "memory %dMB   free : %dKB",
			memtotal / (1024 * 1024), memman_total(memman) / 1024);
	putfonts8_asc_sht(sht_back, 0, 32, COL8_FFFFFF, COL8_008484, s, 40);

	for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			task_sleep(task_a);
			io_sti();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (256 <= i && i <= 511) { 
				sprintf(s, "%02X", i - 256);
				putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
				if (i < 0x54 + 256) {
					if (keytable[i - 256] != 0 && cursor_x < 128) {
						s[0] = keytable[i - 256];
						s[1] = 0;
						putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);
						cursor_x += 8;
					}
				}
				if (i == 256 + 0x0e && cursor_x > 8) { 
					putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
					cursor_x -= 8;
				}
				boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
				sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44);
			} else if (512 <= i && i <= 767) { /* �}�E�X�f�[�^ */
				if (mouse_decode(&mdec, i - 512) != 0) {
					sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
					if ((mdec.btn & 0x01) != 0) {
						s[1] = 'L';
					}
					if ((mdec.btn & 0x02) != 0) {
						s[3] = 'R';
					}
					if ((mdec.btn & 0x04) != 0) {
						s[2] = 'C';
					}
					putfonts8_asc_sht(sht_back, 32, 16, COL8_FFFFFF, COL8_008484, s, 15);
					mx += mdec.x;
					my += mdec.y;
					if (mx < 0) {
						mx = 0;
					}
					if (my < 0) {
						my = 0;
					}
					if (mx > binfo->scrnx - 1) {
						mx = binfo->scrnx - 1;
					}
					if (my > binfo->scrny - 1) {
						my = binfo->scrny - 1;
					}
					sprintf(s, "(%3d, %3d)", mx, my);
					putfonts8_asc_sht(sht_back, 0, 0, COL8_FFFFFF, COL8_008484, s, 10);
					sheet_slide(sht_mouse, mx, my);
					if ((mdec.btn & 0x01) != 0) {
						sheet_slide(sht_win, mx - 80, my - 8);
					}
				}
			} else if (i <= 1) { 
				if (i != 0) {
					timer_init(timer, &fifo, 0); 
					cursor_c = COL8_000000;
				} else {
					timer_init(timer, &fifo, 1); 
					cursor_c = COL8_FFFFFF;
				}
				timer_settime(timer, 50);
				boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
				sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44);
			}
		}
	}
}

任务A用于监控鼠标移动与键盘输入,任务B0-B2则用于其他3个窗口的显示,可以看到多个窗口同时运行的现象。
在这里插入图片描述
3. 设置任务优先级-切换时间

以上完成的内容中,各个任务都被分配了相同的切换时间。实际上通过给任务分配不同的切换时间,也是调整任务优先级的一种方式。通过设置不同的任务切换时实现了任务优先级的差异。

struct TASK {
	int sel, flags;
	int priority;
	struct TSS32 tss;
};

在task结构体中增加了priority变量,设置从1-10的优先级,对应切换时间0.01s-0.1s。
相应修改任务切换的函数:

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();
	task->flags = 2; 
	task->priority = 2; /* 切换时间为0.02s */
	taskctl->running = 1;
	taskctl->now = 0;
	taskctl->tasks[0] = task;
	load_tr(task->sel);
	task_timer = timer_alloc();
	timer_settime(task_timer, task->priority);
	return task;
}

void task_run(struct TASK *task, int priority)
{
	/* 增加优先级设置 */
	if (priority > 0) {
		task->priority = priority;
	}
	if (task->flags != 2) {
		task->flags = 2; 
		taskctl->tasks[taskctl->running] = task;
		taskctl->running++;
	}
	return;
}

void task_switch(void)
{
	struct TASK *task;
	taskctl->now++;
	if (taskctl->now == taskctl->running) {
		taskctl->now = 0;
	}
	task = taskctl->tasks[taskctl->now];
	timer_settime(task_timer, task->priority);
	if (taskctl->running >= 2) {
		farjmp(0, task->sel);
	}
	return;
}

int fifo32_put(struct FIFO32 *fifo, int data)
{
	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, 0); /* 唤醒时不改变优先级,设置为0 */
		}
	}
	return 0;
}

修改内容还是比较容易理解的。增加了priority参数供task_run设置超时时间时进行应用,实现不同任务使用不同的切换时间。

4. 设置任务优先级-Level

通过设置切换时间,确实可以使某些任务获得更多的运行时间,但是仍然要等待其他任务到达切换时间之后才能进行切换。像键盘与鼠标监控这种对于实时性要求很高的任务,如果不能及时切换,会给用户带来卡顿的使用体验。同样的还有网卡,如果不能及时接收,网络数据就可能丢失。因此这种情况下需要牺牲其他任务,优先保证这些任务的运行。

为了解决这个问题,设计了一种Level模式。将任务从高到低分为几个不同的Level,如果高Level中有任务在运行,就永远不会切换到低Level中的任务,只在高Level中选择任务进行切换。这样就可以确保Level高的任务始终会得到优先处理。

修改TASKCTL:

#define MAX_TASKS_LV	100
#define MAX_TASKLEVELS	10

struct TASK {
	int sel, flags; 
	int level, priority;
	struct TSS32 tss;
};
struct TASKLEVEL {
	int running; /* 这一Level中运行的任务数量 */
	int now; /* 正在运行的任务 */
	struct TASK *tasks[MAX_TASKS_LV];
};
struct TASKCTL {
	int now_lv; /* 当前活动中的Level */
	char lv_change; /* 下次任务切换时是否需要改变Level */
	struct TASKLEVEL level[MAX_TASKLEVELS];
	struct TASK tasks0[MAX_TASKS];
};

在TASKCTL中,我们增加了levle数组,当前设置3个Level。每个数组元素都是TASKLEVEL结构体,其中保存着每一Level的任务信息。此外还增加了lv_change变量,用于记录下一次任务切换时是否需要改变Level。

为了完成优先级Level的相关功能,首先要编写操作TASKLEVEL结构体的函数。

struct TASK *task_now(void)
{
	struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
	return tl->tasks[tl->now];
}

void task_add(struct TASK *task)
{
	struct TASKLEVEL *tl = &taskctl->level[task->level];
	tl->tasks[tl->running] = task;
	tl->running++;
	task->flags = 2;
	return;
}

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) {
		/* now的值出现异常,需要修正 */
		tl->now = 0;
	}
	task->flags = 1; /* 设置为休眠 */

	/* 移位 */
	for (; i < tl->running; i++) {
		tl->tasks[i] = tl->tasks[i + 1];
	}

	return;
}

void task_switchsub(void)
{
	int i;
	/* 寻找最高的Level */
	for (i = 0; i < MAX_TASKLEVELS; i++) {
		if (taskctl->level[i].running > 0) {
			break; 
		}
	}
	taskctl->now_lv = i;
	taskctl->lv_change = 0;
	return;
}

task_now用于获取当前运行中的任务地址,需要首先根据当前活动的Level获取TASKLEVEL结构体,再获取该Level中正在运行的任务。

task_add用于向TASKLEVEL结构体中添加一个任务。调用task_add函数时,传入的参数task中已经设置好了需要添加到哪个Level的信息。通过task中的Level信息找到该Level的指针,并将该任务添加到此Level的任务列表中。

task_remove用于从某一Level的列表中删除任务。同样显示通过Level信息找到TASKLEVEL的指针,然后在该Level的任务列表中找到此任务进行移除,将此任务设置为休眠。移除之后,需要将列表中其他的任务进行移位。

task_switchsub用于确定任务切换时切换到哪个Level。

完成了操作TASKLEVEL结构体相关的函数,可以进入其他程序的修改了。
task_init与task_run修改如下:

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();
	task->flags = 2;	/* 活动中 */
	task->priority = 2; /* 0.02s */
	task->level = 0;	/* 先设置为最高Level*/
	task_add(task);
	task_switchsub();	/* 设置Level */
	load_tr(task->sel);
	task_timer = timer_alloc();
	timer_settime(task_timer, task->priority);
	return task;
}

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) { /* 改变活动中的Level */
		task_remove(task); /* 从旧Level的任务列表中移除 */
	}
	if (task->flags != 2) {
		/* 加入新Level的任务列表 */
		task->level = level;
		task_add(task);
	}

	taskctl->lv_change = 1; /* 下次任务切换时检查Level */
	return;
}

改写task_sleep与task_switch:

void task_sleep(struct TASK *task)
{
	struct TASK *now_task;
	if (task->flags == 2) {
		/* 当前处于活动状态 */
		now_task = task_now();
		task_remove(task); /* 将任务从当前Level任务列表中移除 */
		if (task == now_task) {
			/* 当前运行的任务休眠,需要进行切换 */
			task_switchsub();
			now_task = task_now(); /* 经过task_switchsub获取Level后,再获取需要切换的任务 */
			farjmp(0, now_task->sel);
		}
	}
	return;
}

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;
}

在fifo与主程序中也需要相应修改,只需在调用task_run函数时加入相应的Level参数即可。这里将A设置为Level1,将B0-B2设置为Level2,这样在A任务处于活动状态时就不会切换到B0-B2。运行效果相同,但如果频繁移动鼠标,可以发现鼠标的响应相比修改之前会有很大改善。

到这里多任务的主要内容就基本完成了。在逐步完善程序的过程中,对于之前理解模糊的优先级的概念也有了更深入的理解。下一篇的主要内容转向交互,敬请期待。

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

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

相关文章

【Qt】文件对话框QFileDialog

文件对话框QFileDialog ⽂件对话框⽤于应⽤程序中需要打开⼀个外部⽂件或需要将当前内容存储到指定的外部⽂件。 通过QFileDialog 可以选择一个文件&#xff0c;能够获取到这个文件的路径&#xff0c;打开文件/保存文件。 常⽤⽅法介绍&#xff1a; 1、打开⽂件&#xff08;⼀…

【高中生讲机器学习】17. 讲人话的主成分分析,它来了!(上篇)

创建时间&#xff1a;2024-08-13 首发时间&#xff1a;2024-09-05 最后编辑时间&#xff1a;2024-09-05 作者&#xff1a;Geeker_LStar 你好呀~这里是 Geeker_LStar 的人工智能学习专栏&#xff0c;很高兴遇见你~ 我是 Geeker_LStar&#xff0c;一名准高一学生&#xff0c;热爱…

Redis 集群高可用详解及配置

关型数据库 关系型数据库&#xff1a; 是建立在关系模型基础上的数据库&#xff0c;其借助于集合代数等数学概念和方法来处理数据库中的数据 主流的 MySQL、Oracle、MS SQL Server 和 DB2 都属于这类传统数据库 关型数据库的优缺点 特点&#xff1a; 1、数据关系模型基于关系…

Redis使用——Redis的redis.conf配置注释详解(三)

Redis使用——Redis的redis.conf配置注释详解&#xff08;三&#xff09; 背景 日常我们开发时&#xff0c;我们会遇到各种各样的奇奇怪怪的问题&#xff08;踩坑o(╯□╰)o&#xff09;&#xff0c;这个常见问题系列就是我日常遇到的一些问题的记录文章系列&#xff0c;这里整…

鸿蒙轻内核M核源码分析系列四 中断Hwi

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 持续更新中…… 在鸿蒙轻内核源码分析系列前几篇文章中&#xff0c;剖析了重要的数据结构。本文&#xff0c;我们讲述一下中断&#xff0c;会给读者介绍中…

Ubuntu固定USB串口名(包括1拖N的USB串口)

在运行Ubuntu系统的开发板上,如果使用可插拔的USB串口,有时候程序正在运行时,如果突然连接传感器的USB串口设备被插拔了一下,这时,会发现系统中的USB串口名发生了改变。例如,插拔之前是/dev/ttyUSB0,插拔之后变成了/dev/ttyUSB3。发生这种情况的时候,有时候会导致程序无…

Windows I/O系统

硬件存储体系 寄存器 处理器内部定义的存储体&#xff0c;它们除了存储功能&#xff0c;往往还兼有其他的能力&#xff0c;比如参与运算&#xff0c;地址解析&#xff0c;指示处理器的状态&#xff0c;等等。寄存器是由处理器内部专门的触发器电路实现的&#xff0c;处理器往…

jupyter里怎么设置代理下载模型

使用如下方式: %env http_proxyhttp://10.110.146.100:7890 %env https_proxyhttp://10.110.146.100:7890

【SLAM】GNSS的定义,信号原理以及RTK在多传感器融合中的使用方法

【SLAM】GNSS的定义&#xff0c;信号原理以及在多传感器融合中的使用方法 1. GNSS的定义2. GNSS信号原理3. RTK - Real Time Kinematic4。 如何使用RTK做融合和优化 1. GNSS的定义 GPS&#xff08;Global Positioning System&#xff09;和GNSS&#xff08;Global Navigation …

Ubuntu22.04安装colmap

首先上这里查看自己电脑GPU的CMAKE_CUDA_ARCHITECTURES 终端输入以下内容安装预先的前置依赖 sudo apt-get install \git cmake ninja-build build-essential \libboost-program-options-dev libboost-filesystem-dev \libboost-graph-dev libboost-system-dev libboost-tes…

【操作系统存储篇】操作系统的设备管理

目录 一、广义的IO设备 分类 按使用特性分类 按信息交换的单位分类 按设备的共享属性分类 按传输速率分类 二、IO设备的缓冲区 三、SPOOLing技术 一、广义的IO设备 输入设备&#xff1a;对CPU而言&#xff0c;凡是对CPU进行数据输入的。 输出设备&#xff1a;对CPU而…

深度解析:基于离线开发的数据仓库转型落地案例

在当今这个数据驱动的时代&#xff0c;各行各业都正经历着前所未有的变革。伴随技术的飞速发展&#xff0c;数据仓库作为企业数据管理与分析的核心&#xff0c;如何更好地发挥作用&#xff0c;助力企业保持业务的敏捷性与成本效益&#xff0c;成为大家关心的焦点问题。本文将通…

vue使用html2Canvas导出图片 input文字向上偏移

vue使用html2Canvas导出图片 input文字向上偏移 图中 用的是element的输入框 行高 32px,经常测试 你使用原生的input 还是会出现偏移。 解决方法&#xff1a;修改css样式 1.怎么实现导出 网上随便找很多 2.在第一步 获取你要导出的元素id 克隆后 修改他的样式或者 你直接在你需…

web渗透:SSRF漏洞

SSRF漏洞的原理 SSRF&#xff08;Server-Side Request Forgery&#xff0c;服务器端请求伪造&#xff09;是一种安全漏洞&#xff0c;它允许攻击者构造请求&#xff0c;由服务端发起&#xff0c;从而访问服务端无法直接访问的内部或外部资源。这种漏洞通常发生在应用程序允许用…

v$session_longops监控 PDB clone 进度

How to Monitor PDB Clone / Move On Create Pluggable Database with COPY Clause Statement Execution (Doc ID 2866302.1)​编辑To Bottom In this Document Goal Solution References APPLIES TO: Oracle Database - Enterprise Edition - Version 19.14.1.0.0 and later…

leetcode:908. 最小差值 I(python3解法)

难度&#xff1a;简单 给你一个整数数组 nums&#xff0c;和一个整数 k 。 在一个操作中&#xff0c;您可以选择 0 < i < nums.length 的任何索引 i 。将 nums[i] 改为 nums[i] x &#xff0c;其中 x 是一个范围为 [-k, k] 的整数。对于每个索引 i &#xff0c;最多 只能…

【赛题已出】2024数学建模国赛A-E题已发布

2024年高教社杯全国大学生数学建模各题赛题已发布&#xff01; A题 B题 C题 D题 E题

Linux开源监控工具netdata

Netdata 是一个免费、开源、实时、专业的服务器监控工具&#xff0c;它以可视化的形式实时展现监控主机的性能变化&#xff0c;提供了一个交互式 Web 界面来查看您的服务器指标。它可以帮助我们了解监控主机的系统或应用程序中正在发生的事情以及刚刚发生的事情&#xff0c;并且…

macos系统内置php文件列表 系统自带php卸载方法

在macos系统中, 自带已经安装了php, 根据不同的macos版本php的版本号可能不同, 我们可以通过 which php 命令来查看mac自带的默认php安装路径, 不过注意这个只是php的执行文件路径. 系统自带php文件列表 一下就是macos默认安装的php文件列表. macos 10.15内置PHP文件列表配置…

iOS——GCD再学习

GCD 使用GCD好处&#xff0c;具体如下&#xff1a; GCD 可用于多核的并行运算&#xff1b;GCD 会自动利用更多的 CPU 内核&#xff08;比如双核、四核&#xff09;&#xff1b;GCD 会自动管理线程的生命周期&#xff08;创建线程、调度任务、销毁线程&#xff09;&#xff1b…