操作系統(tǒng)就是一個“死循環(huán)”!

操作系統(tǒng)就是一個“死循環(huán)”?
在回答之前,我們先從進程調(diào)度的角度來看看。
進程調(diào)度想必大家都有所了解,又都不太了解。
有所了解是因為這個概念被提到太多次,不太了解是因為總覺得不直觀,浮于概念層。 今天我們從三個視角來看看進程調(diào)度究竟是怎么回事,啟車了請扶好。 小貼士:本文講述的是 linux-0.11 版本的進程調(diào)度機制,學(xué)習(xí)其骨干和框架,不要鉆入細(xì)節(jié)。
1 滴答視角
滴答

#define HZ 100時鐘中斷
set_intr_gate(0x20, &timer_interrupt);_timer_interrupt:
...
// 增加系統(tǒng)滴答數(shù)
incl _jiffies
...
// 調(diào)用函數(shù) do_timer
call _do_timer
...void do_timer(long cpl) {
...
// 當(dāng)前線程還有剩余時間片,直接返回
if ((--current->counter)>0) return;
// 若沒有剩余時間片,調(diào)度
schedule();
}進程的調(diào)度
void schedule(void) {
int i, next, c;
struct task_struct ** p;
...
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);
}別看一大坨,我做個不嚴(yán)謹(jǐn)?shù)暮喕?,你就懂了?/span> void schedule(void) {
int next = get_max_counter_from_runnable();
refresh_all_thread_counter();
switch_to(next);
}
1. 拿到剩余時間片(counter的值)最大且在 runnable 狀態(tài)(state = 0)的進程號 next。

2. 如果所有 runnable 進程時間片都為 0,則將所有進程(注意不僅僅是 runnable 的進程)的 counter 重新賦值(counter = counter/2 + priority),然后再次執(zhí)行步驟 1。
3. 最后拿到了一個進程號 next,調(diào)用了 switch_to(next) 這個方法,就切換到了這個進程去執(zhí)行了。
切換進程
#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,_current\n\t" \
"je 1f\n\t" \
"movw %%dx,%1\n\t" \
"xchgl %%ecx,_current\n\t" \
"ljmp %0\n\t" \
"cmpl %%ecx,_last_task_used_math\n\t" \
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \
"d" (_TSS(n)),"c" ((long) task[n])); \
}1.通過 ljmp 跳轉(zhuǎn)指令跳轉(zhuǎn)到新進程的偏移地址處。
2.將當(dāng)前各個寄存器的值保存在當(dāng)前進程的 TSS 中,并將新進程的 TSS 信息加載到各個寄存器。(這部分是執(zhí)行 ljmp 指令的副作用,并且是由硬件實現(xiàn)的)

上圖來源于《Linux內(nèi)核完全注釋V5.0》

數(shù)據(jù)結(jié)構(gòu)視角
struct task_struct * task[64] = {};struct task_struct {
long state;
long counter;
long priority;
struct tss_struct tss;
};#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 3
#define TASK_STOPPED 4struct tss_struct {
...
long eip;
long eflags;
long eax,ecx,edx,ebx;
long esp;
long ebp;
...
};操作系統(tǒng)啟動流程視角
void main(void) {
...
// 第一步:進程調(diào)度初始化
sched_init();
...
// 第二步:創(chuàng)建一個新進程并做些事
if (!fork()) {
init();
}
// 第三步:死循環(huán),操作系統(tǒng)正式啟動完畢
for(;;) pause();
}void sched_init(void) {
// 初始化第一個進程的 tss
set_tss_desc(...);
// 將進程數(shù)組清零
for(i=1;i<64;i++) {
task[i] = NULL;
...
}
// 設(shè)置始終中斷(滴答)
set_intr_gate(0x20,&timer_interrupt);
...
}
后記
后記
以上,分別從滴答視角、數(shù)據(jù)結(jié)構(gòu)視角、操作系統(tǒng)啟動流程視角,來講解來進程調(diào)度的細(xì)節(jié)。
所謂滴答視角,可以理解為常說的進程調(diào)度視角。所謂數(shù)據(jù)結(jié)構(gòu)視角,可以理解為常說的進程管理視角。
但我更喜歡我起的這兩個名字,尤其是滴答視角,好可愛有木有!
不過本文是以 linux 最早的版本 linux-0.11 為例,在后來的操作系統(tǒng)演進過程中,進程調(diào)度的細(xì)節(jié)也在不斷添枝加葉,比如選出下一個要調(diào)度的進程不再是簡單地比較時間片大小,比如進程實際發(fā)生切換的時機改到了系統(tǒng)調(diào)用返回前,再比如對頁表切換的變化等等。
但整個骨架和流程都是一樣的,也即你再去研究更為復(fù)雜的現(xiàn)代操作系統(tǒng)進程調(diào)度原理時,只要按照這三個視角去分析,總是可以把握主干。

往 期 推 薦 1、網(wǎng)曝IDEA2020.3.2,自動注釋類和方法注釋模板配置
2、牛逼!IntelliJ IDEA居然支持視頻聊天了~速來嘗鮮!快來沖一波
4、知名國產(chǎn)網(wǎng)盤翻車?清空免費用戶文件后,又開始清理付費用戶資源
1、網(wǎng)曝IDEA2020.3.2,自動注釋類和方法注釋模板配置
2、牛逼!IntelliJ IDEA居然支持視頻聊天了~速來嘗鮮!快來沖一波
4、知名國產(chǎn)網(wǎng)盤翻車?清空免費用戶文件后,又開始清理付費用戶資源

點分享

點收藏

點點贊

點在看

