在进程调度中有一些特殊情况需要处理。进程可以主动的调用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号