在进程调度中有一些特殊情况需要处理。进程可以主动的调用sleep()函数来等待一段时间,在这段时间里此进程被移出运行链表,加入到等待链表中。而调度任务每隔一个时间片则遍历一次等待链表,将每个进程的等待时间减少一个时间片的大小,也就是10ms,如果进程等待时间已经结束(等于0)则将此进程移出等待链表并加入到运行链表头。先要编写一个用于普通程序可以调用的两个函数msleep()和sleep():
/* * 等待毫秒 */ void msleep(int ms) { //定义系统调用参数表 int params[2]; //调用索引为2代表是等待 params[0] = 2; //毫秒数 params[1] = ms; //系统中断,将参数表的地址传入中断服务程序 __asm__ volatile("int $0x80" :: "a"(params)); } /* * 等待秒 */ void sleep(int s) { // 1s == 1000ms msleep(s * 1000); }
有了这两个函数,普通程序就可以调用这两个函数来实现等待的功能了。对于普通程序而言直接让其调用系统中断是不友好的,所以编译了一个叫unistd.o文件,让普通程序调用msleep()函数和sleep()函数,从而让普通程序能够不内嵌汇编就可以调用系统功能函数。下面来编写一个example_sleep的程序:
#include <shell/unistd.h> int main(int argc, char **args) { msleep(100); char *msg2 = "Example: sleep 3000ms."; __asm__ volatile("int $0x82" :: "a"(msg2)); msleep(3000); char *msg3 = "Example: sleep 1s."; for (int i = 0; i < 5; i++) { __asm__ volatile("int $0x82" :: "a"(msg3)); msleep(1000); } for (;;) { } return 0; }
将example_sleep.c文件编译成example_sleep.o再通过ld命令将其链接成example_sleep文件,之后使用sysfile工具将example_sleep安装到lidqos.iso光盘镜像中:
$(LD) $(LD_PSHELL_PARAMS) -o $(BUILD_PATH)/$(PSHELL)/example_sleep -e start_main \ - r $(BUILD_PATH)/$(PSHELL)/start_main.o \ $(BUILD_PATH)/$(PSHELL)/example_sleep.o \ $(BUILD_PATH)/$(PSHELL)/unistd.o
这样一个普通程序就编译安装完成了,接下来就要实现内核调度中的等待功能。在int.S中加入0x80号的中断服务程序,里面调用了sys_process()函数:
_int_0x80: cli push %ebp mov %esp, %ebp sub $0x4, %esp movl %eax, (%esp) call sys_process leave sti iret
sys_process()函数的实现如下:
void sys_process(int *params) { set_ds(GDT_INDEX_KERNEL_DS); set_cr3(PAGE_DIR); u32 cr3 = pcb_cur->tss.cr3; params = addr_parse(cr3, params); //载入可执行文件并创建进程 if (params[0] == 0) { } //退出或杀死进程 else if (params[0] == 1) { } //msleep等待 else if (params[0] == 2) { int ms = params[1]; pcb_sleep(pcb_cur, ms); } set_cr3(cr3); set_ds(0xf); }
其中通过params = addr_parse(cr3, params);将params的逻辑地址转换为物理地址,再判断参数表中的第1项的值为2,则说明这是一个msleep功能调用。其中的“载入可执行文件并创建进程”和“退出或杀死进程”功能将在下一节中介绍。pcb_sleep()函数的实现如下:
/* * 将pcb移出到等待链表中 */ void pcb_sleep(s_pcb *pcb, int ms) { //设置等待毫秒 pcb->sleep_ms = ms; //链表节点 s_list *list_node = NULL; //从运行链表中移出此进程 list_pcb = list_remove_node(list_pcb, pcb, &list_node); //加入到等待链表 list_pcb_sleep = list_insert_node(list_pcb_sleep, list_node); //因为当前进程就是调用sleep中断的进程,为了让其等待要执行一次调度 schedule(); }
再编写一个list_sleep_change()函数,它的作用是在每个时间片中都执行此函数,将等待链表中的每一个进程的等待时间都减少一个时间片周期,并判断每个进程是否已“时间到”。如果时间已到,则将其移出等待链表并加入运行链表:
void list_sleep_change() { s_list* p = list_pcb_sleep; while (p != NULL) { s_pcb *pcb = (s_pcb *) p->node; //减少一个时钟周期 pcb->sleep_ms -= CLOCK_TIMER; p = p->next; if (pcb->sleep_ms < 0) { pcb->sleep_ms = 0; } //如果“时间到” if (pcb->sleep_ms == 0) { s_list *list_node = NULL; //从运行链表中移出此进程 list_pcb_sleep = list_remove_node(list_pcb_sleep, pcb, &list_node); //加入到执行链表 list_pcb = list_insert_node(list_pcb, list_node); } } }
在时钟中断服务中调用list_sleep_change()函数:
/* * 时钟中断 */ void int_timer() { //在时钟中断时并没有切换ds和cr3寄存器 set_ds(GDT_INDEX_KERNEL_DS); set_cr3(PAGE_DIR); //通知PIC可以接受新中断 outb_p(0x20, 0x20); //处理等待链表 list_sleep_change(); //任务调度算法 schedule(); }
最后在install_system()函数中载入example程序:
void install_system() { //载入并运行system程序 load_process("/usr/bin/system", ""); load_process("/usr/bin/example_sleep", ""); }
编译运行并查看结果:
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git git git@github.com:magicworldos/lidqos.git subverion https://github.com/magicworldos/lidqos branch v0.19
当进程需要使用某些资源,但这些资源已经被其它进程所占用,那么当前进程需要暂停运行,等待资源被释放后再继续运行。进程的这一等待过程被称作为进程的阻塞。进程的阻塞与进程等待从原理上讲是完全一样的,只不过它们的触发条件不同。阻塞是因为某一资源的不足而产生的。处理进程阻塞的基本思想是使用信号量:
//信号量 typedef struct semaphore { //信号量的值 int value; //阻塞链表block void *list_block; } s_sem;
对信号量执行P操作,如果信号量的值小于1,则阻塞进程到信号量中的阻塞链表中,如果信号量的值大于1,则将其减1。
//信号量的P操作,申请资源 int pcb_sem_P(s_pcb *pcb, s_sem *sem) { if (pcb == NULL) { return 0; } //如果信号量大于0 if (sem->value > 0) { //将信号量减1 sem->value--; return 1; } //阻塞进程 //链表节点 s_list *list_node = NULL; //从运行链表中移出此进程 list_pcb = list_remove_node(list_pcb, pcb, &list_node); //加入到等待链表 sem->list_block = list_insert_node(sem->list_block, list_node); return 0; } //信号量的V操作,释放资源 int pcb_sem_V(s_pcb *pcb, s_sem *sem) { if (pcb == NULL) { return 0; } //信号量加1 sem->value++; //唤醒进程 s_list *list_node = NULL; if (sem->list_block != NULL) { s_list *p = (s_list *) sem->list_block; s_pcb *pcb_wakeup = (s_pcb *) p->node; //从运行链表中移出此进程 sem->list_block = list_remove_node(sem->list_block, pcb_wakeup, &list_node); //加入到执行链表 list_pcb = list_insert_node(list_pcb, list_node); } return 1; }
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git git git@github.com:magicworldos/lidqos.git subverion https://github.com/magicworldos/lidqos branch v0.20
实际上,进程与进程之前是相互独立的。它们各自拥有4G的逻辑内存,并使用不同的栈。对于进程阻塞的主要作用是在多线程中体现的。我们将在下一节中学习多线程的相关内容。
Copyright © 2015-2023 问渠网 辽ICP备15013245号