Linux驅動開發一.字元裝置框架——2.框架完善
在上一章節我們測試了模組的載入,這個就是驅動的根基,下面我們在這個根基上面加上枝幹,也就是完善dev_init()函式,但是顧名思義,這個函式只是實現裝置的初始化,如果我們需要操作裝置(對於linux來說就是個檔案)進行操作,在初始化完成後還需要對其進行讀寫操作,所以還需要新的open(),read(),write()以及close()操作。
file_operation結構體 檔案的讀寫依賴一個叫做file_operation的資料結構。這個結構裡的每一個成員都對應一個系統呼叫。讀取了file_operation裡對應的函式指標後就會把控制權轉交給函式,從而完成了Linux裝置驅動的程式任務。簡而言之,就是系統內部的IO操作,例如開啟、關閉、讀寫等,每個操作都需要一個函式,這些函式組成了一個結構體,這個結構體就是file_operation結構體。這個結構體在include/linux/fs.h中。structfile_operations { struct module *owner; 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 *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*mremap)(struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif };
上面就是檔案操作結構體的所有內容。但是我們不需要都寫出來,只寫我們需要的幾個就可以了,也就是open、release、read和write。具體的函式體先不寫,先把整個函式架構建立出來
/** * @brief 開啟裝置檔案 * * @return int */ static int dev_open() { return 0; } /** * @brief 關閉裝置檔案 * * @return int */ static int dev_release() { return 0; } /** * @brief 裝置檔案寫入資料 * * @return int */ static int dev_write() { return 0; } /** * @brief 裝置檔案讀取資料 * * @return int */ static int dev_read() { return 0; } /** * @brief 自定義檔案操作結構體 * */ static struct file_operations testDev_fops= { .owner = THIS_MODULE, .open = dev_open, .release = dev_release, .read = dev_read, .write = dev_write, };
因為這4個函式我們都還沒有考慮傳參什麼的,所以後面還需要修改,包括定義的方法都要修改。修改的方法可以參考核心裡提供的其他驅動的定義方法。
字元裝置的註冊和登出
在前面章節中我們通過註冊函式實現了模組的載入和解除安裝,實際使用過程中載入模組主要是為了註冊我們需要使用的字元裝置。這裡引入一個新的函式:
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }
這個register_chrdev就是用來註冊字元裝置的。在Linux中好多函式都是成對出現的,有一個註冊肯定就會有個登出,所以還有個登出函式
static inline void unregister_chrdev(unsigned int major, const char *name) { __unregister_chrdev(major, 0, 256, name); }
不論是註冊還是登出的函式比較簡單,可以看出來引數就是一個裝置號和一個裝置名稱。但是註冊還多了個我們前面定義的檔案操作結構體。註冊和登出函式就放在模組載入和解除安裝中。我們在檔案開始定義兩個巨集,分別是主裝置號還有裝置名稱(設定前在用cat /proc/devices列印一下看看有哪個裝置號是空閒的,別衝突了!)。因為要用到一些剛才的兩個函式(路徑為/include/inux/fs.h),標頭檔案也要新增對應庫
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #define DEV_MAJOR 200 #define DEV_NAME "DEVICE_TEST"
如上面程式碼所示,主裝置號我定了個200,裝置名我就隨便整了一個。
把前面模組載入和解除安裝的函式修改一下,增加註冊模組的函式
static int __init dev_init(void) { int ret = 0; printk("device init!\r\n"); //字元設備註冊 ret = register_chrdev(DEV_MAJOR, DEV_NAME, &testDev_fops); if(ret < 0 ){ printk("device init failed\r\n"); } return 0; } static int __exit dev_exit(void) { //字元設備註銷 unregister_chrdev(DEV_MAJOR,DEV_NAME); printk("device exit\r\n"); return 0; }
這個時候make檢查一下,會發現報了一堆錯誤!
這是因為我們定義讀寫開關函式的時候沒有嚴格按照檔案操作結構體裡要求的引數格式寫函式。按照給定的格式要求修改一下,把每個函式內新增列印資訊,下面就是整個dev的驅動檔案
/** * @file testDev.c * @author your name ([email protected]) * @brief * @version 0.1 * @date 2022-03-23 * * @copyright Copyright (c) 2022 * */ // #include <linux/kernel.h> // #include <linux/init.h> // #include <linux/module.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/types.h> #define DEV_MAJOR 200 #define DEV_NAME "DEVICE_TEST" /** * @brief 開啟裝置檔案 * * @return int */ static int dev_open(struct inode *inode, struct file *filp) { printk("dev open!\r\n"); return 0; } /** * @brief 關閉裝置檔案 * * @return int */ static int dev_release(struct inode *inode, struct file *filp) { printk("dev release!\r\n"); return 0; } /** * @brief 裝置檔案寫入資料 * * @return int */ static ssize_t dev_write(struct file *file, char __user *buf, size_t count, loff_t *f_ops) { printk('dev write data!\r\n'); return 0; } /** * @brief 裝置檔案讀取資料 * * @return int */ static ssize_t dev_read(struct file *file, __user char *buf, size_t count, loff_t *f_ops) { printk('dev read data!\r\n'); return 0; } /** * @brief 自定義檔案操作結構體 * */ static struct file_operations testDev_fops= { .owner = THIS_MODULE, .open = dev_open, .release = dev_release, .read = dev_read, .write = dev_write, }; static int __init dev_init(void) { int ret = 0; printk("device init!\r\n"); //字元設備註冊 ret = register_chrdev(DEV_MAJOR, DEV_NAME, &testDev_fops); if(ret < 0 ){ printk("device init failed\r\n"); } return 0; } static int __exit dev_exit(void) { /** * @file testDev.c * @author your name ([email protected]) * @brief * @version 0.1 * @date 2022-03-23 * * @copyright Copyright (c) 2022 * */ // #include <linux/kernel.h> // #include <linux/init.h> // #include <linux/module.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/types.h> #define DEV_MAJOR 200 #define DEV_NAME "DEVICE_TEST" /** * @brief 開啟裝置檔案 * * @return int */ static int dev_open(struct inode *inode, struct file *filp) { printk("dev open!\r\n"); return 0; } /** * @brief 關閉裝置檔案 * * @return int */ static int dev_release(struct inode *inode, struct file *filp) { printk("dev release!\r\n"); return 0; } /** * @brief 裝置檔案寫入資料 * * @return int */ static ssize_t dev_write(struct file *file, char __user *buf, size_t count, loff_t *f_ops) { printk('dev write data!\r\n'); return 0; } /** * @brief 裝置檔案讀取資料 * * @return int */ static ssize_t dev_read(struct file *file, __user char *buf, size_t count, loff_t *f_ops) { printk('dev read data!\r\n'); return 0; } /** * @brief 自定義檔案操作結構體 * */ static struct file_operations testDev_fops= { .owner = THIS_MODULE, .open = dev_open, .release = dev_release, .read = dev_read, .write = dev_write, }; static int __init dev_init(void) { int ret = 0; printk("device init!\r\n"); //字元設備註冊 ret = register_chrdev(DEV_MAJOR, DEV_NAME, &testDev_fops); if(ret < 0 ){ printk("device init failed\r\n"); } return 0; } static int __exit dev_exit(void) { //字元設備註銷 unregister_chrdev(DEV_MAJOR,DEV_NAME); printk("device exit\r\n"); return 0; } module_init(dev_init); //模組載入 module_exit(dev_exit); //模組解除安裝 MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZeqiZ"); //字元設備註銷 unregister_chrdev(DEV_MAJOR,DEV_NAME); printk("device exit\r\n"); return 0; } module_init(dev_init); //模組載入 module_exit(dev_exit); //模組解除安裝 MODULE_LICENSE("GPL"); MODULE_AUTHOR("ZeqiZ");
重新make一下,在開發板里加載模組試一下,能掛載成功,就不在截圖了,但是因為沒有呼叫檔案操作都函式,那4個函式還沒法演示。下面我們寫一個應用程式來演示如何使用。