?? 4.html
字號:
假設該硬件依賴于寄存器0x30按順序依次被設為0、1、2、3來初始化,那么要是有另一個CPU來參乎的話,事情就會搞糟。想象有兩個CPU的情形,它們都在執行這個例程,不過2號CPU進入得稍慢點:<br> CPU 1 CPU 2<p> 0x30 = 1<br> 0x30 = 2 0x30 = 1<br> 0x30 = 3 0x30 = 2<br> 0x30 = 4 0x30 = 3<br> 0x30 = 4<br> 這會發生什么情況呢?從我們設想的硬件設備看來,它在寄存器0x30上收到的字節按順序為:1、2、1、3、2、4、3、4。<br> ??!原本好好的事第二個CPU一來就搞得一團糟了也。所幸的是,我們有防止這類事情發生的辦法。<p>自旋鎖小歷史<p> 2.0.x版本的Linux內核通過給整個內核引入一個全局變量來防止多于一個CPU會造成的問題。這意味著任何時刻只有一個CPU能夠執行來自內核空間的代碼。這樣盡管能工作,但是當系統開始以多于2個的CPU出現時,擴展性能就不怎么好。<br> 2.1.x版本的內核系列加入了粒度更細的SMP支持。這意味著不再依賴于以前作為全局變量出現的“大鎖”,而是每個沒有SMP意識的例程現在都需要各自的自旋鎖。文件asm/spinlock.h中定義了若干類型的自旋鎖。<br> 有了局部化的自旋鎖后,不止一個CPU同時執行內核空間代碼就變得可能了。<p>簡單的自旋鎖<p> 理解自旋鎖的最簡單方法是把它作為一個變量看待,該變量把一個例程或者標記為“我當前在另一個CPU上運行,請稍等一會”,或者標記為“我當前不在運行”。如果1號CPU首先進入該例程,它就獲取該自旋鎖。當2號CPU試圖進入同一個例程時,該自旋鎖告訴它自己已為1號CPU所持有,需等到1號CPU釋放自己后才能進入。<br> spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;<br> unsigned long flags;<p> spin_lock (&my_spinlock);<br> ...<br> critical section<br> ...<br> spin_unlock (&my_spinlock);<p>中斷<p> 設想我們的硬件的驅動程序還有一個中斷處理程序。該處理程序需要修改某些由我們的驅動程序定義的全局變量。這會造成混亂。我們如何解決呢?<br> 保護某個數據結構,使它免遭中斷之修改的最初方法是全局地禁止中斷。在已知只有自己的中斷才會修改自己的驅動程序變量時,這么做效率很低。所幸的是,我們現在有更好的辦法了。我們只是在使用共享變量期間禁止中斷,此后重新使能。<br> 實現這種辦法的函數有三個:<br> disable_irq()<br> enable_irq()<br> disable_irq_nosync()<br> 這三個函數都取一個中斷號作為參數。注意,禁止一個中斷的時間太長會導致難以追蹤程序缺陷,丟失數據,甚至更壞。<br> disable_irq函數的非同步版本允許所指定的IRQ處理程序繼續運行,前提是它已經在運行,普通的disable_irq則所指定的IRQ處理程序不在如何CPU上運行。<br> 如果需要在中斷處理程序中修改自旋鎖,那就不能使用普通的spin_lock()和spin_unlock(),而應該保存中斷狀態。這可通過給這兩個函數添加_irqsave后綴很容易地做到:<br> spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;<br> unsigned long flags;<p> spin_lock_irqsave(&my_spinlock, flags);<br> ...<br> critical section<br> ...<br> spin_unlock_irqrestore (&my_spinlock, flags);<p><p><br><center><A HREF="#Content">[目錄]</A></center><hr><br><A NAME="I412" ID="I412"></A><center><b><font size=+2>內核線程頁目錄的借用</font></b></center><br> 創建內核線程的時候,由于內核線程沒有用戶空間,而所有進程的內核頁目錄都是一樣的((某些情況下可能有不同步的情況出現,主要是為了減輕同步所有進程內核頁目錄的開銷,而只是在各個進程要訪問內核空間,如果有不同步的情況,然后才進行同步處理),所以創建的內核線程的內核頁目錄總是借用進程0的內核頁目錄。<p>>>> kernel_thread以標志CLONE_VM調用clone系統調用<br>/*<br> * Create a kernel thread<br> */<br>int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)<br>{<br> long retval, d0;<p> __asm__ __volatile__(<br> "movl %%esp,%%esi\n\t"<br> "int $0x80\n\t" /* Linux/i386 system call */<br> "cmpl %%esp,%%esi\n\t" /* child or parent? */<br> /* Load the argument into eax, and push it. That way, it does<br> * not matter whether the called function is compiled with<br> * -mregparm or not. */<br> "movl %4,%%eax\n\t"<br> "pushl %%eax\n\t"<br> "call *%5\n\t" /* call fn */<br> "movl %3,%0\n\t" /* exit */<br> "int $0x80\n"<br> "1:\t"<br> :"=&a" (retval), "=&S" (d0)<br> :"0" (__NR_clone), "i" (__NR_exit),<br> "r" (arg), "r" (fn),<br> "b" (flags | CLONE_VM)<br> : "memory");<br> return retval;<br>}<p>>>> sys_clone->do_fork->copy_mm:<br>static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)<br>{<br> struct mm_struct * mm, *oldmm;<br> int retval;<p> 。。。。。。。。<p> tsk->mm = NULL;<br> tsk->active_mm = NULL;<p> /*<br> * Are we cloning a kernel thread?<br> *<br> * We need to steal a active VM for that..<br> */<br>>>> 如果是內核線程的子線程(mm=NULL),則直接退出,此時內核線程mm和active_mm均為為NULL<br> oldmm = current->mm;<br> if (!oldmm)<br> return 0;<p>>>> 內核線程,只是增加當前進程的虛擬空間的引用計數<br> if (clone_flags & CLONE_VM) {<br> atomic_inc(&oldmm->mm_users);<br> mm = oldmm;<br> goto good_mm;<br> }<p> 。。。。。。。。。。<p>good_mm:<br>>>> 內核線程的mm和active_mm指向當前進程的mm_struct結構<br> tsk->mm = mm;<br> tsk->active_mm = mm;<br> return 0;<p> 。。。。。。。<br>}<p>然后內核線程一般調用daemonize來釋放對用戶空間的引用:<br>>>> daemonize->exit_mm->_exit_mm:<br>/*<br> * Turn us into a lazy TLB process if we<br> * aren't already..<br> */<br>static inline void __exit_mm(struct task_struct * tsk)<br>{<br> struct mm_struct * mm = tsk->mm;<p> mm_release();<br> if (mm) {<br> atomic_inc(&mm->mm_count);<br> if (mm != tsk->active_mm) BUG();<br> /* more a memory barrier than a real lock */<br> task_lock(tsk);<br>>>> 釋放用戶虛擬空間的數據結構<br> tsk->mm = NULL;<br> task_unlock(tsk);<br> enter_lazy_tlb(mm, current, smp_processor_id());<p>>>> 遞減mm的引用計數并是否為0,是則釋放mm所代表的映射<br> mmput(mm);<br> }<br>}<p>asmlinkage void schedule(void)<br>{<br> 。。。。。。。。。<br> if (!current->active_mm) BUG();<p> 。。。。。。。。。<p>prepare_to_switch();<br> {<br> struct mm_struct *mm = next->mm;<br> struct mm_struct *oldmm = prev->active_mm;<br>>>> mm = NULL,選中的為內核線程<br> if (!mm) {<br>>>> 對內核線程,active_mm = NULL,否則一定是出錯了<br> if (next->active_mm) BUG();<br>>>> 選中的內核線程active_mm借用老進程的active_mm<br> next->active_mm = oldmm;<br> atomic_inc(&oldmm->mm_count);<br> enter_lazy_tlb(oldmm, next, this_cpu);<br> } else {<br>>>> mm != NULL 選中的為用戶進程,active_mm必須與mm相等,否則一定是出錯了<br> if (next->active_mm != mm) BUG();<br> switch_mm(oldmm, mm, next, this_cpu);<br> }<p>>>> prev = NULL ,切換出去的是內核線程<br> if (!prev->mm) {<br>>>> 設置其 active_mm = NULL 。<br> prev->active_mm = NULL;<br> mmdrop(oldmm);<br> }<br> }<p>}<p>對內核線程的虛擬空間總結一下:<br>1、創建的時候:<br> 父進程是用戶進程,則mm和active_mm均共享父進程的,然后內核線程一般調用daemonize適頭舖m<br> 父進程是內核線程,則mm和active_mm均為NULL<br>總之,內核線程的mm = NULL;進程調度的時候以此為依據判斷是用戶進程還是內核線程。<p>2、進程調度的時候<br> 如果切換進來的是內核線程,則置active_mm為切換出去的進程的active_mm;<br> 如果切換出去的是內核線程,則置active_mm為NULL。<p><p><p><center><A HREF="#Content">[目錄]</A></center><hr><br><A NAME="I413" ID="I413"></A><center><b><font size=+2>代碼分析</font></b></center><br> LINUX系統是分時多用戶系統, 它有多進程系統的特點,CPU按時間片分配給各個用戶使用, 而在實質上應該說CPU按時間片分配給各個進程使用, 每個進程都有自己的運行環境以使得在CPU做進程切換時保存該進程已計算了一半的狀態。<p>進程的切換包括三個層次:<p> ·用戶數據的保存: 包括正文段(TEXT), 數據段(DATA,BSS), 棧段(STACK), 共享內存段(SHARED MEMORY)的保存。<br> ·寄存器數據的保存: 包括PC(program counter,指向下一條要執行的指令的地址), PSW(processor status word,處理機狀態字), SP(stack pointer,棧指針), PCBP(pointer of process control block,進程控制塊指針), FP(frame pointer,指向棧中一個函數的local 變量的首地址), AP(augument pointer,指向棧中函數調用的實參位置), ISP(interrupt stack pointer,中斷棧指針), 以及其他的通用寄存器等。<br> ·系統層次的保存: 包括proc,u,虛擬存儲空間管理表格,中斷處理棧。以便于該進程再一次得到CPU時間片時能正常運行下去。<p> 多進程系統的一些突出的特點:<br>并行化<br> 一件復雜的事件是可以分解成若干個簡單事件來解決的, 這在程序員的大腦中早就形成了這種概念, 首先將問題分解成一個個小問題, 將小問題再細分, 最后在一個合適的規模上做成一個函數。 在軟件工程中也是這么說的。如果我們以圖的方式來思考, 一些小問題的計算是可以互不干擾的, 可以同時處理, 而在關鍵點則需要統一在一個地方來處理, 這樣程序的運行就是并行的, 至少從人的時間觀念上來說是這樣的。 而每個小問題的計算又是較簡單的。<br>簡單有序<br> 這樣的程序對程序員來說不亞于管理一班人, 程序員為每個進程設計好相應的功能, 并通過一定的通訊機制將它們有機地結合在一起, 對每個進程的設計是簡單的, 只在總控部分小心應付(其實也是蠻簡單的), 就可完成整個程序的施工。<br>互不干擾<br> 這個特點是操作系統的特點, 各個進程是獨立的, 不會串位。<br>事務化<br> 比如在一個數據電話查詢系統中, 將程序設計成一個進程只處理一次查詢即可, 即完成一個事務。當電話查詢開始時, 產生這樣一個進程對付這次查詢; 另一個電話進來時, 主控程序又產生一個這樣的進程對付, 每個進程完成查詢任務后消失. 這樣的編程多簡單, 只要做一次查詢的程序就可以了。<p> Linux是一個多進程的操作系統,進程是分離的任務,擁有各自的權利和責任。如果一個進程崩潰,它不應該讓系統的另一個進程崩潰。每一個獨立的進程運行在自己的虛擬地址空間,除了通過安全的核心管理的機制之外無法影響其他的進程。<br> 在一個進程的生命周期中,進程會使用許多系統資源。比如利用系統的CPU執行它的指令,用系統的物理內存來存儲它和它的數據。它會打開和使用文件系統中的文件,會直接或者間接使用系統的物理設備。如果一個進程獨占了系統的大部分物理內存和CPU,對于其他進程就是不公平的。所以Linux必須跟蹤進程本身和它使用的系統資源以便公平地管理系統中的進程。<br> 系統最寶貴的資源就是CPU。通常系統只有一個CPU。Linux作為一個多進程的操作系統,它的目標就是讓進程在系統的CPU上運行,充分利用CPU。如果進程數多于CPU(一般情況都是這樣),其他的進程就必須等到CPU被釋放才能運行。多進程的思想就是:一個進程一直運行,直到它必須等待,通常是等待一些系統資源,等擁有了資源,它才可以繼續運行。在一個單進程的系統中,比如DOS,CPU被簡單地設為空閑,這樣等待資源的時間就會被浪費。而在一個多進程的系統中,同一時刻許多進程在內存中,當一個進程必須等待時,操作系統將CPU從這個進程切換到另一個更需要的進程。<br> 我們組分析的是Linux進程的狀態轉換以及標志位的作用,它沒有具體對應某個系統調用,而是分布在各個系統調用中。所以我們詳細而廣泛地分析了大量的原碼,對進程狀態轉換的原因、方式和結果進行了分析,大致總結了整個Linux系統對進程狀態管理的實現機制。<p> Linux中,每個進程用一個task_struct的數據結構來表示,用來管理系統中的進程。Task向量表是指向系統中每一個task_struct數據結構的指針的數組。這意味著系統中的最大進程數受到Task向量表的限制,缺省是512。這個表讓Linux可以查到系統中的所有的進程。操作系統初始化后,建立了第一個task_struct數據結構INIT_TASK。當新的進程創建時,從系統內存中分配一個新的task_struct,并增加到Task向量表中。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -