操作系统实验报告16

实验内容

  • 实验内容:CPU 调度。

    • 讨论课件 Lecture19-20 中 CPU 调度算法的例子,尝试基于 POSIX API 设计一个简单调度器(不考虑资源竞争问题):

      • 创建一些 Pthread 线程任务,建立一个管理链队列,结点内容起码包括到达时间、WCT、优先级、调度状态(运行、就绪、阻塞)等调度参数;
      • 每个任务有一个调度信号量,任务启动后在其调度信号量上执行 wait;
      • 调度器按照调度策略对处于运行态的任务(如果有的话)的调度信号量执行 wait,并选取适当任务的调度信号量执行 signal;
      • 实现简单调度策略:FCFS、SJF、Priority。分别计算任务平均等待时间。
    • 拓展问题1:设计若干资源信号量模拟资源竞争情况;增加时间片参数实现 RR 调度;验证优先级反转;建立多个链队列实现多级反馈调度。
    • 拓展问题2:设计一个抢占式优先策略实时调度器,测试在一个给定的工作负载下优先级反转的情况。

实验环境

  • 架构:Intel x86_64 (虚拟机)
  • 操作系统:Ubuntu 20.04
  • 汇编器:gas (GNU Assembler) in AT&T mode
  • 编译器:gcc

技术日志

实验内容原理

  • CPU调度程序

    • 每当CPU空闲时,操作系统就应从就绪队列中选择一个进程来执行。进程执行选择短期调度程序或CPU调度程序。调度程序从内存中选择一个能够执行的进程,并为其分配CPU。
    • 注意,就绪队列不必是先进先出队列。就绪队列的实现可以是FIFO队列、优先队列、树或简单的无序链表等。然而,在概念上,就绪队列内的所有进程都要排队以便等待在CPU上运行。队列内的记录通常为进程控制块(PCB)。
  • 抢占调度
    • 需进行CPU调度的情况可分为以下四种:

      • 当一个进程从运行状态切换到等待状态时(例如,I/O请求,或wait()调用以便等待一个子进程的终止)。
      • 当一个进程从运行状态切换到就绪状态时(例如,当出现中断时)。
      • 当一个进程从等待状态切换到就绪状态时(例如,I/O完成)。
      • 当一个进程终止时。
    • 对于第1种和第4种情况,除了调度没有选择。一个新进程(如果就绪队列有一个进程存在)必须被选择执行。不过,对于第2种和第3种情况,还是有选择的。
    • 如果调度只能发生在第1种和第4种情况下,则调度方案称为非抢占的或协作的;否则,调度方案称为抢占的。
      • 在非抢占调度下,一旦某个进程分配到CPU,该进程就会一直使用CPU,直到它终止或切换到等待状态。
    • 当多个进程共享数据时,抢占调度可能导致竞争情况。
  • 调度算法
    • 先到先服务(FCFS)调度

      • 是非抢占式算法
      • 采用这种方案,先请求CPU的进程首先分配到CPU。
      • FCFS策略可以通过FIFO队列容易地实现。当一个进程进入就绪队列时,它的PCB会被链接到队列尾部。当CPU空闲时,它会分配给位于队列头部的进程,并且这个运行进程从队列中移去。
      • FCFS调度代码编写简单并且理解容易。缺点是,平均等待时间往往很长。
      • 护航效果:所有其他进程都等待一个大进程释放CPU。与让较短进程先进行相比,这会导致CPU和设备的使用率降低。
    • 最短作业优先(SJF)调度
      • 有非抢占式也有抢占式算法
      • 这个算法将每个进程与其下次CPU执行的长度关联起来。当CPU变为空闲时,它会被赋给具有最短CPU执行的进程。如果两个进程具有同样长度的CPU执行,那么可以由FCFS来处理。
      • 注意,一个更为恰当的表示是最短下次CPU执行算法,这是因为调度取决于进程的下次CPU执行的长度,而不是其总的长度。
      • 当一个新进程到达就绪队列而以前进程正在执行时,就需要选择使用非抢占式算法还是抢占式算法了。新进程的下次CPU执行,与当前运行进程的尚未完成的CPU执行相比,可能还要小。
        • 抢占SJF算法会抢占当前运行进程。
        • 非抢占SJF算法会允许当前运行进程以先完成CPU执行。
      • 抢占SJF调度有时称为最短剩余时间优先调度
    • 优先级调度(Priority)
      • 有非抢占式也有抢占式算法
      • 每个进程都有优先级与其关联,而具有最高优先级的进程会分配到CPU。具有相同优先级的进程按FCFS顺序调度。
      • 当一个进程到达就绪队列时,比较它的优先级与当前运行进程的优先级。如果新到达进程的优先级高于当前运行进程的优先级,那么抢占优先级调度算法就会抢占CPU。非抢占式优先级调度算法只是将新的进程加到就绪队列的头部。
      • 优先级调度算法的一个主要问题是无穷阻塞或饥饿。就绪运行但是等待CPU的进程可以认为是阻塞的。优先级调度算法可让某个低优先级进程无穷等待CPU。
      • 低优先级进程的无穷等待问题的解决方案之一是老化。老化逐渐增加在系统中等待很长时间的进程的优先级。

设计报告

调度器设计图

代码设计

// scheduler.c文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>#define STATUS_RUNNING 1  // 运行调度状态
#define STATUS_READY 2    // 就绪调度状态
#define STATUS_WAITING 3  // 阻塞调度状态#define POLICY_FCFS 1             // 先到先服务调度策略
#define POLICY_SJF_PEM 2          // 抢占式最短作业优先调度策略
#define POLICY_SJF_NOT_PEM 3      // 非抢占式最短作业优先调度策略
#define POLICY_PRIORITY_PEM 4     // 抢占式优先级调度策略
#define POLICY_PRIORITY_NOT_PEM 5 // 非抢占式优先级调度策略#define MAX_TASK_NUM 100 // 最大任务个数typedef struct node {int arrival_time;     // 到达时间int WCT;              // 最坏预期执行时间int priority;         // 优先级int schedule_status;  // 调度状态pthread_t ptid;       // 线程号sem_t wait_sem;       // 任务启动时的调度等待信号量struct node *next;    // 指向下一个结点的指针
} Tasknode; // 管理链队列结点结构typedef struct {Tasknode *head;               // 管理链队列表头pthread_t sched_ptid;         // 调度器线程的线程号pthread_mutex_t sched_mutex;  // 调度器互斥锁int schedule_policy;          // 调度策略int finished_task;            // 完成任务的个数
} Scheduler; // 调度器结构Scheduler sched; // 调度器
sigset_t zeromask; // 阻塞线程函数sigsuspend()所用参数
long wait_sum_time = 0; // 单个线程等待总时间long begin_us, run_us; // 总计时器部分,测试用
struct timeval t_time;// 初始化调度器
void init_scheduler(int task_num);
// 创建任务管理信息结点
Tasknode* creat_thread_node(int arrival_time, int WCT, int priority);// 每个线程执行的任务
void *task_runner(void *arg);
// 调度器运行线程
void *schedule_runner(void *arg);// 将任务信息结点按照调度策略放入管理链队列
void fcfs(Tasknode *new_node);
void sjf_pem(Tasknode *new_node);
void sjf_not_pem(Tasknode *new_node);
void priority_pem(Tasknode *new_node);
void priority_not_pem(Tasknode *new_node);// 向管理链队列添加任务
void add_task(Tasknode *node, int sched_policy);
// 从管理链队列删除任务
void delete_task();// 发送信号阻塞线程,直到接收到一个新的信号
void thread_wait_sighand(int signo);
// 发送信号使阻塞线程继续运行
void thread_cont_sighand(int signo);
// 调度器线程接收信号后调度器选择一个任务,即表头任务运行
void sched_run_sighand(int signo);// 打印任务信息列表,测试用
void print_tasklist(int *arrival_time, int *WCT, int *priority, int task_num);
// 计算任务平均等待时间
double cal_aver_wait_time(int *arrival_time, int *WCT, int *priority, int task_num, int sched_policy);// 调度器运行线程
void *schedule_runner(void *arg) {int task_num = *(int *)arg;// 当完成任务个数等于总任务个数时,调度器线程退出while (sched.finished_task != 5 * task_num);pthread_exit(0);
}// 创建任务管理信息结点
Tasknode* creat_thread_node(int arrival_time, int WCT, int priority) {Tasknode *new_node = (Tasknode *)malloc(sizeof(Tasknode));// 设置线程的到达时间,最长预期执行时间,优先级new_node->arrival_time = arrival_time;new_node->WCT = WCT;new_node->priority = priority;new_node->next = NULL;// 初始化线程的调度等待信号量为0int ret = sem_init(&new_node->wait_sem, 1, 0);if (ret == -1) {perror("creat_thread_node(): sem_init-wait_sem");exit(1);}return new_node;
}// 初始化调度器
void init_scheduler(int task_num) {// 管理链队列的表头为空sched.head = NULL;// 调度策略默认为先到先服务sched.schedule_policy = POLICY_FCFS;// 已经完成任务个数为0sched.finished_task = 0;// 初始化调度器互斥锁pthread_mutex_init(&sched.sched_mutex, NULL);// 创建调度器运行线程int ret = pthread_create(&sched.sched_ptid, NULL, &schedule_runner, &task_num);if (ret != 0) {fprintf(stderr, "init_scheduler(): pthread_create error: %s\n", strerror(ret));exit(1);}
}// 将任务信息结点按照先到先服务策略放入管理链队列
void fcfs(Tasknode *new_node) {// 如果管理链队列的表头为空,那么直接插入结点if (sched.head == NULL) {sched.head = new_node;return;}// 找到管理链队列的表尾,插入结点Tasknode *cur_node = sched.head;while (cur_node->next != NULL) {cur_node = cur_node->next;}cur_node->next = new_node;
}// 将任务信息结点按照抢占式最短作业优先策略放入管理链队列
void sjf_pem(Tasknode *new_node) {// 如果管理链队列的表头为空,那么直接插入结点if (sched.head == NULL) {sched.head = new_node;return;}// 如果结点的WCT小于表头结点的WCT,那么直接在表头结点之前插入结点else if (new_node->WCT < sched.head->WCT) {new_node->next = sched.head;sched.head = new_node;return;}Tasknode *cur_node = sched.head;Tasknode *pre_node = NULL;while (cur_node != NULL && new_node->WCT >= cur_node->WCT) {pre_node = cur_node;cur_node = cur_node->next;}pre_node->next = new_node;new_node->next = cur_node;
}// 将任务信息结点按照非抢占式最短作业优先策略放入管理链队列
void sjf_not_pem(Tasknode *new_node) {// 如果管理链队列的表头为空,那么直接插入结点if (sched.head == NULL) {sched.head = new_node;return;}// 寻找管理链队列中前一个结点的WCT小于等于结点,后一个结点的WCT大于结点的位置插入结点,如果没有那么就在表尾插入结点Tasknode *cur_node = sched.head->next;Tasknode *pre_node = sched.head;while (cur_node != NULL && new_node->WCT >= cur_node->WCT) {pre_node = cur_node;cur_node = cur_node->next;}pre_node->next = new_node;new_node->next = cur_node;
}// 将任务信息结点按照抢占式优先级策略放入管理链队列
void priority_pem(Tasknode *new_node) {// 如果管理链队列的表头为空,那么直接插入结点if (sched.head == NULL) {sched.head = new_node;return;}// 如果结点的优先级大于表头结点的优先级,那么直接在表头结点之前插入结点else if (new_node->priority > sched.head->priority) {new_node->next = sched.head;sched.head = new_node;return;}// 寻找管理链队列中前一个结点的WCT小于等于结点,后一个结点的WCT大于结点的位置插入结点,如果没有那么就在表尾插入结点Tasknode *cur_node = sched.head;Tasknode *pre_node = NULL;while (cur_node != NULL && new_node->priority <= cur_node->priority) {pre_node = cur_node;cur_node = cur_node->next;}pre_node->next = new_node;new_node->next = cur_node;
}// 将任务信息结点按照非抢占式优先级策略放入管理链队列
void priority_not_pem(Tasknode *new_node) {// 如果管理链队列的表头为空,那么直接插入结点if (sched.head == NULL) {sched.head = new_node;return;}// 寻找管理链队列中前一个结点的优先级大于等于结点,后一个结点的优先级小于结点的位置插入结点,如果没有那么就在表尾插入结点Tasknode *cur_node = sched.head->next;Tasknode *pre_node = sched.head;while (cur_node != NULL && new_node->priority <= cur_node->priority) {pre_node = cur_node;cur_node = cur_node->next;}pre_node->next = new_node;new_node->next = cur_node;
}// 向管理链队列添加任务
void add_task(Tasknode *node, int sched_policy) {// 获取调度器的互斥锁,防止其它线程更改调度器pthread_mutex_lock(&sched.sched_mutex);// 如果有任务正在运行,那么先阻塞这个任务,将这个任务的调度状态改为就绪态if (sched.head != NULL) {pthread_kill(sched.head->ptid, SIGUSR1);sched.head->schedule_status = STATUS_READY;}// 添加的任务信息结点的调度状态为阻塞态node->schedule_status = STATUS_WAITING;// 根据调度策略,向管理链队列中插入任务信息结点switch (sched_policy) {case POLICY_FCFS :fcfs(node);break;case POLICY_SJF_PEM:sjf_pem(node);break;case POLICY_SJF_NOT_PEM:sjf_not_pem(node);break;case POLICY_PRIORITY_PEM:priority_pem(node);break;case POLICY_PRIORITY_NOT_PEM:priority_not_pem(node);break;}sleep(0);// 向调度器线程发送信号,选取适当的任务执行pthread_kill(sched.sched_ptid, SIGUSR2);// 释放调度器的互斥锁pthread_mutex_unlock(&sched.sched_mutex);
}// 从管理链队列删除任务
void delete_task() {// 获取调度器的互斥锁,防止其它线程更改调度器pthread_mutex_lock(&sched.sched_mutex);// 运行完了的任务结点是管理链队列的头结点,释放其资源Tasknode *temp = sched.head;sched.head = sched.head->next;sem_destroy(&temp->wait_sem);free(temp);temp = NULL;// 如果管理链队列中还有任务,那么继续运行if (sched.head != NULL) {pthread_kill(sched.head->ptid, SIGCONT);}// 释放调度器的互斥锁pthread_mutex_unlock(&sched.sched_mutex);
}// 每个线程执行的任务
void *task_runner(void *arg) {int ret;// 计时器部分long start_us, end_us;struct timeval t;// 获取每个线程的任务信息结点Tasknode *task_node = (Tasknode *)arg;struct timespec req, rem;// 设置这个任务信息结点的线程号task_node->ptid = pthread_self();// 设置线程运行时间为任务信息结点中的最长预期执行时间req.tv_sec = task_node->WCT;req.tv_nsec = 0;// 线程休眠任务信息结点中的到达时间后再加入管理链队列,模拟到达时间sleep(task_node->arrival_time);// 获取任务到达时间gettimeofday(&t, 0);start_us = (long)(t.tv_sec * 1000 * 1000) + t.tv_usec;add_task(task_node, sched.schedule_policy);// 获取任务开始时间gettimeofday(&t_time, 0);run_us = (long)(t_time.tv_sec * 1000 * 1000) + t_time.tv_usec;// 打印这个执行任务的线程的开始任务时间点,测试用printf("Task ptid = %ld starts at Time: %lf sec\n", pthread_self(), (double)(run_us - begin_us) / 1000000.0);// 线程先阻塞,等待调度器调度sem_wait(&task_node->wait_sem);// 线程休眠时间模拟执行时间,如果有信号中断,ret返回-1,剩余时间存储rem中ret = nanosleep(&req, &rem);// 返回中断后继续休眠剩余时间模拟完整执行时间while (ret < 0) {req = rem;ret = nanosleep(&req, &rem);}// 获取结束时间gettimeofday(&t, 0);end_us = (long)(t.tv_sec * 1000 * 1000) + t.tv_usec;// 打印任务结束时间gettimeofday(&t_time, 0);run_us = (long)(t_time.tv_sec * 1000 * 1000) + t_time.tv_usec;// 打印这个执行任务的线程的结束任务时间点,测试用printf("Task ptid = %ld ends at Time: %lf sec\n", pthread_self(), (double)(run_us - begin_us) / 1000000.0);// 等待时间为实际运行时间减去预期执行时间wait_sum_time += end_us - start_us - task_node->WCT * 1000 * 1000;// 执行完后从管理链队列中删除任务结点delete_task();// 调度器已完成的任务数加一sched.finished_task++;pthread_exit(0);
}// 发送信号阻塞线程,直到接收到一个新的信号
void thread_wait_sighand(int signo) {// 获取当前时间gettimeofday(&t_time, 0);run_us = (long)(t_time.tv_sec * 1000 * 1000) + t_time.tv_usec;// 打印这个执行任务的线程的阻塞时间点,测试用printf("Task ptid = %ld stops at Time: %lf sec\n", pthread_self(), (double)(run_us - begin_us) / 1000000.0);sigsuspend(&zeromask);
}// 发送信号使阻塞线程继续运行
void thread_cont_sighand(int signo) {// 获取当前时间gettimeofday(&t_time, 0);run_us = (long)(t_time.tv_sec * 1000 * 1000) + t_time.tv_usec;// 打印这个执行任务的线程的阻塞之后继续运行时间点,测试用printf("Task ptid = %ld continues at Time: %lf sec\n", pthread_self(), (double)(run_us - begin_us) / 1000000.0);
}// 调度器线程接收信号后调度器选择一个任务,即表头任务运行
void sched_run_sighand(int signo) {// 如果这个任务处于阻塞态,那么先转为就绪态,再转为运行态,用sem_post()函数开始运行任务if (sched.head->schedule_status == STATUS_WAITING) {sched.head->schedule_status = STATUS_READY;sched.head->schedule_status = STATUS_RUNNING;sem_post(&sched.head->wait_sem);// 获取任务执行时间点 gettimeofday(&t_time, 0);run_us = (long)(t_time.tv_sec * 1000 * 1000) + t_time.tv_usec;// 打印这个执行任务的线程的运行任务时间点,测试用printf("Task ptid = %ld starts to run at Time: %lf sec\n", sched.head->ptid, (double)(run_us - begin_us) / 1000000.0);}// 如果这个任务处于就绪态,那么转为运行态,发送信号使其继续运行else if (sched.head->schedule_status == STATUS_READY) {sched.head->schedule_status = STATUS_RUNNING;pthread_kill(sched.head->ptid, SIGCONT);}
}// 打印任务信息列表,测试用
void print_tasklist(int *arrival_time, int *WCT, int *priority, int task_num) {printf("Task list:\n");printf("------------------------------\n");printf("|Id|Arrival time|WCT|Priority|\n");printf("------------------------------\n");for (int i = 0; i < task_num; ++i) {printf("|%2d|    %3d     |%3d|   %2d   |\n", i + 1, arrival_time[i], WCT[i], priority[i]);}printf("------------------------------\n");printf("\n");
}// 计算任务平均等待时间
double cal_aver_wait_time(int *arrival_time, int *WCT, int *priority, int task_num, int sched_policy) {int ret;pthread_t ptid[MAX_TASK_NUM];double aver_wait_time;printf("\n----------------------------------------------------------\n");printf("schedule policy: ");// 打印调度策略switch (sched_policy) {case POLICY_FCFS :printf("FCFS\n\n");break;case POLICY_SJF_PEM:printf("SJF(preemptive)\n\n");break;case POLICY_SJF_NOT_PEM:printf("SJF(not preemptive)\n\n");break;case POLICY_PRIORITY_PEM:printf("Priority(preemptive)\n\n");break;case POLICY_PRIORITY_NOT_PEM:printf("Priority(not preemptive)\n\n");break;}// 打印任务列表print_tasklist(arrival_time, WCT, priority, task_num);// 设置调度策略sched.schedule_policy = sched_policy;wait_sum_time = 0;// 获取计时开始时间gettimeofday(&t_time, 0);begin_us = (long)(t_time.tv_sec * 1000 * 1000) + t_time.tv_usec;// 根据信息创建任务信息结点和相应的线程for (int i = 0; i < task_num; i++) {Tasknode *new_node = creat_thread_node(arrival_time[i], WCT[i], priority[i]);ret = pthread_create(&ptid[i], NULL, &task_runner, (void *)new_node);if(ret != 0) {fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(1);}printf("Task%d ptid:%ld\n", i + 1, ptid[i]);}printf("\n");// 主线程等待所有子线程运行结束后再继续执行for (int i = 0; i < task_num; i++) {ret = pthread_join(ptid[i], NULL);if(ret != 0) {fprintf(stderr, "pthread_join error: %s\n", strerror(ret));exit(1);}}// 计算任务平均等待时间printf("\nThe waiting time = %lf sec\n", (double)wait_sum_time / 1000000.0);aver_wait_time = (double)wait_sum_time / 1000000.0 / (double)task_num;printf("The average of waiting time = %lf sec\n", aver_wait_time);printf("----------------------------------------------------------\n");return aver_wait_time;
}int main() {int arrival_time[MAX_TASK_NUM];int WCT[MAX_TASK_NUM];int priority[MAX_TASK_NUM];int task_num;printf("Please input the number of tasks: ");scanf("%d", &task_num);// 设置不同捕捉信号的信号处理函数struct sigaction act1, act2, act3;memset(&act1, 0, sizeof(act1));memset(&act2, 0, sizeof(act2));memset(&act3, 0, sizeof(act3));sigemptyset(&act1.sa_mask);sigemptyset(&act2.sa_mask);sigemptyset(&act3.sa_mask);act1.sa_flags = 0;act2.sa_flags = 0;act3.sa_flags = 0;act1.sa_handler = thread_wait_sighand;act2.sa_handler = thread_cont_sighand;act3.sa_handler = sched_run_sighand;// 设置捕捉到SIGUSR1后信号处理函数为使线程阻塞sigaction(SIGUSR1, &act1, NULL);// 设置捕捉到SIGCONT后信号处理函数为使阻塞的线程继续sigaction(SIGCONT, &act2, NULL);// 设置捕捉到SIGUSR2后信号处理函数为使调度器选择一个任务运行sigaction(SIGUSR2, &act3, NULL);// 初始化调度器init_scheduler(task_num);// 输入每个任务的到达时间,最长预期运行时间,优先级等for (int i = 0; i < task_num; i++) {printf("Please input task%d's arrival_time, WCT, priority:\n", i + 1);scanf("%d %d %d", &arrival_time[i], &WCT[i], &priority[i]);}double aver_wait_time_fcfs = cal_aver_wait_time(arrival_time, WCT, priority, task_num, POLICY_FCFS);double aver_wait_time_sjf_pem = cal_aver_wait_time(arrival_time, WCT, priority, task_num, POLICY_SJF_PEM);double aver_wait_time_sjf_not_pem = cal_aver_wait_time(arrival_time, WCT, priority, task_num, POLICY_SJF_NOT_PEM);double aver_wait_time_priority_pem = cal_aver_wait_time(arrival_time, WCT, priority, task_num, POLICY_PRIORITY_PEM);double aver_wait_time_priority_not_pem = cal_aver_wait_time(arrival_time, WCT, priority, task_num, POLICY_PRIORITY_NOT_PEM);// 打印不同调度策略平均等待时间列表printf("\nAverage waiting time list(sec):\n");printf("-----------------------------------------------\n");printf("|         Policy         |Average waiting time|\n");printf("-----------------------------------------------\n");printf("|          FCFS          |     %10lf     |\n", aver_wait_time_fcfs);printf("|     SJF(preemptive)    |     %10lf     |\n", aver_wait_time_sjf_pem);printf("|   SJF(not preemptive)  |     %10lf     |\n", aver_wait_time_sjf_not_pem);printf("|  Priority(preemptive)  |     %10lf     |\n", aver_wait_time_priority_pem);printf("|Priority(not preemptive)|     %10lf     |\n", aver_wait_time_priority_not_pem);printf("-----------------------------------------------\n");
}

执行命令:

gcc scheduler.c -pthread
./a.out

验证各个调度算法的正确性

测试用例1:

3
0 5 1
2 4 3
4 3 2
Task Arrival Time WCT Priority
1 0 5 1
2 2 4 3
3 4 3 2

先到先服务调度策略:

可以看到,任务1在0s时开始执行;
在2s时任务2到达,运行着的任务1先阻塞,加入任务2后调度器根据先到先服务策略继续选择任务1执行,任务1在2s时继续执行,任务2阻塞;
在4s时任务3到达,运行着的任务1先阻塞,加入任务3后调度器根据先到先服务策略继续选择任务1执行,任务1在4s时继续执行,任务3阻塞;
在5s时任务1结束,调度器根据先到先服务策略选择任务2执行,任务2在5s继续执行;
在9s时任务2结束,调度器根据先到先服务策略选择任务3执行,任务2在9s继续执行;
在12s时任务3结束,任务全部完成。

过程符合先到先服务的调度策略。

甘特图:

计算等待时间为 (0 - 0) + (5 - 2) + (9 - 4) = 8
计算平均等待时间为 8 / 3 = 2.67s
计算也正确。

抢占式最短作业优先调度策略:

可以看到,任务1在0s时开始执行;
在2s时任务2到达,运行着的任务1先阻塞,加入任务2后,由于任务2的WCT比任务1小,所以调度器根据抢占式最短作业优先调度策略,选择任务2执行,任务2在2s时开始执行,任务1阻塞;
在4s时任务3到达,运行着的任务2先阻塞,加入任务3后,由于任务3的WCT比任务2小,所以调度器根据抢占式最短作业优先调度策略选择任务3执行,任务3在4s时开始执行,任务2阻塞;
在7s时任务3结束,调度器根据抢占式最短作业优先调度策略选择任务2执行,任务2在7s继续执行;
在9s时任务2结束,调度器根据抢占式最短作业优先调度策略选择任务1执行,任务1在9s继续执行;
在12s时任务3结束,任务全部完成。

