【深入理解Linux內核架構】6.6 資源分配


  一段摘自《Linux設備驅動程序》的話:

    每種外設都通過讀寫寄存器進行控制。大部分外設都有多個寄存器,不管是內存地址空間還是I/O地址空間,這些寄存器的訪問地址都是連續的。

    在硬件層,內存區域和I/O區域沒有概念上的區別:它們都通過向地址總線和控制總線發送電平信號進行訪問,在通過數據總線讀寫數據。一些CPU制造廠商在它們的芯片中使用單一的地址空間,而另一些則為外設保留了獨立的地址空間,以便和內存區分開來。一些處理器(主要是X86家族的)還為I/O端口的讀寫提供了單獨的線路,並且使用特殊的CPU指令訪問端口。

 

  I/O端口和I/O內存是兩種概念上的方法,用以支持設備驅動程序和設備之間的通信。為使得各種不同的驅動程序彼此互不干擾,有必要事先為驅動程序分配端口和I/O內存范圍。這確保幾種設備驅動程序不會試圖訪問同樣的資源。

6.6.1 資源管理

  我們首先看看管理資源的數據結構和函數。

  1. 樹數據結構

  Linux提供了一個通用框架,用於在內存中構建數據結構。這些結構描述了系統可用的資源,使得內核代碼能夠管理和分配資源。注意,其中關鍵的數據結構式resource,定義如下:

/* ./include/linux/ioport.h */
struct resource {
    resource_size_t start;
    resource_size_t end;      // start 和 end 通常表示某個地址空間中的一個區域 const char *name;         // 資源名稱,實際與內核無關,只是在以可讀形式輸出資源列表(在proc文件系統中)時比較有用
    unsigned long flags;      // 更准確的描述資源及其當前狀態 struct resource *parent, *sibling, *child; // 這3個指向resource的指針建立了一個樹形層次的結構
};

  

        樹形結構中的資源管理

從上圖可以發現,resource的parent、child、sibling成員的規則如下:

  • 每個子結點只有一個父結點
  • 一個父結點可以有人以數目的子結點
  • 同一個父結點的所有子結點,會連接到兄弟結點連表上

在內存中,表示數據結構時,必須注意以下問題:

  • 盡管每個子結點都有一個指針指向父結點,但父結點只有一個指針指向第一個子結點。所有其他子結點都通過兄弟結點訪問鏈表。
  • 指向父結點的指針同樣可以為NULL,在這種情況下,說明已經沒有更高層次的結點了。

  如何將層次結構一年關於設備驅動程序呢?我們來考察一個系統總線的例子,其附接了一塊網卡。網卡支持兩個輸出,每個都分配一塊特定的內存區域,用於數據的輸入和輸出。總線自身也有一個I/O內存區域,其中一些部分由網卡使用。

  該方案可以完美地融入到樹形層次結構中。總線的內存區域理論上占用了0和1000之間的內存范圍,充當根結點(最高的父結點)。網卡要求使用100和199之間的內存區域,這是根結點的一個子結點。網卡的子結點表示各個網絡輸出,分配I/O內存區分別為100到149和150到199.原來較大的資源區域被划分為了較小的部分,每次細分都表示了抽象模型中的一個層次。因此,子結點可用於將內存區划分為越來越小、功能越來越具體的部分。

 

  2. 請求和釋放資源

  為確保可靠地配置資源,內核必須提供一種機制來分配和釋放資源。一旦資源已經被分配,則不能由任何其他驅動程序使用。

  請求和釋放資源,無非是從資源樹中添加和刪除而已。

  • 請求資源

  內核提供了一個__request_resource函數,用於請求一個資源區域。這函數需要一系列參數,包括一個指向父結點的指針,資源區域的起始地址和結束地址,表示該區域名稱的字符串。

/* ./kernel/resource.c */

/* Return the conflict entry if you can't request it */
static struct resource * __request_resource(struct resource *root, struct resource *new)
{
    resource_size_t start = new->start;
    resource_size_t end = new->end;
    struct resource *tmp, **p;

    if (end < start)
        return root;
    if (start < root->start)
        return root;
    if (end > root->end)
        return root;          // 判斷要request source是否在root source的范圍內
    p = &root->child;
    for (;;) {                // 連續地掃描現存資源,將新資源添加到正確的位置,或者發現與已分配區域的沖突
        tmp = *p;
        if (!tmp || tmp->start > end) {  // 在合適的位置插入 new->sibling = tmp;
            *p = new;
            new->parent = root;
            return NULL;
        }
        p = &tmp->sibling;  // 僅僅遍歷同一層次的兄弟結點,不會掃描更底層子結點的鏈表 if (tmp->end < start)
            continue;
        return tmp;
    }
}


/**
 * request_resource_conflict - request and reserve an I/O or memory resource
 * @root: root resource descriptor
 * @new: resource descriptor desired by caller
 *
 * Returns 0 for success, conflict resource on error.
 */
struct resource *request_resource_conflict(struct resource *root, struct resource *new)
{
    struct resource *conflict;

    write_lock(&resource_lock);
    conflict = __request_resource(root, new);
    write_unlock(&resource_lock);
    return conflict;
}

/**
 * request_resource - request and reserve an I/O or memory resource
 * @root: root resource descriptor
 * @new: resource descriptor desired by caller
 *
 * Returns 0 for success, negative error code on error.
 */
