既然引進了多進程,其實也就是在進程之間來回切換,那麼就會有進程之間的調度問題。實則是在可運行進程之間分配有限的處理器時間資源的內核子系統。html
其實就是一個先進先出隊列了,也就是說先申請的進程,先執行。當CPU空閒時,它會分配給位於隊列頭部的進程,而且這個運行進程從隊列中移去。FCFS調度代碼編寫簡單而且理解容易。算法
可是對於一個須要和用戶進行交互的進程,這種調度算法就會形成體驗很是很差,由於週轉時間須要完成一整個隊列的任務,很是的長數組
但FCFS調度算法是非搶佔的。一旦 CPU 分配給了一個進程,該進程就會使用 CPU 直到釋放 CPU 爲止,即程序終止或是請求I/O。函數
<!--more-->this
SJF調度算法就指對短做業或者短進程優先調度的算法,將每一個進程與其估計運行時間進行關聯選取估計計算時間最短的做業投入運行。這樣就能夠縮短週轉時間spa
最短做業優先(SJF)調度算法將每一個進程與其下次CPU執行的長度關聯起來。當 CPU 變爲空閒時,它會被賦給具備最短 CPU 執行的進程。若是兩個進程具備一樣長度的 CPU 執行那麼能夠由FCFS來處理。code
該算法中,將一個較小時間單元定義爲時間量或時間片。時間片的大小一般爲 10~100ms。就緒隊列做爲循環隊列。CPU調度程序循環整個就緒隊列,爲每一個進程分配不超過一個時間片的CPU。htm
爲了實現RR調度,咱們再次將就緒隊列視爲進程的 FIFO 隊列。新進程添加到就緒隊列的尾部。CPU 調度程序從就緒隊列中選擇第一個進程,將定時器設置在一個時間片後中斷,最後分派這個進程。blog
在運行的許多進程裏,有的進程更關心響應時間,有的進程更關心週轉時間,因此調度算法就須要折中,可是如何折中又是一個問題。隊列
schedule是Linux0.11裏最主要的調度算法,可是十分簡潔
task_struct是用來描述一個進程的結構體
task_struct的counter是調度算法實現折中的一個關鍵,它既用來表示分配的時間片,又用來表示進程的優先級
首先從任務數組中最後一個任務開始循環檢測一些域
若是設置過任務的定時值alarm,而且已通過期(alarm<jiffies),則在信號位圖中置SIGALRM信號,即向任務發送SIGALARM信號。而後清alarm。還有一些信號量相關的會在後面再提
找到數值最大的一個couter,也就是運行時間最少的一個進程,切換到它的進程
當執行完一回的輪轉就會從新分配一次時間片,這時候對於已經輪轉過的進程,時間片將會被設置爲初值,可是對於那些阻塞的進程,時間片將會增長,也就是進行了一次折中的調度。
void schedule(void) { int i,next,c; struct task_struct ** p; /* check alarm, wake up any interruptible tasks that have got a signal */ for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) { if ((*p)->alarm && (*p)->alarm < jiffies) { (*p)->signal |= (1<<(SIGALRM-1)); (*p)->alarm = 0; } if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) && (*p)->state==TASK_INTERRUPTIBLE) (*p)->state=TASK_RUNNING; } /* this is the scheduler proper: */ while (1) { c = -1; next = 0; i = NR_TASKS; p = &task[NR_TASKS]; while (--i) { if (!*--p) continue; if ((*p)->state == TASK_RUNNING && (*p)->counter > c) c = (*p)->counter, next = i; } if (c) break; for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) (*p)->counter = ((*p)->counter >> 1) + (*p)->priority; } switch_to(next); }
咱們在順便來看一下sched_init,這個函數內核調度程序的初始化子程序,就是初始化一些中斷和描述符
首先調用set_tss_desc和set_ldt_desc設置進程0的tss和ldt
清任務數組和描述符表項
以後初始化8253定時器
最後是設置時鐘中斷和系統調用中斷
void sched_init(void) { int i; struct desc_struct * p; if (sizeof(struct sigaction) != 16) panic("Struct sigaction MUST be 16 bytes"); set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss)); set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt)); p = gdt+2+FIRST_TSS_ENTRY; for(i=1;i<NR_TASKS;i++) { task[i] = NULL; p->a=p->b=0; p++; p->a=p->b=0; p++; } /* Clear NT, so that we won't have troubles with that later on */ __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); ltr(0); lldt(0); outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */ outb_p(LATCH & 0xff , 0x40); /* LSB */ outb(LATCH >> 8 , 0x40); /* MSB */ set_intr_gate(0x20,&timer_interrupt); outb(inb_p(0x21)&~0x01,0x21); set_system_gate(0x80,&system_call); }
_set_tssldt_desc其實就是根據描述符各個位的做用來進行設置
#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),((int)(addr)),"0x89") #define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),((int)(addr)),"0x82") #define _set_tssldt_desc(n,addr,type) \ __asm__ ("movw $104,%1\n\t" \ "movw %%ax,%2\n\t" \ "rorl $16,%%eax\n\t" \ "movb %%al,%3\n\t" \ "movb $" type ",%4\n\t" \ "movb $0x00,%5\n\t" \ "movb %%ah,%6\n\t" \ "rorl $16,%%eax" \ ::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)), \ "m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7)) \ )
#define _set_gate(gate_addr,type,dpl,addr) \ __asm__ ("movw %%dx,%%ax\n\t" \ "movw %0,%%dx\n\t" \ "movl %%eax,%1\n\t" \ "movl %%edx,%2" \ : \ : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \ "o" (*((char *) (gate_addr))), \ "o" (*(4+(char *) (gate_addr))), \ "d" ((char *) (addr)),"a" (0x00080000)) #define set_intr_gate(n,addr) \ _set_gate(&idt[n],14,0,addr) #define set_system_gate(n,addr) \ _set_gate(&idt[n],15,3,addr)
這一篇主要看了一下Linux0.11裏的調度算法,十分的簡潔,可是又照顧了響應時間,又照顧了週轉時間。
而後提了一下內核調度程序的初始化子程序,其實就是根據以前說的設置一些描述符和中斷處理
原文出處:https://www.cnblogs.com/secoding/p/11422525.html