1. scull的記憶體管理
兩個基本函數void *kmalloc(siez_t size,int flags);
void kfree(void *ptr);
記憶體中關於量子和量子集選擇合適的數值時一個策略而非機制問題,而且最有數值依賴於如何使用裝置。因此裝置驅動不應對量子和量子集大小的尺寸強制使用特定的數值。在scull裝置中,可以通過下一方式來修改這些值:在編譯時間,可以修改scull.h中的宏SCULL_QUANTUM和SCULL_QSET:也可在載入模組是,設scull_quantum和scull_qset的值。這和在前邊選擇主裝置號的方法類似。
內部表示scull裝置的scull_dev結構比較重要:
struct scull_dev{
struct scull_qest *data; /*指向第一個量子集指標
int quantum; /*當前量子的大小,是指scull_qset中data資料項目的大小。
int qest; /*當前數組的大小,從後邊scull_trim中可以看出,qset是指scull_qset中data資料的多少。
這兩個項一個描述data中量子的大小,一個描述量子的多少,這樣理解才能和後邊的scull_trim對應起來。
unsigned long size; /*儲存在其中的資料量
unsigned int access_key; /*有sculluid和scullpriv使用
struct semaphore sem; /*互斥訊號量
struct cdev cdev; /*字元裝置結構
};
該結構的quantum和qset欄位分別儲存裝置餓量子和量子集大小,但是實際的資料由另外的資料結構進行處理,該結構為scull_qest“
struct scull_qest {
void **data;
struct scull_qest *next;
};
data之所以為2級指標,在於其指向一個數組,其中封裝了各個量子。
scull_trim函數負責釋放整個資料區,並且在檔案以寫入方式開啟時由scull_open調用。它簡單的調用鏈表,釋放所有找到的量子和量子集。
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next,*dptr;
int qset = dev->qset;
int i;
for(dptr = dev->data;dptr;dptr=next){ //外層迴圈,通過dptr和next對scull_qset進行遍曆
if(dptr->data){
for(i=0;i<qest;i++){ //內層迴圈,如下scull_dev分析的那樣,通過qset即每個scull_qset中data中量子數的多少進行遍曆。
kfree(dptr->data[i]);
}
kfree(data);
dptr->data = NULL;
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->data = NULL;
return 0;
}
模組的清除函數也調用scull_trim,一邊將由scull鎖使用的記憶體返回給系統。
這裡這個函數可以結合圖3-1,比較好理解。
這裡先介紹下read/write中使用
struct scull_qset *scull_follow(strcut scull_dev *dev,int n)
{
struct scull_qset *qs = dev->data; //分配第一個量子指標
if(!qs){ //如果dev中的data欄位為空白,則為其開闢新的空間
qs = dev->data = kmalloc(sizeof( struct scull_qset),GFP_KERNEL);
if(qs == NULL)
return NULL; //未開闢成功則至為NULL
memset(qs,0,sizeof(struct scull_qset)); //開闢成功則對開闢的空間清零
}
while(n--){
if(!qs->next){
qs->next = kmalloc(sizeof(struct scull_qset),GFP_KERNEL);
if(qs->next == NULL)
return NULL;
memset(qs,0,sizeof(struct scull_qset));
}
qs = qs->next;
continue;
}
return qs;
}
這個函數的實質是:如果已經在這個scull_qset,就返回這個scull_qset的指標。如果不存在這個scull_qset,一邊沿鏈表為scull_qset分配空間一邊沿鏈表前行,直到所需要的scull_qset被分配到空間並初始化為止,就返回這個scull_qset的指標。
2.read/write
ssize_t read(struct file *filp,char __user *buff,size_t count,loff_t *offp);
ssize_t write(struct file *filp,char __user *buff,size_t count,loff_t *offp);
對於其中的buff參數,由於有__user標誌表明buff是使用者空間的緩衝,所以在核心空間裡不能隨便訪問,或者說不應該也不允許訪問使用者空間的指標。但是上邊的兩個函數需要訪問使用者空間已完成任務,為了訪問安全核心提供了幾個函數來進行資料交換。
unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);
unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);
read和write函數是對以上兩個函數的封裝。
copy_to_user和copy_from_user兩個函數,拷貝成功返回零,失敗返回沒有拷貝的位元組數。在編寫字元裝置驅動時需要包含<asm/uaccess.h>標頭檔。
當核心空間啟動並執行代碼訪問使用者空間時要多加小心。由於採用虛擬記憶體分頁機制,被定址的使用者空間頁面可能當前並不在記憶體中,於是虛擬記憶體系統會把當前進程轉入睡眠狀態,知道該頁面被送到期望的位置。上述兩個函數除了拷貝資料外,還檢查使用者空間的指標是否有效。如果指標無效就不會進行拷貝。
如果進程A正在讀取裝置,進程B以寫入的方式開啟了這個裝置,於是裝置會被截斷為長度0,這種,進程A突然發現自己超過了檔案尾,並且下次調用read時返回。
ssize_t scull_read(struct file *filp,char __user &buf,size_t count,loff_t *offp)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum,qset = dev->qset;
int itemsize = quantum *qset;
int item,s_pos,q_pos,rest;
ssize_t retval = 0;
if(down_intertuptible(&dev->sem){
return -ERESTARTSYS;
if(*f_pos >= dev->size)
goto out;
if(*f_pos + count > dev->size)
count = dev->size - *f_ops;
item = (long)*f_pos/itemsize;
rest = (long)*f_ops % itemsize;
s_pos = rest/quantum;q_pos = rest % quantum;
dptr = scull_follow(dev,item);
if(dptr == NULL || !dptr->data || !dptr->data[s_pos]){
goto out;
if(count > quantum - q_pos)
count = quantum-q_pos;
if(copy_to_user(buf,dptr->data[s_pos]+q_pos,count)){
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
up(&dev->sem);
return retval;
}
write函數的大體和read相同,只列出不相同的代碼:
dptr = scull_follow(dev,item);
if(dptr == NULL)
goto out;
if(!dptr->data){
dptr ->data = kmalloc(qset *sizeof(char *),GFP_KERNEL);
if(!dptr->data)
goto out;
memset(dptr->data,0,qset * sizeof(char *));
}
if(!dptr->data[s_pos]){
dptr->data[s_pos] = kmalloc(quantum,GFP_KERNEL);
if(!dptr->data[s_pos])
goto out;
}
if(count>quantum - q_pos)
count = quantum - q_pos;