lab4:PartC

lab通关记录

MIT-6.828实验通关记录

C 部分:抢占式多任务和进程间通信 (IPC)

我们现在需要实行抢占式调度,并且实现具有权限检查的进程通信。

Exercise13

外部中断(即设备中断)称为 IRQ。有 16 个可能的 IRQ,编号为 0 到 15。从 IRQ 编号到 IDT 条目的映射不是固定的。 pic_initpicirq.c映射的IRQ 0-15到IDT入口IRQ_OFFSET通过IRQ_OFFSET+15

inc/trap.h 中IRQ_OFFSET定义为十进制 32。因此,IDT 条目 32-47 对应于 IRQ 0-15。例如,时钟中断是IRQ 0。因此,IDT[IRQ_OFFSET+0](即IDT[32])包含内核中时钟中断处理程序的地址。IRQ_OFFSET选择此选项是为了使设备中断不会与处理器异常重叠,这显然会导致混淆。(事实上,在运行MS-DOS的PC的初期,IRQ_OFFSET有效地零,这的确造成处理硬件中断和处理处理器异常之间巨大的混乱!)

在 JOS 中,与 xv6 Unix 相比,我们做了一个关键的简化。外部设备中断在内核中总是被禁用(并且像 xv6 一样,在用户空间中启用)。外部中断由寄存器的FL_IF标志位控制%eflags(参见inc/mmu.h)。当该位被设置时,外部中断被使能。虽然可以通过多种方式修改该位,但由于我们的简化,我们将仅通过在%eflags进入和离开用户模式时保存和恢复寄存器的过程来处理它。

我们必须确保FL_IF在用户环境运行时在用户环境中设置该标志,以便在中断到达时将其传递给处理器并由您的中断代码处理。

处理器在调用硬件中断处理程序时从不推送错误代码。此时,您可能需要重新阅读 80386 参考手册的第 9.2 节 或 IA-32 英特尔架构软件开发人员手册第 3 卷的第 5.8 节 。

修改kern/ trapentry.Skern/trap.c初始化所述IDT中的相应条目,并通过15提供为IRQs 0处理程序

然后修改代码中的env_alloc()kern/ env.c,以确保用户环境总是在启用中断的情况下运行。

不要忘了还要取消注释sched_halt() 中sti指令

kern/ trapentry.S

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TRAPHANDLER_NOEC(th32, IRQ_OFFSET)
TRAPHANDLER_NOEC(th33, IRQ_OFFSET + 1)
TRAPHANDLER_NOEC(th34, IRQ_OFFSET + 2)
TRAPHANDLER_NOEC(th35, IRQ_OFFSET + 3)
TRAPHANDLER_NOEC(th36, IRQ_OFFSET + 4)
TRAPHANDLER_NOEC(th37, IRQ_OFFSET + 5)
TRAPHANDLER_NOEC(th38, IRQ_OFFSET + 6)
TRAPHANDLER_NOEC(th39, IRQ_OFFSET + 7)
TRAPHANDLER_NOEC(th40, IRQ_OFFSET + 8)
TRAPHANDLER_NOEC(th41, IRQ_OFFSET + 9)
TRAPHANDLER_NOEC(th42, IRQ_OFFSET + 10)
TRAPHANDLER_NOEC(th43, IRQ_OFFSET + 11)
TRAPHANDLER_NOEC(th44, IRQ_OFFSET + 12)
TRAPHANDLER_NOEC(th45, IRQ_OFFSET + 13)
TRAPHANDLER_NOEC(th46, IRQ_OFFSET + 14)
TRAPHANDLER_NOEC(th47, IRQ_OFFSET + 15)

kern/trap.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
trap_init function:添加IDT的支持