过程符合抢占式最短作业优先的调度策略。

甘特图:

计算等待时间为 (9 - 2) + (7 - 4) + (4 - 4) = 10
计算平均等待时间为 10 / 3 = 3.33s
计算也正确。

非抢占式最短作业优先调度策略:

可以看到,任务1在0s时开始执行;
在2s时任务2到达,运行着的任务1先阻塞,加入任务2后,虽然任务2的WCT比任务1小,但是调度器根据非抢占式最短作业优先调度策略,继续选择任务1执行,任务1在2s时继续执行,任务2阻塞;
在4s时任务3到达,运行着的任务1先阻塞,加入任务3后,虽然任务3的WCT比任务1小,但是调度器根据非抢占式最短作业优先调度策略继续选择任务1执行,任务1在4s时继续执行,任务3阻塞;
在5s时任务1结束,调度器根据非抢占式最短作业优先调度策略选择WCT更小的任务3执行,任务3在5s继续执行;
在8s时任务3结束,调度器根据非抢占式最短作业优先调度策略选择任务2执行,任务2在8s继续执行;
在12s时任务2结束,任务全部完成。

过程符合非抢占式最短作业优先的调度策略。

甘特图:

计算等待时间为 (0 - 0) + (8 - 2) + (5 - 4) = 7
计算平均等待时间为 7 / 3 = 2.33s
计算也正确。

