# <center> 作業系統工程期末project <br> <font size=5>李奕承 611121212</font></center> ## 我選擇做的是timer(alarm) 先前已經先做了 1.將這學期寫的每個作業組合起來 2.將資料結構課寫的 double_linked_list.a 移植進來 3.實現了顯示所有 task PCB 及系統時間的簡單桌面 (建議老師直接編譯執行,可以看到整個系統的變化) ## PCB結構 ```c typedef struct pcb{ dllNode_t node; context context; uint8_t stack[STACK_SIZE]; int ID; int priority; int finish; int waiting; int error; reg_t pasue_address; }pcb_t; ``` 當我新增一個 task 時,會 malloc 一個 pcb_t 結構加入 task 清單串列,並設定好必要的值 ```c int task_create(void (*start_routin)(char *param), char *param, uint8_t priority) { pcb_t * porcess = (pcb_t *)malloc(sizeof(pcb_t)); if (porcess != NULL) { porcess->ID = process_ID; porcess->finish = 0; porcess->waiting = 0; porcess->node.next = NULL; porcess->node.prev = NULL; porcess->priority = priority; porcess->pasue_address = (reg_t) start_routin; porcess->context.sp = (reg_t) &(porcess->stack[STACK_SIZE -1]); porcess->context.ra = (reg_t) start_routin; porcess->context.a0 = (reg_t) param; process_ID++; DLL_add_tail(&porcess->node, pcb_list); return 0; } else { return -1; } } ``` 在中斷處理結束前會使用一次 showPCB() 來刷新畫面,畫面中包含 - 畫出一個PCB表格 - 運作中的timer(alarm) - 系統時間 - 還有使用者輸入過的文字暫存 ```c #include"os.h" void showPCB() { int task_count = DLL_num_nodes(pcb_list); printf("\033[?25h"); uart_puts("\E[H\E[J"); dllNode_t * target = pcb_list; printf("\n| task ID "); while(target->next != NULL) { target = DLL_next_node(target); printf("| %d ", ((pcb_t *)target)->ID); } printf("|\n"); printf("------------"); for(int i = 0 ; i < task_count ; i++) { printf("---------"); } printf("\n| priority "); target = pcb_list; while(target->next != NULL) { target = DLL_next_node(target); //顯示所有task的優先級 printf("| %d ", ((pcb_t *)target)->priority); } printf("|\n"); printf("------------"); for(int i = 0 ; i < task_count ; i++) { printf("---------"); } printf("\n| finish "); target = pcb_list; while(target->next != NULL) { target = DLL_next_node(target); if(((pcb_t *)target)->finish == 1) { printf("|\033[1;31;1m 1 \033[0;37;1m"); } else { //顯示所有task是否被執行過的狀態 printf("| 0 "); } } printf("|\n"); printf("------------"); for(int i = 0 ; i < task_count ; i++) { printf("---------"); } printf("\n| waiting "); target = pcb_list; while(target->next != NULL) { target = DLL_next_node(target); if(((pcb_t *)target)->waiting == 1) { printf("|\033[1;31;1m 1 \033[0;37;1m"); } else { //顯示所有task是否被執行過的狀態 printf("| 0 "); } } printf("|\n"); printf("------------"); for(int i = 0 ; i < task_count ; i++) { printf("---------"); } printf("\n| error "); target = pcb_list; while(target->next != NULL) { target = DLL_next_node(target); if(((pcb_t *)target)->error == 1) { printf("|\033[1;31;1m 1 \033[0;37;1m"); } else { //顯示所有task是否被執行過的狀態 printf("| 0 "); } } printf("|\n\n"); show_alarm(); clock(); printf(":%s", trap_temp); } ``` 畫面長這樣 ![](https://i.imgur.com/ujqTWiH.png) 以下是我為了測試所產生的9個 task ,他們每個會輸出不一樣的圖形。 其中第 3、6、8 task 會在運行時申請一個鬧鐘,並進入等待狀態。 task 2 會故意產生一個異常(上圖可以看到)。 ```c //第1個task void user_task1(char* santams) { printf("Task 1: Running...|%s\n", santams); uart_puts(" |...\n"); uart_puts(" |...\n"); uart_puts(" |...\n"); asm volatile("wfi"); task_exit(); } //第2個task void user_task2(char* santams) { printf("Task 2: Running............|.........%s\n", santams); uart_puts(" ...|\n"); uart_puts(" ...|\n"); uart_puts(" ...|\n"); trap_test(); asm volatile("wfi"); task_exit(); } //第3個task void user_task3(char* santams) { printf(" Task 3: Running...|%s\n", santams); uart_puts(" |...\n"); uart_puts(" |...\n"); uart_puts(" |...\n"); create_alarm(20); task_wait(); asm volatile("wfi"); task_exit(); } //第4個task void user_task4(char* santams) { printf(" Task 4: Running............|.........%s\n", santams); uart_puts(" ...|\n"); uart_puts(" ...|\n"); uart_puts(" ...|\n"); asm volatile("wfi"); task_exit(); } //第5個task void user_task5(char* santams) { printf(" Task 5: Running...|%s\n", santams); uart_puts(" |...\n"); uart_puts(" |...\n"); uart_puts(" |...\n"); asm volatile("wfi"); task_exit(); } //第6個task void user_task6(char* santams) { printf(" Task 6: Running............|.........%s\n", santams); uart_puts(" ...|\n"); uart_puts(" ...|\n"); uart_puts(" ...|\n"); create_alarm(10); task_wait(); asm volatile("wfi"); task_exit(); } //第7個task void user_task7(char* santams) { printf(" Task 7: Running...|%s\n", santams); uart_puts(" |...\n"); uart_puts(" |...\n"); uart_puts(" |...\n"); asm volatile("wfi"); task_exit(); } //第8個task void user_task8(char* santams) { printf(" Task 8: Running............|.........%s\n", santams); uart_puts(" ...|\n"); uart_puts(" ...|\n"); uart_puts(" ...|\n"); create_alarm(40); task_wait(); asm volatile("wfi"); task_exit(); } //第9個task void user_task9(char* santams) { printf(" Task 9: Running...|%s\n", santams); uart_puts(" |...\n"); uart_puts(" |...\n"); uart_puts(" |...\n"); asm volatile("wfi"); task_exit(); } ``` ## alarm_t 結構 ```c typedef struct alarm{ dllNode_t node; int tickets; int ID; dllNode_t * owner; }alarm_t; ``` 當要產生一個鬧鐘時,會 malloc 一個 alarm_t 結構加入 alarm 清單串列,並設定好必要的值,接著判斷要插在哪個時鐘前面,插入後減掉在這之前所有鬧鐘加總的 tickets,另外我的下一個鬧鐘也要減掉我的 tickets ```c void create_alarm(int tickets) { alarm_t * new = (alarm_t *)malloc(sizeof(alarm_t)); new->node.next = NULL; new->node.prev = NULL; new->owner = current_task; new->tickets = tickets; new->ID = ((pcb_t *)current_task)->ID; dllNode_t * temp = alarm_list; int before = 0; int after = 0; while (1) { if(temp->next == NULL) { DLL_addto_next(&new->node, temp); new->tickets -= after; return; } else { temp = temp->next; before += after; after += ((alarm_t *)temp)->tickets; if(new->tickets < after) { DLL_addto_prev(&new->node, temp); new->tickets -= before; ((alarm_t *)temp)->tickets -= new->tickets; return; } } } } ``` 每當一個時鐘中斷來時,除了更新系統時間,也會把 alarm 清單串列的第一個鬧鐘減掉 1 ticket ,如果減為0,就把自己從串列中刪除,並把當初申請鬧鐘的 task 喚醒 ```c void alarm_handle() { dllNode_t * temp = DLL_next_node(alarm_list); if(temp != NULL) { ((alarm_t *)temp)->tickets--; if(((alarm_t *)temp)->tickets == 0) { ((pcb_t *)(((alarm_t *)temp)->owner))->waiting = 0; DLL_delete(temp); free(temp); } } } ``` alarm_handle() 放在中斷處理程式中 timer_handle()的下面 ```c reg_t trap_handler(reg_t epc, reg_t cause) { ((pcb_t *)current_task)->pasue_address = epc; reg_t return_pc = epc; reg_t cause_code = cause & 0xfff; if(cause & 0x80000000) { switch (cause_code) { case 3: //uart_puts("software interruption!\n"); { int id = r_mhartid(); *(uint32_t *)CLINT_MSIP(id) = 0; //((pcb_t *)current_task)->context.ra = return_pc; showPCB(); //schedule(); current_task = &(manage->node); return (reg_t)schedule; } break; case 7: //uart_puts("timer interruption!\n"); timer_handle(); alarm_handle(); break; case 11: //uart_puts("external interruption!\n"); external_interrupt_handler(); break; default: uart_puts("unknown async exception!\n"); break; } } else { printf("Sync exceptions!, code = %d\n", cause_code); uart_puts("OOPS! What can I do!\n"); uart_puts("next time, this task will start at pc where exeception next \n"); ((pcb_t *)current_task)->error = 1; //return return_pc += 4; return (reg_t)schedule; } showPCB(); return return_pc; } ``` 以下是我的 schedule() 他會掃描整個 task 清單串列,尋找可以執行的task並選出 priority 最大的,然後跳進去執行。如果只剩下 ID 為 0 的task(schedule本身的ID),代表目前沒有其他事可做,所以會開始回收已經被標註為 finish 或者 error 的 task,將其pcb_t結構從清單中刪除並釋放。 ```c void schedule() { int task_count = DLL_num_nodes(pcb_list); dllNode_t * max = &(manage->node); dllNode_t * target = pcb_list; int temp = 0; while(target->next != NULL) { target = DLL_next_node(target); //找到優先度最高的task,並且必須是沒有被做過的,且沒有在等待中 if(((pcb_t *)target)->priority >= temp && ((pcb_t *)target)->finish != 1 && ((pcb_t *)target)->error == 0 && ((pcb_t *)target)->waiting == 0) { //將最優先要做的task編號設給 max 變數 temp = ((pcb_t *)target)->priority; max = target; } } //如果 ((pcb_t *)max)->priority 為 0 且 ((pcb_t *)max)->finish 也是 0,代表系統目前沒有其他任務要執行 if(((pcb_t *)max)->priority == 0 && ((pcb_t *)max)->finish == 0) { current_task = max; target = pcb_list; while(target->next != NULL) { target = DLL_next_node(target); if(((pcb_t *)target)->error == 1 || ((pcb_t *)target)->finish == 1) { DLL_delete(target); free(target); } } asm volatile("wfi"); } //不是的話代表還有task沒做完,於是將下個 task 的 context 指針設為 max 所代表的task 然後switch_to() else { printf("max = task %d\n\n\n", ((pcb_t *)max)->ID); struct context *next = &(((pcb_t *)max)->context); current_task = max; switch_to(next, ((pcb_t *)current_task)->pasue_address); } } ``` ## 其他 另外,如果程式是正常結束的,finish 就會被標記為 1 ```c void task_exit() { ((pcb_t *)current_task)->finish = 1; showPCB(); switch_to( &(manage->context), manage->pasue_address); } ``` 這是我的系統時間 ```c //時間進位的邏輯 void tick() { seconds++; if(seconds == 60) { mintues++; seconds = 0; if(mintues == 60) { hours++; mintues = 0; if(hours == 24) { hours = 0; } } } } ``` 這是當剩下的3個 task 都在等待鬧鐘喚醒的桌面 ![](https://i.imgur.com/Efom2VC.png) 所有task都執行完進入閒置狀態的畫面 ![](https://i.imgur.com/eG6UtMy.png)