跳到主要內容

中文試譯:Motherboard Chipsets and the Memory Map

在網路上看到了有人寫了一系列講解電腦內部運作的好文章,作者為 Gustavo Duarte ,作為一個軟體學徒,看到這麼牛的前輩花了如此多的精神整理出來的心得,實在很想推荐給大家,所以斗膽試譯一下,順便督促自己仔細地閱讀過一遍。這將會是一系列的譯文。從主機板常識、CPU cache,x86的開機過程、Linux開機過程、kernel記憶體管理、行程的address space、虛擬記憶體的管理等等,個人覺得真是一氣呵成,很是精彩啊~ 

OK,第1篇,講的是主機板晶片組與其memory map。

作者: Gustavo Duarte

主機板晶片組與其memory map

Motherboard Chipsets and the Memory Map


為了解釋現代的核心是如何工作的,我將會陸續寫幾篇文章來解釋電腦的內部運作。我希望這些文章可以讓一些沒有太多經驗的愛好者以及程式設計師覺得有所幫助。我們會聚焦在Linux, Windows以及Intel處理器。內部機制是我的興趣,我已經寫過一些kernel-mode的程式碼但沒有持續一直玩它。第1篇文章描述現代以Intel為基礎的主機板是如何存取記憶體以及系統記憶體的對應。

我們先來看看一個Intel電腦是怎麼接在一起的。下圖表示了主機板上的主要的元件:


現代主機板方塊圖。southbridge與northbridge構成了chipset。

如同你看到的,最要緊的是要記住CPU並不會真的知道什麼東西跟它接在一起。它透過pin腳跟外界相連,但它不在乎外在世界為何。外在世界可能是電腦裡的一塊主機板,但也可能是個烤麵包機、路由器、大腦植入器或者是CPU測試機台。外界與CPU溝通主要有三種方式:memory address space, I/O address space以及interrupt。我們目前只先關心主機板與memory。[譯註:環境(主機板)與溝通方式(memory)]

在主機板上,CPU的對外通道就是替它連接到northbridge的front-side bus。每當CPU要讀寫記憶體時,是這麼透過bus去進行的:它透過一些pin傳出想讀寫的physical address,然後另一些pin送出要寫的值或是收要讀的值。一個Intel Core 2 QX6600有33根pin來傳輸physical address(所以有233個記憶體位址),以及64根pin拿來收送資料(所以資料是以64位元在傳送,也就是一個8-byte)。這允許CPU實際上可定址64GB的記憶體(233 個位址 * 8 bytes),即使一般的chipset最多只能處理到8GB的記憶體。[譯註:當然2011年已經可以到64GB嘍!]

現在困難來了。我們習慣以RAM來思考記憶體,這個東西就是程式一直在讀寫的東東。而大部份的時候的確如此,CPU對記憶體的需求都會透過northbridge到RAM模組。但並不總是這樣。Physical memory addresses也被用來和主機板上的各種裝置溝通(這種溝通方式稱為memory-mapped I/O)。這類裝置包括視訊卡、大多數的PCI卡(掃描器或SCSI卡)以及在BIOS中的flash memory

當northbridge收到一個physical memory的需求時,它會決定如何如何進行:它應該去RAM存取嗎?或是視訊卡呢?這種選擇會依據memory address map來決定。對每一段physical memory address的區域來說,memory map知道哪個裝置擁有該區域。 大部分的addresses會對應到RAM,但是chipset會知道當無法對應到RAM時,要由哪個裝置來處理。這些不屬於RAM的addresses在PC memory中形成了著名的"洞",範圍從640KB到1MB。當一些memory addresses被視訊卡與PCI裝置保留時,還會有更大的洞產生。這也是為何32位元的作業系統一般在使用4GB RAM時會有些問題。在Linux中,/prco/iomem整齊地列出這些位址對應。下圖呈現出Intel PC中前4GB的physical memory addresses典型的memory map:
Intel電腦的physical memory address前4GB的分布 

實際的addresses與範圍會根據主機板與在其上的裝置有所不同,但大部分的Core 2系統跟上圖非常接近。所有棕色的區域都不是對應到RAM。記住,這是在主機板的bus上使用的physical addresses。在CPU內部(舉例來說,我們跑的那些程式),記憶體位址是 logical 的,並且在送到bus上存取memory前,必須被CPU轉換成physical addresses。

logical addresses轉換成pyhsical addresses的規則非常複雜,並且跟CPU正在執行的mode有關(real mode, 32-bit protected mode以及64-bit protected mode)。無論何種轉換機制,CPU mode都會決定有多少的physical addresses可以被存取。舉例來說,如果CPU跑在32-bit mode,那麼他就只能夠定址4GB(嗯,其實有一個例外,叫作physical address extension,但我們先忽略它)。既然大約有1GB的physical addresses被對應到主機板上的裝置,所以CPU僅能用到大約3GB的RAM(有時候會更少,我有一台Vista,只有2.4GB被使用)。如果CPU在real mode中,那它僅能夠定址到1MB的physical RAM(這是早期的Intel處理器的唯一一種mode)。另一方面,當CPU跑在64-bit mode時,可以定址到64GB(僅少數chipset支援這麼多RAM)。在64-bit mode中,有可能用比全部physical RAM的數量還多的pyhsical addresses去存取主機板的裝置。這叫作reclaiming memory,需由chipset的協助來完成。[譯註:這句沒把握...-_-]