抢占式优先级调度策略:

可以看到,任务1在0s时开始执行;
在2s时任务2到达,运行着的任务1先阻塞,加入任务2后,由于任务2的优先级比任务1大,所以调度器根据抢占式优先级调度策略,选择任务2执行,任务2在2s时开始执行,任务1阻塞;
在4s时任务3到达,运行着的任务2先阻塞,加入任务3后,由于任务3的优先级比任务2小,所以调度器根据抢占式优先级调度策略继续选择任务2执行,任务2在4s时继续执行,任务3阻塞;
在6s时任务2结束,调度器根据抢占式优先级调度策略选择优先级更大的任务3执行,任务3在6s继续执行;
在9s时任务3结束,调度器根据抢占式优先级调度策略选择任务1执行,任务1在9s继续执行;
在12s时任务1结束,任务全部完成。

过程符合抢占式优先级的调度策略。

甘特图:

计算等待时间为 (9 - 2) + (2 - 2) + (6 - 4) = 9
计算平均等待时间为 9 / 3 = 3s
计算也正确。

非抢占式优先级调度策略:

可以看到,任务1在0s时开始执行;
在2s时任务2到达,运行着的任务1先阻塞,加入任务2后,虽然任务2的优先级比任务1大,但是调度器根据非抢占式优先级调度策略,继续选择任务1执行,任务1在2s时继续执行,任务2阻塞;
在4s时任务3到达,运行着的任务1先阻塞,加入任务3后,虽然任务3的优先级比任务1大,但是调度器根据非抢占式优先级调度策略继续选择任务1执行,任务1在4s时继续执行,任务3阻塞;
在5s时任务1结束,调度器根据非抢占式优先级调度策略选择优先级更大的任务2执行,任务2在5s继续执行;
在9s时任务2结束,调度器根据非抢占式优先级调度策略选择任务3执行,任务3在9s继续执行;
在12s时任务3结束,任务全部完成。

