博主按:下文原作者在linux2.4.0基礎上分析的,我現在的核心是2.6.32。在有區別的地方我會用紅色文字標出,作為對原文的一些補充吧。
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), /*2.6.32中alignf函數多了一個參數void (*alignf)(void *, struct resource *,
resource_size_t size, resource_size_t align)*/
void *alignf_data)
{
struct resource *this = root->child;
new->start = root->start;
/*
* Skip past an allocated resource that starts at 0, since the assignment
* of this->start - 1 to new->end below would cause an underflow.
*/
if (this && this->start == 0) {
new->start = this->end + 1;
this = this->sibling;
}
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前面那一段未使用的資源區間。
②如果this指標為空白,那就讓new->end等於root->end。這有兩層意思:第一種情況就是根結點的child指標為
NULL(即根節點沒有任何子資源)。因此此時先暫時將new->end放到最大。第二種情況就是已經遍曆完整個child鏈表,所以此時就讓
new表示最後一個子資源後面那一段未使用的資源區間。
③根據參數min和max修正new->[start,end]的值,以使資源new被包含在[min,max]地區內。
④接下來進行對齊操作。
⑤然後,判斷經過上述這些步驟所形成的資來源區域new是否是一段有效資源(end必須大於或等於start),而且資來源區域的長度滿足size參數
的要求(end-start+1>=size)。如果這兩個條件均滿足,則說明我們已經找到了一段滿足條件的資源空洞。因此在對new->
end的值進行修正後,然後就可以返回了(傳回值0表示成功)。
⑥如果上述兩條件不能同時滿足,則說明還沒有找到,因此要繼續掃描鏈表。在繼續掃描之前,我們還是要判斷一下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), /*2.6.32中alignf函數多了一個參數void (*alignf)(void *, struct resource *,
resource_size_t size, resource_size_t align)*/
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檔案)。其原始碼如下:
2.6.32中已經刪除了這個函數
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{}迴圈以及遞迴嵌套調用來實現,較為簡單,這裡就不在詳細解釋了。
3.3 管理I/O Region資源
linux將基於I/O映射方式的I/O連接埠和基於記憶體映射方式的I/O連接埠資源統稱為“I/O地區”(I/O
Region)。I/O
Region仍然是一種I/O資源,因此它仍然可以用resource結構類型來描述。下面我們就來看看Linux是如何管理I/O
Region的。
3.3.1 I/O Region的分配
在函數__request_resource()的基礎上,linux實現了用於分配I/O地區的函數__request_region(),如下:
struct resource * __request_region(struct resource *parent,
unsigned long start, unsigned long n, const char *name, int flags
) //新核心增加了一個flags
{
struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);
struct resource *res = kzalloc(sizeof(*res), GFP_KERNEL);
if (!res)
return NULL;
if (res) {
memset(res, 0, sizeof(*res));
res->name = name;
res->start = start;
res->end = start + n - 1;
res->flags = IORESOURCE_BUSY;
res->flags |= flags;
write_lock(&resource_lock);
for (;;) {
struct resource *conflict;
conflict = __request_resource(parent, res);
if (!conflict)
break;
if (conflict != parent) {
parent = conflict;
if (!(conflict->flags & IORESOURCE_BUSY))
continue;
}
/* Uhhuh, that didn't work out.. */
kfree(res);
res = NULL;
break;
}
write_unlock(&resource_lock);
}
return res;
}
NOTE:
①首先,調用kmalloc()函數在SLAB分配器緩衝中分配一個resource結構。
②然後,相應的根據參數值初始化所分配的resource結構。注意!flags成員被初始化為IORESOURCE_BUSY。
③接下來,用一個for迴圈開始進行資源分派,迴圈體的步驟如下:
l
首先,調用__request_resource()函數進行資源分派。如果返回NULL,說明分配成功,因此就執行break語句推出for迴圈,返回所分配的resource結構的指標,函數成功地結束。
l
如果__request_resource()函數分配不成功,則進一步判斷所返回的衝突資源節點是否就是父資源節點parent。如果不是,則將分配行
為下降一個層次,即試圖在當前衝突的資源節點中進行分配(只有在衝突的資源節點沒有設定IORESOURCE_BUSY的情況下才可以),於是讓
parent指標等於conflict,並在conflict->flags&IORESOURCE_BUSY為0的情況下執行
continue語句繼續for迴圈。
l
否則如果相衝突的資源節點就是父節點parent,或者相衝突資源節點設定了IORESOURCE_BUSY標誌位,則宣告分配失敗。於是調用kfree()函數釋放所分配的resource結構,並將res指標置為NULL,最後用break語句推出for迴圈。
④最後,返回所分配的resource結構的指標。
3.3.2 I/O Region的釋放
函數__release_region()實現在一個父資源節點parent中釋放給定範圍的I/O
Region。實際上該函數的實現思想與__release_resource()相類似。其原始碼如下:
void __release_region(struct resource *parent,
unsigned long start, unsigned long n)
{
struct resource **p;
unsigned long end;
p = &parent->child;
end = start + n - 1;
write_lock(&resource_lock);
for (;;) {
struct resource *res = *p;
if (!res)
break;
if (res->start <= start && res->end >= end)
{
if (!(res->flags & IORESOURCE_BUSY)) {
p = &res->child;
continue;
}
if (res->start != start' 'res->end != end)
break;
*p = res->sibling;
write_unlock(&resource_lock);
kfree(res);
return;
}
p = &res->sibling;
}
write_unlock(&resource_lock);
printk("Trying to free nonexistent resource
<%08lx-%08lx>
", start, end);
}
類似地,該函數也是通過一個for迴圈來遍曆父資源parent的child鏈表。為此,它讓指標res指向當前正被掃描的子資源節點,指標p指向前
一個子資源節點的sibling成員變數,p的初始值為指向parent->child。For迴圈體的步驟如下:
①讓res指標指向當前被掃描的子資源節點(res=*p)。
②如果res指標為NULL,說明已經掃描完整個child鏈表,所以退出for迴圈。
③如果res指標不為NULL,則繼續看看所指定的I/O地區範圍是否完全包含在當前資源節點中,也即看看[start,start+n-1]是否包
含在res->[start,end]中。如果不屬於,則讓p指向當前資源節點的sibling成員,然後繼續for迴圈。如果屬於,則執行下列步
驟:
l
先看看當前資源節點是否設定了IORESOURCE_BUSY標誌位。如果沒有設定該標誌位,則說明該資源節點下面可能還會有子節點,因此將掃描過程下降一個層次,於是修改p指標,使它指向res->child,然後執行continue語句繼續for迴圈。
l
如果設定了IORESOURCE_BUSY標誌位。則一定要確保當前資源節點就是所指定的I/O地區,然後將當前資源節點從其父資源的child鏈表中去
除。這可以通過讓前一個兄弟資源節點的sibling指標指向當前資源節點的下一個兄弟資源節點來實現(即讓*p=res->sibling),最
後調用kfree()函數釋放當前資源節點的resource結構。然後函數就可以成功返回了。
3.3.3 檢查指定的I/O Region是否已被佔用
函數__check_region()檢查指定的I/O
Region是否已被佔用。其原始碼如下:
int __check_region(struct resource *parent, unsigned long start,
unsigned long n)
{
struct resource * res;
res = __request_region(parent, start, n, "check-region");
if (!res)
return -EBUSY;
release_resource(res);
kfree(res);
return 0;
}
該函數的實現與__check_resource()的實現思想類似。首先,它通過調用__request_region()函數試圖在父資源parent中分配指定的I/O
Region。如果分配不成功,將返回NULL,因此此時函數返回錯誤值-EBUSY表示所指定的I/O
Region已被佔用。如果res指標不為空白則說明所指定的I/O
Region沒有被佔用。於是調用__release_resource()函數將剛剛分配的資源釋放掉(實際上是將res結構從parent的child鏈表去除),然後調用kfree()函數釋放res結構所佔用的記憶體。最後,返回0值表示指定的I/O
Region沒有被佔用。