uClinux移植與分析(3)

來源:互聯網
上載者:User
 

進程切換部分代碼實現

  移植linux,修改的主要就是和平台相關的那部分代碼.linux裡面和平台相關的代碼,包括很多方面,比如boot過程,系統調用,中斷處理,裝置驅 動,還有部分訊號(非強制中斷)處理等,進程切換也有很小一部分平台相關代碼.相對其它部分,我覺得這部分平台相關代碼還是相對簡單的.

  schedule()是uClinux中實現進程調度的函數.通過一定演算法,進行調度.假設有2各進程a,b,a運行時,調用了schedule(),那 麼os就要從進程就緒隊列中挑選一個合適的進程,如果沒有合適進程,則後面繼續運行a,假設找到了合適進程b,則就要從當前進程a切換到b.這個切換過程 是在switch_to()中進行的.

  switch_to()出現在schedule()函數裡面。調用形式switch_to(prev, next, last);prev,next都是進程式控制制塊task_struct的指標.prev指向當前啟動並執行進程,next指向要切換的進程.

  講一下我移植的代碼.由於代碼是組譯工具,首先介紹一下cpu結構。我用的cpu採用16位指令,32位的地址和資料。有16個通用寄存器,記作r0- r15。r0作為堆棧指標寄存器sp,r1用途不固定,r2-r6作為參數傳遞寄存器,函數調用如果有不超過5個的參數,則參數從左至右依次放在r2- r6中。同時,r2還作為函數傳回值寄存器,函數的傳回值都放在r2裡面。r7-r14是局部變數寄存器。r15是函數返回地址寄存器,也叫link register,存放的是function call地返回地址。

  #define switch_to(prev,next,last) {           /
(1) register void *_prev __asm__ ("r2") = (prev);   /
(2) register void *_next __asm__ ("r3") = (next);   /
(3) register void *_last;                   /
(4)       __asm__ __volatile__(             /
(5)       "jbsr " SYMBOL_NAME_STR(resume) "/n/t" /
(6)       "mfcr %0, ss4"                 /
(7)         : "=r" (_last)               /
(8)         : "r" (_prev),               /
(9)           "r" (_next)                 /
(10)         : "r2", "r2", "r3");           /
(11) (last) = _last;                       /
  }
  switch_to()所做的工作其實相當於為調用resume做一些準備。(1)-(2)的意思是將變數_prev,_next分別放在寄存器r2, r3裡面,他們的值分別等於prev和next,就是兩個task_struct的指標。這麼做是為調用resume準備好參數。第三行是聲明一個寄存器 臨時變數_last。

  第(5)行是調用resume函數實現進程切換。jbsr是一條跳轉指令,字面意思是跳入到子程式(jump to subroutine),這條指令做的工作是將現將當前pc+2儲存到r15中(因為是16位指令,所以+2),相當於儲存函數的傳回值,然後再將pc設 置成彙編指令參數中給出的地址(就是跳轉,這裡就是resume的地址)。

  第(6)行是將控制寄存器ss4內容放到_last對應的寄存器中。這一行指令有一些trick,先講指令所做的操作,再講為什麼這樣做。mfcr是從控 制寄存器移動到通用寄存器的指令。cpu除了有16個通用寄存器,還有16各控制寄存器。所有涉及控制寄存器的操作都要在cpu的超級使用者模式下進行。 cpu模式切換通過設定第0號控制寄存器來完成。16個控制寄存器分別為cr0-cr15,其中cr0也叫psr是程式狀態寄存器。cr6-cr10
也叫ss0-ss4是用於儲存狀態的寄存器。第(6)代碼就是將ss4內容放入到變數_last所對應的寄存器中。

  (7)-(10)行的意義請參考AT&T彙編。

  (11)行是一個賦值,last=_last。

  switch_to()所做的工作其實相當於為調用resume做一些準備。(1)-(2)的意思是將變數_prev,_next分別放在寄存器r2, r3裡面,他們的值分別等於prev和next,就是兩個task_struct的指標。這麼做是為調用resume準備好參數。第三行是聲明一個寄存器 臨時變數_last。

  第(5)行是調用resume函數實現進程切換。jbsr是一條跳轉指令,字面意思是跳入到子程式(jump to subroutine),這條指令做的工作是將現將當前pc+2儲存到r15中(因為是16位指令,所以+2),相當於儲存函數的傳回值,然後再將pc設 置成彙編指令參數中給出的地址(就是跳轉,這裡就是resume的地址)。

  第(6)行是將控制寄存器ss4內容放到_last對應的寄存器中。這一行指令有一些trick,先講指令所做的操作,再講為什麼這樣做。mfcr是從控 制寄存器移動到通用寄存器的指令。cpu除了有16個通用寄存器,還有16各控制寄存器。所有涉及控制寄存器的操作都要在cpu的超級使用者模式下進行。 cpu模式切換通過設定第0號控制寄存器來完成。16個控制寄存器分別為cr0-cr15,其中cr0也叫psr是程式狀態寄存器。cr6-cr10
