《操作系统》实验报告二
Shell及系统调用
一、 实验内容
-
- 巩固操作系统的进程调度机制和策略
-
- 熟悉MINIX系统调用和MINIX调度器的实现
二、实验要求
- 在MINIX3中实现Earliest-Deadline-First近似实时调度功能:
-
- 提供设置进程执行期限的系统调度chrt (long deadline),用于将调用该系统调用的进程设为实时进程,其执行的期限为:从调用处开始deadline秒。
-
- 在内核进程表中需要增加一个条目,用于表示进程的实时属性;修改相关代码,新增一个系统调用chrt,用于设置其进程表中的实时属性。
-
- 修改proc.c和proc.h中相关的调度代码,实现最早deadline的用户进程相对于其它用户进程具有更高的优先级,从而被优先调度运行。
-
- 在用户程序中,可以在不同位置调用多次chrt系统调用,在未到deadline之前,调用chrt将会改变该程序的deadline。
-
- 未调用chrt的程序将以普通的用户进程(非实时进程)在系统中运行。
三、注意事项
- MINIX的不同服务模块和内核都是运行在不同进程中,只能使用基于消息的进程间系统调用/内核调用,不能使用直接调用普通C函数。
- 添加调用编号,需要修改取值范围限制。
- 以源码为准(博客等资料版本落后)。
- 善用source insight高级功能(调用关系,全局搜索)。
- 善用git diff 检查代码修改。修改涉及文件较多,git diff可直观看到修改内容,避免引入无意的错误。
- 善用FileZilla功能。连接虚拟机,拉取需修改的文件,修改后上传到虚拟机。
- 消息结构体在include/minix/ipc.h中
四、实验准备
- 下载源码
cd /usr
git clone git://git.minix3.org/minix src
git branch –a # 查看代码版本
git checkout R3.3.0 # 将代码版本切换为 3.3.0
得到虚拟机上有如下文件
- 编译
cd /usr/src
make build #首次编译以及修改了头文件
make build MKUPDATE #增量式编译
首次编译用时较长
3. 内核串口调试
- 在vmware虚拟机关闭情况下,在设置中点击添加设备,再选择添加串口,简单情况下可以使用物理机文件output.txt作为串口输出目标。
- 启动虚拟机,运行 echo “hello world” > /dev/tty00,可以在output.txt中观察到hello world的输出结果。
- 在内核调试中,可以通过向/dev/tty00文件写入字符的形式进行调试
五、实验过程
- 增加系统调用chrt:
1. 应用层:需要添加的系统调用chrt可以定义在unistd头文件中,并在libc中添加chrt函数体实现- 在/usr/src/include/unistd.h 中添加chrt函数定义
// 加入到适当的位置
int chrt(long); //定义chrt函数
- 在/usr/src/minix/lib/libc/sys/chrt.c中添加chrt函数实现。可用alarm函数实现超时强制终止
chrt的功能主要是要将参数deadline添加到message信息中,然后利用_syscall()函数通信息结构体进行IPC通信,传递deadline
具体代码实现参考同文件夹下的fork.c中实现fork函数的代码
#include <sys/cdefs.h>
#include "namespace.h"
#include <lib.h>
#include <string.h>
#include <unistd.h>
#ifdef __weak_alias
__weak_alias(fork, _fork)
#endif
pid_t fork(void)
{
message m;
memset(&m, 0, sizeof(m));
return(_syscall(PM_PROC_NR, PM_FORK, &m));
}
注意的一点是可以在usr/src/minix/include/minix/ipc.h文件中查找到message结构体中long型数据的存储形式如下
typedef struct {
int64_t m2ll1;
int m2i1, m2i2, m2i3;
long m2l1, m2l2;
char *m2p1;
sigset_t sigset;
short m2s1;
uint8_t padding[6];
} mess_2;
_ASSERT_MSG_SIZE(mess_2);
typeofdef struct{
/*......*/
union{
/*......*/
mess_2 m_m2
/*......*/
}
/*......*/
} message __aligned(16);
#define m2_l2 m_m2.m2l2
可以得到long型数据存储位置
因为message中应包含进程结束时的时间,所以还需要获取到现在系统的时间加上设置的deadline来得message中应该传输的时间
系统时间通过time.tv_sec得到
所以整合上述要点后可以写出chrt.c代码
/* chrt.c */
#include <sys/cdefs.h>
#include "namespace.h"
#include <lib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
int chrt(long deadline){
struct timespec time;
message m;
memset(&m, 0, sizeof(m));
alarm((unsigned int)deadline);
if (deadline < 0)
{
return 0;
}
if (deadline > 0)
{
clock_gettime(CLOCK_REALTIME,&time);
deadline = time.tv_sec + deadline; //向服务层发送的信息为实际应该结束的时间
}
m.m2_l1 = deadline;
return (_syscall(PM_PROC_NR, PM_CHRT, &m));
}
- 在/usr/src/minix/lib/libc/sys中Makefile.inc文件添加chrt.c条目
2. 服务层:需要向MINIX系统的进程管理服务中注册chrt,使得chrt服务可以向应用层提供
- 在/usr/src/minix/servers/pm/proto.h中添加chrt函数定义
这里虽然说是chrt函数的定义,但是这里定义的其实是服务层对chrt函数的处理,向内核层发送message,而不是应用层中用户使用的chrt函数。
/* chrt.c */
int do_chrt(void);
- 在/usr/src/minix/servers/pm/chrt.c中添加chrt函数实现,调用sys_chrt()
int do_chrt(){
sys_chrt(who_p, m_in.m2_l1);/*sys_chrt()的参数来自同文件下的glo.h*/
return (OK);
}
- 同样地在同文件夹下的Makefile文件中添加chrt条目
SRCS= main.c forkexit.c exec.c time.c alarm.c \
signal.c utility.c table.c trace.c getset.c misc.c \
profile.c mcontext.c schedule.c chrt.c
- 在/usr/src/minix/include/minix/callnr.h中定义PM_CHRT编号
#define PM_CHRT (PM_BASE + 48)
#define NR_PM_CALLS 49 /* highest number from base plus one */
- 在/usr/src/minix/servers/pm/table.c 中调用映射表
CALL(PM_CHRT) = do_chrt
- 在/usr/src/minix/include/minix/syslib.h 中添加 sys_ chrt () 定义
/* sys_chrt() */
int sys_chrt(endpoint_t proc_ep, long deadline);
- 在/usr/src/minix/lib/libsys/sys_chrt.c 中添加 sys_chrt () 实现
#include "syslib.h"
int sys_chrt(proc_ep,deadline)
endpoint_t proc_ep; /* which proc_ep has exited */
long deadline;
{
/* A proc_ep has to be signaled via PM. Tell the kernel. */
message m;
m.m2_i1 = proc_ep;
m.m2_l1 = deadline;
return(_kernel_call(SYS_CHRT, &m));
}
- 在/usr/src/minix/lib/libsys 中的 Makefile 中添加 sys_chrt.c 条目
3. 内核层:在MINIX内核中实现进程调度功能,此处可以直接修改内核信息,例如进程的截至时间 - 在/usr/src/minix/kernel/system.h中添加do_chrt函数定义
- 在/usr/src/minix/kernel/config.h中将USE_CHRT设为1
#define USE_CHRT
- 在/usr/src/minix/kernel/system/do_chrt.c中添加do_chrt函数实现
#include "kernel/system.h"
#include <minix/endpoint.h>
#include "kernel/vm.h"
#include <signal.h>
#include <string.h>
#include <assert.h>
#include <minix/u64.h>
/*===========================================================================*
* do_chrt *
*===========================================================================*/
#if USE_CHRT
int do_chrt(struct proc *caller, message *m_ptr){
struct proc *rp;
rp = proc_addr(m_ptr->m2_i1);
rp->p_deadline = m_ptr->m2_l1;
return (OK);
}
#endif /* USE_CHRT */
- 在/usr/src/minix/kernel/system/ 中Makefile.inc文件添加do_chrt.c条目
- 在/usr/src/minix/include/minix/com.h中定义SYS_CHRT编号
# define SYS_CHRT (KERNEL_CALL + 58)
/* Total */
#define NR_SYS_CALLS 59 /* number of kernel calls */
- 在/usr/src/minix/kernel/system.c 中添加SYS_CHRT编号到do_chrt的映射
map(SYS_CHRT, do_chrt);
- 在/usr/src/minix/commands/service/parse.c的system_tab中添加名称编号
{ "CHRT", SYS_CHRT },
4. 进程调度:修改影响进程调度顺序的部分
- 在/usr/src/minix/kernel/proc.h struct proc 维护每个进程的信息,用于调度决策。添加deadline成员
struct proc {
long p_deadline; /*进程deadline项*/
......
}
- 在/usr/src/minix/kernel/proc.c修改enqueue_head(),enqueue(),pick_proc()
- enqueue_head() 按优先级将进程加入列队首。实验中需要将实时进程的优先级设置成合适的优先级
if (rp->p_deadline>=0)
{
rp->p_priority = 5;
}
- enqueue() 按优先级将进程加入列队尾
- pick_proc() 从队列中返回一个可调度的进程 。遍历设置的优先级队列,返回剩余时间最小并可运行的进程
static struct proc * pick_proc(void)
{
/* Decide who to run now. A new process is selected an returned.
* When a billable process is selected, record it in 'bill_ptr', so that the
* clock task can tell who to bill for system time.
*
* This function always uses the run queues of the local cpu!
*/
register struct proc *rp,*tmp; /* process to run */
struct proc **rdy_head;
int q; /* iterate over queues */
/* Check each of the scheduling queues for ready processes. The number of
* queues is defined in proc.h, and priorities are set in the task table.
* If there are no processes ready to run, return NULL.
*/
rdy_head = get_cpulocal_var(run_q_head);
for (q=0; q < NR_SCHED_QUEUES; q++) {
if(!(rp = rdy_head[q])) {
TRACE(VF_PICKPROC, printf("cpu %d queue %d empty\n", cpuid, q););
continue;
}
assert(proc_is_runnable(rp));
if(q==5){
rp = rdy_head[q];
tmp = rp->p_nextready;
while (tmp!=NULL){
if(tmp->p_deadline > 0){
if(tmp->p_deadline < rp->p_deadline){
if (proc_is_runnable(tmp)){
rp = tmp;
}
}
}
tmp = tmp->p_nextready;
}
if (priv(rp)->s_flags & BILLABLE)
get_cpulocal_var(bill_ptr) = rp; /* bill for system time */
return rp;
}
}
return NULL;
}
五、测试运行
将下面代码上传到重新编译后的虚拟机中,编译运行
/* test-2.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <lib.h>
#include <time.h>
void proc(int id);
int main(void)
{
//创建三个子进程,并赋予子进程id
for (int i = 1; i < 4; i++)
{
if (fork() == 0)
{
proc(i);
}
}
return 0;
}
void proc(int id)
{
int loop;
switch (id)
{
case 1: //子进程1,设置deadline=25
chrt(25);
printf("proc1 set success\n");
sleep(1);
break;
case 2: //子进程2,设置deadline=15
chrt(15);
printf("proc2 set success\n");
sleep(1);
break;
case 3: //子进程3,普通进程
chrt(0);
printf("proc3 set success\n");
break;
}
for (loop = 1; loop < 40; loop++)
{
//子进程1在5s后设置deadline=5
if (id == 1 && loop == 5)
{
chrt(5);
printf("Change proc1 deadline to 5s\n");
}
//子进程3在10s后设置deadline=3
if (id == 3 && loop == 10)
{
chrt(3);
printf("Change proc3 deadline to 3s\n");
}
sleep(1); //睡眠,否则会打印很多信息
printf("prc%d heart beat %d\n", id, loop);
}
exit(0);
}
得到结果如图所示
六、参考资料
- 如何添加Minix内核调用
https://wiki.minix3.org/doku.php?id=developersguide:newkernelcall - Minix内核调用APIs
http://wiki.minix3.org/doku.php?id=developersguide:kernelapi - Minix讨论区
https://groups.google.com/forum/#!forum/minix3
七、备注
因为Minix源码在虚拟机上下载太满了,所以我在网上找到了一个gitee上的源码,如有需要可以去我的仓库中获取或者去源仓库获取。
我的仓库:https://gitee.com/zhongershun/minix
源仓库:https://gitee.com/jiangsheng1987/minix
另外,本次实验报告本人也已将将完整的修改后代码以及实验报告上传至GitHub仓库
GitHub:https://github.com/zhongershun/os-Earliest-Deadline-First