在MINIX3中实现Earliest-Deadline-First近似实时调度功能

《操作系统》实验报告二

Shell及系统调用

一、  实验内容

    1. 巩固操作系统的进程调度机制和策略
    1. 熟悉MINIX系统调用和MINIX调度器的实现

二、实验要求

  • 在MINIX3中实现Earliest-Deadline-First近似实时调度功能:
    1. 提供设置进程执行期限的系统调度chrt (long deadline),用于将调用该系统调用的进程设为实时进程,其执行的期限为:从调用处开始deadline秒。
    1. 在内核进程表中需要增加一个条目,用于表示进程的实时属性;修改相关代码,新增一个系统调用chrt,用于设置其进程表中的实时属性。
    1. 修改proc.c和proc.h中相关的调度代码,实现最早deadline的用户进程相对于其它用户进程具有更高的优先级,从而被优先调度运行。
    1. 在用户程序中,可以在不同位置调用多次chrt系统调用,在未到deadline之前,调用chrt将会改变该程序的deadline。
    1. 未调用chrt的程序将以普通的用户进程(非实时进程)在系统中运行。

三、注意事项

  1. MINIX的不同服务模块和内核都是运行在不同进程中,只能使用基于消息的进程间系统调用/内核调用,不能使用直接调用普通C函数。
  2. 添加调用编号,需要修改取值范围限制。
  3. 以源码为准(博客等资料版本落后)。
  4. 善用source insight高级功能(调用关系,全局搜索)。
  5. 善用git diff 检查代码修改。修改涉及文件较多,git diff可直观看到修改内容,避免引入无意的错误。
  6. 善用FileZilla功能。连接虚拟机,拉取需修改的文件,修改后上传到虚拟机。
  7. 消息结构体在include/minix/ipc.h中

四、实验准备

  1. 下载源码
cd /usr
git clone git://git.minix3.org/minix src
git branch –a # 查看代码版本
git checkout R3.3.0 # 将代码版本切换为 3.3.0

得到虚拟机上有如下文件
在这里插入图片描述

  1. 编译
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);
}

得到结果如图所示
在这里插入图片描述

六、参考资料

  1. 如何添加Minix内核调用
    https://wiki.minix3.org/doku.php?id=developersguide:newkernelcall
  2. Minix内核调用APIs
    http://wiki.minix3.org/doku.php?id=developersguide:kernelapi
  3. 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

如有问题,欢迎指正。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值