• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • Linux對I/O端口資源的管理 (1)

    發表于:2007-05-26來源:作者:點擊數: 標簽:
    摘要 本文主要從內核實現的角度分析 Linux 2.4.0內核IO子系統中對IO端口資源的管理的實現原理。本文是為那些想要深入分析Linux的IO子系統的讀者和設備驅動程序 開發 人員而寫的。 申明:這份文檔是按照自由軟件開放源代碼的精神發布的,任何人可以免費獲得、
    摘要
        本文主要從內核實現的角度分析Linux 2.4.0內核IO子系統中對IO端口資源的管理的實現原理。本文是為那些想要深入分析Linux的IO子系統的讀者和設備驅動程序開發人員而寫的。
      
      申明:這份文檔是按照自由軟件開放源代碼的精神發布的,任何人可以免費獲得、使用和重新發布,但是你沒有限制別人重新發布你發布內容的權利。發布本文的目的是希望它能對讀者有用,但沒有任何擔保,甚至沒有適合特定目的的隱含的擔保。更詳細的情況請參閱GNU通用公共許可證(GPL),以及GNU自由文檔協議(GFDL)。
      
        
       幾乎每一種外設都是通過讀寫設備上的寄存器來進行的。外設寄存器也稱為 “I/O端口”,通常包括:控制寄存器、狀態寄存器和數據寄存器三大類,而且一個外設的寄存器通常被連續地編址。CPU對外設IO端口物理地址的編址方式有兩種:一種是I/O映射方式(I/O-mapped),另一種是內存映射方式(Memory-mapped)。而具體采用哪一種則取決于CPU的體系結構。
      
        有些體系結構的CPU(如,PowerPC、m68k等)通常只實現一個物理地址空間(RAM)。在這種情況下,外設 I/O端口的物理地址就被映射到CPU的單一物理地址空間中,而成為內存的一部分。此時,CPU可以象訪問一個內存單元那樣訪問外設I/O端口,而不需要設立專門的外設I/O指令。這就是所謂的“內存映射方式”(Memory-mapped)。
      
        而另外一些體系結構的CPU(典型地如X86)則為外設專門實現了一個單獨地地址空間,稱為“I/O地址空間”或者“I/O端口空間”。這是一個與CPU地RAM物理地址空間不同的地址空間,所有外設的I/O端口均在這一空間中進行編址。CPU通過設立專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元(也即 I/O端口)。這就是所謂的“I/O映射方式”(I/O-mapped)。與RAM物理地址空間相比,I/O地址空間通常都比較小,如x86 CPU的I/O空間就只有64KB(0-0xffff)。這是“I/O映射方式”的一個主要缺點。
      
        Linux將基于I/O映射方式的或內存映射方式的I/O端口通稱為“I/O區域”(I/O region)。在討論對I/O區域的管理之前,我們首先來分析一下Linux是如何實現“I/O資源”這一抽象概念的。
      
      3.1 Linux對I/O資源的描述
      
        Linux設計了一個通用的數據結構resource來描述各種I/O資源(如:I/O端口、外設內存、DMA和IRQ等)。該結構定義在include/linux/ioport.h頭文件中:
      
      
        struct resource {
      const char *name;
      unsigned long start, end;
      unsigned long flags;
      struct resource *parent, *sibling, *child;
        };
      
      
      
        各成員的含義如下:
      
        1. name指針:指向此資源的名稱。
        2. start和end:表示資源的起始物理地址和終止物理地址。它們確定了資源的范圍,也即是一個閉區間[start,end]。
        3. flags:描述此資源屬性的標志(見下面)。
        4. 指針parent、sibling和child:分別為指向父親、兄弟和子資源的指針。
      
        屬性flags是一個unsigned long類型的32位標志值,用以描述資源的屬性。比如:資源的類型、是否只讀、是否可緩存,以及是否已被占用等。下面是一部分常用屬性標志位的定義(ioport.h):
      
      
      /*
      * IO resources have these defined flags.
      */
      #define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
      
      #define IORESOURCE_IO 0x00000100 /* Resource type */
      #define IORESOURCE_MEM 0x00000200
      #define IORESOURCE_IRQ 0x00000400
      #define IORESOURCE_DMA 0x00000800
      
      #define IORESOURCE_PREFETCH 0x00001000 /* No side effects */
      #define IORESOURCE_READONLY 0x00002000
      #define IORESOURCE_CACHEABLE 0x00004000
      #define IORESOURCE_RANGELENGTH 0x00008000
      #define IORESOURCE_SHADOWABLE 0x00010000
      #define IORESOURCE_BUS_HAS_VGA 0x00080000
      
      #define IORESOURCE_UNSET 0x20000000
      #define IORESOURCE_AUTO 0x40000000
      #define IORESOURCE_BUSY 0x80000000
      /* Driver has marked this resource busy */
        3.2 Linux對I/O資源的管理
      
        Linux是以一種倒置的樹形結構來管理每一類I/O資源(如:I/O端口、外設內存、DMA和IRQ)的。每一類I/O資源都對應有一顆倒置的資源樹,樹中的每一個節點都是一個resource結構,而樹的根結點root則描述了該類資源的整個資源空間。
      
        基于上述這個思想,Linux在kernel/Resource.c文件中實現了對資源的申請、釋放及查找等操作。
      
        3.2.1 I/O資源的申請
      
        假設某類資源有如下這樣一顆資源樹:
      
        節點root、r1、r2和r3實際上都是一個resource結構類型。子資源r1、r2和r3通過sibling指針鏈接成一條單向非循環鏈表,其表頭由root節點中的child指針定義,因此也稱為父資源的子資源鏈表。r1、r2和r3的parent指針均指向他們的父資源節點,在這里也就是圖中的root節點。
      
        假設想在root節點中分配一段I/O資源(由圖中的陰影區域表示)。函數 request_resource()實現這一功能。它有兩個參數:①root指針,表示要在哪個資源根節點中進行分配;②new指針,指向描述所要分配的資源(即圖中的陰影區域)的resource結構。該函數的源代碼如下(kernel/resource.c):
      
      
        int request_resource(struct resource *root, struct resource *new)
        {
      struct resource *conflict;
      
      write_lock(&resource_lock);
      conflict = __request_resource(root, new);
      write_unlock(&resource_lock);
      return conflict ? -EBUSY : 0;
        }
      
      
      
        對上述函數的NOTE如下:
      
       ?、儋Y源鎖resource_lock對所有資源樹進行讀寫保護,任何代碼段在訪問某一顆資源樹之前都必須先持有該鎖。其定義如下(kernel/Resource.c):
      
      
        static rwlock_t resource_lock = RW_LOCK_UNLOCKED;
      
      
      
       ?、诳梢钥闯?,函數實際上是通過調用內部靜態函數__request_resource()來完成實際的資源分配工作。如果該函數返回非空指針,則表示有資源沖突;否則,返回NULL就表示分配成功。
      
       ?、圩詈?,如果conflict指針為NULL,則request_resource()函數返回返回值0,表示成功;否則返回-EBUSY表示想要分配的資源已被占用。
      
        函數__request_resource()完成實際的資源分配工作。如果參數new所描述的資源中的一部分或全部已經被其它節點所占用,則函數返回與new相沖突的resource結構的指針。否則就返回NULL。該函數的源代碼如下
      
      
     ?。╧ernel/Resource.c):
      /* Return the conflict entry if you can't request it */
      static struct resource * __request_resource
        (struct resource *root, struct resource *new)
      {
      unsigned long start = new->start;
      unsigned long end = new->end;
      struct resource *tmp, **p;
      
      if (end < start)
      return root;
      if (start < root->start)
      return root;
      if (end > root->end)
      return root;
      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;
      }
      }
      
      
      
        對函數的NOTE:
      
       ?、偾叭齻€if語句判斷new所描述的資源范圍是否被包含在root內,以及是否是一段有效的資源(因為end必須大于start)。否則就返回root指針,表示與根結點相沖突。
      
       ?、诮酉聛碛靡粋€for循環遍歷根節點root的child鏈表,以便檢查是否有資源沖突,并將new插入到child鏈表中的合適位置(child 鏈表是以I/O資源物理地址從低到高的順序排列的)。為此,它用tmp指針指向當前正被掃描的resource結構,用指針p指向前一個resource 結構的sibling指針成員變量,p的初始值為指向root->sibling。For循環體的執行步驟如下:
      
        l 讓tmp指向當前正被掃描的resource結構(tmp=*p)。
      
        l 判斷tmp指針是否為空(tmp指針為空說明已經遍歷完整個child鏈表),或者當前被掃描節點的起始位置start是否比new的結束位置end還要大。只要這兩個條件之一成立的話,就說明沒有資源沖突,于是就可以把new鏈入child鏈表中:①設置new的sibling指針指向當前正被掃描的節點tmp(new->sibling=tmp);②當前節點tmp的前一個兄弟節點的sibling指針被修改為指向new這個節點(*p= new);③將new的parent指針設置為指向root。然后函數就可以返回了(返回值NULL表示沒有資源沖突)。
      
        l 如果上述兩個條件都不成立,這說明當前被掃描節點的資源域有可能與new相沖突(實際上就是兩個閉區間有交集),因此需要進一步判斷。為此它首先修改指針 p,讓它指向tmp->sibling,以便于繼續掃描child鏈表。然后,判斷tmp->end是否小于new->start,如果小于,則說明當前節點tmp和new沒有資源沖突,因此執行continue語句,繼續向下掃描child鏈表。否則,如果tmp->end大于或等于new->start,則說明tmp->[start,end]和new->[start,end]之間有交集。所以返回當前節點的指針tmp,表示發生資源沖突。
      
        3.2.2 資源的釋放
      
        函數release_resource()用于實現I/O資源的釋放。該函數只有一個參數——即指針old,它指向所要釋放的資源。起源代碼如下:
      
      
      int release_resource(struct resource *old)
      {
      int retval;
      
      write_lock(&resource_lock);
      retval = __release_resource(old);
      write_unlock(&resource_lock);
      return retval;
      }
      
      
      
        可以看出,它實際上通過調用__release_resource()這個內部靜態函數來完成實際的資源釋放工作。函數 __release_resource()的主要任務就是將資源區域old(如果已經存在的話)從其父資源的child鏈表重摘除,它的源代碼如下:
      
      
      static int __release_resource(struct resource *old)
      {
      struct resource *tmp, **p;
      
      p = &old->parent->child;
      for (;;) {
      tmp = *p;
      if (!tmp)
      break;
      if (tmp == old) {
      *p = tmp->sibling;
      old->parent = NULL;
      return 0;
      }
      p = &tmp->sibling;
      }
      return -EINVAL;
      }
      
      
      
        對上述函數代碼的NOTE如下:
      
        同函數__request_resource()相類似,該函數也是通過一個for循環來遍歷父資源的child鏈表。為此,它讓tmp指針指向當前被掃描的資源,而指針p則指向當前節點的前一個節點的sibling成員(p的初始值為指向父資源的child指針)。循環體的步驟如下:
      
       ?、偈紫?,讓tmp指針指向當前被掃描的節點(tmp=*p)。
      
       ?、谌绻鹴mp指針為空,說明已經遍歷完整個child鏈表,因此執行break語句推出for循環。由于在遍歷過程中沒有在child鏈表中找到參數old所指定的資源節點,因此最后返回錯誤值-EINVAL,表示參數old是一個無效的值。
      
       ?、劢酉聛?,判斷當前被掃描節點是否就是參數old所指定的資源節點。如果是,那就將old從child鏈表中去除,也即讓當前結點tmp的前一個兄弟節點的sibling指針指向tmp的下一個節點,然后將old->parent指針設置為NULL。最后返回0值表示執行成功。
      
       ?、苋绻斍氨粧呙韫濣c不是資源old,那就繼續掃描child鏈表中的下一個元素。因此將指針p指向tmp->sibling成員。
      
        3.2.3 檢查資源是否已被占用,
      
        函數check_resource()用于實現檢查某一段I/O資源是否已被占用。其源代碼如下:
      
      
      int check_resource(struct resource *root, unsigned long start, unsigned long len)
      {
      struct resource *conflict, tmp;
      
      tmp.start = start;
      tmp.end = start + len - 1;
      write_lock(&resource_lock);
      conflict = __request_resource(root, &tmp);
      if (!conflict)
      __release_resource(&tmp);
      write_unlock(&resource_lock);
      return conflict ? -EBUSY : 0;
      }
      
      
      
        對該函數的NOTE如下:
      
       ?、贅嬙煲粋€臨時資源tmp,表示所要檢查的資源[start,start+end-1]。
      
       ?、谡{用__request_resource()函數在根節點root申請tmp所表示的資源。如果tmp所描述的資源還被人使用,則該函數返回 NULL,否則返回非空指針。因此接下來在conflict為NULL的情況下,調用__release_resource()將剛剛申請的資源釋放掉。
      
       ?、圩詈蟾鶕onflict是否為NULL,返回-EBUSY或0值。
      
        3.2.4 尋找可用資源
      
        函數find_resource()用于在一顆資源樹中尋找未被使用的、且滿足給定條件的(也即資源長度大小為size,且在[min,max]區間內)的資源。其函數源代碼如下:
      
      
      /*
      * Find empty slot in the resource tree given range and alignment.
      */
      static int find_resource(struct resource *root, struct resource *new,
      unsigned long size,
      unsigned long min, unsigned long max,
      unsigned long align,
      void (*alignf)(void *, struct resource *, unsigned long),
      void *alignf_data)
      {
      struct resource *this = root->child;
      
      new->start = root->start;
      for(;;) {
      if (this)
      new->end = this->start;
      else
      new->end = root->end;
      if (new->start < min)
      new->start = min;
      if (new->end > max)
      new->end = max;
      new->start = (new->start + align - 1) & ~(align - 1);
      if (alignf)
      alignf(alignf_data, new, size);
      if (new->start < new->end && new->end - new->start + 1 >= size)
      {
      new->end = new->start + size - 1;
      return 0;
      }
      if (!this)
      break;
      new->start = this->end + 1;
      this = this->sibling;
      }
      return -EBUSY;
      }
      
      
      
        對該函數的NOTE如下:
      
        同樣,該函數也要遍歷root的child鏈表,以尋找未被使用的資源空洞。為此,它讓this指針表示當前正被掃描的子資源節點,其初始值等于 root->child,即指向child鏈表中的第一個節點,并讓new->start的初始值等于root->start,然后用一個for循環開始掃描child鏈表,對于每一個被掃描的節點,循環體執行如下操作:
      
       ?、偈紫?,判斷this指針是否為NULL。如果不為空,就讓new->end等于this->start,也即讓資源new表示當前資源節點this前面那一段未使用的資源區間。
      
       ?、谌绻鹴his指針為空,那就讓new->end等于root->end。這有兩層意思:第一種情況就是根結點的child指針為 NULL(即根節點沒有任何子資源)。因此此時先暫時將new->end放到最大。第二種情況就是已經遍歷完整個child鏈表,所以此時就讓 new表示最后一個子資源后面那一段未使用的資源區間。
      
       ?、鄹鶕祄in和max修正new->[start,end]的值,以使資源new被包含在[min,max]區域內。
      
       ?、芙酉聛磉M行對齊操作。
      
       ?、萑缓?,判斷經過上述這些步驟所形成的資源區域new是否是一段有效的資源(end必須大于或等于start),而且資源區域的長度滿足size參數的要求(end-start+1>=size)。如果這兩個條件均滿足,則說明我們已經找到了一段滿足條件的資源空洞。因此在對new-> end的值進行修正后,然后就可以返回了(返回值0表示成功)。
      
       ?、奕绻鲜鰞蓷l件不能同時滿足,則說明還沒有找到,因此要繼續掃描鏈表。在繼續掃描之前,我們還是要判斷一下this指針是否為空。如果為空,說明已經掃描完整個child鏈表,因此就可以推出for循環了。否則就將new->start的值修改為this->end+1,并讓this指向下一個兄弟資源節點,從而繼續掃描鏈表中的下一個子資源節點。
      
        3.2.5 分配接口allocate_resource()
      
        在find_resource()函數的基礎上,函數allocate_resource()實現:在一顆資源樹中分配一條指定大小的、且包含在指定區域[min,max]中的、未使用資源區域。其源代碼如下:
      
      
      /*
      * Allocate empty slot in the resource tree given range and alignment.
      */
      int allocate_resource(struct resource *root, struct resource *new,
      unsigned long size,
      unsigned long min, unsigned long max,
      unsigned long align,
      void (*alignf)(void *, struct resource *, unsigned long),
      void *alignf_data)
      {
      int err;
      
      write_lock(&resource_lock);
      err = find_resource(root, new, size, min, max, align, alignf, alignf_data);
      if (err >= 0 && __request_resource(root, new))
      err = -EBUSY;
      write_unlock(&resource_lock);
      return err;
      }
      
      
      
        3.2.6 獲取資源的名稱列表
      
        函數get_resource_list()用于獲取根節點root的子資源名字列表。該函數主要用來支持/proc/文件系統(比如實現proc/ioports文件和/proc/iomem文件)。其源代碼如下:
      
      
      int get_resource_list(struct resource *root, char *buf, int size)
      {
      char *fmt;
      int retval;
      
      fmt = %08lx-%08lx : %s
      ;
      if (root->end < 0x10000)
      fmt = %04lx-%04lx : %s
      ;
      read_lock(&resource_lock);
      retval = do_resource_list(root->child, fmt, 8, buf, buf + size) - buf;
      read_unlock(&resource_lock);
      return retval;
      }
      
      
      
        可以看出,該函數主要通過調用內部靜態函數do_resource_list()來實現其功能,其源代碼如下:
      
      
      /*
      * This generates reports for /proc/ioports and /proc/iomem
      */
      static char * do_resource_list(struct resource *entry, const char *fmt,
        int offset, char *buf, char *end)
      {
      if (offset < 0)
      offset = 0;
      
      while (entry) {
      const char *name = entry->name;
      unsigned long from, to;
      
      if ((int) (end-buf) < 80)
      return buf;
      
      from = entry->start;
      to = entry->end;
      if (!name)
      name = ;
      
      buf += sprintf(buf, fmt + offset, from, to, name);
      if (entry->child)
      buf = do_resource_list(entry->child, fmt, offset-2, buf, end);
      entry = entry->sibling;
      }
      
      return buf;
      }
      
      
      
        函數do_resource_list()主要通過一個while{}循環以及遞歸嵌套調用來實現,較為簡單,這里就不在詳細解釋了。

    原文轉自:http://www.kjueaiud.com

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>