1. 程式人生 > >51微控制器ucos ii任務切換匯編程式碼分析(1)

51微控制器ucos ii任務切換匯編程式碼分析(1)

ucos中任務切換函式都是彙編寫的,屬於“需移植”檔案,

這個彙編檔名一般叫做:OS_CPU_A.ASM

要想看懂任務切換的原理,首先遇到的第一個難點,就是OS_CPU_A.ASM這個彙編檔案裡的一大堆不常見的彙編偽指令,搞懂這些指令是搞懂程式原理的第一步。

這篇文章先只分析這些彙編指令。

這個檔案為ucos作業系統提供了4個API函式,分別是:

	PUBLIC OSStartHighRdy;函式功能:切換到已就緒的任務橫縱優先順序最高的那個任務中去
        PUBLIC OSCtxSw ;函式功能:一般的上下文切換,ContextSwitch,上下文切換又叫任務切換
        PUBLIC OSIntCtxSw ;函式功能:在中斷中進行上下文切換
        PUBLIC OSTickISR ;函式功能:系統滴答

PULIC是彙編虛擬碼,表明所宣告的函式可以被其他檔案呼叫

首先來學習一個知識點,如何在彙編程式碼中寫一個函式,才能使得這個函式能夠被其他檔案呼叫?不僅是加PUBLIC關鍵字這麼簡單,另外,我們還必須遵守一定的規範,可參考這篇文章,連結:點選開啟連結

這個檔案OS_CPU_A.ASM除了供外部檔案引用自己的函式外,也需要引用別的檔案的函式和變數,例如: 

EXTRN IDATA (OSRunning) ;宣告引用IDATA 區的變數OSRunning
MOV  R0,#LOW (OSRunning) ;在彙編中使用外部變數
EXTRN CODE  (_?OSTaskSwHook) ;宣告引用外部函式(程式碼),OSTaskSwHook()
LCALL _?OSTaskSwHook ;在彙編中呼叫C語言函式OSTaskSwHook()

解釋:函式OSTaskSwHook是用C語言寫的,名字為OSTaskSwHook,但是在彙編中引用它的話,必須在前面加字首才行,由前面連結裡的文章我們知道,如果我們在彙編中引用的是可重入函式,那麼必須在函式名前面加_?字首才能被彙編檔案識別到。為什麼要加字首?因為C51的C語言函式轉換為彙編的時候,keil編譯器會自動把C語言的函式名給改掉,當然keil所做的改動是有規律的,例如,我們宣告的可重入的C函式,keil轉成彙編後,會自動在原先的函式名前加字首“_?” 。

keil會自動新增什麼字首,新增的字首有什麼規範?這些問題可以參考keil的幫助檔案,依次點選選單欄->help->uVision help,在開啟的幫助檔案中搜索“Segment Naming Conventions(段命名慣例)”可查閱相關資訊。

在彙編中寫一個供C語言呼叫的函式的標準格式如下:

?PR?OSStartHighRdy?OS_CPU_A    SEGMENT CODE;先宣告一個可重定位的程式碼段(這個語句的用法與解釋可參考本部落格的另一篇文章)
RSEG ?PR?OSStartHighRdy?OS_CPU_A;進行重定位,下面的程式碼都將被連結到在RSEG指令所指定的段中
OSStartHighRdy:      ;地址標號,作為函式名
·········;彙編函式的函式體,直到遇到CSEG/DSEG/RSEG 等段分配指令

上述程式碼解釋:?PR?OSStartHighRdy?OS_CPU_A    SEGMENT CODE這一句不僅是聲明瞭一個可重定位段的段名,這個段名的字首為?PR?,這個字首的意義是:該段是一個函式段(Executable program code)

再來看一個彙編呼叫C語言函式的例子:

在main.c檔案中,我們定義了這樣一個函式:

char add_two(char a1, char a2) REENTRANT
{	
	return a1+a2;
}
這個函式在keil編譯之後如下:
    85: char add_two(char a1, char a2) REENTRANT 
    86: {        
C:0x269B    90FFFF   MOV      DPTR,#0xFFFF
C:0x269E    120436   LCALL    C?ADDXBP(C:0436)
C:0x26A1    ED       MOV      A,R5
C:0x26A2    F0       MOVX     @DPTR,A
C:0x26A3    90FFFF   MOV      DPTR,#0xFFFF
C:0x26A6    120436   LCALL    C?ADDXBP(C:0436)
C:0x26A9    EF       MOV      A,R7
C:0x26AA    F0       MOVX     @DPTR,A
    87:         return a1+a2; 
C:0x26AB    850883   MOV      DPH(0x83),?C_XBP(0x08)
C:0x26AE    850982   MOV      DPL(0x82),OutTxBuf(0x09)
C:0x26B1    A3       INC      DPTR
C:0x26B2    E0       MOVX     A,@DPTR
C:0x26B3    FF       MOV      R7,A
C:0x26B4    850883   MOV      DPH(0x83),?C_XBP(0x08)
C:0x26B7    850982   MOV      DPL(0x82),OutTxBuf(0x09)
C:0x26BA    E0       MOVX     A,@DPTR
C:0x26BB    2F       ADD      A,R7
C:0x26BC    FF       MOV      R7,A
    88: } 
C:0x26BD    900002   MOV      DPTR,#0x0002
C:0x26C0    020436   LJMP     C?ADDXBP(C:0436)

由彙編程式碼我們可以看到,該函式被放在了0x269B地址處,繼續觀察keil生成的.m51檔案(即map檔案),搜尋add_two,發現相關內容如下:

(1)            CODE    269BH     0028H     UNIT         ?PR?_?ADD_TWO?MAIN

(2) C:269BH         PUBLIC        _?add_two
(3)-------         PROC          _?ADD_TWO
  x:0000H         SYMBOL        a1
  x:0001H         SYMBOL        a2
  C:269BH         LINE#         85
  C:26ABH         LINE#         87
  C:26BDH         LINE#         88
  -------         ENDPROC       _?ADD_TWO

有上述查到的內容發現,keil在編譯add_two()函式的過程中,做了3個工作:

(1)為add_two()函式聲明瞭一個段,位置從269BH開始,大小為0028H ,這個段中只含有add_two這個函式的程式碼段,不含其他函式,也不不含任何資料段,段名為:?PR?_?ADD_TWO?MAIN(段名由3部分組成:一是固定字首?PR?,二是函式名的大寫ADD_TWO,並且keil在為函式生成彙編時,自動為可重入函式的函式名前加字首_?,三是模組名,預設的模組名即該函式所在的檔名)。

(2)keil對“add_two()生成的彙編函式”進行了PUBLIC宣告,以供其他檔案呼叫該函式,但是宣告函式名的時候,加了_?字首:_?add_two,如果有彙編檔案打算呼叫add_two()函式,就得這樣:

EXTRN CODE  (_?add_two) ;宣告引用外部函式(程式碼):add_two()
LCALL _?add_two ;在彙編中呼叫C語言函式add_two()

按照keil的命名慣例,

_?字首的函式是可重入函式。

拓展資料:其他型別的函式的命名慣例如下:

無參函式: ?PR?函式名?檔名
有參函式: ?PR?_函式名?檔名
可重入函式: ?PR?_?函式名?檔名

(3)這是add_two()函式的符號表,也就是指出了add_two()函式中的形參和區域性變數所在的儲存位置。那麼這裡有個疑問,為什麼a1、a2被放在了00h和01H地址處呢,這兩個地址不是暫存器R0和R1嗎?這就涉及到了keil的編譯規則,形參的型別、數量不同時,傳參的方法都是不一樣的,形參往哪裡放有專門的文章介紹,一般來說,形參和區域性變數較少時,全部都用暫存器Rn來傳遞和儲存;數量較多時,不可重入函式的形參和區域性變數在Rn不夠用時,其餘的存放到固定的記憶體地址中,可重入函式的形參和區域性變數在Rn不夠用時,其餘的入模擬棧。