一,知識結構
二、驅動分類 可分為 :字元驅動、塊裝置驅動、網絡裝置驅動
字元裝置驅動以字元為通路機關(一個字元可能對應多個位元組)進行順序通路,不能随機讀取。
塊裝置驅動:以塊為通路機關(塊可以在核心中進行配置),通常512位元組,或者更大的2的n次方。
塊裝置可以随機讀取。在linux中塊裝置也可以以位元組為機關進行通路,塊裝置跟字元裝置
主要差別是通路接口的不同,并且塊裝置可以随機通路。
網絡裝置:網絡裝置是以網絡接口為通路對象的。可以是一個實體實體,也可以是存軟體,例如linux下的lo裝置是個存軟體的網絡裝置。
二、驅動的安裝方式
1.驅動可以直接被編譯核心,也可以以子產品的方式安裝,驅動設計的模型跟核心子產品設計一樣,入口都為module_init(),出口為module_exit();如果想要把驅動編譯進核心,需要配置相應的kconfig 跟config還有makefile檔案。
三、字元裝置驅動程式
字元裝置驅動設計流程:
裝置号:裝置号是一個unsigned 型,高12位為住裝置号,低20位為次裝置号,linux系統提供了major(dev_t num) ,minor(),mkdev(major,minor).來提取跟分離裝置号。其中,主裝置号是表明裝置類型,是建立應用程式跟裝置程式的紐帶,往往我們有一個産品中有多個一樣的裝置,那麼我們通常隻有一套驅動程式,一個主裝置号。通過不同的次裝置号來區分通路不同的實體接口。一個主+次裝置号對應一個裝置檔案。
首先申請裝置号,可以通過 alloc_chdev_region(dev_t* dev,unsinged from ,unsigned count ,char* name)裝置方法動态申請裝置号,當然,也可以用函數 register_chdev_region(dev_t num,unsigned count,char*
name)來靜态注冊裝置号。在靜态注冊前需要通過cat /dev/ 來檢視目前沒有使用的裝置号,才能配置設定給我們的裝置,要不然,可能會産生裝置号重複,而使我們的裝置不能加載進核心。裝置登出時需要釋放裝置号unregister_chdev_region(dev_t num,int cout);
2.裝置初始化申明結構體 struct cdev cdev(如果申明為指針在使用前要注意配置設定記憶體),定義結構體 struct file_operations file_ops={
.open = mem_open,
.read = mem_read,
.write = mem_write,
.ioctl = mem_ioctl,
.release = mem_release,
.llseek = mem_lseek,
};并初始化功能函數,為對功能函數初始化的,預設為null,
然後初始化裝置 init_cdev(&cdev,&file_ops),指定子產品所有者為子產品本身 cdev.owner = this_module;
初始化完成後,添加子產品:cdev_add(struct cdev* cdev,dev_t dev_num,unsigned count);此時,子產品注冊已經完成。
在裝置解除安裝時需要釋放子產品cdev_del(struct cdev* cdev);
3\需要注意的三個重要結構
1.struct file 在打開檔案時由系統建立,代表目前打開檔案的相關讀寫資訊。在檔案關閉後釋放。重要成員 loff_t f_pos;目前讀寫位置,struct file_operations* f_op;
2.struct innode 結構,表示檔案的實體資訊,一個檔案可以對應多個struct file 但隻能對應一個 struct innode。重要成員 dev_t ir_dev;
3.struct file_operations 是一個函數指針集合,實作驅動相關函數。在裝置添加中,我們講 file_ops結構傳給了cdev結構,但如何再傳給struct file結構體的,還不明确,需要再深入研究。
應用程式在調用庫函數fread時最後都會使用到系統調用read然後關聯vfs_read,最後将file_operation結構裡面的函數關聯起來。
裝置檔案的建立:裝置檔案可以通過兩種方式建立,一種是手工建立裝置檔案 mknod name c major minor
另一種是自動建立裝置檔案,分三部完成:
struct class *myclass ;
class_create(this_module, “my_device_driver”);
device_create(myclass, null, mkdev(major_num, minor_num), null, “my_device”);
這樣的module被加載時,udev daemon(在嵌入式linux中是mdev,自動建立裝置節點實際上是應用層面進行的。 需要在busybox裡面配置号相關選項才可可以)就會自動在/dev下建立my_device裝置檔案。
我們在剛開始寫linux裝置驅動程式的時候,很多時候都是利用mknod指令手動建立裝置節點,實際上linux核心為我們提供了一組函數,可以用來在子產品加載的時候自動在 /dev目錄下建立相應裝置節點,并在解除安裝子產品時删除該節點,當然前提條件是使用者空間移植了udev。
核心中定義了struct class結構體,顧名思義,一個struct class結構體類型變量對應一個類,核心同時提供了class_create(…)函數,可以用它來建立一個類,這個類存放于sysfs下面,一旦建立好了這個類,再調用device_create(…)函數來在/dev目錄下建立相應的裝置節點。這樣,加載子產品的時候,使用者空間中的udev會自動響應
device_create(…)函數,去/sysfs下尋找對應的類進而建立裝置節點。
注意,在2.6較早的核心版本中,device_create(…)函數名稱不同,是class_device_create(…),是以在新的核心中編譯以前的子產品程式有時會報錯,就是因為函數名稱 不同,而且裡面的參數設定也有一些變化。
其中,讀寫等,有從使用者空間向核心空間傳遞位址的,位址在使用前必須做有效性檢測(因為應用空間使用的是虛拟位址,有可能位址已經被釋放了)
檢測方法: _access_ok(void* start,)_access_ok(unsigned long addr, unsigned long size)
其中 copy_from_user(void*to ,void* from,size_t size),copy_to_user 都包含了參數檢測功能,但__puts_user(),__get_user(),函數并未做參數有效性檢測。
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 *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
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 (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, 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 **);
以上隻是一些基礎的講解方面初學者入門,以後将推出信号量,自旋鎖,異步通知等的文章。