1. 程式人生 > >核心中與驅動相關的記憶體操作之三(記憶體模型)

核心中與驅動相關的記憶體操作之三(記憶體模型)

1.使用者記憶體空間和核心記憶體空間:

    32位的CPU可訪問的實體地址空間為4GB(2^32).LINUX核心中把這4G典型的劃分是:前3GB用於使用者空間,最後1GB用於核心空間.如下圖所示:

 其中,我們比較關注的核心空間部分又被劃分為三個區:

1).ZONE_DMA(0-16 MB):包含 ISA/PCI裝置需要的低端實體記憶體區域中的記憶體範圍; 

2).ZONE_NORMAL(16-896 MB)核心大部分關於記憶體的操作都位於此區域,它裡面記錄的是邏輯地址,其地址與實質的實體地址是線性關係.所謂的低端記憶體; 

3).ZONE_HIGHMEM(896 MB 以及更高的記憶體):是核心遍歷所有實體記憶體的一種"可能手段",它的存在是補償ZONE_NORMAL線性關係導致只能部分記憶體空間訪問的不足.因此,它是非線性的.見[附1:]

2. 地址:

    關於記憶體所涉及的"地址"主要有三種:實體地址、邏輯地址、虛擬地址(線性地址).

實體地址:

    實體記憶體塊(如RAM、DDR2)對應的起始位置及區域,以及CPU內部控制器暫存器的值.它是最終地址的結果.

         比如說,S3C2440外掛了一個64M的SDRAM,其起始地址是:0x3000 0000 ~ 0x34000000.這裡的0x3000 0000 和0x3400 0000及其間的區域的值就是實體地址.其對應的空間就是實體記憶體空間.

邏輯地址:

    是指由程式產生的地址數值.例如,在C語言指標程式設計中,可以讀取指標變數本身值(&操作),實際上這個值就是邏輯地址,它是相對於你當前程序資料段的地址,不和絕對實體地址相干.這也是我們程式中涉及最多的一種地址.它和實體地址只是相差了一個偏移量.見[附1:]低端記憶體部分內容.

    在反彙編中,我們最經常接觸到邏輯地址.比如說,下面的程式:

#include <stdlib.h>
#include <stdio.h>

void func(void)
{
        ;
}

int main(int argc,char **argv)
{
        func();

        printf("Hello World!\n");

        return 0;
}

    反彙編出來的部分程式碼:

    其中,第一列是程式碼的執行地址,也就是邏輯地址;第二列是機器碼.計算機本身也只關注這兩個東東.它們一個是決定了記憶體的位置,一個是決定此位置上記憶體的內容.也就是說,在記憶體位置00008384這個位置上存放的內容是e52db004.後面的彙編只是給人類看的,計算機不懂.

虛擬地址(線性地址):

    表徵一個CPU實際定址的能力範圍,比如32bit的CPU,它的虛擬地址空間為4G.實體地址空間只是虛擬地址空間的一個子集.就算我們在支援虛擬記憶體的系統裡面看到某個地址值落在實體地址空間範圍內,我們也是不能簡單地理解成是實體地址--你看到的,不一定是真的.

[附1:]

低端記憶體:

    在分配給核心的1G空間裡面,其低於896M以下的就是低端記憶體. 也就是我們實際編譯中接觸最多的一個記憶體區間.它包括前面三個ZONE(ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM)中的ZONE_DMA、ZONE_NORMAL.在啟用MMU的情況下,此區間裡面的地址就是邏輯地址--和實體地址保持著永久性的線性對映關係,即它和實體地址之間只相差了一個固定的偏移量--|PAGE_OFFSET - PHYS_OFFSET|.如果PHYS_OFFSET為0,即這個固定的偏移量為PAGE_OFFSET.

    比如說,S3C2440的SDRAM是接在BANK6上,其PHYS_OFFSET為0x3000 0000.如下:

/* arch/arm/mach-s3c2410/include/mach/memory.h
 *  from arch/arm/mach-rpc/include/mach/memory.h
 *
 *  Copyright (C) 1996,1997,1998 Russell King.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
*/

#ifndef __ASM_ARCH_MEMORY_H
#define __ASM_ARCH_MEMORY_H

#define PHYS_OFFSET	UL(0x30000000)

#endif

    ARM平臺的PAGE_OFFSET的定義則位於相應的平臺標頭檔案,如下:

