Linux裝置驅動工程師之路——簡單字元裝置驅動程式
K-Style
轉載請註明來自于衡陽師範學院08電2 K-Style http://blog.csdn.net/ayangke,QQ:843308498 郵箱:yangkeemail@qq.com
一、重要知識點
1. 主次裝置號
dev_t
dev_t是核心中用來表示裝置編號的資料類型;
int MAJOR(dev_t dev)
int MINOR(dev_t dev)
這兩個宏抽取主次裝置號。
dev_t MKDEV(unsigned int major, unsignedint minor)
這個宏由主/次裝置號構造一個dev_t結構。
2. 分配和釋放裝置號
int register_chardev_region(dev_t first,unsigned int count, char *name)
靜態申請裝置號。
Int alloc_chardev_region(dev_t *dev,unsigned int firstminor, unsigned int count, char *name)
動態申請裝置號,注意第一個參數是傳地址,而靜態則是傳值。
3. 幾種重要的資料結構
struct file
file結構代表一個開啟的檔案,它由核心在open時建立,並傳遞給該檔案上進行操作的所有函數,直到最後的close函數。
file結構private_data是跨系統調用時儲存狀態資訊非常有用的資源。
file結構的f_ops 儲存了檔案的當前讀寫位置。
struct inode
核心用inode代表一個磁碟上的檔案,它和file結構不同,後者表示開啟的檔案描述符。對於單個檔案,可能會有許多個表示開啟檔案的檔案描述符file結構,但他們都指單個inode結構。inode的dev_t i_rdev成員包含了真正的裝置編號,struct cdev *i_cdev包含了指向struct cdev結構的指標。
struct file_operations
file_operations結構儲存了字元裝置驅動程式的方法。
4. 字元裝置的註冊和登出
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *dev, structfile_operations *fops);
int cdev_add(struct cdev *dev, dev_t num,unsigned int count);
void cdev_del(struct cdev *dev);
用來管理cdev結構的函數,核心中使用該結構表示字元裝置。注意cdev_add函數的count參數為次裝置的個數,要想擁有多個次裝置,就必須將該參數設為次裝置的個數。
5. 並發處理
訊號量和自旋鎖的區別,使用訊號量時當調用進程試圖獲得一個鎖定了的鎖時會導致進程睡眠,而自旋鎖則是一直循法的等待一直到該鎖解鎖了為止。
1)訊號量
DECLARE_MUTEX(name);
DECLARE_MUTEX_LOCKED(name);
聲明和初始化用在互斥模式中的訊號量的兩個宏
void init_MUTEX(struct semaphore *sem)
void init_MUTEX_LOCKER(struct semaphore*sem);
這兩個函數可以在運行時初始化訊號量
void down(struct semaphore *sem);
int down_interruptible(struct semaphore*sem);
int down_trylock(struct semahpore *sem);
void up(struct semaphore *sem);
鎖定和解鎖訊號量。如果必要,down會將調用進程置於不可中斷的休眠狀態;相反,down_interruptible可被訊號中斷。down_trylock不會休眠,並且會在訊號量不可用時立即返回。鎖定訊號量的代碼最後必須使用up解鎖該訊號量。
2)自旋鎖
spionlock_t lock = SPIN_LOCK_UNLOCKED;
spin_lock_init(spinlock_t *lock);
初始化自旋鎖的兩種方式。
voidspin_lock(spinlock_t *lock);
鎖定自旋鎖
voidspin_unlock(spinlock_t *lock);
解鎖自旋鎖
二、驅動代碼
#include <linux/module.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/mm.h>#include <linux/sched.h>#include <linux/init.h>#include <linux/cdev.h>#include <asm/io.h>#include <asm/system.h>#include <asm/uaccess.h> #define MEMDEV_MAJOR 251#define MEMDEV_NUM 2#define MEMDEV_SIZE 1024 struct mem_dev{ unsignedint size; char*data; structsemaphore sem;}; static int mem_major = MEMDEV_MAJOR; struct cdev mem_cdev;struct mem_dev *mem_devp; static int mem_open(struct inode *inode,struct file *filp){ structmem_dev *dev; unsignedint num; printk("mem_open.\n"); num= MINOR(inode->i_rdev);//獲得次裝置號 if(num> (MEMDEV_NUM -1)) //檢查次裝置號有效性 return-ENODEV; dev= &mem_devp[num]; filp->private_data= dev; //將裝置結構儲存為私人資料 return0;} static int mem_release(struct inode *inode,struct file *filp){ printk("mem_release.\n"); return0;} static ssize_t mem_read(struct file *filp,char __user *buf, size_t size, loff_t *ppos){ intret = 0; structmem_dev *dev; unsignedlong p; unsignedlong count; printk("mem_read.\n"); dev= filp->private_data;//獲得裝置結構 count= size; p= *ppos; //檢查位移量和資料大小的有效性 if(p> MEMDEV_SIZE) return0; if(count> (MEMDEV_SIZE-p)) count= MEMDEV_SIZE - p; if(down_interruptible(&dev->sem))//鎖定互斥訊號量 return -ERESTARTSYS; //讀取資料到使用者空間 if(copy_to_user(buf,dev->data+p, count)){ ret= -EFAULT; printk("copyfrom user failed\n"); } else{ *ppos+= count; ret= count; printk("read%d bytes from dev\n", count); } up(&dev->sem);//解鎖互斥訊號量 returnret;} static ssize_t mem_write(struct file *filp,const char __user *buf, size_t size, loff_t *ppos)//注意:第二個參數和read方法不同{ intret = 0; structmem_dev *dev; unsignedlong p; unsignedlong count; printk("mem_write.\n"); dev= filp->private_data; count= size; p= *ppos; if(p> MEMDEV_SIZE) return0; if(count> (MEMDEV_SIZE-p)) count= MEMDEV_SIZE - p; if(down_interruptible(&dev->sem))//鎖定互斥訊號量 return-ERESTARTSYS; if(copy_from_user(dev->data+p,buf, count)){ ret= -EFAULT; printk("copyfrom user failed\n"); } else{ *ppos+= count; ret= count; printk("write%d bytes to dev\n", count); } up(&dev->sem);//解鎖互斥訊號量 returnret;} static loff_t mem_llseek(struct file *filp,loff_t offset, int whence){ intnewpos; printk("mem_llseek.\n"); switch(whence) { case0: newpos= offset; break; case1: newpos= filp->f_pos + offset; break; case2: newpos= MEMDEV_SIZE - 1 + offset; break; default: return-EINVAL; } if((newpos<0)|| (newpos>(MEMDEV_SIZE - 1))) return-EINVAL; filp->f_pos= newpos; returnnewpos;}static const struct file_operationsmem_fops = { .owner= THIS_MODULE, .open= mem_open, .write= mem_write, .read= mem_read, .release= mem_release, .llseek= mem_llseek,}; static int __init memdev_init(void){ intresult; interr; inti; //申請裝置號 dev_tdevno = MKDEV(mem_major, 0); if(mem_major) result= register_chrdev_region(devno, MEMDEV_NUM, "memdev");//注意靜態申請的dev_t參數和動態dev_t參數的區別 else{ //靜態直接傳變數,動態傳變數指標 result= alloc_chrdev_region(&devno, 0, MEMDEV_NUM, "memdev"); mem_major= MAJOR(devno); } if(result< 0){ printk("can'tget major devno:%d\n", mem_major); returnresult; } //註冊裝置驅動 cdev_init(&mem_cdev,&mem_fops); mem_cdev.owner= THIS_MODULE; err= cdev_add(&mem_cdev, MKDEV(mem_major, 0), MEMDEV_NUM);//如果有N個裝置就要添加N個裝置號 if(err) printk("addcdev faild,err is %d\n", err); //分配裝置記憶體 mem_devp= kmalloc(MEMDEV_NUM*(sizeof(struct mem_dev)), GFP_KERNEL); if(!mem_devp){ result = - ENOMEM; goto fail_malloc; } memset(mem_devp,0, MEMDEV_NUM*(sizeof(struct mem_dev))); for(i=0;i<MEMDEV_NUM; i++){ mem_devp[i].size= MEMDEV_SIZE; mem_devp[i].data= kmalloc(MEMDEV_SIZE, GFP_KERNEL); memset(mem_devp[i].data,0, MEMDEV_SIZE); init_MUTEX(&mem_devp[i].sem);//初始化互斥鎖 } returnresult; fail_malloc: unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM); returnresult;} static void memdev_exit(void){ cdev_del(&mem_cdev); unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM);//注意釋放的裝置號個數一定要和申請的裝置號個數儲存一致 //否則會導致裝置號資源流失 printk("memdev_exit\n");} module_init(memdev_init);module_exit(memdev_exit); MODULE_AUTHOR("Y-Kee");MODULE_LICENSE("GPL");
三、疑點痛點
1.
__init
__initdata
__exit
__exitdata
僅用於模組初始化或清除階段的函數(__init和__exit)和資料(__initdata和__exitdata)標記。標記為初始化項目會在初始化結束後丟棄;而退出在核心未被配置為可卸載模組的情況下被簡單的丟棄。被標記為__exit的函數只能在模組卸載或者系統關閉時被調用,其他任何用法都是錯誤的。核心通過對應的目標對象放置在可執行檔的特殊ELF段中而讓這些標記起作用。
2.static
初始化函數應該被聲明為static,因為這種函數在特定檔案之外沒有其他意義。因為一個模組函數要對核心其他部分課件,則必須顯示匯出,因此這並不是什麼強制性規則。
3. struct module *owner
核心使用這個欄位以避免在模組操作正在使用時卸載該模組。幾乎在所有的情況下,該成員都會被初始化為THIS_MODULE。它是定義在<linux/module.h>中的一個宏。
4 __user
我們會注意到許多參數包括含有__user字串,它其實是一種形式的文檔而已,表面指標是一個使用者指標,因此不能被直接用。對通常的編譯來講,__user沒有任何效果,但是可由外部檢查軟體使用,用來尋找對使用者空間地址錯誤使用。