這是我們在下一篇文章中會用到的記憶體,會描述從上電到boot loader,然後進入kernel的開機過程。如果你對這些東西有興趣,我高度推荐Intel手冊。I'm big into primary sources overall,但Intel手冊寫的特別好並且精確。這邊是其中一些:[譯註:我不知道I'm big into...是啥意思...-_-]


  • Datasheet for Intel G35 Chipset 很好的描述了Core 2 的chipset。是這篇文章的主要參考。
  • Datasheet for Intel Core 2 Quad-Core Q6000 Sequence 處理器的datasheet。描述了處理器的pin腳(並沒有那麼多,尤其當你把它們分類後)。有趣的資料,雖然有些部份有些艱澀。
  • Intel Software Developer’s Manuals 非常的出色。一點也不硬,描述架構的各部份都非常傑出。Volume 1及3A有很好的資料(不要因為名子不看它,一個"volume"很小,而且你可以選擇性地讀)
  • Pádraig Brady 建議了Ulrich Drepper的極佳論文。很好的資料,我本來打算在另一篇有關記憶體的文章給出這個連結,不過嘛...,愈多愈好嘍~


  • 留言

    這個網誌中的熱門文章

    誰在呼叫我?不同的backtrace實作說明好文章

    今天下班前一個同事問到:如何在Linux kernel的function中主動印出backtrace以方便除錯? 寫過kernel module的人都知道,基本上就是用dump_stack()之類的function就可以作到了。但是dump_stack()的功能是如何作到的呢?概念上其實並不難,慣用手法就是先觀察stack在function call時的變化(一般OS或計組教科書都有很好的說明,如果不想翻書,可以參考 這篇 ),然後將對應的return address一層一層找出來後,再將對應的function名稱印出即可(透過執行檔中的section去讀取函式名稱即可,所以要將KALLSYM選項打開)。在userspace的實作可參考Jserv介紹過的 whocallme 或對岸好手實作過的 backtrace() ,都是針對x86架構的很好說明文章。 不過從前面兩篇文章可以知道,只要知道編譯器的calling convention,就可以實作出backtrace,所以是否GCC有提供現成的機制呢?Yes, that is what __builtin_return_address() for!! 可以參考這篇 文章 。該篇文章還提到了其他可以拿來實作功能更齊全的backtrace的 程式庫 ,在了解了運作原理後,用那些東西還蠻方便的。 OK,那Linux kernel是怎麼做的呢?就是用頭兩篇文章的方式啦~ 每個不同的CPU架構各自手工實作一份dump_stack()。 為啥不用GCC的機制?畢竟...嗯,我猜想,除了backtrace以外,開發者還會想看其他register的值,還有一些有的沒的,所以光是GCC提供的介面是很難印出全部所要的資訊,與其用半套GCC的機制,不如全都自己來~ arm的實作 大致上長這樣,可以看到基本上就只是透過迭代fp, lr, pc來完成: 352 void unwind_backtrace (struct pt_regs * regs , struct task_struct *tsk) 353 { 354 struct stackframe frame ; 355 register unsigned long current_sp asm ( "

    淺讀Linux root file system初始化流程

    在Unix的世界中,file system佔據一個極重要的抽象化地位。其中,/ 所代表的rootfs更是所有後續新增file system所必須依賴前提條件。以Linux為例,黑客 Jserv 就曾經詳細說明過 initramfs的背後設計考量 。本篇文章不再重複背景知識,主要將追蹤rootfs初始化的流程作點整理,免得自己日後忘記。 :-) file system與特定CPU架構無關,所以我觀察的起點從init/main.c的start_kernel()開始,這是Linux作完基本CPU初始化後首先跳進的C function(我閱讀的版本為 3.12 )。跟root file system有關的流程羅列如下: start_kernel()         -> vfs_caches_init_early()         -> vfs_caches_init()                 -> mnt_init()                         -> init_rootfs()                         -> init_mount_tree()         -> rest_init()                 -> kernel_thread(kernel_init,...) 其中比較重要的是mnt_int()中的init_rootfs()與init_mout_tree()。init_rootfs()實作如下: int __init init_rootfs(void) {         int err = register_filesystem(&rootfs_fs_type);         if (err)                 return err;         if (IS_ENABLED(CONFIG_TMPFS) && !saved_root_name[0] &&                 (!root_fs_names || strstr(root_fs_names, "tmpfs"))) {          

    kernel panic之後怎麼辦?

    今天同事在處理一個陌生的模組時遇到kernel panic,Linux印出了backtrace,同事大致上可以知道是在哪個function中,但該function的長度頗長,短時間無法定位在哪個位置,在這種情況下,要如何收斂除錯範圍呢?更糟的是,由於加入printk會改變模組行為,所以printk基本上無法拿來檢查參數的值是否正常。 一般這樣的問題會backtrace的資訊來著手。從這個資訊我們可以知道在function中的多少offset發生錯誤,以x86為例(從 LDD3 借來的例子): Unable to handle kernel NULL pointer dereference at virtual address 00000000 printing eip: d083a064 Oops: 0002 [#1] SMP CPU:    0 EIP:    0060:[<d083a064>]    Not tainted EFLAGS: 00010246   (2.6.6) EIP is at faulty_write+0x4/0x10 [faulty] eax: 00000000   ebx: 00000000   ecx: 00000000   edx: 00000000 esi: cf8b2460   edi: cf8b2480   ebp: 00000005   esp: c31c5f74 ds: 007b   es: 007b   ss: 0068 Process bash (pid: 2086, threadinfo=c31c4000 task=cfa0a6c0) Stack: c0150558 cf8b2460 080e9408 00000005 cf8b2480 00000000 cf8b2460 cf8b2460        fffffff7 080e9408 c31c4000 c0150682 cf8b2460 080e9408 00000005 cf8b2480        00000000 00000001 00000005 c0103f8f 00000001 080e9408 00000005 00000005 Call Trace:  [<c0150558>] vfs