不積跬步,何以至千里。掌握知識都是從很小的點開始的。下面是小編整理的Linux系統字元裝置驅動框架筆記,歡迎閱讀!
字元裝置是Linux三大裝置之一(另外兩種是塊裝置,網路裝置),字元裝置就是位元組流形式通訊的I/O裝置,絕大部分裝置都是字元裝置,常見的字元裝置包括滑鼠、鍵盤、顯示器、串列埠等等,當我們執行 ls -l /dev 的時候,就能看到大量的裝置檔案, c 就是字元裝置, b 就是塊裝置,網路裝置沒有對應的裝置檔案。編寫一個外部模組的字元裝置驅動,除了要實現編寫一個模組所需要的程式碼之外,還需要編寫作為一個字元裝置的程式碼。
驅動模型
Linux一切皆檔案,那麼作為一個裝置檔案,它的操作方法介面封裝在 struct file_operations ,當我們寫一個驅動的時候,一定要實現相應的介面,這樣才能使這個驅動可用,Linux的核心中大量使用"註冊+回撥"機制進行驅動程式的編寫,所謂註冊回撥,簡單的理解,就是當我們open一個裝置檔案的時候,其實是通過VFS找到相應的inode,並執行此前建立這個裝置檔案時註冊在inode中的'open函式,其他函式也是如此,所以,為了讓我們寫的驅動能夠正常的被應用程式操作,首先要做的就是實現相應的方法,然後再建立相應的裝置檔案。
#include //for struct cdev
#include //for struct file
#include //for copy_to_user
#include //for error number
/* 準備操作方法集 */
/*
struct file_operations {
struct module *owner; //THIS_MODULE
//讀裝置
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
//寫裝置
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
//對映核心空間到使用者空間
int (*mmap) (struct file *, struct vm_area_struct *);
//讀寫裝置引數、讀裝置狀態、控制裝置
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
//開啟裝置
int (*open) (struct inode *, struct file *);
//關閉裝置
int (*release) (struct inode *, struct file *);
//重新整理裝置
int (*flush) (struct file *, fl_owner_t id);
//檔案定位
loff_t (*llseek) (struct file *, loff_t, int);
//非同步通知
int (*fasync) (int, struct file *, int);
//POLL機制
unsigned int (*poll) (struct file *, struct poll_table_struct *);
。。。
};
*/
ssize_t myread(struct file *filep, char __user * user_buf, size_t size, loff_t* offset)
{
return 0;
}
struct file fops = {
r = THIS_MODULE,
= myread,
...
};
/* 字元裝置物件型別 */
struct cdev {
//public
struct module *owner; //模組所有者(THIS_MODULE),用於模組計數
const struct file_operations *ops; //操作方法集(分工:開啟、關閉、讀/寫、...)
dev_t dev; //裝置號(第一個)
unsigned int count; //裝置數量
//private
...
};
static int __init chrdev_init(void)
{
...
/* 構造cdev裝置物件 */
struct cdev *cdev_alloc(void);
/* 初始化cdev裝置物件 */
void cdev_init(struct cdev*, const struct file_opeartions*);
/* 為字元裝置靜態申請裝置號 */
int register_chedev_region(dev_t from, unsigned count, const char* name);
/* 為字元裝置動態申請主裝置號 */
int alloc_chedev_region(dev_t* dev, unsigned baseminor, unsigned count, const char* name);
MKDEV(ma,mi) //將主裝置號和次裝置號組合成裝置號
MAJOR(dev) //從dev_t資料中得到主裝置號
MINOR(dev) //從dev_t資料中得到次裝置號
/* 註冊字元裝置物件cdev到核心 */
int cdev_add(struct cdev* , dev_t, unsigned);
...
}
static void __exit chrdev_exit(void)
{
...
/* 從核心登出cdev裝置物件 */
void cdev_del(struct cdev* );
/* 從核心登出cdev裝置物件 */
void cdev_put(stuct cdev *);
/* 回收裝置號 */
void unregister_chrdev_region(dev_t from, unsigned count);
...
}
實現read,write
Linux下各個程序都有自己獨立的程序空間,即使是將核心的資料對映到使用者程序,該資料的PID也會自動轉變為該使用者程序的PID,由於這種機制的存在,我們不能直接將資料從核心空間和使用者空間進行拷貝,而需要專門的拷貝資料函式/巨集:
long copy_from_user(void *to, const void __user * from, unsigned long n)
long copy_to_user(void __user *to, const void *from, unsigned long n)
這兩個函式可以將核心空間的資料拷貝到回撥該函式的使用者程序的使用者程序空間,有了這兩個函式,核心中的read,write就可以實現核心空間和使用者空間的資料拷貝。
ssize_t myread(struct file *filep, char __user * user_buf, size_t size, loff_t* offset)
{
long ret = 0;
size = size > MAX_KBUF?MAX_KBUF:size;
if(copy_to_user(user_buf, kbuf,size)
return -EAGAIN;
}
return 0;
}