操作系统实验报告:CPU调度

操作系统实验报告


实验内容

  • 实验内容:CPU调度

    1. 编译运行 alg.19-1-scheduler-SJF-1.c,讨论其中模拟的 SJF 调度策略;

    2. 编译运行 alg.19-2-scheduler-RR-5.c 和 alg.19-2-scheduler-RR-5-logfile.c , 讨论其中模拟的 RR 调度策略;

    3. 在 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;
    }

    执行程序命令:

    1. gcc alg.19-1-scheduler-SJF-1.c -pthread

    2. ./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;
    }
    

    执行程序命令:

    1. gcc alg.19-2-scheduler-RR-5.c -pthread

    2. ./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

    执行程序命令:

    1. gcc alg.19-2-scheduler-RR-5-logfile.c -pthread

    2. ./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;
    }

    执行程序命令:

    1. gcc LSF.c -pthread

    2. ./a.out

    实验数据:

    执行结果截图:

    分析:

    调度程序在1s时开始调度,调度周期为1s,绘制Laxity表如下:

    Time123456789101112
    t0999987654333
    t1444444
    t287654
    Time131415161718192021222324
    t03321
    t1
    t2322222222222

    可以看到,在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结束,任务队列为空,程序正常退出。

    绘制甘特图如下:

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值