2015年3月30日 星期一

LDD3 潤飾 3.6 scull’s Memory Usage

3.6. scull 的memory使用

在介紹read, write operation前, 我們最好看看如何以及為什麼scull 進行memory分配. "如何"是需要全面理解代碼, "為什麼"演示了driver編寫者需要做的選擇, 儘管scull 明確地不是典型設備.

本節只處理scull 中的memory分配策略, 不展示給你編寫真正driver需要的硬件管理技能. 這些技能在第9 章和第10 章介紹. 因此, 你可跳過本章, 如果你不感興趣於理解memory-oriented的scull 驅動的內部工作.

scull 使用的memory區, 也稱為一個設備, 長度可變. 你寫的越多, 它增長越多; 通過使用一個短文件覆蓋設備來進行修整.

scull 驅動引入2 個核心函數來管理Linux 內核中的memory. 這些函數, 定義在<linux/slab.h>, 是:

void *kmalloc(size_t size, int flags);
void kfree(void *ptr);
對kmalloc 的調用試圖分配size 字節的memory; 返回值是指向那個memory的pointer或者如果分配失敗為NULL. flags 參數用來描述memory應當如何分配; 我們在第8 章詳細查看這些flags. 對於現在,我們一直使用GFP_KERNEL. 分配的memory應當用kfree 來釋放. 你不能傳遞任何不是從kmalloc 獲得的東西給kfree. 但是, 傳遞一個NULL pointer給kfree 是合法的.

kmalloc 不是分配大memory區最有效的方法(見第8 章), 所以挑選給scull 的實現不是一個特別巧妙的. 一個巧妙的源碼實現可能更難閱讀, 而本節的目標是展示讀和寫, 不是memory管理. 這是為什麼代碼只是使用kmalloc 和kfree 而不依靠整頁的分配, 儘管這個方法會更有效.

另一方面, 我們不想限制"設備"區的大小, 由於理論上的和實踐上的理由. 理論上, 在被管理的數據項目施加武斷的限制總是個壞想法. 實踐上, scull 可用來暫時地吃光你係統中的memory, 以便運行在低memory條件下的測試. 運行這樣的測試可能會幫助你理解系統的內部. 你可以使用命令cp /dev/zero /dev/scull0 來用scull 吃掉所有的真實RAM, 並且你可以使用dd 工具來選擇拷貝多少數據給scull 設備.

在scull,每個設備是一個pointer的linked list,每個都指向一個scull_dev結構.每個這樣的結構,預設地,指向最多4兆字節,通過一個中間pointer陣列.發行代碼使用一個1000個pointer的array指向每個4000字節的區域.我們稱每個memory區域為一個quantum,陣列(或者它的長度)為一個quantum set.一個scull設備和它的memory區如圖一個scull設備的佈局所示.
一個scull 設備的佈局
圖 3.1. 一個scull 設備的佈局

一個scull 設備的佈局
選定的數字是這樣, 在scull 中寫單個一個字節消耗8000 或12,000 KB memory: 4000 是quantum, 4000 或者8000 是quantum set(根據pointer在目標平台上是用32位還是64位表示). 相反, 如果你寫入大量數據, linked list的開銷不是太壞. 每4 MB 數據只有一個list元素, 設備的最大尺寸受限於計算機的memory大小.

為quantum和quantum set選擇合適的值是一個策略問題, 而不是機制, 並且優化的值依賴於設備如何使用. 因此, scull driver不應當強制給quantum和quantum set使用任何特別的值. 在scull 中,用戶有幾個途徑可以改變這些值:編譯時通過改變scull.h 中的巨集SCULL_QUANTUM 和SCULL_QSET, 在模塊加載時設定整數值scull_quantum 和scull_qset, 或者使用ioctl 在運行時改變當前值和預設值.

使用巨集定義和一個整數值來進行編譯時和加載時配置, 是讓人聯想起major number如何選擇的. 我們在driver中任何與策略相關或專斷的值上運用這個技術.

餘下的唯一問題是如果選擇預設值. 在這個特殊情況下, 問題是在下列兩種情況中找到最好的平衡, 由填充了一半的quantum和quantum set導致memory浪費, 以及quantum和quantum set很小的情況下allocation ,deallocation,和pointer連接 的開銷. 另外, kmalloc 的內部設計應當考慮進去. (現在我們不追求這點, 不過; kmalloc 的內部在第8 章探索.)預設值的選擇來自假設測試時可能有大量數據寫進scull, 儘管設備的正常使用最可能只傳送幾KB 數據.

我們已經見過內部代表我們設備的scull_dev 結構. 結構的quantum 和qset 分別代表設備的quantum和quantum set大小. 實際數據, 但是, 是由一個不同的結構跟蹤, 我們稱為struct scull_qset:

struct scull_qset {
 void **data;
 struct scull_qset *next;
};
下一個代碼片段展示了實際中struct scull_dev 和struct scull_qset 是如何被用來持有數據的. sucll_trim 函數負責釋放整個數據區, 由scull_open 在文件為寫而打開時調用. 它簡單地遍歷列表並且釋放它發現的任何quantum和quantum set.

int scull_trim(struct scull_dev *dev)
{
        struct scull_qset *next, *dptr;
        int qset = dev->qset; /* "dev" is not-null */
        int i;
        for (dptr = dev->data; dptr; dptr = next)
        { /* all the list items */
                if (dptr->data) {
                        for (i = 0; i < qset; i++)
                                kfree(dptr->data[i]);
                        kfree(dptr->data);
                        dptr->data = NULL;
                }

                next = dptr->next;
                kfree(dptr);
        }
        dev->size = 0;
        dev->quantum = scull_quantum;
        dev->qset = scull_qset;
        dev->data = NULL;
        return 0;
}
scull_trim 也用在模塊清理函數中, 來歸還scull 使用的memory給系統.

沒有留言: