自己動手寫CPU之第九階段(7)——MIPS32中的LL、SC指令說明

將陸續上傳新書《自己動手寫CPU》,今天是第46篇。


在MIPS32指令集中有兩條特殊的存儲加載指令:鏈接加載指令LL、條件存儲指令SC,本次將介紹這兩條指令,在後續將實現這兩條指令。


9.6 鏈接加載指令ll、條件存儲指令sc說明

      在本章前面的部分,筆者花費很多筆墨介紹了OpenMIPS中除llsc之外的加載、存儲指令的實現過程,本節至9.9節將專門介紹鏈接加載指令ll、條件存儲指令sc的實現過程。llsc指令是MIPS32指令集架構中比較特殊的加載存儲指令,用來實現信號量機制。

      在多線程系統中,需要RMWRead-Modify-Write)操作序列保證對某個資源的獨佔性,RMW操作序列的含義是,讀取內存某個地址的數據,讀取的數據經過修改,然後再保存回內存原地址,這個過程不能有任何打擾,因此需要建立一個臨界區域(Critical Region),臨界區域中完成的操作通常稱爲原子操作,原子操作不被打擾。操作系統建立臨界區域的方式通常是信號量機制,如下。

waitsemaphore;

原子操作;

 signalsemaphore;

      semaphore是一個信號量,爲1表示信號量使用中,爲0表示信號量空閒。進行原子操作前,使用wait函數查詢semaphore的值,如果爲1,則等待,否則,將其置爲1,開始執行原子操作,操作結束後,signal函數將semaphore置爲0,這樣其它線程就可以執行原子操作了。

      需要注意的是,wait函數的執行也是一個原子操作,是一種“先檢測後設置”操作(test-and-set operation),這種操作一般不希望被外部設備中斷,也不希望被其它線程打斷,很多處理器都有專門的指令用來實現“先檢測後設置”操作,比如:680x0 CPUx86 CPU等。這也是一種信號量機制。

      MIPS32架構採用特殊的方式實現信號量機制,對於原子操作,MIPS32架構並不保證它一定是原子性的,也就是允許檢測和設置在沒有原子性保證的情況下運行,但只在它確實原子的運行了的時候才讓“設置”生效。MIPS32架構採用鏈接加載指令ll、條件存儲指令sc來實現這種信號量機制。

      ll指令同一般的加載指令一樣,從內存中加載一個字,但是,有一點不同,ll指令還會將處理器內部的一個鏈接狀態位LLbit置爲1,表明發生了一個鏈接加載操作,並將鏈接加載的地址保存到一個特殊寄存器LLAddr中(這個寄存器在多處理器中有作用,OpenMIPS是單處理器,所以在OpenMIPS實現過程中並沒有實現LLAddr寄存器)。

      ll指令執行完畢後,會進行一定的操作(如:修改加載得到的數據),然後執行sc指令,這可以認爲是一個RMW序列。有如下兩種情況干擾這個RMW序列,受到干擾後,處理器會設置鏈接狀態位LLbit0

  •  在llsc指令之間產生異常,從而進入異常處理例程,或者發生線程切換,導致RMW序列受到干擾。
  •  多處理器的系統中,另一個CPU改寫了RMW序列要操作的內存空間。

      對於OpenMIPS而言,只有第1種情況。

      執行sc指令時,會對從ll指令開始的RMW序列進行檢查,判斷是否受到干擾,實際就是判斷LLbit是否爲1,如果沒有受到任何干擾,LLbit保持爲1,那麼操作是原子的,sc指令會對ll指令加載數據的地址進行寫回操作,並設置一個通用寄存器的值爲1,表示成功,反之不進行寫回操作,並設置一個通用寄存器的值爲0,表示失敗。

      llsc指令的格式如圖9-28所示。從圖中可知,可以依據指令碼對這2條指令進行區分。


  •  當指令中的指令碼爲6'b110000時,是ll指令,鏈接加載指令

      指令用法爲:ll rt, offset(base)

      指令作用爲:從內存中指定的加載地址處,讀取一個字節,然後符號擴展至32位,保存到地址爲rt的通用寄存器中。其中加載地址的計算方法如下。

      加載地址 = signed_extended(offset) + GPR[base]

      此外,還要設置鏈接狀態位LLbit1

  •  當指令中的指令碼爲6'b111000時,是sc指令,條件存儲指令

      指令用法爲:sc rt, offset(base)

      指令作用爲:如果RMW序列沒有受到干擾,也就是LLbit1,那麼將地址爲rt的通用寄存器的值保存到內存中指定的存儲地址處,同時設置地址爲rt的通用寄存器的值爲1,設置LLbit0。如果RMW序列受到了干擾,也就是LLbit0,那麼不修改內存,同時設置地址爲rt的通用寄存器的值爲0。其中存儲地址的計算方法如下。

      存儲地址 = signed_extended(offset) + GPR[base]

      下面通過一個例子體會llsc指令的作用,這個例子實現了上面介紹的wait函數,不過此處是使用llsc指令實現的。


wait:
ori $1, $0, sem         // sem是信號量的地址,將這個地址賦給寄存器$1

TryAgain:
ll  $2, 0($1)           // 獲取信號量的值,保存到寄存器$2
bne $2, $0, WaitForSem  // 如果信號量被佔用(其值爲1),那麼轉移到地址WaitForSem
                        // 繼續等待;如果信號量空閒(其值爲0),那麼執行下面的指令

nop
ori $2, $0, 1
sc  $2, 0($1)           // 如果沒有被幹擾,那麼設置信號量被佔用(將1保存到信號
                        // 量中),同時,設置寄存器$2爲1,反之,不修改信號量,
                        // 設置寄存器$2爲0

beq $2, $0, TryAgain    // 如果寄存器$2爲0,表示ll、sc指令沒有成功,未獲取到
                        // 信號量,回到TryAgain繼續嘗試
nop

jr  $31                  // 反之, 表示ll、sc指令成功,獲取到信號量,可以進入
                         // “臨界區域”了。調用wait函數時,會將返回地址放在
                         // 寄存器$31,所以此處jr $31指令就是回到調用過程,
                         // 進入臨界區域


下一次將修改OpenMIPS以實現LL、SC指令

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章