过程符合非抢占式优先级的调度策略。

甘特图:

计算等待时间为 (0 - 0) + (5 - 2) + (9 - 4) = 8
计算平均等待时间为 8 / 3 = 2.67s
计算也正确。

可以看出,各个调度算法基本正确。

测试用例2:

5
0 3 1
2 6 3
4 4 4
6 5 2
8 2 5
Task Arrival Time WCT Priority
1 0 3 1
2 2 6 3
3 4 4 4
4 6 5 2
5 8 2 5

执行截图:

甘特图:

[(0 - 0) + (3 - 2) + (9 - 4) + (13 - 6) + (18 - 8)] / 5 = 23 / 5 = 4.6

[(0 - 0) + (3 - 2 + 15 - 2) + (4 - 4) + (10 - 6) + (8 - 8)] / 5 = 18 / 5 = 3.6

[(0 - 0) + (3 - 2) + (11 - 4) + (15 - 6) + (9 - 8)] / 5 = 18 / 5 = 3.6

[(19 - 0) + (10 - 2) + (4 - 4) + (14 - 6) + (8 - 8)] / 5 = 33 / 5 = 6.6

[(0 - 0) + (3 - 2) + (11 - 4) + (15 - 6) + (9 - 8)] / 5 = 18 / 5 = 3.6

