一、字符設備驅動框架解析
設備的操作函數如果比喻是樁的話(性質類似于設備操作函數的函數,在一些場合被稱為樁函數),則:
驅動實現設備操作函數 ----------- 做樁
insmod調用的init函數主要作用 --------- 釘樁
rmmod調用的exitt函數主要作用 --------- 拔樁
應用層通過系統調用函數間接調用這些設備操作函數 ------- 用樁
1.1 兩個操作函數中常用的結構體說明
內核中記錄文件元信息的結構體
struct inode
{
//…
dev_t i_rdev;//設備號
struct cdev i_cdev;//如果是字符設備才有此成員,指向對應設備驅動程序中的加入系統的struct cdev對象
//…
}
/
1. 內核中每個該結構體對象對應著一個實際文件,一對一
2. open一個文件時如果內核中該文件對應的inode對象已存在則不再創建,不存在才創建
3. 內核中用此類型對象關聯到對此文件的操作函數集(對設備而言就是關聯到具體驅動代碼)
*/
讀寫文件內容過程中用到的一些控制性數據組合而成的對象------文件操作引擎(文件操控器)
struct file
{
//…
mode_t?f_mode;//不同用戶的操作權限,驅動一般不用
loff_t?f_pos;//position 數據位置指示器,需要控制數據開始讀寫位置的設備有用
unsigned?int?f_flags;//open時的第二個參數flags存放在此,驅動中常用
struct?file_operations?*f_op;//open時從struct inode中i_cdev的對應成員獲得地址,驅動開發中用來協助理解工作原理,內核中使用
void?*private_data;//本次打開文件的私有數據,驅動中常來在幾個操作函數間傳遞共用數據
struct?dentry?f_dentry;//驅動中一般不用,除非需要訪問對應文件的inode,用法flip->f_dentry->d_inode
int refcnt;//引用計數,保存著該對象地址的位置個數,close時發現refcnt為0才會銷毀該struct file對象
//…
};
/
1. open函數被調用成功一次,則創建一個該對象,因此可以認為一個該類型的對象對應一次指定文件的操作
2. open同一個文件多次,每次open都會創建一個該類型的對象
3. 文件描述符數組中存放的地址指向該類型的對象
4. 每個文件描述符都對應一個struct file對象的地址
*/
1.2 字符設備驅動程序框架分析
驅動實現端:
驅動使用端:
整體流程以open函數為例
應用層app系統調用open函數使用文件名,struct inode查詢是否有該文件名的對象,若沒有則創建,有則查詢該設備的設備好devno對應的cdev對象,再通過cdev對象得到操作函數集fops,然后open函數創建struct file對象將操作函數集設置到該對象下面的成員f_ops里面,再把struc file對象填到描述符數組里面,得到文件描述符,同時調用驅動程序的drive_open函數,再通過system_open函數返回文件描述符給到應用層的open函數。
若要調用read函數則根據文件描述符找到對應的struct file 對象然后通過里面的f_ops操作函數集調用驅動程序對應的read函數。
syscall_open函數實現的偽代碼:
int syscall_open(const char *filename,int flag)
{
dev_t devno;
struct inode *pnode = NULL;
struct cdev *pcdev = NULL;
struct file *pfile = NULL;
int fd = -1;
/*根據filename在內核中查找該文件對應的struct inode對象地址找到則pnode指向該對象未找到則創建新的struct inode對象,pnode指向該對象,并從文件系統中讀取文件的元信息到該對象*/
if(/*未找到對應的struct inode對象*/)
{/*根據文件種類決定如何進行下面的操作,如果是字符設備則執行如下操作*//*從pnode指向對象中得到設備號*/devno = pnode->i_rdev;/*用devno在字符設備鏈表查找對應節點,并將該節點的地址賦值給pcdev*//*pcdev賦值給pnode的i_cdev成員*/pnode->i_cdev = pcdev;
}/*創建struct file對象,并將該對象的地址賦值給pfile*/pfile->f_op = pnode->i_cdev->ops;
pfile->f_flags = flag;/*調用驅動程序的open函數*/
pfile->f_op->open(pnode,pfile,flag);/*將struct file對象地址填入進程的描述符數組,得到對應位置的下標賦值給fd*/return fd;
}
syscall_read函數實現的偽代碼
int syscall_read(int fd,void *pbuf,int size)
{
struct file *pfile = NULL;
struct file_operations *fops = NULL;
int cnt;
/*將fd作為下標,在進程的描述符數組中獲得struct file對象的地址賦值給pfile*//*從struct file對象的f_op成員中得到操作函數集對象地址賦值給fops*//*從操作函數集對象的read成員得到該設備對應的驅動程序中read函數,并調用之*/
cnt = fops->read(pfile,pbuf,size,&pfile->f_pos);。。。。
return cnt;
}
1.3 參考原理圖
字符設備驅動框架
Linux字符設備驅動工作原理圖
1.4 常用操作函數說明
int (*open) (struct inode *, struct file ); //打開設備
/
指向函數一般用來對設備進行硬件上的初始化,對于一些簡單的設備該函數只需要return 0,對應open系統調用,是open系統調用函數實現過程中調用的函數,
*/
int (*release) (struct inode *, struct file ); //關閉設備
/
,指向函數一般用來對設備進行硬件上的關閉操作,對于一些簡單的設備該函數只需要return 0,對應close系統調用,是close系統調用函數實現過程中調用的函數
*/
ssize_t (*read) (struct file *, char __user *, size_t, loff_t ); //讀設備
/
指向函數用來將設備產生的數據讀到用戶空間,對應read系統調用,是read系統調用函數實現過程中調用的函數
*/
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t ); //寫設備
/
指向函數用來將用戶空間的數據寫進設備,對應write系統調用,是write系統調用函數實現過程中調用的函數
*/
loff_t (*llseek) (struct file , loff_t, int); //數據操作位置的定位
/
指向函數用來獲取或設置設備數據的開始操作位置(位置指示器),對應lseek系統調用,是lseek系統調用函數實現過程中調用的函數
*/
long (*unlocked_ioctl) (struct file , unsigned int, unsigned long);//讀寫設備參數,讀設備狀態、控制設備
/
指向函數用來獲取、設置設備一些屬性或設備的工作方式等非數據讀寫操作,對應ioctl系統調用,是ioctl系統調用函數實現過程中調用的函數
*/
unsigned int (*poll) (struct file *, struct poll_table_struct );//POLL機制,實現對設備的多路復用方式的訪問
/
指向函數用來協助多路復用機制完成對本設備可讀、可寫數據的監控,對應select、poll、epoll_wait系統調用,是select、poll、epoll_wait系統調用函數實現過程中調用的函數
*/
int (*fasync) (int, struct file , int); //信號驅動
/
指向函數用來創建信號驅動機制的引擎,對應fcntl系統調用的FASYNC標記設置,是fcntl系統調用函數FASYNC標記設置過程中調用的函數
*/