int request_resource(struct resource *root, struct resource *new)
{
    struct resource *conflict;

    conflict = request_resource_conflict(root, new);
    return conflict ? -EBUSY : 0;
}

EXPORT_SYMBOL(request_resource);
  • 釋放資源
static int __release_resource(struct resource *old)
{
    struct resource *tmp, **p;

    p = &old->parent->child;
    for (;;) {  // 遍歷與old同一層的所有結點,發現與old相同的結點即old結點,刪除該結點並鏈接鏈表
        tmp = *p;
        if (!tmp)
            break;
        if (tmp == old) {
            *p = tmp->sibling;
            old->parent = NULL;
            return 0;
        }
        p = &tmp->sibling;
    }
    return -EINVAL;
}


/**
 * release_resource - release a previously reserved resource
 * @old: resource pointer
 */
int release_resource(struct resource *old)
{
    int retval;

    write_lock(&resource_lock);
    retval = __release_resource(old);
    write_unlock(&resource_lock);
    return retval;
}

 

6.6.2 I/O內存

  資源管理還有一個很重要的方面是I/O內存的分配方式,因為在所有平台上這都是與外設通信的主要方法(IA-32除外,其中I/O端口更為主要)。

  I/O內存不僅包括與擴展設備通信直接使用的內存區域,還包括系統中可用的物理內存和ROM存儲器,以及包含在資源列表中的內存。

  看下kernel 3.08的iomem信息:

root@paramount:/ # cat /proc/iomem                                             

00000000-0fefffff : System RAM

00010000-005c7e73 : Kernel code

005c7e74-00770d87 : Kernel data

10003000-100030ff : jz-rtc.0

10003000-100030ff : jz-rtc

10020000-1002006f : dsp.0

10020000-1002006f : dsp

10030000-10030fff : jz-uart.0

10033000-10033fff : jz-uart.3

10050000-10050fff : jz-i2c.0

10051000-10051fff : jz-i2c.1

10052000-10052fff : jz-i2c.2

10071000-10071000 : dsp.1

10071000-10071000 : dsp

13040000-130407ff : galcore register region

13050000-130517ff : jz-fb.0

13050000-130517ff : jz-fb

13080000-13087fff : jz-ipu.0

13080000-13087fff : jz-ipu

13200000-132effff : jz-vpu.0

13300000-13350000 : ovisp-camera

13300000-13350000 : ovisp-camera

13420000-1342ffff : jz-dma

13420000-1342ffff : jz-dma

13450000-13450fff : jzmmc_v1.2.0

13460000-13460fff : jzmmc_v1.2.1

13500000-1353ffff : jz-dwc2

13500000-1353ffff : jz-dwc2

13500000-1353ffff : dwc2

  arm或mips架構,對kernel而言,其實所有的系統資源就是memory,或者所有的系統資源就是iomem。

  比如,以我們使用的mips32架構為例:

/* kernel/arch/mips/xburst/soc-m200/common/setup.c */

void __init plat_mem_setup(void)
{
    /* jz mips cpu special */
    __asm__ (
        "li    $2, 0xa9000000 \n\t"
        "mtc0  $2, $5, 4      \n\t"
        "nop                  \n\t"
        ::"r"(2));

    /* use IO_BASE, so that we can use phy addr on hard manual
     * directly with in(bwlq)/out(bwlq) in io.h.
     */
    set_io_port_base(IO_BASE);
    ioport_resource.start    = 0x00000000;
    ioport_resource.end    = 0xffffffff;
    iomem_resource.start    = 0x00000000;
    iomem_resource.end    = 0xffffffff;       // iomem_resource是表示整個系統的memory空間,所有其他資源都是從屬於該子資源的child或Grandson
    setup_init();
    init_all_clk();

#ifdef CONFIG_ANDROID_PMEM
    /* reserve memory for pmem. */
    board_pmem_setup();
#endif
    return;
}

  在使用I/O內存時,分配內存區域並不是所需的唯一操作。取決於總線系統和處理器類型,可能必須將擴展設備的地址空間映射到內核地址空間中,才能訪問該設備(陳志偉軟件I/O映射)。這是通過使用ioremap內核函數適當的設置系統頁表而實現的,內核源代碼中有若干不同地方使用了該函數,其定義與體系結構相關。同樣地,提供了特定於體系結構的iounmap函數來解除映射。

  在某種程度上,實現對進城頁表的操作冗長而復雜。特別地,不同系統的實現有很大的差別,而且它對理解設備驅動程序並不重要,所以此處不再討論其實現。一般地說,更重要的是:將一個物理地址映射到處理器的虛擬地址空間中,使得內核可以使用該地址。就設備驅動而言,這意味着擴展總線的地址空間映射到處理器的虛擬地址空間中,使得能夠用普通內存訪問函數操作總線/設備。

 

  1. request_source分配內存區域 2. ioremap建立擴展總線地址空間到內核虛擬地址空間的映射。

  通過上述兩步,是否意味着在驅動程序中需要訪問io時,可以直接對ioremap的指針進行反引用的?答案是否定的。

6.3.3 I/O端口

  I/O端口是一種與設備和總線通信的流行方法,特別是在IA-32平台上。

  可以使用 cat /proc/ioports 查看I/O端口。

 


免責聲明!

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



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