SETGATE(idt[IRQ_OFFSET], 0, GD_KT, th32, 0);
SETGATE(idt[IRQ_OFFSET + 1], 0, GD_KT, th33, 0);
SETGATE(idt[IRQ_OFFSET + 2], 0, GD_KT, th34, 0);
SETGATE(idt[IRQ_OFFSET + 3], 0, GD_KT, th35, 0);
SETGATE(idt[IRQ_OFFSET + 4], 0, GD_KT, th36, 0);
SETGATE(idt[IRQ_OFFSET + 5], 0, GD_KT, th37, 0);
SETGATE(idt[IRQ_OFFSET + 6], 0, GD_KT, th38, 0);
SETGATE(idt[IRQ_OFFSET + 7], 0, GD_KT, th39, 0);
SETGATE(idt[IRQ_OFFSET + 8], 0, GD_KT, th40, 0);
SETGATE(idt[IRQ_OFFSET + 9], 0, GD_KT, th41, 0);
SETGATE(idt[IRQ_OFFSET + 10], 0, GD_KT, th42, 0);
SETGATE(idt[IRQ_OFFSET + 11], 0, GD_KT, th43, 0);
SETGATE(idt[IRQ_OFFSET + 12], 0, GD_KT, th44, 0);
SETGATE(idt[IRQ_OFFSET + 13], 0, GD_KT, th45, 0);
SETGATE(idt[IRQ_OFFSET + 14], 0, GD_KT, th46, 0);
SETGATE(idt[IRQ_OFFSET + 15], 0, GD_KT, th47, 0);

env_alloc:中断是IF flag,所以我们添加即可

1
e->env_tf.tf_eflags |= FL_IF;

Exericse14

抢占式调度:我们会通过时钟的IRQ来强迫进入内核态,进而切换用户环境。

修改内核trap_dispatch()函数,使其sched_yield() 在发生时钟中断时调用查找并运行不同的环境。

到这里会得到65/80的分数

trap_dispatch

1
2
3
4
5
if (tf->tf_trapno == IRQ_OFFSET + IRQ_TIMER) {
lapic_eoi();
sched_yield();
return;
}

Exercise 15

我们将会通过两个系统调用来实现IPC,sys_ipc_recv以及 sys_ipc_try_send. 他们被两个库包装器 ipc_recvipc_send调用.

IPC 机制相互发送的“消息”由两个组件组成:单个 32 位值和可选的单个页面映射。

发送和接收消息

要接收消息,环境调用 sys_ipc_recv. 这个系统调用取消了当前环境的调度,并且在收到消息之前不会再次运行它。当一个环境正在等待接收消息时, 任何其他环境都可以向它发送消息 - 不仅仅是特定环境,也不仅仅是与接收环境有父/子安排的环境。

为了尝试发送一个值,环境调用 sys_ipc_try_send,参数是接收者的环境 ID 和要发送的值。如果目标环境实际上正在等待接收(它已调用 sys_ipc_recv但尚未获得值),则我们可以发送消息并返回 0。否则发送返回-E_IPC_NOT_RECV以指示目标环境当前不期望接收值。

ipc_recv用户空间中的 库函数将负责调用sys_ipc_recv,然后在当前环境的struct Env.

同样,库函数ipc_send将负责重复调用,sys_ipc_try_send 直到发送成功。

传输页面

当环境sys_ipc_recv 使用有效dstva参数(如下UTOP)调用时,环境表示它愿意接收页面映射。如果发送方发送一个页面,那么该页面应该被映射dstva 到接收方的地址空间中。如果接收方已经在 处映射了一个页面dstva,则该前一个页面将被取消映射。

当环境sys_ipc_try_send 使用有效srcva(如下UTOP)调用时,这意味着发送方希望将当前映射srcva到的页面发送给接收方,并具有权限perm。IPC 成功后,发送方srcva在其地址空间中保留其对 at 页面的原始映射,但接收方也在dstva接收方地址空间中的接收方最初指定的同一物理页面上获得了该物理页面的映射。因此,此页面在发送方和接收方之间共享。

如果发送方或接收方未指示应传送页面,则不传送页面。在任何 IPC 之后,内核将env_ipc_perm 接收者Env结构中的新字段设置为接收到的页面的权限,如果没有接收到页面,则为零。

实现 IPC

我们的进程通信只是进程之间的mapping page而已,不过比较复杂的是权限检查。

