主要的文件操作方法實現
文件操作函數有很多的操作接口,驅動編程需要實現這些接口,在用戶編程時候系統調用時候會調用到這些操作
structfile_operations?{
...
loff_t?(*llseek)?(structfile?*,?loff_t,int);
ssize_t?(*read)?(structfile?*,char__user?*,size_t,?loff_t?*);
ssize_t?(*write)?(structfile?*,constchar__user?*,size_t,?loff_t?*);
int(*open)?(structinode?*,structfile?*);
int(*release)?(structinode?*,structfile?*);
...
};
以上只列出了主要的操作,下面會依次介紹:
本次的測試代碼上傳在:char_step2
結構體:
首先 我們會模擬寫一個不操作任何設備,而僅僅是存儲的一個驅動。
定義自己的一個結構體為:
structsimple_dev{
chardata[MAX_SIMPLE_LEN];
loff_t?count;
structsemaphore?semp;
};
data 保存數據, count表示文件的數據有效的位置, semp是一個信號量鎖,在以后的編程中使用,
之后的程序中結構體也會做相應的變化,以適應linux編寫驅動的習慣
open方法:
打開設備并進一步初始化工作,在沒有定義open方法時內核以一種默認的方式打開設備,保證每次都能正確打開。
open方法中有有struct inode參數,包含了設備號,程序中可以使用次設備號得到正操作的設備
在struct file中主要的操作是private_data指針,他可以傳遞任何自己創建的結構。
總得說來open方法的作用有3
1、獲得操作的設備(通過設備號)
2、進一步的初始化設備
3、初始化file結構體的private_data
staticintsimple_open(structinode?*inodp,structfile?*filp)
{
structsimple_dev?*temp_dev?=?NULL;
intminor?=?0;
#if?SIMPLE_DEBUG
printk(KERN_INFO?"In?%s?\n",?__func__);
#endif
minor?=?iminor(inodp);//獲得操作的設備的次設備號
if(minor?>?DEV_COUNT-1){
printk(KERN_ERR?"the?char?dev?in?invalid?\n");
return-ENODEV;
}
#if?SIMPLE_DEBUG
printk(KERN_INFO?"the?minor?is??%d?\n",?minor);
#endif
temp_dev?=?&char2_dev[minor];//獲得真正操作的設備
/*?進一步?初始化設備?因為是操作一個模擬的設備?故省去*/
filp->private_data?=?temp_dev;?//初始化?private_data
return0;
}
release方法:
主要是對open進一步初始化的操作的反操作
比如open時候分配了內存,在release時就需要釋放它等
例子中因為操作內存設備,故在release時無需做什么事
read方法:
read 是把設備中的數據傳遞給調用者
主要步驟
1、檢測偏移量有效(有些設備驅動不需要檢測)
2、檢測用戶空間地址有效
3、將數據傳給用戶(在此步驟中調用的函數可能會自己檢測步驟2)
4、調整偏移量
5、返回讀到的數據長度
(read write 用法相對靈活,不要依賴上邊的步驟,設備驅動程序要根據設備特性去設計此方法)
這里先介紹一個會檢測用戶空間地址是否有效的copy函數
用戶調用read讀設備,而在內核空間就是將數據傳給用戶,是一個to的操作
unsignedlong__must_check?copy_to_user(void__user?*to,constvoid*from,?unsignedlongn)
__must_check表述必須檢測其返回值,操作成功返回0,不成功返回負的錯誤碼
to是用戶空間指針 也就是read函數傳入的用戶空間的指針,
from指向設備要傳送的數據
n標識傳入長度
上圖是 摘自LDD3上的經典視圖, 應該比較能說明read的方法
staticssize_t?simple_read(structfile?*filp,char__user?*userstr,size_tcount,?loff_t?*loff)
{
structsimple_dev?*dev?=?NULL;
intdata_remain?=?0;
interr;
#if?SIMPLE_DEBUG
printk(KERN_INFO?"In?%s?\n",?__func__);
#endif
dev?????????=?filp->private_data;
data_remain?=?dev->count?-?*loff;
if(MAX_SIMPLE_LEN?
{
printk(KERN_ERR?"the?offset?is?illegal?in?func?%s?\n",__func__?);
return-EINVAL;
}
elseif(data_remain?<=?0)
{
printk(KERN_WARNING?"there?was?not?much?data?in?the?device\n");
return0;
}
else
{
if(count?>?data_remain)
{
#if?SIMPLE_DEBUG
printk(KERN_INFO?"the?data?is?less?than?the?user?want?to?read\n");
#endif
count?=?data_remain;
}
else
{
}
}
err?=?copy_to_user(userstr,?(dev->data)+(*loff),?count);?//調用內核函數進行數據拷貝,它會檢測用戶地址是否有效
if(err?!=?0)
{
printk(KERN_ERR?"an?error?occured?when?copy?data?to?user\n");
returnerr;
}
else
{
#if?SIMPLE_DEBUG
printk(KERN_INFO?"data?copy?to?user?OK\n");
#endif
*loff?=?*loff?+?count;?//調整偏移量
returncount;//返回寫入的數據量
}
}
write方法:
與read類似 它是從用戶傳數據給設備驅動
從內核空間看就是一個從用戶空間取數據 是一個from操作
long__must_check?strncpy_from_user(char*dst,constchar__user?*src,longcount)
dst 驅動保存數據的地址
src 用戶空間傳入的數據
count 標識數據長度
staticssize_t?simple_write(structfile?*filp,constchar__user?*userstr,size_tcount,?loff_t?*loff)
{
structsimple_dev?*dev?=?NULL;
interr;
intremain_space?=?0;
#if?SIMPLE_DEBUG
printk(KERN_INFO?"In?%s\n",__func__);
#endif
dev??????????=?filp->private_data;
if(MAX_SIMPLE_LEN?<=?*loff)//檢測偏移量
{
printk(KERN_ERR?"the?offset?is?illegal?in?func?%s\n",?__func__);
return-EINVAL;
}
else
{
remain_space?=?MAX_SIMPLE_LEN?-?*loff;
if(count?>?remain_space)
{
#if?SIMPLE_DEBUG
printk(KERN_WARNING?"the?data?is?to?long?to?write?to?the?device\n");
#endif
count?=?remain_space;
}
else
{
}
}
err?=?copy_from_user((dev->data)+(*loff),userstr,count);//取得數據
if(err?!=?0)
{
printk(KERN_ERR?"an?error?occured?when?copy?data?from?user\n");
returnerr;
}
else
{
#if?SIMPLE_DEBUG
printk(KERN_INFO?"data?copy?from?user?OK\n");
#endif
*loff?=?*loff?+?count;?//跳著偏移
if(*loff?>?dev->count)
{
dev->count?=?*loff;
}
else
{
}
returncount;//返回寫入的數據量
}
}
lseek方法:
根據用戶傳入的參數調整文件偏移
mode
SEEK_SET
從文件起始處開始偏移
SEEK_CUR
從文件當前位置計算偏移
SEEK_END
從文件末尾計算偏移
file結構的f_pos保存了文件的偏移量
在調整文件偏移后需要 更新file中得f_pos成員
staticloff_t?simple_llseek(structfile?*filp,?loff_t?loff,intmode)
{
structsimple_dev?*dev?=?NULL;
loff_t?tmp_len;
#if?SIMPLE_DEBUG
printk(KERN_INFO?"In?%s\n",__func__);
#endif
dev??????????=?filp->private_data;
switch(?mode?)
{
caseSEEK_SET:
if(?loff?
{
printk(KERN_ERR?"can't?move?above?file?line?%d?\n",?__LINE__);
return-1;
}
elseif(loff?>?dev->count)
{
printk(KERN_ERR?"offset?is?too?long?line?%d\n",?__LINE__);
return-1;
}
else
{
filp->f_pos?=?loff;
}
break;
caseSEEK_CUR:
if((tmp_len?=?filp->f_pos+loff)?
{
printk(KERN_ERR?"can't?move?above?file?line?%d?\n",?__LINE__);
return-1;
}
elseif(tmp_len?>?dev->count)
{
printk(KERN_ERR?"offset?is?too?long?line?%d\n",?__LINE__);
return-1;
}
else
{
filp->f_pos?=?tmp_len;
}
break;
caseSEEK_END:
if((tmp_len?=?dev->count+loff?)?
{
printk(KERN_ERR?"can't?move?above?file?line?%d?\n",?__LINE__);
return-1;
}
elseif(tmp_len?>?dev->count)
{
printk(KERN_ERR?"offset?is?too?long?line?%d\n",?__LINE__);
return-1;
}
else
{
filp->f_pos?=?tmp_len;
}
break;
default:
printk(KERN_INFO?"illigal?lseek?mode!?\n");
return-1;
break;
}
returnfilp->f_pos;
}