也叫ss0-ss4是用於儲存狀態的寄存器。第(6)代碼就是將ss4內容放入到變數_last所對應的寄存器中。

  (7)-(10)行的意義請參考AT&T彙編。

  (11)行是一個賦值,last=_last。

  其實,上面並不是一個非常最佳化的做法。完全可以省掉_last變數,不過當初我做時,看到m68k版本用了_last變數,而又不很清楚他的作用,為防止出錯,照辦了過來。其實經過後面分析,可知這個變數其實是冗餘的。

  那麼,為什麼要有(6)和(11)行的代碼呢?回頭可以看一下schedule()的代碼,在switch_to()調用過後,schedule()中調 用了schedule_tail(prev)函數。顯然prev作為參數,應該放到r2裡面,所以就有了switch_to()代碼的第(11)行。那麼 為什麼prev是來自ss4呢?

  在調用resume之前,prev存放在r2中。r2中的內容屬於進程的上下文,在做進程切換時,要存放在棧中。同時切換到另一個進程時,還要將另一個進 程的上下文裝入到寄存器中。在裝入新進程時,r2的值就會被衝掉。舉個例子,比如你通過fork系統調用建立了一個新進程。我們知道,fork地傳回值如 果是0就表示子進程,大於0就是父進程。對於子進程,這個棧裡r2就是0(前面說過,r2用作放函數傳回值),如果此時schedule選了一個經 fork後的子進程開始執行,則切換到該子進程後,其r2顯然為0,當然就不是prev了。所以,我的實現是在進程切換時,將r2值存放在ss4中,切換 完畢後,再進行區別對待。如果是兩個已經運行過的進程切換,則返回就返回到原來switch_to的地方。如果是新的fork出來的進程,則第一次調用, 在resume返回時,返回的是ret_from_fork,這是另外處理的。

  (11)行是一個賦值,last=_last。

  其實,上面並不是一個非常最佳化的做法。完全可以省掉_last變數,不過當初我做時,看到m68k版本用了_last變數,而又不很清楚他的作用,為防止出錯,照辦了過來。其實經過後面分析,可知這個變數其實是冗餘的。

  那麼,為什麼要有(6)和(11)行的代碼呢?回頭可以看一下schedule()的代碼,在switch_to()調用過後,schedule()中調 用了schedule_tail(prev)函數。顯然prev作為參數,應該放到r2裡面,所以就有了switch_to()代碼的第(11)行。那麼 為什麼prev是來自ss4呢?

  在調用resume之前,prev存放在r2中。r2中的內容屬於進程的上下文,在做進程切換時,要存放在棧中。同時切換到另一個進程時,還要將另一個進 程的上下文裝入到寄存器中。在裝入新進程時,r2的值就會被衝掉。舉個例子,比如你通過fork系統調用建立了一個新進程。我們知道,fork地傳回值如 果是0就表示子進程,大於0就是父進程。對於子進程,這個棧裡r2就是0(前面說過,r2用作放函數傳回值),如果此時schedule選了一個經 fork後的子進程開始執行,則切換到該子進程後,其r2顯然為0,當然就不是prev了。所以,我的實現是在進程切換時,將r2值存放在ss4中,切換 完畢後,再進行區別對待。如果是兩個已經運行過的進程切換,則返回就返回到原來switch_to的地方。如果是新的fork出來的進程,則第一次調用, 在resume返回時,返回的是ret_from_fork,這是另外處理的。

  上面說了這麼多,可能讀者還是糊裡糊塗的,我也覺得自己沒說清楚,所以這裡的這點實現有那麼一點點trick,需要對cpu的ABI和linux的核心代碼非常熟悉才行。
    (11)ldw   r7, (r0)         /* restore r7 */
    (12)ldw   r8, (r0, 4)       /* restore r8 */
    (13)addi   r0, 8
    (14)SAVE_SWITCH_STACK
    (15)lrw   r8, TASK_THREAD   /* the position of thread in task_struct */
    (16)addu   r8, r2
    (17)mfcr   r6, ss1           /* Get current usp */
    (18)stw   r6, (r8, THREAD_USP) /* Save usp in task struct */
    (19)stw   r0, (r8, THREAD_KSP) /* Save ksp in task struct */

    (20)lrw   r8, TASK_THREAD
    (21)lrw   r7, SYMBOL_NAME(_current_task)
    (22)stw   r3, (r7)         /* Set new task */
    (23)addu   r8, r3           /* Pointer to thread in task_struct */

    /* Set up next process to run */
    (24)ldw   r0, (r8, THREAD_KSP) /* Set next ksp */
    (25)ldw   r6, (r8, THREAD_USP) /* Set next usp */
    (26)mtcr   r6, ss1
    (27)ldw   r7, (r8, THREAD_SR)   /* Set next PSR */
    (28)mtcr   r7, psr
    (29)RESTORE_SWITCH_STACK
              ----------------
              |   r11     |
              ----------------
              |   r10     |
              ----------------
              |   r9     |
              ----------------
              |   r8     |
              ----------------
              |   r7     |
              ----------------
              |   r6     |
              ----------------
              |   r5     |
              ----------------
              |   r4     |
              ----------------
              |   r3     |
              ----------------
              |   r2     |
              ----------------
        0x1effC4 |   r1     |
              ----------------
