簡單字符設備驅動程序
linux驅動程序開發步驟
1)查看原理圖、數據手冊,了解設備的操作方法。
2)在內核中找到相近的驅動程序,以它為模板進行開發,有時候需要從零開始。
3)實現驅動程序的初始化:比如向內核注冊驅動程序,這樣應用程序傳入文件名時,內核才能找到相應的驅動程序。
4)設計所要實現的操作,比如open,read,write,close。
5)實現中斷服務(不是必需的)
6)編譯該驅動程序到中,或者用insmod命令加載。
7)測試驅動程序。
驅動框架
應用程序API接口read,open,write 是對應驅動程序上的led_read,led_open,led_write的
1)寫出驅動函數led_read,led_open,led_write
每個系統調用,驅動程序怎樣和它對應起來??
2)定義一個file_operations結構,和應用層API對接,
Linux系統怎么知道去調用哪個驅動程序的file_operations??
3)根據設備類型和主設備號
例如應用層程序open("dev/led");根據打開的文件屬性中的根據設備類型和主設備號,找到內核中注冊了的file_operations結構來判斷是哪個驅動程序。
所以,要將注冊函數將主設備號和file_operations一起注冊到內核
4)最后把驅動程序加載到內核
如module_init(memdev_init);
加載時,就調用驅動初始化函數,向內核注冊
|--------------------------------------------------------------------|
| APP:open("dev/xxx");write();read() |
| 文件屬性:設備類型 c 主設備號 11 |
|--------------------------------------------------------------------|
| C庫 |
|--------------------------------------------------------------------|
| VFS 內核 |
| chrdev數組 0,1,2...11... |
| 找到對應的 file_operations |
|--------------------------------------------------------------------|
| 1)led_read();led_write();led_open(); |
|驅動程序:2)定義了file_operations 關聯APP函數 |
| 3)chardev_region()注冊到內核 |
|--------------------------------------------------------------------|
注意: chardev_region()是2.4的接口, struct cdev是2.6的接口
一、重要知識點
1. 主次設備號
dev_t
dev_t是內核中用來表示設備編號的數據類型;
int MAJOR(dev_t dev)
int MINOR(dev_t dev)
這兩個宏抽取主次設備號。
dev_t MKDEV(unsigned int major, unsigned int minor)
這個宏由主/次設備號構造一個dev_t結構。
2. 分配和釋放設備號
靜態申請設備號
int register_chardev_region(dev_t first,unsigned int count, char *name) //分配成功時,返回0,錯誤時,返回一個負的錯誤碼
參數:
first 要注冊的第一個設備號
count 要注冊的設備號個數
name 設備名
Int alloc_chardev_region(dev_t *dev,unsigned int firstminor, unsigned int count, char *name)
//分配成功時,返回0,錯誤時,返回一個負的錯誤碼
//alloc_chardev_region函數調用成功后,會把得到的設備號放入dev中
參數:
dev 分配到的設備號
firstminor 起始次設備號
count、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結構保存了字符設備驅動程序的方法。它是一個在 <linux/fs.h> 中定義的 struct file_operations 結構,這是一個內核結構,不會出現在用戶空間的程序中,它定義了常見文件 I/O 函數的入口。系統調用函數通過內核,最終調用對應的 struct file_operations 結構的接口函數(例如,open() 文件操作是通過調用對應文件的file_operations 結構的 open 函數接口而被實現)。當然,每個設備的驅動程序不一定要實現其中所有的函數操作,若不需要定義實現時,則只需將其設為 NULL 即可。
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);
解鎖自旋鎖
6. 驅動程序的加載和卸載
在2.6的內核中,模塊的擴展名為.ko
使用insmod命令加載,當使用時,模塊的初始化函數被調用,它用來向內核注冊驅動程序
rmmod卸載
lsmod查看內核中已經加載了哪些模塊
module_init(memdev_init);
module_exit(memdev_exit);
注意:模塊有多種,例如文件系統也可以編譯為模塊,不一定只是驅動程序。
- 二、驅動代碼
- #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 /*預設的mem的主設備號*/
- #define MEMDEV_NUM 2 /*設備數*/
- #define MEMDEV_SIZE 1024 //分配的內存大小
- struct mem_dev
- {
- unsignedint size;
- char*data;
- structsemaphore sem;
- };
- static int mem_major = MEMDEV_MAJOR; /*預設的mem的主設備號*/
- struct cdev mem_cdev;
- struct mem_dev *mem_devp; /*設備結構體指針*/
- /*文件打開函數*/
- static int mem_open(struct inode *inode,struct file *filp)
- {
- struct mem_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_operations mem_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)
- {
- int result;
- int err;
- int i;
- //申請設備號
- dev_t devno = 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); /*初始化cdev結構*/
- 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沒有任何效果,但是可由外部檢查軟件使用,用來尋找對用戶空間地址錯誤使用。
from:http://blog.csdn.net/jianchi88/article/details/6797057
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成