操作系统上机随笔《实验二》

实验目的

深入理解处理机调度算法,了解硬实时概念,掌握周期性实时任务调度算法EDF(Earliest Deadline First)和RMS(Rate Monotonic Scheduling)的可调度条件,并能在可调度情况下给出具体调度结果。

实验内容

在Linux环境中采用用户级线程模拟实现EDF和RMS两种实时调度算法。给定一组实时任务,按照EDF算法和RMS算法分别判断是否可调度,在可调度的情况下,创建一组用户级线程,分别代表各个实时任务,并按算法确定的调度次序安排各个线程运行,运行时在终端上画出其Gantt图。为避免图形绘制冲淡算法,Gantt图可用字符表示。

实验准备

理解处理机调度算法

(20条消息) 计算机操作系统——处理机调度算法_白芷加茯苓的博客-CSDN博客_处理机调度算法

(20条消息) 处理机调度及常用的几个调度算法_硕子鸽的博客-CSDN博客_处理机调度算法有哪些

硬实时概念

实时系统是一种时间起着主导作用的系统。典型地,一种或多种外部物理设备发给计算机一个服务请求,而计算机必须在一个确定的时间范围内恰当地作出反应。

实时系统通常可以分为硬实时和软实时,硬实时的含义是必须满足绝对的截止时间。实时性能是通过把程序划分为一组进程而实现的,其中每个进程的行为是可预测和提前掌握的。这些进程一般寿命较短,并且极快地运行完成。在检测到一个外部信号时,调度程序的任务就是按照满足所有截止时间的要求调度进程。

EDF算法和RMS算法的可调度条件及调度原则

最早截止时间优先即EDF(Earliest Deadline First)算法,动态的调度方法

(20条消息) 实时调度算法之EDF算法_马冬冬的博客-CSDN博客_edf算法

EDF(在本实验中主要考察)为可抢先式调度算法,其调度条件为sum(Ci/Ti)≤1;

任务按单调速率优先级分配(RMPA)的调度算法,称为单调速率调度(RMS)。RMPA是指任务的优先级按任务周期T来分配。它根据任务的执行周期的长短来决定调度优先级,那些具有小的执行周期的任务具有较高的优先级,周期长的任务优先级低

(20条消息) 机器调度算法RMS和EDF_sdu_wei的博客-CSDN博客_edf和rms

RMS是一种静态的多任务调度方法

RMS算法为不可抢先调度算法,其调度条件为sum(Ci/Ti)n(exp(ln(2)/n)-1)

在Linux环境中创建用户级线程的函数

用户级线程

不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供的创建、同步、调度和管理线程的函数来控制用户线程。另外,用户线程是应用进程利用线程库创建和管理,不依赖于操作系统核心。不需要用户态/核心态切换,速度快。操作系统内核不知道多线程的存在,因此一个线程阻塞将使得整个进程(包括它的所有线程)阻塞。由于这里的处理器时间片分配是以进程为基本单位,所以每个线程执行的时间相对减少。

        用户进程ULT仅存在于用户空间中。对于这种线程的创建、撤销、线程之间的同步和通信等功能,都无需系统调用来实现。对于同一进程的线程之间切换仍然是不需要内核支持的。所以呢,内核也完全不会知道用户级线程的存在。但是有一点必须注意:设置了用户级线程的系统,其调度仍然是以进程为单位进行的。

        用户级线程仅存在于用户空间中,与内核无关;就内核而言,它只是管理常规的进程—单线程进程,而感知不到用户级线程的存在;每个线程控制块都设置在用户空间中,所有对线程的操作也在用户空间中由线程库中的函数完成,无需内核的帮助;设置了用户级线程的系统,其调度仍是以进程为单位进行的。

优点:

  • 线程切换不需要转换到内核空间,节省了宝贵的内核空间;
  • 调度算法可以是进程专用,由用户程序进行指定;
  • 用户级线程实现和操作系统无关;​​​​​​

缺点:

  • 系统调用阻塞,同一进程中一个线程阻塞和整个进程都阻塞了。
  • 一个进程只能在一个cpu上获得执行

创建用户级线程的库函数为:    

int pthread_create (pthread_t *THREAD,
                    pthread_attr_t * ATTR,
                    void * (*START_ROUTINE)(void *),
                    void * ARG)
pthread_create(tid,NULL,func,arg);

其中

  • 第一个参数是pthread_t型的指针,用于保存线程id;
  • 第二个参数是pthread_attr_t的指针,用于说明要创建的线程的属性,NULL表示使用缺省属性;
  • 第三个参数void * (*START_ROUTINE)(void *)指明了线程的入口,是一个只有一个(void *)参数的函数;
  • 第四个参数void * ARG是传给线程入口函数的参数。

详细说明在

(20条消息) 多线程之pthread_create()函数_wushuomin的博客-CSDN博客_pthread_create

实验设计

  • 实时任务用task数据结构描述,设计四个函数:select_proc()用于实现调度算法,被选中任务执行proc(),在没有可执行任务时执行idle(),主函数mian()初始化相关数据,创建实时任务并对任务进行调度。
  • 为模拟调度算法,给每个线程设置一个等待锁,暂不运行的任务等待在相应的锁变量上。主线程按调度算法唤醒一个子线程,被选中线程执行一个时间单位,然后将控制权交给主线程判断是否需要重新调度。

代码

#include "math.h"
#include "sched.h"
#include "pthread.h"
#include "stdlib.h"
#include "semaphore.h" 
#include "stdio.h"
#include <unistd.h>
typedef struct{  //实时任务描述
    char task_id;
    int call_num;  //任务发生次数
    int ci;  // Ci
    int ti;  //Ti 
    int ci_left;
    int ti_left; 
    int flag;  //任务是否活跃,0否,2是
    int arg;  //参数
    pthread_t th;  //任务对应线程
}task;
void proc(int* args);
void* idle();
int select_proc();
int task_num = 0;
int idle_num = 0;
int alg;  //所选算法,1 for EDF,2 for RMS
int curr_proc=-1;
int demo_time = 100;  //演示时间
task* tasks;
pthread_mutex_t proc_wait[100];
pthread_mutex_t main_wait, idle_wait;
float sum=0;
pthread_t idle_proc;
int main(int argc,char** argv)
{   
    pthread_mutex_init(&main_wait,NULL);
    pthread_mutex_lock(&main_wait);  //下次执行lock等待
    pthread_mutex_init(&idle_wait,NULL);
    pthread_mutex_lock(&idle_wait);  //下次执行lock等待
    printf("Please input number of real time tasks:\n");
    scanf("%d",&task_num);
    tasks = (task*)malloc(task_num*sizeof(task));
    int i;
for(i=0;i<task_num;i++)
{
        pthread_mutex_init(&proc_wait[i],NULL);
        pthread_mutex_lock(&proc_wait[i]);
    }
for(i=0;i<task_num;i++)
{
        printf("Please input task id, followed by Ci and Ti:\n");
    getchar();
        scanf("%c,%d,%d,",&tasks[i].task_id,&tasks[i].ci,&tasks[i].ti);
        tasks[i].ci_left=tasks[i].ci;
        tasks[i].ti_left=tasks[i].ti;
        tasks[i].flag=2;
        tasks[i].arg=i;
        tasks[i].call_num=1; 
        sum=sum+(float)tasks[i].ci/(float)tasks[i].ti; 
    }
    printf("Please input algorithm, 1 for EDF, 2 for RMS:");
    getchar();
    scanf("%d",&alg);
    printf("Please input demo time:");
    scanf("%d",&demo_time);
    double r=1;  //EDF算法
    if(alg==2)
    {  //RMS算法
        r=((double)task_num)*(exp(log(2)/(double)task_num)-1);
        printf("r is %lf\n",r);
    }
    if(sum>r)
    {  //不可调度
        printf("(sum=%lf > r=%lf) ,not schedulable!\n",sum,r);
        exit(2);
    }

    pthread_create(&idle_proc,NULL,(void*)idle,NULL); //创建闲逛线程
    for(i=0;i<task_num;i++)  //创建实时任务线程
        pthread_create(&tasks[i].th,NULL,(void*)proc,&tasks[i].arg);
    for(i=0;i<demo_time;i++)
    {
         int j; 
         if((curr_proc=select_proc(alg))!=-1)
         {  //按调度算法选线程
             pthread_mutex_unlock(&proc_wait[curr_proc]);  //唤醒
             pthread_mutex_lock(&main_wait);  //主线程等待
          }
          else
          {   //无可运行任务,选择闲逛线程
              pthread_mutex_unlock(&idle_wait);  
              pthread_mutex_lock(&main_wait);
          }
         for(j=0;j<task_num;j++)
          {  //Ti--,为0时开始下一周期
              if(--tasks[j].ti_left==0)
              {
                  tasks[j].ti_left=tasks[j].ti;
                  tasks[j].ci_left=tasks[j].ci;
                  pthread_create(&tasks[j].th,NULL,(void*)proc,&tasks[j].arg);
                  tasks[j].flag=2;
              }
         }
    }
    printf("\n");
    sleep(10); 
};

