主要的檔案操作方法實現
檔案操作函數有很多的操作介面,驅動編程需要實現這些介面,在使用者編程時候系統調用時候會調用到這些操作
struct file_operations {...loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);...};
以上只列出了主要的操作,下面會依次介紹:
本次的測試代碼上傳在:char_step2
結構體:
首先 我們會類比寫一個不操作任何裝置,而僅僅是儲存的一個驅動。
定義自己的一個結構體為:
struct simple_dev{char data[MAX_SIMPLE_LEN];loff_t count;struct semaphore semp;};
data 儲存資料, count表示檔案的資料有效位置, semp是一個訊號量鎖,在以後的編程中使用,
之後的程式中結構體也會做相應的變化,以適應linux編寫驅動的習慣
open方法:
開啟裝置並進一步初始化工作,在沒有定義open方法時核心以一種預設的方式開啟裝置,保證每次都能正確開啟。
open方法中有有struct inode參數,包含了裝置號,程式中可以使用次裝置號得到正操作的裝置
在struct file中主要的操作是private_data指標,他可以傳遞任何自己建立的結構。
總得說來open方法的作用有3
1、獲得操作的裝置(通過裝置號)
2、進一步的初始化裝置
3、初始化file結構體的private_data
static int simple_open(struct inode *inodp, struct file *filp){struct simple_dev *temp_dev = NULL;int minor = 0;#if SIMPLE_DEBUGprintk(KERN_INFO "In %s \n", __func__);#endifminor = iminor(inodp);//獲得操作的裝置的次裝置號if(minor > DEV_COUNT-1){printk(KERN_ERR "the char dev in invalid \n");return -ENODEV;}#if SIMPLE_DEBUGprintk(KERN_INFO "the minor is %d \n", minor);#endiftemp_dev = &char2_dev[minor];//獲得真正操作的裝置/* 進一步 初始化裝置 因為是操作一個類比的裝置 故省去*/filp->private_data = temp_dev; //初始化 private_datareturn 0;}
release方法:
主要是對open進一步初始化的操作的反操作
比如open時候分配了記憶體,在release時就需要釋放它等
例子中因為操作記憶體裝置,故在release時無需做什麼事
read方法:
read 是把裝置中的資料傳遞給調用者
主要步驟
1、檢測位移量有效(有些裝置驅動不需要檢測)
2、檢測使用者空間地址有效
3、將資料傳給使用者(在此步驟中調用的函數可能會自己檢測步驟2)
4、調整位移量
5、返回讀到的資料長度
(read write 用法相對靈活,不要依賴上邊的步驟,裝置驅動程式要根據裝置特性去設計此方法)
這裡先介紹一個會檢測使用者空間地址是否有效copy函數
使用者調用read讀裝置,而在核心空間就是將資料傳給使用者,是一個to的操作
unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
__must_check表述必須檢測其傳回值,操作成功返回0,不成功返回負的錯誤碼
to是使用者空間指標 也就是read函數傳入的使用者空間的指標,
from指向裝置要傳送的資料
n標識傳入長度
是 摘自LDD3上的傳統檢視, 應該比較能說明read的方法
static ssize_t simple_read(struct file *filp, char __user *userstr, size_t count, loff_t *loff){struct simple_dev *dev = NULL;int data_remain = 0;int err;#if SIMPLE_DEBUGprintk(KERN_INFO "In %s \n", __func__);#endifdev= filp->private_data;data_remain = dev->count - *loff;if(MAX_SIMPLE_LEN < *loff)//檢測位移量{printk(KERN_ERR "the offset is illegal in func %s \n",__func__ );return -EINVAL;}else if(data_remain <= 0){printk(KERN_WARNING "there was not much data in the device\n");return 0;}else{if(count > data_remain){#if SIMPLE_DEBUGprintk(KERN_INFO "the data is less than the user want to read\n");#endifcount = 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");return err;}else{#if SIMPLE_DEBUGprintk(KERN_INFO "data copy to user OK\n");#endif*loff = *loff + count; //調整位移量return count; //返回寫入的資料量}}
write方法:
與read類似 它是從使用者傳資料給裝置驅動
從核心空間看就是一個從使用者空間取資料 是一個from操作
long __must_check strncpy_from_user(char *dst, const char __user *src, long count)
dst 驅動儲存資料的地址
src 使用者空間傳入的資料
count 標識資料長度
static ssize_t simple_write(struct file *filp, const char __user *userstr, size_t count, loff_t *loff){struct simple_dev *dev = NULL;int err;int remain_space = 0;#if SIMPLE_DEBUGprintk(KERN_INFO "In %s\n",__func__);#endifdev = 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_DEBUGprintk(KERN_WARNING "the data is to long to write to the device\n");#endifcount = 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");return err;}else{#if SIMPLE_DEBUGprintk(KERN_INFO "data copy from user OK\n");#endif*loff = *loff + count; //跳著位移if(*loff > dev->count){dev->count = *loff;}else{}return count; //返回寫入的資料量}}
lseek方法:
根據使用者傳入的參數調整檔案位移
mode
| SEEK_SET |
從檔案起始處開始位移 |
| SEEK_CUR |
從檔案當前位置計算位移 |
| SEEK_END |
從檔案末尾計算位移 |
file結構的f_pos儲存了檔案的位移量
在調整檔案位移後需要 更新file中得f_pos成員
static loff_t simple_llseek(struct file *filp, loff_t loff, int mode){struct simple_dev *dev = NULL;loff_t tmp_len;#if SIMPLE_DEBUGprintk(KERN_INFO "In %s\n",__func__);#endifdev = filp->private_data;switch ( mode ){case SEEK_SET:if( loff < 0 ){printk(KERN_ERR "can't move above file line %d \n", __LINE__);return -1;}else if(loff > dev->count){printk(KERN_ERR "offset is too long line %d\n", __LINE__);return -1;}else{filp->f_pos = loff;}break;case SEEK_CUR:if((tmp_len = filp->f_pos+loff) < 0){printk(KERN_ERR "can't move above file line %d \n", __LINE__);return -1;}else if(tmp_len > dev->count){printk(KERN_ERR "offset is too long line %d\n", __LINE__);return -1;}else{filp->f_pos = tmp_len;}break;case SEEK_END:if((tmp_len = dev->count+loff ) < 0){printk(KERN_ERR "can't move above file line %d \n", __LINE__);return -1;}else if(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;}return filp->f_pos;}