曆時將近兩個月,終於化零為整,對《Linux裝置驅動程式》(3rd edition)中第三章scull驅動有了詳細的瞭解,下面對scull的驅動進行解析,方便以後的查看。
一、建立字元驅動的步驟:
1、獲得裝置號:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
上述兩個函數,前者為在確定主裝置號(first)的時候,進行裝置號的註冊,後者為自動獲得主裝置號並註冊(獲得的裝置號存入dev中)。
2、將裝置操作函數與裝置號聯絡到一起:
初始化:void cdev_init(struct cdev *dev, struct file_operations *fops);
添加到核心中:int cdev_add(struct cdev *dev, dev_t num, unsigned int count)
注意:步驟2用到的fops已經初始化完成,設計裝置驅動的open,read,write,release等函數;而需要定義全域變數struct cdev my_cdev,或在驅動代碼中利用kalloc對其分配空間。
二、代碼常式:
可參考本書內建的常式,附件中的main.c中scull_init_module的實現。
三、scull驅動程式碼分析以及應用程式調用scull
1、scull驅動代碼中的全域變數:struct scull_dev *scull_devices; //通過在scull_init_module中為其分配空間;
scull_major, scull_minor:裝置號
2、當scull驅動編譯完成,且安裝到核心中後,可利用應用程式進行讀取來驗證。如:file1=fopen("/dev/scull0", "rb+"); stringlen = fwrite(stringBuffer, 1, sizeof(stringBuffer), file1)
3、關於fopen的實現:
a)應用程式在調用fopen庫函數後,會執行底層應用函數介面int open(const char *path, int oflags),進而會執行到核心代碼 long do_sys_open(int dfd, const char __user *filename, int flags, int mode) (核心代碼open.c檔案中)。do_sys_open實現通過filename得到struct
file *f,並把f與int fd綁定,返回fd。得到的fd即為open函數的傳回值。
b)scull驅動中的open函數int scull_open(struct inode *inode, struct file *filp)
struct scull_dev *dev;
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
在scull驅動中主要是通過filp->private_data(指向dev),進行資料的write和read。
在看這段代碼時,有很多疑問,如inode->i_cdev應該是指向的之前在scull驅動中定義的scull_devices.dev部分,那麼是在什麼時候jiangscull_devices.dev的地址賦值給的inode->i_cdev;再有就是scull_open與核心代碼中的do_sys_open之間的關係。自己的理解如下:
(1)scull_init_module調用scull_setup_cdev函數,其中scull_setup_cdev實現cdev_init和cdev_add的功能,cdev_add實現告訴核心,對應的裝置代碼對應特定的裝置檔案和操作函數(scull_devices以及其中的f_ops),即在核心中進行註冊,之後通過裝置號就可找到對應的scull_devices。在進行節點建立時(mknod),會用到裝置號(如:mknod /dev/scull0 -C
$majorNumber 0),此時建立的inode,其i_cdev已經指向了對應特定裝置號的scull_devices。
(2)核心源碼do_sys_open主要實現通過檔案路徑path得到file指標f,並與對應的控制代碼fd綁定。
do_sys_open調用struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);do_filp_open函數中實質性的開啟工作是do_last函數。
在do_last函數中,無論open_flag中的標誌位判斷該檔案是不是需要被建立,最後都會調用函數nameidata_to_filp,nameidata_to_filp會調用__dentry_open,在__dentry_open函數中會調用到f->f_op->open也就是scull_open。
關於檔案節點是否需要建立在調用f_op 的時刻會有不同,需要建立節點時,在__open_namei_create之後調用nameidata_to_filp;不需要建立節點時,在finish_open中調用nameidata_to_filp。
(3)在scull驅動過程中,通過file->private_data來傳遞scull_devices的地址(主要在scull_open代碼中實現),進而對scull_devices的data資料去進行讀寫。
4、scull_devices指向的資料空間(可參考,第三章61頁的圖片):
在進行scull_write和scull_read時,調用scull_follow函數,實現對資料空間的分配(kmalloc)
struct scull_dev {
struct scull_qset *data; /* Pointer to first quantum set */
int quantum; /* the current quantum size */
int qset; /* the current array size */
unsigned long size; /* amount of data stored here */
unsigned int access_key; /* used by sculluid and scullpriv */
struct semaphore sem; /* mutual exclusion semaphore */
struct cdev cdev;
/* Char device structure */
};
struct scull_qset {
void **data;
struct scull_qset *next;
};
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
參考資料:
1、《LInux裝置驅動程式》第三章
2、Linux核心源碼
3、關於核心代碼open的解析:http://www.cublog.cn/u3/119372/showart_2518539.html
4、關於file->private_data http://linux.chinaunix.net/techdoc/develop/2007/09/09/967423.shtml