Linux是開源的,為什麽不去讀Linux的源代碼?
新版本太長,寫不下去。我給妳最經典的0.11版本的流程管理源代碼。剩下的妳自己查。作為壹名優秀的程序員,妳必須學會充分利用搜索引擎。
-
Linux0.11通過進程數組管理進程,最多允許64個進程。進程的狀態有就緒、運行、暫停、睡眠和死亡。睡眠狀態可以分為可中斷和不可中斷。對於流程的管理,我覺得根據流程的狀態會更清晰。
任務1.0
0號比較特殊,是“純手工”的,下面是制作過程。
mem_init(主_內存_開始,內存_結束);
trap_init()。
blk _ dev _ init();
char _ dev _ init();
tty _ init();
time _ init();
sched _ init();
buffer_init(緩沖區_內存_結束);
HD _ init();
floppy_init()。
STI();
move _ to _ user _ mode();
當然,這麽多init並不都是為0任務準備的,他們是在初始化系統。對於任務0,sched_init()和move_to_user_mode()很重要。Sched_init()手動設置任務0的任務段描述符tss和本地段描述符ldt,並設置tr和ldtr寄存器:
set_tss_desc(init _ task . task . TSS));//設置tss段描述符
set _ ldt _ desc(gdt+FIRST _ LDT _ ENTRY & amp;(init _ task . task . ldt));//設置ldt描述符
...
ltr(0);//將tss描述符地址加載到tr
lldt(0);//將ldt描述符地址加載到ldtr
我們來看看任務0的tss和ldt是什麽樣子的。
/*ldt*/ {0,0},\
{0x9f,0xc0fa00},\
{0x9f,0xc0f200},\
/*tss*/{0,PAGE _ SIZE+(long)& amp;init_task,0x10,0,0,0,0,(long)& amp;pg_dir,\
0,0,0,0,0,0,0,0,\
0,0,0x17,0x17,0x17,0x17,0x17,\
_LDT(0),0X80000000,\
{}\
},\
從ldt可以看出,任務的代碼和數據段都是640k,基址是0,DPL=3。這說明task 0的代碼雖然還在內核段,但是task的級別已經在用戶模式了。這也可以從tss中看出,其中代碼和數據都設置為0x17,這是本地段描述符表中的第二項。還可以看到task 0的內核棧設置在init_task之後的壹頁,用戶態棧是move_to_user_mode之前的內核態棧。這與普通進程不同,普通進程的用戶模式堆棧位於其64Mb地址空間的末端。
Move_to_user_mode是在堆棧中創建任務開關的假象。用iret跳轉到外層3,這樣cpu會根據tr自動加載tss,初始化各個寄存器運行任務0。因此,任務0實際上是內核空間中的用戶態任務。
2.流程創建
進程創建由系統調用sys_fork完成,主要使用find_empty_process和copy_process兩個函數。前者在進程數組中為子進程找到壹個未使用的進程號,後者完成子進程信息的創建,主要是復制父進程的信息。
我們來看看copy_process的代碼:
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
長ebx,長ecx,長edx,長fs,長es,長ds,
長eip、長cs、長eflags、長esp、長ss)
{
struct task _ struct * p;
int I;
結構文件* f;
//首先,為子進程的進程描述符分配壹塊內存。
p =(struct task _ struct *)get _ free _ page();
如果(!p)
return-EAGAIN;
//將新任務結構指針添加到數組中。
task[NR]= p;
//將當前用戶的任務結構復制到子進程中。當然,堆棧不會復制。
* p = *電流;
//將子進程的狀態設置為不間斷等待,以防止它現在被調度。
p->;state = TASK _不間斷;
p->;pid = last _ pid
p->;父親=當前-& gt;pid
p->;count = p-& gt;優先級;
p->;信號= 0;
p->;報警= 0;
p->;leader = 0;
p->;utime = p-& gt;stime = 0;
p->;cutime = p-& gt;cstime = 0;
p->;start _ time = jiffies
p->;TSS . back _ link = 0;
//新進程的內核狀態堆棧在進程描述符頁的末尾。
p->;TSS . esp0 = PAGE _ SIZE+(long)p;
p->;TSS . ss0 = 0x 10;
//ip為父進程調用fork的下壹條指令。
p->;tss.eip = eip
fork的返回值對於子進程為0,對於父進程為其pid。通過這種差異,在fork調用返回後,父進程和子進程的代碼段被分割。
p->;TSS . eax = 0;
//盡管它們是在代碼文件中編寫的。
p->;tss.ecx = ecx
p->;tss.edx = edx
p->;tss.ebx = ebx
p->;tss.esp = esp
p->;tss.ebp = ebp
p->;tss.esi = esi
p->;tss.edi = edi
p->;tss.es = es & amp0xffff
p->;tss.cs = cs & amp0xffff
p->;= ss & amp0xffff
p->;tss.ds = ds & amp0xffff
p->;= fs & amp0xffff
p->;tss.gs = gs & amp0xffff
p->;TSS . ldt = _ LDT;
p->;tss.trace _ bitmap = 0x80000000
//如果父任務使用了協處理器,則保存在tss中。
if(last _ task _ used _ math = =當前)
_ ASM(" clts;fn save % 0 "::" m "(p-& gt;TSS . i387));
//為新任務設置新的代碼和數據段基址。註意因為linux0.11只支持64M的進程空間,所以進程的線性地址空間在64M的邊界。
//然後為新任務復制父進程的頁表。通過copy_page_tales,父子任務此時使用的是只讀代碼數據段。寫入時,寫入時復制將為新進程申請新的物理頁面。
if(copy_mem(nr,p)){
task[NR]= NULL;
free_page((長)p);
return-EAGAIN;
}
for(I = 0;我& ltNR _ OPENi++)
if(f = p-& gt;filp)
f-& gt;f _ count++;
如果(當前-& gt;pwd)
當前->;pwd-& gt;I _ count++;
如果(當前-& gt;根)
當前->;root-& gt;I _ count++;
如果(當前-& gt;可執行)
當前->;可執行-& gt;I _ count++;
//為新任務設置tss和ldt描述符。
set _ TSS _ desc(gdt+(NR & lt;& lt1)+FIRST _ TSS _ ENTRY & amp;(p->;TSS));
set _ ldt _ desc(gdt+(NR & lt;& lt1)+FIRST _ LDT _ ENTRY;(p->;ldt));
//返回子進程的pid。
返回last _ pid
}
參數都是棧中的值,nr是調用copy_process之前find_empty_process的返回值,是任務數組的序號。
3.進程調度
流程在創建後不會立即執行。系統將在適當的時間調用系統調度函數schedule,並選擇適當的進程來運行。調度函數可能在很多地方被調用,比如系統調用、進程暫停/休眠/退出、時鐘中斷等。調度功能主要是通過進程的時間片選擇壹個運行時間最短的進程。如果沒有找到,運行空閑暫停系統調用,在暫停中,調度函數會被再次調用。以下是主要代碼
while(1){
c =-1;
next = 0;
i = NR _ TASKS
p = & amp任務[NR _ TASKS];
//在就緒任務中找出最短的運行任務。
while( - i){
如果(!(* - p))
繼續;
if((* p)-& gt;狀態= =任務運行& amp& amp(* p)-& gt;計數器& gtc)
c =(* p)-& gt;計數器,next = I;
}
//如果找到,退出。否則,重新計算任務的時間片。註意,如果沒有任務,next總是0,結果切換到任務0。
如果(c)斷裂;
for(p = & amp;LAST _ TASKp & gt& ampFIRST _ TASK- p)
如果(*p)
(* p)-& gt;counter =((* p)-& gt;計數器& gt& gt1)+(*)-& gt;優先級;
}
switch_to(下壹個);//通過long jump將任務轉換為新任務。
}
時鐘中斷是執行調度的重要手段。正是由於調度的不斷執行,系統才能在多任務間切換,完成多任務操作系統的目標。當調度程序初始化時,除了手動設置任務0,它還啟動8253的定時器中斷,並“點燃”系統。do_timer函數在中斷中被調用。這是壹個非常短的函數:
void do_timer(長cpl)
{
...
//根據優先級調整進程運行時間。
中頻(cpl)
當前->;utime++;
其他
當前->;stime++;
//處理定時器中斷隊列
if(next_timer){
next _ timer-& gt;jiffies-;
while(next _ timer & amp;& ampnext _ timer-& gt;jiffies & lt=0){
void(* fn)(void);
fn = next _ timer-& gt;fn;
next _ timer-& gt;fn = NULL
next _ timer = next _ timer-& gt;接下來;
(fn)();
}
}
..
//進程時間片減去1。如果大於0,則表示該時間片尚未用完。繼續。
如果((-當前-& gt;計數器& gt0)返回;
//註意,內核態的任務是不能被搶占的。在0.11中,除非內核任務主動放棄處理器,否則會壹直運行。
如果(!cpl)返回;
//調度
調度();
}
4.過程的終止
該過程可以自行停止,也可以由停止信號停止。Do_exit執行退出。如果它有壹個子進程,那麽這個子進程就會變成壹個孤兒進程,它的子進程會委托給進程1(也就是init進程)進行管理。變成僵屍進程後,妳也要通知父進程,因為他的父進程可能在等它。