经过计算,结果基本正确。

测试用例3:

4
0 7 1
2 4 3
4 1 4
5 4 2
Task Arrival Time WCT Priority
1 0 7 1
2 2 4 3
3 4 1 4
4 5 4 2

执行截图:

经过计算,结果基本正确。

测试用例4:

4
0 8 1
1 4 3
2 9 4
3 5 2
Task Arrival Time WCT Priority
1 0 8 1
2 1 4 3
3 2 9 4
4 3 5 2

执行截图:

经过计算,结果基本正确。

操作系统实验报告16:CPU 调度相关推荐

  1. 操作系统实验报告15:进程同步与互斥线程池

    操作系统实验报告15 实验内容 实验内容:进程同步. 内容1:编译运行课件 Lecture18 例程代码. Algorithms 18-1 ~ 18-9. 内容2:在 Lab Week 13 的基础上 ...

  2. 操作系统实验报告10:线程1

    操作系统实验报告10 实验内容 实验内容:线程(1). 编译运行课件 Lecture13 例程代码: Algorithms 13-1 ~ 13-8 实验环境 架构:Intel x86_64 (虚拟机) ...

  3. 操作系统实验报告1:ucore Lab 1

    操作系统实验报告1 实验内容 阅读 uCore 实验项目开始文档 (uCore Lab 0),准备实验平台,熟悉实验工具. uCore Lab 1:系统软件启动过程 (1) 编译运行 uCore La ...

  4. 山东大学linux实验四CSDN,山东大学操作系统实验报告材料4进程同步实验

    <山东大学操作系统实验报告材料4进程同步实验>由会员分享,可在线阅读,更多相关<山东大学操作系统实验报告材料4进程同步实验(15页珍藏版)>请在人人文库网上搜索. 1.实用标准 ...

  5. 操作系统实验报告【太原理工大学】

    操作系统实验报告 温馨提示:仅供参考! 目录 操作系统实验报告 一.进程调度程序设计 1.程序清单 2.运行结果 3.分析总结 二.页式虚拟存储管理程序设计 1.程序清单 2.运行结果 3.分析总结 ...

  6. 操作系统实验报告18:硬盘柱面访问调度算法

    操作系统实验报告18 实验内容 实验内容:硬盘调度. 编写 C 程序模拟实现课件 Lecture25 中的硬盘柱面访问调度算法 包括 FCFS.SSTF.SCAN.C-SCAN.LOOK.C-LOOK ...

  7. 操作系统实验报告14:Peterson 算法

    操作系统实验报告14 实验内容 实验内容:Peterson 算法. 把 Lecture08 示例 alg.8-1~8-3 拓展到多个读线程和多个写线程,应用 Peterson 算法原理设计实现共享内存 ...

  8. 操作系统实验报告13:线程池简单实现

    操作系统实验报告13 实验内容 实验内容:设计实现一个线程池 (Thread Pool) 使用 Pthread API FIFO 先不考虑互斥问题 编译.运行.测试用例 实验环境 架构:Intel x ...

  9. 操作系统实验报告12:线程2

    操作系统实验报告12 实验内容 实验内容:线程(2). 编译运行课件 Lecture14 例程代码: Algorithms 14-1 ~ 14-7. 比较 pthread 和 clone() 线程实现 ...