0x1f0000和0x1effc4分別是執行過(14)前後r0的值。這是個contex save的操作。

註:lrw是立即數裝入操作,addu是無符號加法,mfcr和mtcr是控制寄存器移動 操作,bclri是位清除操作,ldw是load word操作,addi是立即數加法操作。

  (15)-(19)是做的棧指標儲存操作。將當前進程使用者棧和核心棧儲存到進程式控制制塊相應的資料結構中。linux下除了核心線程(只有核心棧)每個進程 都有2個棧,一個在使用者空間一個在核心空間。如果是核心線程,則不用關心它的使用者堆棧,反正不會用到,是什麼值都可以。如果使用者進程,則在使用者進程執行系 統調用或者在使用者進程執行時發生中斷時,都需要從使用者空間進入核心空間,在進入時,原先的使用者空間棧指標就會暫時存放到ss1中。所以(17)-(18) 兩行就是從ss1中取出使用者空間棧指標,存入task_struct中。(15)-(19)的操作可以總結為:
  prev->thread.usp = ss1 儲存使用者棧指標
  prev->thread.ksp = r0   儲存核心棧指標

  那麼有人可能會問,ss1能夠保證就是正確的目前使用者棧指標嗎?當然可以因為核心線程沒有使用者棧,所以這個值是什麼無所謂。而對於使用者進程,進入 resume的唯一入口就是schedule,而這又都是作業系統核心代碼。使用者進程進入核心手段就有系統調用和中斷,而在系統調用和中斷處理一進來就保 存了使用者堆棧到ss1,所以在運行時,只要在核心裡用的都是核心棧,使用者棧指標不會變。

  (20)-(23)執行的操作相當於_current_task = next。不再詳細解釋。

  (24)-(28)執行的是裝入新進程內容相關的準備工作,也就是準備裝入next了。

(24)-(25)是裝入next進程的核心和使用者棧。因為進程的上下文都儲存在該進程的核心棧裡面,所以第一步就是裝入該進程的棧指標。(27)-(28)是裝入next進程在切換前的狀態資訊。(26)就是更新ss1,現在要裝入新進程了,當然就要設定新的使用者棧。

  (29)是裝入next進程的上下文。next進程在棧裡有一個和一樣的上下文,現在就要裝入。

  (30)是函數調用返回。如果這個進程是剛fork出來的子進程,則上下文裡面r15=ref_from_fork(可以參看copy_thread函數),否則就是返回到switch_to裡面第(6)句位置。

  上面就是進程切換的部分。這部分是和平台相關的。以上是我實現的代碼,感覺效率並不是非常高,但功能是正確的。可能有些地方我沒有講得很清楚,有什麼問題歡迎提出。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.