void proc(int* args)
{
    while(tasks[*args].ci_left>0)
    {
        pthread_mutex_lock(&proc_wait[*args]);  //等待被调度
        if(idle_num!=0)
{
            printf("idle(%d)",idle_num);
idle_num=0;
        }
        printf("%c%d",tasks[*args].task_id,tasks[*args].call_num);
        tasks[*args].ci_left--;  //执行一个时间单位
        if(tasks[*args].ci_left==0)
        {
            printf("(%d)",tasks[*args].ci);
            tasks[*args].flag=0;
            tasks[*args].call_num++;
        }
        pthread_mutex_unlock(&main_wait); //唤醒主线程
    }
};
void* idle()
{
     while(1)
      {
        pthread_mutex_lock(&idle_wait);  //等待被调度
        printf("->");  //空耗一个时间单位
        idle_num++;
        pthread_mutex_unlock(&main_wait);  //唤醒主控线程
    }
};
int select_proc(int alg)
{
    int j;
    int temp1,temp2;
    temp1=10000;
    temp2=-1;
    if((alg==2)&&(curr_proc!=-1)&&(tasks[curr_proc].flag!=0))
        return curr_proc; 

      for(j=0;j<task_num;j++)
      {
        if(tasks[j].flag==2)
          {
            switch(alg)
              {
                case 1:    //EDF算法
                     if(temp1>tasks[j].ti_left)
                        {
                           temp1=tasks[j].ti_left;
                            temp2=j;
                    }
                case 2:    //RMS算法
                   if(temp1>tasks[j].ti)
                          {
                        temp1=tasks[j].ti;
                        temp2=j;
                    }
               }
             }
         }
            return temp2;
}

输出结果:

书中例1算法EDF结果

Please input number of real time tasks:

2

Please input task id, followed by Ci and Ti:

a,10,20,

Please input task id, followed by Ci and Ti:

b,25,50,

Please input algorithm, 1 for EDF, 2 for RMS:1

Please input demo time:200

书中例2算法RMS结果

Please input number of real time tasks:

3

Please input task id, followed by Ci and Ti:

a,20,100,

Please input task id, followed by Ci and Ti:

b,40,150,

Please input task id, followed by Ci and Ti:

c,100,350,

Please input algorithm, 1 for EDF, 2 for RMS:2

Please input demo time:400

书中第三章习题27算法EDF结果

Please input number of real time tasks:

3

Please input task id, followed by Ci and Ti:

a,10,30,

Please input task id, followed by Ci and Ti:

b,15,40,

Please input task id, followed by Ci and Ti:

c,5,50,

Please input algorithm, 1 for EDF, 2 for RMS:1

Please input demo time:300

书中第三章习题27算法RMS结果

Please input number of real time tasks:

3

Please input task id, followed by Ci and Ti:

a,10,30,

Please input task id, followed by Ci and Ti:

b,15,40,

Please input task id, followed by Ci and Ti:

c,5,50,

Please input algorithm, 1 for EDF, 2 for RMS:2

Please input demo time:300

结果分析

函数功能:

main函数:

初始化主线程,子线程的互斥锁,初始化每一个task所需要的值,创建idle线程和proc线程,并且通过select_proc的返回值进行调度。

select_proc函数:

根据传入参数以及tasks的情况选择返回值,-1为调度idle线程,否则调度对应的task线程。

idle函数:

执行该函数空耗一个时间单位,然后唤醒主线程

proc函数:

执行该函数对对应的task执行一个时间单位,然后唤醒主线程

函数之间调用关系:

main函数在for循环(在输入的demo_time)中首先调用select_proc函数进行判断应该调度哪一个线程,并返回对应值,然后在main函数中根据返回值判断是调用idle函数还是proc函数。调用完了继续回到main函数执行for循环。

关键语句功能说明:

pthread_mutex_init(&main_wait,NULL);

    pthread_mutex_lock(&main_wait);  //下次执行lock等待

    pthread_mutex_init(&idle_wait,NULL);

pthread_mutex_lock(&idle_wait);  //下次执行lock等待

这一段是添加并尝试获取互斥锁,为了使我们可以控制线程的运行

for(i=0;i<task_num;i++)

{

        pthread_mutex_init(&proc_wait[i],NULL);

        pthread_mutex_lock(&proc_wait[i]);

    }

这一段也是同理给这些不同的任务添加并尝试获取互斥锁

scanf("%c,%d,%d,",&tasks[i].task_id,&tasks[i].ci,&tasks[i].ti);

    tasks[i].ci_left=tasks[i].ci;

    tasks[i].ti_left=tasks[i].ti;

    tasks[i].flag=2;

    tasks[i].arg=i;

    tasks[i].call_num=1;

    sum=sum+(float)tasks[i].ci/(float)tasks[i].ti;

这一段是给每一个task初始化的

if(alg==2)

    {  //RMS算法

        r=((double)task_num)*(exp(log(2)/(double)task_num)-1);

        printf("r is %lf\n",r);

}

如果是RMS算法的话,要在程序运行最开始打印出来r的值

    if(sum>r)

    {  //不可调度

        printf("(sum=%lf > r=%lf) ,not schedulable!\n",sum,r);

        exit(2);

}

判断是否能不能调度,如果不能的话打印标识并直接退出

if((curr_proc=select_proc(alg))!=-1)

    {  //按调度算法选线程

         pthread_mutex_unlock(&proc_wait[curr_proc]);  //唤醒

         pthread_mutex_lock(&main_wait);  //主线程等待

    }

    else

    {   //无可运行任务,选择闲逛线程

          pthread_mutex_unlock(&idle_wait);  

          pthread_mutex_lock(&main_wait);

    }

根据select_proc(alg)的返回值,如果返回-1,则无可运行任务,选择idle线程运行,否则运行proc线程

switch(alg)

    {

          case 1:    //EDF算法

              if(temp1>tasks[j].ti_left)

              {

                    temp1=tasks[j].ti_left;

                    temp2=j;

              }

          case 2:    //RMS算法

              if(temp1>tasks[j].ti)

              {

                    temp1=tasks[j].ti;

                    temp2=j;

              }

     }

在selete_proc(alg)中根据传入参数的不同,选择不同的调度算法。

思考题

  • 上述参考算法中,被选中任务每运行一个时间单位即将控制权交给主线程,判断是否需要切换实时任务,这可看作发生一次时钟中断。实际上时钟中断的发生频率远没有这样频繁,因而上述调度会产生较大的开销。改进上述算法,使其只在需要重新调度任务时才返回主控线程。
  • 在上述改进的基础上,对一组可调度实时事务,统计对不同调度算法的线程切换次数(不计主线程切换),并将其显示出来

还没写

参考

猜你喜欢

转载自blog.csdn.net/linwuwu985/article/details/125158439