最新文章

  1. 用Python玩转PPT!
  2. 反射效果,自己公司内部编码,在text上输入内容后,通过输入内容查询数据库,弹出下拉框
  3. windows 任务管理器,查看进程id,进程标识符pid
  4. 使用 jquery 创建数组
  5. Oracle表字段的增删改和重命名
  6. 方法参数泛型_无参数泛型方法反模式
  7. 谈论源码_5,000名开发人员谈论他们的薪水
  8. Docker使用Dockerfile构建镜像
  9. mysql 查询默认编码_查看及修改MYSQL默认编码
  10. 【王道操作系统笔记】进程定义组成组织方式特征
  11. linux下开通ssh
  12. Java : logback简单配置
  13. 【FPGA从0开始系列】黑金EP4CE10F17C8开发板按键实验(二)
  14. CardView-卡片布局
  15. win7用友u8安装教程_技术编辑教你在win7系统中安装用友u8的方案?
  16. matlab语言中的[~,b]=sort(A)用法介绍
  17. unity 通过摄像机模拟实现小地图
  18. 性能测试专项:帧率测试 FPS
  19. 查看计算机内存过高,物理内存过高怎么办,小编教你电脑物理内存过高怎么办...
  20. java编程实现斗地主发牌

热门文章

  1. 常见Linux面试题总结
  2. ModBus的小于3.5S间隔的理解
  3. python学习笔记 day20 序列化模块(二)
  4. PHP 学习 一 基础
  5. Codeforces-33C. Wonderful Randomized Sum
  6. Django 模板中使用css, javascript
  7. JSON数据表示格式简介(JavaScript对象表示法)
  8. web开发中的长度单位(px,em,ex,rem),如何运用,看完这篇就够了!
  9. (转)Linux后台开发应该具备技能
  10. mysql55和57的区别_技术分享 | MySQL:count(*)、count(字段) 实现上区别