简单来说如何IPC?

  1. 当环境调用sys_ipc_recv()后,该环境会阻塞(ENV_NOT_RUNNABLE),直到接受消息。并且sys_ipc_recv()传入dstva参数时,代表请求接受。
  2. 环境调用sys_ipc_try_send()向目标环境发送“消息”,根据请求消息的参数决定发送,然后返回0,否则返回-E_IPC_NOT_RECV。当传入srcva参数时,将共享srcva映射的物理页。

问题:为什么要阻塞?

A:假设环境运行, 或许对应的页面进行新映射,而另一个环境毫不知情,所以必须要有一个signal来通知。而这个阻塞能够完成“我等你”的通知,但是并没有保证“你”是谁,所以存在抢占式的共享。并不是锁,更像是信号机制:”等待某个有缘人“

实现sys_ipc_recv()和sys_ipc_try_send()。包装函数ipc_recv()和 ipc_send()。

细节繁多,请注意查看lab给的注释和lab前面的讲解,否则实验debug会没有头绪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
{
// LAB 4: Your code here.
int r;
struct Env * dstenv;
pte_t * pte;
struct PageInfo *pp;
//err begin:
r = envid2env(envid, &dstenv, 0);
if (r < 0) return -E_BAD_ENV; //1.env not exit
if (!dstenv->env_ipc_recving)return -E_IPC_NOT_RECV; //2.target env not blocked:

//if srcva < UTOP,what err will exit?
if ((uint32_t)srcva < UTOP) {
pp = page_lookup(curenv->env_pgdir, srcva, &pte);//find pp and pte

if ((uint32_t)srcva & 0xfff)return -E_INVAL; //3.not page-aligned
int flag = PTE_U | PTE_P;
if (flag != (flag & perm)) return -E_INVAL; //4.perm not appropriate

if (!pp) return -E_INVAL; //5.srcva not mapped the same ppage

if ((perm & PTE_W) && !(*pte & PTE_W)) return -E_INVAL; //6.srcva read-only

r = page_insert(dstenv->env_pgdir, pp, dstenv->env_ipc_dstva, perm);
if (r < 0) return -E_NO_MEM; //7.not enough memory for new page table

dstenv->env_ipc_perm = perm;
}

//succeed: update
dstenv->env_ipc_recving = 0;
dstenv->env_ipc_value = value;
dstenv->env_ipc_from = curenv->env_id;
dstenv->env_status = ENV_RUNNABLE;
dstenv->env_tf.tf_regs.reg_eax = 0;
return 0;
}

static int
sys_ipc_recv(void *dstva)
{
// LAB 4: Your code here.
if (dstva < (void *)UTOP && dstva != ROUNDDOWN(dstva, PGSIZE)) {
return -E_INVAL;
}
curenv->env_ipc_recving = 1;
curenv->env_status = ENV_NOT_RUNNABLE;
curenv->env_ipc_dstva = dstva;
sys_yield();
return 0;
}
int32_t
ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
{
// LAB 4: Your code here.
if (pg == NULL) {
pg = (void *)-1;
}
int r = sys_ipc_recv(pg);
if (r < 0) { //err
if (from_env_store) *from_env_store = 0;
if (perm_store) *perm_store = 0;
return r;
}
if (from_env_store)
*from_env_store = thisenv->env_ipc_from;
if (perm_store)
*perm_store = thisenv->env_ipc_perm;
return thisenv->env_ipc_value;
}

void
ipc_send(envid_t to_env, uint32_t val, void *pg, int perm)
{
// LAB 4: Your code here.
if (pg == NULL) {
pg = (void *)-1;
}
int r;
while(1) {
r = sys_ipc_try_send(to_env, val, pg, perm);
if (r == 0) { //sucess
return;
} else if (r == -E_IPC_NOT_RECV) { //target env not blocked
sys_yield();
} else { //other err
panic("ipc_send: sys_ipc_try_send failed\n");
}

}
}

happy!

代码在这:Source Code

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2024 环烷烃
  • Visitors: | Views:

我很可爱,请我喝一瓶怡宝吧~

支付宝
微信