/*
 *  arch/arm/include/asm/memory.h
 *
 *  Copyright (C) 2000-2002 Russell King
 *  modification for nommu, Hyok S. Choi, 2004
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 *  Note: this file should not be included by non-asm/.h files
 */
#ifndef __ASM_ARM_MEMORY_H
#define __ASM_ARM_MEMORY_H

#include <linux/compiler.h>
#include <linux/const.h>
#include <mach/memory.h>
#include <asm/sizes.h>

/*
 * Allow for constants defined here to be used from assembly code
 * by prepending the UL suffix only with actual C code compilation.
 */
#define UL(x) _AC(x, UL)

#ifdef CONFIG_MMU

/*
 * PAGE_OFFSET - the virtual address of the start of the kernel image
 * TASK_SIZE - the maximum size of a user space task.
 * TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area
 */
#define PAGE_OFFSET		UL(CONFIG_PAGE_OFFSET)

    CONFIG_PAGE_OFFSET即是在配置檔案裡面指定的.如mini2440平臺的配置檔案arch/arm/configs/mini2440_defconfig見到下面字碼:

#
# Kernel Features
#
CONFIG_VMSPLIT_3G=y
# CONFIG_VMSPLIT_2G is not set
# CONFIG_VMSPLIT_1G is not set
CONFIG_PAGE_OFFSET=0xC0000000
# CONFIG_PREEMPT is not set
CONFIG_HZ=200
CONFIG_AEABI=y

    因此,對於S3C2440平臺,其偏移量為|PAGE_OFFSET - PHYS_OFFSET| = 0x9000 0000.

    下面用一個簡單的示例來演示一個低端記憶體區域的邏輯地址值對應的實體地址值是多少?

    首先,我們要知道核心映象是如何被排布建立起來的?以ARM平臺為例,見arch/arm/kernel/vmlinux.lds下面字樣:

OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
 . = 0xC0000000 + 0x00008000;
 .init : { /* Init code and data      

    因此,我們便確定vmlinux便是以地址0xC0008000為基地址往上佈局的.反彙編vmlinux可以看到:

vmlinux:     file format elf32-littlearm

Disassembly of section .init:

c0008000 <stext>:
c0008000:       e321f0d3        msr     CPSR_c, #211    ; 0xd3
c0008004:       ee109f10        mrc     15, 0, r9, cr0, cr0, {0}
c0008008:       eb0000ba        bl      c00082f8 <__lookup_processor_type>

    也就是說,我們在核心使用的所有的地址,必須是大於等於0xc0008000.比如我們要操作低端記憶體區域的某地址0xc0008004,這個地址值對應的實體地址值為:

0xc000 8004 - PAGE_OFFSET + PHYS_OFFSET = 0xc000 8004 - 0xc000 0000 + 0x3000 0000 = 0x3000 8004

    也就是說,實體記憶體[0x3000 0000,0x3000,8000]這段區間是預留出來的,核心執行後是沒辦法再去訪問的.

    核心為我們實現虛擬地址和實體地址的相互轉換也提供了相應的API:

#define __virt_to_phys(x)	((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x)	((x) - PHYS_OFFSET + PAGE_OFFSET)

高階記憶體:

    在分配給核心的1G空間裡面,其高於896M以上的記憶體就是高階記憶體.使CPU能訪問所有記憶體空間提供一種可能的手段,它相當於一個中介”,屬於二級運算”.比如說核心試圖訪問高於896MB的記憶體空間時,會在高階地址空間範圍內找一段相應大小空閒的邏輯地址空間,作為橋樑借用.借用這段邏輯地址空間,建立對映到想訪問的那段實體記憶體,用完後歸還.這樣別人也可以再次借用這段高階地址空間訪問其他實體記憶體,因此,很明顯,這是非線性對映的.這樣一來,雖然空間上得到了擴充,但是時間效率上就受到了一定的制約.因為它的內部工作比較煩瑣,至少多了下面的步驟:

申請高階記憶體的地址空間-->建立申請到的高階記憶體地址空間到實體地址的對映-->釋放申請的高階記憶體地址空間. 

    示意圖如下:

    紅色部分位於高階記憶體空間,它的可以指向物理空間的橙黃部分,也可以指向物理空間紫色部分.應該指向物理空間哪一部分呢?這就是通過記憶體管理演算法建立頁表的過程.