MIT6.828 LAB4 Part C: Preemptive Multitasking and Inter-Process communication (IPC)

  Lab 4的最后一部分就是实现抢占式调度和进程间通信。
  
Clock Interrupts and Preemption
  先前的调度是进程资源放弃CPU,但是实际中没有进程会这样做的,而为了不让某一进程耗尽CPU资源,需要抢占式调度,也就需要硬件定时。但是外部硬件定时在Bootloader的时候就关闭了,至今都没有开启。而JOS采取的策略是,在内核中的时候,外部中断是始终关闭的,在用户态的时候,需要开启中断。
  Exercise 13:
  修改kern/trapentry.S和kern/trap.c 来初始化IDT中IRQs0-15的入口和处理函数。然后修改env_alloc函数来确保进程在用户态运行时中断是打开的。
  回答:
  模仿原先设置默认中断向量即可,在kern/trapentry.S中定义IRQ0-15的处理例程。

TRAPHANDLER(irq0_entry, IRQ_OFFSET + 0, 0, 0);
TRAPHANDLER(irq1_entry, IRQ_OFFSET + 1, 0, 0);
TRAPHANDLER(irq2_entry, IRQ_OFFSET + 2, 0, 0);
TRAPHANDLER(irq3_entry, IRQ_OFFSET + 3, 0, 0);
TRAPHANDLER(irq4_entry, IRQ_OFFSET + 4, 0, 0);
TRAPHANDLER(irq5_entry, IRQ_OFFSET + 5, 0, 0);
TRAPHANDLER(irq6_entry, IRQ_OFFSET + 6, 0, 0);
TRAPHANDLER(irq7_entry, IRQ_OFFSET + 7, 0, 0);
TRAPHANDLER(irq8_entry, IRQ_OFFSET + 8, 0, 0);
TRAPHANDLER(irq9_entry, IRQ_OFFSET + 9, 0, 0);
TRAPHANDLER(irq10_entry, IRQ_OFFSET + 10, 0, 0);
TRAPHANDLER(irq11_entry, IRQ_OFFSET + 11, 0, 0);
TRAPHANDLER(irq12_entry, IRQ_OFFSET + 12, 0, 0);
TRAPHANDLER(irq13_entry, IRQ_OFFSET + 13, 0, 0);
TRAPHANDLER(irq14_entry, IRQ_OFFSET + 14, 0, 0);
TRAPHANDLER(irq15_entry, IRQ_OFFSET + 15, 0, 0);

  然后在IDT中注册,修改trap_init,由于先前已经实现简化,故此无需做处理。
  最后在env_alloc函数中打开中断。

        // Enable interrupts while in user mode.
        e->env_tf.tf_eflags |= FL_IF;

Handling Clock Interrupts
  现在虽然中断使能已经打开,在用户态进程运行的时候,外部中断会产生并进入内核,但是现在还没有能处理这类中断。所以需要修改trap_dispatch,在发生外部定时中断的时候,调用调度器,调度另外一个可运行的进程。
  Exercise 14:
  修改trap_dispatch函数,当发生时钟中断时调用sched_yield函数来调度下一个进程。
  回答:

if (tf->tf_trapno == IRQ_OFFSET + IRQ_TIMER) {
                lapic_eoi();
                sched_yield();
                return;
 }

Inter-Process communication (IPC)
  IPC是计算机系统中非常重要的一部分。在JOS实现IPC的方式是当两个进程需要通信的话,一方要发起recv,然后阻塞,直到有一个进程调用send向正在接受的进程发送了信息,阻塞的进程才会被唤醒。在JOS中,可以允许传递两种信息,一是一个32位整数,另外一个就是传递页的映射,在这个过程中,接收方和发送方将同时映射到一个相同的物理页,此时也就实现了内存共享。最后将这两个功能实现在了同一个系统调用。
  
Implementing IPC
  在JOS的IPC实现机制中,修改Env结构体如下:

struct Env {
        struct Trapframe env_tf;        // Saved registers
        struct Env *env_link;           // Next free Env
        envid_t env_id;                 // Unique environment identifier
        envid_t env_parent_id;          // env_id of this env's parent
        enum EnvType env_type;          // Indicates special system environments
        unsigned env_status;            // Status of the environment
        uint32_t env_runs;              // Number of times environment has run
        int env_cpunum;                 // The CPU that the env is running on

        // Address space
        pde_t *env_pgdir;               // Kernel virtual address of page dir

        // Exception handling
        void *env_pgfault_upcall;       // Page fault upcall entry point

        // Lab 4 IPC
        bool env_ipc_recving;           // Env is blocked receiving
        void *env_ipc_dstva;            // VA at which to map received page
        uint32_t env_ipc_value;         // Data value sent to us
        envid_t env_ipc_from;           // envid of the sender
        int env_ipc_perm;               // Perm of page mapping received
};

  其中增加了5个成员:
  env_ipc_recving:
  当进程使用env_ipc_recv函数等待信息时,会将这个成员设置为1,然后堵塞等待;当一个进程向它发消息解除堵塞后,发送进程将此成员修改为0。
  env_ipc_dstva:
  如果进程要接受消息并且是传送页,保存页映射的地址,且该地址<=UTOP。
  env_ipc_value:
  若等待消息的进程接收到消息,发送方将接收方此成员设置为消息值。
  env_ipc_from:
  发送方负责设置该成员为自己的envid号。
  env_ipc_perm:
  如果进程要接收消息并且传送页,那么发送方发送页之后将传送的页权限赋给这个成员。
  
  Exercise 15:
  实现在kern/syscall.c中的sys_ipc_recv和sys_ipc_try_send函数。最后实现用户态的ipc_recv和ipc_send。
  回答:
  首先是sys_ipc_recv函数,其功能是当一个进程试图去接收信息的时候,应该将自己标记为正在接收信息,而且为了不浪费CPU资源,应该同时标记自己为ENV_NOT_RUNNABLE,只有当有进程向自己发了信息之后,才会重新恢复可运行。最后将自己标记为不可运行之后,调用调度器运行其他进程。

static int
sys_ipc_recv(void *dstva)
{
        if (dstva < (void *)UTOP && PGOFF(dstva))
                return -E_INVAL;
        curenv->env_ipc_recving = true;
        curenv->env_ipc_dstva = dstva;
        curenv->env_status = ENV_NOT_RUNNABLE;
        curenv->env_ipc_from = 0;
        sched_yield();
        return 0;
}

  接着是sys_ipc_try_send函数,其实现相对来说麻烦很多,因为有很多的检测项,包括权限是否符合要求,要传送的页有没有,能不能将这一页映射到对方页表中去等等。如果srcva是在UTOP之下,那么说明是要共享内存,那就首先要在发送方的页表中找到srcva对应的页表项,然后在接收方给定的虚地址处插入这个页表项。接收完成之后,重新将当前进程设置为可运行,同时把env_ipc_recving设置为0,以防止其他的进程再发送,覆盖掉当前的内容。

static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
{
        int r;
        pte_t *pte;
        struct PageInfo *pp;
        struct Env *env;

        if ((r = envid2env(envid, &env, 0)) < 0)
                return -E_BAD_ENV;
        if (env->env_ipc_recving != true || env->env_ipc_from != 0)
                return -E_IPC_NOT_RECV;
        if (srcva < (void *)UTOP && PGOFF(srcva))
                return -E_INVAL;
        if (srcva < (void *)UTOP) {
                if ((perm & PTE_P) == 0 || (perm & PTE_U) == 0)
                        return -E_INVAL;
                if ((perm & ~(PTE_P | PTE_U | PTE_W | PTE_AVAIL)) != 0)
                        return -E_INVAL;
        }
        if (srcva < (void *)UTOP && (pp = page_lookup(curenv->env_pgdir, srcva, &pte)) == NULL)
                return -E_INVAL;
        if (srcva < (void *)UTOP && (perm & PTE_W) != 0 && (*pte & PTE_W) == 0)
                return -E_INVAL;
        if (srcva < (void *)UTOP && env->env_ipc_dstva != 0) {
                if ((r = page_insert(env->env_pgdir, pp, env->env_ipc_dstva, perm)) < 0)
                        return -E_NO_MEM;
                env->env_ipc_perm = perm;
        }

        env->env_ipc_from = curenv->env_id;
        env->env_ipc_recving = false;
        env->env_ipc_value = value;
        env->env_status = ENV_RUNNABLE;
        env->env_tf.tf_regs.reg_eax = 0;
        return 0;
}

  完成后需要要加上分发机制,将调用号加上。
  最后是2个用户态库函数的实现。

int32_t
ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
{
        int r;

        if (pg == NULL)
                r = sys_ipc_recv((void *)UTOP);
        else
                r = sys_ipc_recv(pg);
        if (from_env_store != NULL)
                *from_env_store = r < 0 ? 0 : thisenv->env_ipc_from;
        if (perm_store != NULL)
                *perm_store = r < 0 ? 0 : thisenv->env_ipc_perm;
        if (r < 0)
                return r;
        else
                return thisenv->env_ipc_value;
}

void
ipc_send(envid_t to_env, uint32_t val, void *pg, int perm)
{
        int r;
        void *dstpg;

        dstpg = pg != NULL ? pg : (void *)UTOP;
        while((r = sys_ipc_try_send(to_env, val, dstpg, perm)) < 0) {
                if (r != -E_IPC_NOT_RECV)
                        panic("ipc_send: send message error %e", r);
                sys_yield();
        }
}

  至此,Lab4的part C部分就完成了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值