go語言調度器源代碼情景分析之二:CPU寄存器


本文是《go調度器源代碼情景分析》系列 第一章 預備知識的第1小節。

寄存器是CPU內部的存儲單元,用於存放從內存讀取而來的數據(包括指令)和CPU運算的中間結果,之所以要使用寄存器來臨時存放數據而不是直接操作內存,一是因為CPU的工作原理決定了有些操作運算只能在CPU內部進行,二是因為CPU讀寫寄存器的速度比讀寫內存的速度快得多。

為了便於交流和使用匯編語言進行編程,CPU廠商為每個寄存器都取了一個名字,比如AMD64 CPU中的rax, rbx, rcx, rdx等等,這樣程序員就可以很方便的在匯編代碼中使用寄存器的名字來進行編程,為了對寄存器的使用有個直觀的感受,我們用個例子來簡單的說明一下。

假設有如下go語言編寫的一行代碼:

c = a + b

在AMD64 Linux平台下,使用go編譯器編譯它可得到如下AT&T格式的匯編代碼(如果對匯編代碼不熟悉的話可以直接看每一條指令后面的注釋,不影響我們理解):

mov (%rsp),%rdx #把變量a的值從內存中讀取到寄存器rdx中 mov 0x8(%rsp),%rax #把變量b的值從內存中讀取到寄存器rax中 add %rdx,%rax #把寄存器rdx和rax中的值相加,並把結果放回rax寄存器中 mov %rax,0x10(%rsp) #把寄存器rax中的值寫回變量c所在的內存

可以看到,上面的一行go語言代碼被編譯成了4條匯編指令,指令中出現的rax,rdx和rsp都是寄存器的名字(AT&T格式的匯編代碼中所有寄存器名字前面都有一個%符號),雖然這里只有4條指令,但也從一個側面說明匯編代碼其實比較簡單,它所做的工作不外乎就是把數據在內存和寄存器中搬來搬去或做一些基礎的數學和邏輯運算。

不同體系結構的CPU,其內部寄存器的數量、種類以及名稱可能大不相同,這里我們只介紹目前使用最為廣泛的AMD64這種體系結構的CPU,這種CPU共有20多個可以直接在匯編代碼中使用的寄存器,其中有幾個寄存器在操作系統代碼中才會見到,而應用層代碼一般只會用到如下分為三類的19個寄存器。

  • 通用寄存器:rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8, r9, r10, r11, r12, r13, r14, r15寄存器。CPU對這16個通用寄存器的用途沒有做特殊規定,程序員和編譯器可以自定義其用途(下面會介紹,rsp/rbp寄存器其實是有特殊用途的);
  • 程序計數寄存器(PC寄存器,有時也叫IP寄存器):rip寄存器。它用來存放下一條即將執行的指令的地址,這個寄存器決定了程序的執行流程;
  • 段寄存器:fs和gs寄存器。一般用它來實現線程本地存儲(TLS),比如AMD64 linux平台下go語言和pthread都使用fs寄存器來實現系統線程的TLS,在本章線程本地存儲一節和第二章詳細分析goroutine調度器的時候我們可以分別看到Linux平台下Pthread線程庫和go是如何使用fs寄存器的。


上述這些寄存器除了fs和gs段寄存器是16位的,其它都是64位的,也就是8個字節,其中的16個通用寄存器還可以作為32/16/8位寄存器使用,只是使用時需要換一個名字,比如可以用eax這個名字來表示一個32位的寄存器,它使用的是rax寄存器的低32位。

為了便於查閱,下表列出這些64通用寄存器對應的32/16/8位寄存器的名字:

通用寄存器的用法如前面我們所見,主要用於臨時存放數據,后面的章節我們還會見到大量使用通用寄存器的例子,所以這里就不對其進行詳細介紹了,但有三個比較特殊的寄存器值得在這里單獨提出來做一下說明:

  • rip寄存器

rip寄存器里面存放的是CPU即將執行的下一條指令在內存中的地址。看如下匯編語言代碼片段:

0x0000000000400770:    add %rdx,%rax 0x0000000000400773:    mov $0x0,%ecx

假設當前CPU正在執行第一條指令,這條指令在內存中的地址是0x0000000000400770,緊接它后面的下一條指令的地址是0x0000000000400773,所以此時rip寄存器里面存放的值是0x0000000000400773。

這里需要牢記的就是rip寄存器的值不是正在被CPU執行的指令在內存中的地址,而是緊挨這條正在被執行的指令后面那一條指令的地址。

讀者可能會有疑問,在前面的兩個匯編指令片段中並沒有指令修改rip寄存器的值,是怎么做到讓它一直指向下一條即將執行的指令的呢?其實修改rip寄存器的值是CPU自動控制的,不需要我們用指令去修改,當然CPU也提供了幾條可以間接修改rip寄存器的指令,在匯編語言一節中我們會詳細介紹CPU自動修改以及用指令修改rip寄存器值的兩種方式。

  • rsp 棧頂寄存器和rbp棧基址寄存器

這兩個寄存器都跟函數調用棧有關,其中rsp寄存器一般用來存放函數調用棧的棧頂地址,而rbp寄存器通常用來存放函數的棧幀起始地址,編譯器一般使用這兩個寄存器加一定偏移的方式來訪問函數局部變量或函數參數,比如:

mov 0x8(%rsp),%rdx

這條指令把地址為 0x8(%rsp) 的內存中的值拷貝到rdx寄存器,這里的0x8(%rsp) 就利用了 rsp 寄存器加偏移 8 的方式來讀取內存中的值。

寄存器的內容我們就先簡單的介紹到這里,但這些並不是我們需要了解的有關寄存器的全部內容,有些內容需要等我們學習了匯編指令和函數調用棧之后才能更加深刻的理解,到時候我們再回頭來繼續介紹相關的知識。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM