操作系统实验报告
实验内容
-
实验内容:CPU调度
-
编译运行 alg.19-1-scheduler-SJF-1.c,讨论其中模拟的 SJF 调度策略;
-
编译运行 alg.19-2-scheduler-RR-5.c 和 alg.19-2-scheduler-RR-5-logfile.c , 讨论其中模拟的 RR 调度策略;
-
在 alg.19-2-scheduler-RR-5.c 的框架上编写程序实现实时系统的 Least Laxity First调度:输入一个包含若干任务的到达时刻、执行时间和截止时刻的固定任务表,固定一个调度时间间隔,给出单 CPU 下的 LLF/LSF 调度结果的甘特图,并计算任务的平均响应时间(任务从到达至实际完成的时间)。
-
-
架构:Intel x86_64 (虚拟机)
-
操作系统: Ubuntu 18.04
-
汇编器: gas (GNU Assembler) in AT&T mode
-
编译器: gcc
技术日志
-
编译运行 alg.19-1-scheduler-SJF-1.c
实现细节解释:
结构体task_table表示任务项,其中arrival表示进程到达时间,burst表示进程所需的CPU执行时间,prio表示进程优先级,status表示进程状态,pid和tid为进程id号和线程id号
struct { int arrival; int burst; int prio; int status; pid_t pid; pid_t tid; } task_table[MAX_T];
结构体prio_queue表示任务队列,存放任务的编号和优先级
struct { int task_no; int prio; } prio_queue[MAX_T + 1];
函数void time_counter(int i)和static void *thread_time(void *arg)用于模拟时钟计数器
函数void task_worker(int burst)用于模拟进程执行的CPU区间
函数int init_task_table(void)用于初始化任务表
函数int push_prio_queue(int task_no)用于将任务添加到任务队列中,函数int pop_prio_queue(void)用于从任务队列中取出任务,任务队列实现为优先队列,每次取出优先级最高(所需CPU区间最短)的任务
函数static void *thread_ftn(void *arg)用于模拟进程调入任务队列:当前时间达到进程到达时间时,设置进程任务的状态为ready,优先级为所需CPU时间burst,接着将进程压入任务队列,接着调用sem_wait(&sem_task[num])通过信号量机制等待调度程序的调度,当进程被调度程序调度时,打印执行的进程和相应burst,接着调用task_worker(task_table[num].burst)模拟进程执行任务,最后将进程状态设置为TERM表示进程终止,接着调用sem_post(&sem_sche)唤醒调度程序
static void *thread_ftn(void *arg) { int *numptr = (int *)arg; int num = *numptr; int curr_time; while (1) { curr_time = timer; if(curr_time >= task_table[num].arrival) { break; } } task_table[num].status = READY; task_table[num].prio = task_table[num].burst; /* SJF */ task_table[num].pid = getpid(); task_table[num].tid = gettid(); pthread_mutex_lock(&mutex); push_prio_queue(num); pthread_mutex_unlock(&mutex); sem_wait(&sem_task[num]); /* waiting for scheduling */ printf("timer: %d running task num = %d, burst = %d\n", timer, num, task_table[num].burst); task_worker(task_table[num].burst); /* task process simulator */ task_table[num].status = TERM; /* task terminated */ sem_post(&sem_sche); /* wake up the scheduler */ pthread_exit(NULL); }
函数void scheduler(void)用于模拟CPU调度程序:若就绪队列不为空,则从就绪队列中取出优先级最高(所需CPU时间最少)的进程,将进程状态更新为RUNNING并调用sem_post(&sem_task[task_no])通知相应线程运行,模拟进程的执行。
void scheduler(void) { int task_no; while (1) { // printf("sem_task: %ld %ld %ld %ld\n", sem_task[0].__align, sem_task[1].__align, sem_task[2].__align, sem_task[3].__align); pthread_mutex_lock(&mutex); task_no = pop_prio_queue(); /* select the first task */ if(task_no != -1) { task_table[task_no].status = RUNNING; sem_post(&sem_task[task_no]); /* scheduling selected task */ pthread_mutex_unlock(&mutex); sem_wait(&sem_sche); /* scheduler sleeping */ } else { pthread_mutex_unlock(&mutex); } } }
程序首先初始化任务表,初始化用于模拟调度的信号量,创建用于模拟计时器的线程,创建模拟进程执行的若干线程,接着调用scheduler()开始模拟进程调度,等待所有模拟进程执行的线程模拟结束后,销毁相应的信号量和互斥锁后结束运行。
int main(void) { int i, ret; max_num = init_task_table(); if (max_num < 1 || max_num > MAX_T) { printf("Illegal max_thread_num = %d\n", max_num); return EXIT_FAILURE; } prio_queue[0].task_no = -1; prio_queue[0].prio = -1; /* a sentry value */ for (i = 0; i < max_num; i++) { sem_init(&sem_task[i], 0, 0); } sem_init(&sem_sche, 0, 0); int thread_num[max_num]; for (i = 0; i < max_num; i++) { thread_num[i] = i; } pthread_t ptid_time; ret = pthread_create(&ptid_time, NULL, &thread_time, NULL); if(ret != 0) { fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); return EXIT_FAILURE; } pthread_t ptid[max_num]; for (i = 0; i < max_num; i++) { ret = pthread_create(&ptid[i], NULL, &thread_ftn, (void *)&thread_num[i]); if(ret != 0) { fprintf(stderr, "pthread_create error: %s\n", strerror(ret)); return EXIT_FAILURE; } } scheduler(); for (i = 0; i < max_num; i++) { ret = pthread_join(ptid[i], 0); if(ret != 0) { fprintf(stderr, "pthread_join error: %s\n", strerror(ret)); return EXIT_FAILURE; } } for (i = 0; i < max_num; i++) { sem_destroy(&sem_task[i]); } sem_destroy(&sem_sche); pthread_mutex_destroy(&mutex); return EXIT_SUCCESS; }
执行程序命令:
-
gcc alg.19-1-scheduler-SJF-1.c -pthread
-
./a.out
执行结果截图:
分析:
通过构造最小堆,将任务队列实现为优先队列,保证优先级最高的进程位于队首,调度程序每次从队头取出进程执行,来模拟SJF调度策略
可以看到,所有进程都在0时刻到达,进程执行按CPU区间从小到大的顺序执行,正确实现了SJF调度策略
-
-
编译运行 alg.19-2-scheduler-RR-5.c
实现细节解释:
结构体task_table表示任务项,其中arrival表示进程到达时间,burst表示进程所需的CPU执行时间,exp_us表示进程已经运行的时间,status表示进程状态,pid为进程id号
struct { int arrival; /* arrival time in quantum number */ int burst; /* burst time in quantum number */ long int exp_us; /* experienced time of this task in usecond*/ int prio; /* not used */ int status; /* NEW, READY, SCHE or TERM */ pid_t pid; pid_t tid; /* not used */ } task_table[MAX_T];
结构体sche_queue用于实现就绪队列,就绪队列实现为循环队列
函数long int time_us(void)用于获取当前时间(us)
函数void busy_burst(int task_no)和void task_worker(int task_no)用于模拟进程执行,函数void task_worker(int task_no)根据传入的进程任务编号调用函数void busy_burst(int task_no),执行相应进程;函数将进程当前已使用CPU时间初始化为0,进入一个循环模拟进程执行:每过100us更新一次已使用CPU时间,当进程已使用CPU时间达到时间片长度时,退出循环
函数int init_task_table(void)用于初始化时间片、调度间隔和任务表
函数static int task_pro(void *arg)用于模拟进程调入任务队列:当前时间达到进程到达时间时,设置进程任务的状态为ready,接着将进程压入就绪队列,接着调用sem_wait(&sem_task[task_no])通过信号量机制等待调度程序的调度,当进程被调度程序调度时,调用task_worker(task_no)模拟进程执行任务,最后将进程状态设置为TERM表示进程终止
static int task_pro(void *arg) { int *numptr = (int *)arg; int task_no = *numptr; usleep(TIME_q * 1000 * task_table[task_no].arrival); printf("task_no %d arrival_us at %ld\n", task_no, time_us() - init_us); task_table[task_no].status = READY; task_table[task_no].prio = task_table[task_no].burst; /* not used */ task_table[task_no].pid = getpid(); int posn; while (1) { if(sche_queue_len < MAX_T) { /* if free_queue_front != -1 */ pthread_mutex_lock(&mutex); /* insert sche_queue(num) */ posn = free_queue_front; free_queue_front = sche_queue[free_queue_front].next; sche_queue[posn].task_no = task_no; if(sche_queue_len == 0) { sche_queue_front = posn; sche_queue_end = posn; sche_queue[posn].next = posn; } else { sche_queue[posn].next = sche_queue[sche_queue_end].next; sche_queue[sche_queue_end].next = posn; sche_queue_end = posn; } sche_queue_len++; pthread_mutex_unlock(&mutex); break; } } sem_wait(&sem_task[task_no]); /* waiting for scheduling */ task_worker(task_no); /* task process simulator */ printf("\ntask_no: %d terminated at %ld\n", task_no, time_us() - init_us); task_table[task_no].status = TERM; /* task terminated */ _exit(0); }
函数void scheduler(void)用于模拟调度程序,不断循环从任务队列中取出进程,若进程状态为终止,则将其移除;若进程状态为就绪,则通过信号量机制让相应进程运行一个时间片,同时调度程序挂起,一个时间片后调度程序恢复运行,控制相应进程暂停,继续选择其他进程执行
void scheduler(void) { int task_no; int posn, pre_posn, dele_posn; int ret, silence_counter = 0; long exp_us, burst_us; printf("scheduler start . . .\n"); posn = sche_queue_end; while (1) { if(sche_queue_len > 0) { silence_counter = 0; /* reset silence_counter */ pre_posn = posn; posn = sche_queue[posn].next; task_no = sche_queue[posn].task_no; burst_us = (long)(TIME_q * 1000 * task_table[task_no].burst); switch (task_table[task_no].status) { case TERM: /* delete sche_queue(posn) */ pthread_mutex_lock(&mutex); sche_queue_len--; exp_us = task_table[task_no].exp_us; printf("sche_queue_len = %d, task_no %d, exp_us: %ld, burst_us: %ld, sche_us: %ld [SCHE ==> TERM]\n", sche_queue_len, task_no, exp_us, burst_us, time_us() - init_us); dele_posn = posn; if(sche_queue_len != 0) { sche_queue[pre_posn].next = sche_queue[posn].next; posn = pre_posn; } sche_queue[dele_posn].next = free_queue_front; free_queue_front = dele_posn; pthread_mutex_unlock(&mutex); break; case READY: task_table[task_no].status = SCHE; sem_post(&sem_task[task_no]); usleep(TIME_q * 1000 * RR_q); kill(task_table[task_no].pid, SIGSTOP); exp_us = task_table[task_no].exp_us; printf("sche_queue_len = %d, task_no %d, exp_us: %ld, burst_us: %ld, sche_us: %ld [SCHE <== READY]\n", sche_queue_len, task_no, exp_us, burst_us, time_us() - init_us); break; case SCHE: ret = kill(task_table[task_no].pid, SIGCONT); if(ret == 0) { usleep(TIME_q * 1000 * RR_q); kill(task_table[task_no].pid, SIGSTOP); exp_us = task_table[task_no].exp_us; printf("sche_queue_len = %d, task_no %d, exp_us: %ld, burst_us: %ld, sched_us: %ld\n", sche_queue_len, task_no, exp_us, burst_us, time_us() - init_us); }; break; default: break; } } else { printf("silence_counter = %d\n", silence_counter); if(silence_counter > 10) { break; } sleep(1); silence_counter++; } } return; }
程序首先初始化任务表和就绪队列,初始化用于模拟调度的信号量,创建模拟进程执行的若干进程,接着调用scheduler()开始模拟进程调度,等待所有模拟进程执行的进程模拟结束后,释放申请的内存、销毁相应的信号量和互斥锁,打印运行时间后结束运行。
int main(void) { int i, ret; int task_num[MAX_T]; max_num = init_task_table(); if (max_num < 1 || max_num > MAX_T) { printf("Illegal max_thread_num = %d\n", max_num); return EXIT_FAILURE; } for (i = 0; i < MAX_T; i++) { sche_queue[i].task_no = -1; sche_queue[i].next = i + 1; } sche_queue[MAX_T-1].next = -1; free_queue_front = 0; for (i = 0; i < max_num; i++) { sem_init(&sem_task[i], 0, 0); } for (i = 0; i < max_num; i++) { task_num[i] = i; } init_us = time_us(); pid_t task_pid[max_num]; char *task_stack[max_num]; for (i = 0; i < max_num; i++) { task_stack[i] = malloc(STACK_SIZE*sizeof(char)); task_pid[i] = clone(task_pro, task_stack[i] + STACK_SIZE, CLONE_VM | SIGCHLD, (void *)&task_num[i]); // usleep(1); if(task_pid[i] == -1) { perror("clone() for task_pro"); return EXIT_FAILURE; } } scheduler(); for (i = 0; i < max_num; i++) { free(task_stack[i]); task_stack[i] = NULL; } int status = 0; for (i = 0; i < max_num; i++) { if(waitpid(task_pid[i], &status, 0) == -1) { perror("waitpid()"); return EXIT_FAILURE; } } for (i = 0; i < max_num; i++) { sem_destroy(&sem_task[i]); } pthread_mutex_destroy(&mutex); printf("Total runtime = %ld us\n", time_us() - init_us); return EXIT_SUCCESS; }
执行程序命令:
-
gcc alg.19-2-scheduler-RR-5.c -pthread
-
./a.out
执行结果截图:
分析:
调度程序不断循环从任务队列中取出进程,通过信号量机制让相应进程运行一个时间片,同时调度程序挂起,一个时间片后调度程序恢复运行,控制相应进程暂停,继续选择其他进程执行,从而模拟实现了轮换调度策略。
可以看到,进程到达顺序为1、0、(1和0同时到达)3、2、4,结束顺序为4、0、3、2、1,调度程序通过一个循环就绪队列管理进程,每隔一个时间片唤醒一次调度程序,通过kill函数传递信号量SIGCONT或SIGTOP来继续或暂停进程,实现进程轮换执行,程序正确模拟了轮换调度策略。
-
-
编译运行alg.19-2-scheduler-RR-5-logfile.c
实现细节解释:
实现细节与alg.19-2-scheduler-RR-5.c基本一致,仅修改输出使输出更加简洁,同时将输出写入logfile.txt
执行程序命令:
-
gcc alg.19-2-scheduler-RR-5-logfile.c -pthread
-
./a.out
执行结果截图:
分析:
调度程序不断循环从任务队列中取出进程,通过信号量机制让相应进程运行一个时间片,同时调度程序挂起,一个时间片后调度程序恢复运行,控制相应进程暂停,继续选择其他进程执行,从而模拟实现了轮换调度策略。
-
-
编写程序实现实时系统的 Least Laxity First调度
设计思路:
首先修改表示任务项的结构体,添加成员变量ddl,表示截止时间
struct { int arrival; /* arrival time in quantum number */ int burst; /* burst time in quantum number */ long int exp_us; /* experienced time of this task in usecond*/ int ddl; int status; /* NEW, READY, SCHE or TERM */ pid_t pid; pid_t tid; } task_table[MAX_T];
接着修改任务表初始化函数,读取任务项
int init_task_table(void) { FILE *fp; int i = 0; int ret; fp = fopen("./LSF.txt", "rb"); if(!fp) { printf("./task_table.txt fopen() error!\n"); exit(EXIT_FAILURE); } fscanf(fp, "%d\n", &TIME_q); fscanf(fp, "%d\n", &RR_q); printf("TIME_q = %d, RR_q = %d\n", TIME_q, RR_q); while (!feof(fp)) { ret = fscanf(fp, "%d %d %d %d %d %d\n", &task_table[i].arrival, &task_table[i].burst, &task_table[i].ddl, &task_table[i].status, &task_table[i].pid, &task_table[i].tid); if(ret == EOF) { break; } task_table[i].status = NEW; printf("task num = %d: %d %d %d %d %d %d\n", i, task_table[i].arrival, task_table[i].burst, task_table[i].ddl, task_table[i].status, task_table[i].pid, task_table[i].tid); i++; } fclose(fp); return i; }
修改调度程序:调度程序不断扫描任务队列,若扫描到状态为终止的进程,则将其移除后重新扫描;否则取松弛度最低的进程,让该进程执行一个周期,循环重复直到任务队列为空。
void scheduler(void) { int task_no; int posn, pre_posn, dele_posn; int ret, silence_counter = 0; long exp_us, burst_us; printf("scheduler start . . .\n"); while (1) { if(sche_queue_len > 0) { silence_counter = 0; /* reset silence_counter */ int sel_task; long min_laxity=99999; int laxity; int flag=1; pre_posn = sche_queue_end; posn = sche_queue_front; for(int i=0;i<sche_queue_len;i++) { task_no = sche_queue[posn].task_no; burst_us = (task_table[task_no].burst*10000*TIME_q); laxity=(task_table[task_no].ddl-(task_table[task_no].burst-task_table[task_no].exp_us/1000000)-time_us()/1000000); if(task_table[task_no].status==TERM){ pthread_mutex_lock(&mutex); sche_queue_len--; exp_us = task_table[task_no].exp_us; printf("sche_queue_len = %d, task_no %d, exp_us: %ld, burst_us: %ld, sche_us: %ld [SCHE ==> TERM]\n", sche_queue_len, task_no, exp_us, burst_us, time_us() - init_us); dele_posn = posn; if(sche_queue_len != 0) { if(posn==sche_queue_front){ sche_queue_front=sche_queue[posn].next; } sche_queue[pre_posn].next = sche_queue[posn].next; posn = pre_posn; } sche_queue[dele_posn].next = free_queue_front; free_queue_front = dele_posn; pthread_mutex_unlock(&mutex); flag=0; break; } else if(min_laxity>=laxity ){ min_laxity=laxity; sel_task=task_no; } pre_posn=posn; posn=sche_queue[posn].next; } if(flag){ switch (task_table[sel_task].status) { case READY: task_table[sel_task].status = SCHE; sem_post(&sem_task[sel_task]); usleep(TIME_q * 10000 * RR_q); kill(task_table[sel_task].pid, SIGSTOP); exp_us = task_table[sel_task].exp_us; printf("sche_queue_len = %d, task_no %d, exp_us: %ld, burst_us: %ld, sche_us: %ld [SCHE <== READY]\n", sche_queue_len, sel_task, exp_us, burst_us, time_us() - init_us); break; case SCHE: ret = kill(task_table[sel_task].pid, SIGCONT); if(ret == 0) { usleep(TIME_q * 10000 * RR_q); kill(task_table[sel_task].pid, SIGSTOP); exp_us = task_table[sel_task].exp_us; printf("sche_queue_len = %d, task_no %d, exp_us: %ld, burst_us: %ld, sched_us: %ld\n", sche_queue_len, sel_task, exp_us, burst_us, time_us() - init_us); }; break; default: break; } } } else { printf("silence_counter = %d\n", silence_counter); if(silence_counter > 10) { break; } sleep(1); silence_counter++; } } return; }
执行程序命令:
-
gcc LSF.c -pthread
-
./a.out
实验数据:
执行结果截图:
分析:
调度程序在1s时开始调度,调度周期为1s,绘制Laxity表如下:
Time 1 2 3 4 5 6 7 8 9 10 11 12 t0 9 9 9 9 8 7 6 5 4 3 3 3 t1 4 4 4 4 4 4 t2 8 7 6 5 4 Time 13 14 15 16 17 18 19 20 21 22 23 24 t0 3 3 2 1 t1 t2 3 2 2 2 2 2 2 2 2 2 2 2 可以看到,在T=1时,任务队列只有t0,调度程序调度t0执行;在T=4时,任务t1到达,t1的松弛度为4,比t0的松弛度9小,故调度程度调度t1执行;在T=8时,任务t2到达,t2的松弛度为8,大于t1的松弛度4,故调度程序仍调度t1执行;T=10时,任务t1结束,此时t0松弛度为3,t2松弛度为6,故调度程序调度t0执行;T=14时,t2的松弛度为2,小于t0的松弛度1,故调度程序调度t2执行;T=16时,t0的松弛度为1,t2的松弛度为2,故调度程序调度t0执行;T=17时,任务t0结束,调度程序调度t2执行;最终任务t2结束,任务队列为空,程序正常退出。
绘制甘特图如下:
-