BUAA-OS-2023-Lab4-1-Exam
中途脑梗了好几次,差点寄了,不过好在这次把题留下来了(
Exam - 进程组 ipc 通信
题干
在 Linux 中,进程理论上所拥有的权限与执行它的用户的权限相同。进程运行时能够访问哪些资源或文件,不取决于进程文件的属主属组,而是取决于运行该命令的用户身份的 uid/gid,以该身份获取各种系统资源。
所以我们需要完成同一个进程组ID的不同进程的通信。具体而言,需要做到:
- 在 Env 结构体中添加
u_int env_gid
字段代表进程所在的进程组,初始值为 0。 - 实现一个修改
gid
字段的用户态函数:void set_gid(u_int gid);
- 实现一个仅能向同组块发送消息的用户态函数:
int ipc_group_send(u_int whom, u_int val, const void *srcva, u_int perm);
- 实现 2、3 两点中对应的系统调用函数和调用接口
教程组已经把两个用户态函数实现了,我们只需要考虑系统调用函数 syscall_set_gid
和 syscall_ipc_try_group
即可
具体要求
- 在内核中为每个进程维护进程组ID,并保证每个进程创建时的的组ID为0。
- 在
user/include/lib.h
中:- 添加以下两个用户函数的声明:
void set_gid(u_int gid);
int ipc_group_send(u_int whom, u_int val, const void *srcva, u_int perm);- 添加以下两个系统调用函数的声明:
void syscall_set_gid(u_int gid);
int syscall_ipc_try_group_send(u_int whom, u_int val, const void *srcva, u_int perm); - 在
include/error.h
中,增加以下新错误码E_IPC_NOT_GROUP
,表示组间通信时通信双方进程的组ID不匹配。 - 两个用户态函数的实现已经给出(请参看实验提供代码部分),你需要将其复制到
user/lib/ipc.c
*_,具体代码的解释在*_提示部分给出。 - 在
include/syscall.h
中:定义两个新的系统调用号。请注意新增系统调用号的位置,应当位于MAX_SYSNO
之前。 - 在
user/lib/syscall_lib.c
中:实现上述两个系统调用函数,发起系统调用。 - 在
kern/syscall_all.c
中:添加两个系统调用在内核中的实现函数。请保证两个函数的定义位于系统调用函数表void *syscall_table[MAX_SYSNO]
之前。 - 在
kern/syscall_all.c
中的void *syscall_table[MAX_SYSNO]
系统调用函数表中,为你定义的系统调用号添加对应的内核函数指针。 - 编写
syscall_ipc_try_group_send
系统调用在内核中的实现函数时,判断-E_IPC_NOT_RECV
错误的优先级高于-E_IPC_NOT_GROUP
实验提供代码
/* copy to user/lib/ipc.c */ |
一种可行的做法
课上写了半天总是说函数名找不到,仔细一看写了一半的 group_send
,写了一半的 send_group
,难绷
int sys_ipc_try_group_send(u_int whom, u_int val, const void *srcva, |
思路
按照题目给的思路顺下来其实很容易就能写完,顺便也可以用这个题回顾一下系统调用:怎样添加一个新的、可用的系统调用?
- 首先从内核态出发,编写一个能够完成功能的函数,看一下它都需要什么参数 -
kern/syscall_all.c
- 然后在
void *syscall_table[MAX_SYSNO]
把函数添加进去,使得do_syscall
函数能跳到这个新函数里:这需要随便写一个字符串当成系统调用号,无所谓了 -kern/syscall_all.c
- 找到刚才那个系统调用号的枚举类,把定义加上 -
include/syscall.h
-
do_syscall
不需要变化,然后再上一层是msyscall
,它需要一个syscall_
开头的函数进行调用。到这里我们就完成了内核态中所需要做的所有改动 - 回到用户态,编写一个用户态的
syscall_new
函数调用msyscall
,同时需要注意参数的第一个参数需要是刚才加进去的调用号 -users/lib/syscall_lib.c
- 最后编写顶层的用户态函数,其中调用
syscall_new
函数,用户态工作也就完成了 -users/某个文件
- 最最后别忘了加上函数声明:内核态不需要,用户态加在
users/include/lib.h
即可
Extra - 家族 ipc 广播
题干
课下我们在 MOS 系统中实现了进程间通信。
现在你需要仿照 ipc_send
函数在 user/lib/ipc.c
中实现 ipc_broadcast
函数,使得调用 ipc_broadcast
可以使当前进程向其后代进程(也即当前广播进程的子进程、子进程的子进程、子进程的子进程的子进程…以此类推)发起广播消息,当后代进程进入 recv 后进行发送。
具体要求
ipc_broadcast
需要在 user/lib/ipc.c
新增:
void ipc_broadcast(u_int val, void * srcva, u_int perm); |
参数:
-
val
:进程广播传递的具体数值, 与ipc_send
函数中的定义相同。 -
srcva
:进程广播发送页的对应用户虚地址,与ipc_send
函数中的定义相同。 -
perm
: 传递的页面的权限位设置,与ipc_send
函数中的定义相同。
注意点
- 你可以实现
syscall_ipc_try_broadcast
系统调用,使其行为类似于syscall_ipc_try_send
,但尝试发送给当前进程的所有后代进程。 - 你也可以尝试在用户空间利用
envs
实现相关行为。 - 发送广播消息时,你可以先等待所有后代进程进入接受状态,再统一进行实际传输,也可以依次等待每个后代进程,一旦其处于接受状态,当即对其进行实际传输。
也就是说可以在用户态使用这些已有的调用函数完成目标,也可以像 Exam 中添加一种新的系统调用处理这种请求。
如果注意到原来提供的 ipc_send
函数能使用 bfs 操作进程块数组,那实际上难度就会降低很多。无所谓,我没看出来
两种可行的做法
新建系统调用 - sys_ipc_broadcast
类似在刚才 Exam 中提到的思路,加一个新的系统调用
int sys_ipc_broadcast(u_int val, void *srcva, u_int perm) { |
用户态通过已有函数处理
因为代码不是我的,所以我就不贴了,说一说思路吧
首先和第一种做法一样,需要用循环和队列 bfs 出所有满足条件的进程 env_id
,最后实际上可以直接用 ipc_send
解决,太快了。
一点废话
其实一开始读这个题我理解成调用 env_alloc
函数来创建进程了,然后就在 Env 里面加了一个数组字段保存自己的孩子,同时在 env_alloc
里通过 env_parent_id
更新所有祖宗进程的字段,最后在系统调用进行 send 的时候直接查一下自己的字段就能跑了
结果这个题它创建进程最后用的是宏 ENV_CREATE_PRIORITY
,也就是调用了 env_create
函数。甚至 parent_id
是下面这么加进去的!看起来两个函数好像没啥差别,但是 env_create
函数只能产生 parent_id = 0
的新进程,我这一套直接寄了
struct Env *ppa1 = ENV_CREATE_PRIORITY(test_ppa, 5); |
幸亏看了一眼测试代码,要不寄大发了。不过反正写完这一版才发现写的不对,实际上已经寄了。
不然我觉得我那个做法将能算得上是绝杀(可能吧)
总之 lab4 这样就算结束一半了,之后再看吧。