跟我一起写操作系统

    返回首页    发表留言
本文作者:李德强
          第四节 等待与阻塞
 
 

一、等待

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