高級字符設備驅動在簡單字符驅動的基礎上添加ioctl方法、阻塞非阻塞讀寫、poll方法、和自動創建設備文件的功能。
一、重要知識點
1.ioctl
ioctl命令:使用4個字段定義一個ioctl命令,包括
type: 幻數,一般使用一個字符定義,在內核中唯一。
number: 序數。
direction: 數據傳輸方向,當不涉及數據傳輸時,此字段無效。
size: 所涉及用戶數據的大小,當不涉及數據傳輸時,此字段無效。
_IOC_NONE
_IOC_READ
_IOC_WRITE
“方向”字段的可能值。“讀”和“寫”是不同的位,可以用“OR”在一起指定讀寫。
_IOC(dir, type, size)
_IO(type,nr)
_IOR(type, nr, size)
_IOW(type, nr, size)
用于生產ioctl命令的宏
_IOC_DIR(cmd)
_IOC_TYPE(cmd)
_IOC_NR(cmd)
_IOC_SIZE(cmd)
用于解碼ioctl命令的宏
intaccess_ok(int type, const void *addr, unsigned long size)
這個函數驗證指向用戶空間的指針是否可用,如果允許訪問,access_ok返回非0值。
int put_user(datum, ptr)
int get_user(local, ptr)
int __put_user(datum, ptr)
int __get_user(local, ptr)
用于向(或從)用戶空間保存(或獲取)單個數據項的宏。傳送的字節數目由sizeof(*ptr)決定。前兩個要先調用access_ok,后兩個(__put_user和__get_user)則假設access_ok已經被調用過了。
2.阻塞型I/O
typedef struct {/*…..*/} wait_queue_head_t
void init_waitqueue_head(wait_queue_head_t*queue)
DECLARE_WAIT_QUEUE_HEAD(queue)
預先定義的Linux內核等待隊列類型。wait_queue_head_t類型必須顯示地初始化,初始化方法可以在運行時調用init_waitqueue_head,或在編譯時DECLARE_WAIT_QUEUE_HEAD。
void wait_event((wait_queue_head_t q, intcondition)
int wait_event_interruptible(wait_queue_head_tq, int condition)
int wait_event_timeout(wait_queue_head_t q,int condition, int time)
int wait_event_interruptible_timeout(wait_queue_head_tq, int condition, int time)
使進程在指定的隊列上休眠,直到給定的condition值為真。
void wake_up(struct wait_queue **q)
void wake_up_interruptible(structwait_queue **q)
這些函數喚醒休眠在隊列q上的進程。_interruptible形式的函數只能喚醒可中斷的進程。在實踐中約定做法是在使用wait_event時用wake_up,而在使用wait_event_interruptible時使用wake_up_interruptible。
3.poll方法
poll方法分兩步處理,第一步調用poll_wait指定等待隊列,第二步返回是否可操作的掩碼。
POLLIN表示設備可讀的掩碼,POLLRDORM表示數據可讀的掩碼。POLLOUT表示設備可寫的掩碼,POLLWRNORM表示數據可讀的掩碼。一般同時返回POLLIN和POLLRDORM或者POLLOUT和POLLWRNORM。
4.select系統調用
原型為intselect(int mafdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set*restrict exceptfds, struct timeval *restrict tvptr)
返回值:就緒的描述符數,若超時則返回0,若出錯則返回-1
void FD_ISSET(int fd, fd_set *fdset)
void FD_CLR(int fd, fd_set *fdset)
void FD_SET(int fd, fd_set *fdset)
void FD_ZERO(fd_set *fdset)
調用FD_ZERO將一個指定的fd_set變量的所有位設置為0。調用FD_SET設置一個fd_set變量指定位。調用FD_CLR則將一指定位清除。最后,調用FD_ISSET測試一指定位是否設置。
5.自動創建設備文件
struct class *class_create(struct module*owner, const char *name)
struct device *device_create(struct class*class, struct device *parent, dev_t devt, const char *fmt, ...)??????????
通過這兩個函數可以專門用來創建一個字符設備文件節點,class_create 第一個參數指定所有者,第二參數指定類得名字。class_device_create第一個參數指定第一個參數指定所要創建的設備所從屬的類,第二個參數是這個設備的父設備,如果沒有就指定為NULL,第三個參數是設備號,第四個參數是設備名稱。
二、驅動代碼
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#include?
#define?MEMDEV_MAJOR?251??
#define?MEMDEV_NUM?2??
#define?MEMDEV_SIZE?1024??
//定義設備IOCTL命令??
#define?MEMDEV_IOC_MAGIC?'k'??
#define?MEMDEV_IOC_NR?2??
#define?MEMDEV_IOC_PRINT_IO(MEMDEV_IOC_MAGIC,?0)??
#define?MEMDEV_IOC_RD_IOR(MEMDEV_IOC_MAGIC,?1,?int)??
#define?MEMDEV_IOC_WT_IOW(MEMDEV_IOC_MAGIC,?2,?char)??
struct?mem_dev??
{??
unsignedint?size;??
char*data;??
structsemaphore?sem;??
wait_queue_head_t??inque;??
};??
static?int?mem_major?=?MEMDEV_MAJOR;??
struct?cdev?mem_cdev;??
struct?mem_dev?*mem_devp;??
bool?havedata?=?false;??
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;??
while(!havedata)??
{??
up(&dev->sem);??
if(filp->f_flags&?O_NONBLOCK)??
return-EAGAIN;??
printk("readyto?go?sleep");??
if(wait_event_interruptible(dev->inque,havedata))//等待數據??
return-ERESTARTSYS;??
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%ld?bytes?from?dev\n",?count);??
havedata=?false;//數據已經讀出??
}??
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%ld?bytes?to?dev\n",?count);??
havedata=?true;??
wake_up_interruptible(&dev->inque);//喚醒等待數據的隊列??
}??
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?int?mem_ioctl(struct?inode?*inode,struct?file?*filp,?unsigned?int?cmd,?unsigned?long?arg)??
{??
interr?=?0,?ret?=?0;??
intioarg?=?0;??
charrdarg?=?'0';??
//參數檢查??
if(_IOC_TYPE(cmd)!=?MEMDEV_IOC_MAGIC)//參數類型檢查??
return-ENOTTY;??
if(_IOC_NR(cmd)>?MEMDEV_IOC_NR)//參數命令號檢查??
return-ENOTTY;??
//用戶空間指針有效性檢查??
if(_IOC_DIR(cmd)&?_IOC_READ)??
err=?!access_ok(VERIFY_WRITE,?(void?__user?*)arg,?_IOC_SIZE(cmd));??
elseif(_IOC_DIR(cmd)?&?_IOC_WRITE)??
err=?!access_ok(VERIFY_WRITE,?(void?__user?*)arg,?_IOC_SIZE(cmd));??
if(err)??
return-ENOTTY;??
//根據命令執行操作??
switch(cmd)??
{??
case?MEMDEV_IOC_PRINT:??
printk("memdevioctl?print?excuting...\n");??
break;??
caseMEMDEV_IOC_RD:??
ioarg=?1024;??
ret??=?__put_user(ioarg,?(int?*)arg);//用戶空間向內核空間獲得數據??
printk("memdevioctl?read?excuting...?\n");??
break;??
caseMEMDEV_IOC_WT:??
ret=?__get_user(rdarg,?(char?*)arg);//用戶空間向內核空間傳輸數據??
printk("memdevioctl?write?excuting...?arg:%c\n",?rdarg);??
break;??
default:??
return-ENOTTY;??
}??
returnret;??
}??
static?unsigned?int?mem_poll(struct?file*filp,?poll_table?*wait)??
{??
structmem_dev?*dev;??
unsignedint?mask?=?0;??
dev=?filp->private_data;??
if(down_interruptible(&dev->sem))//鎖定互斥信號量??
return-ERESTARTSYS;??
poll_wait(filp,&dev->inque,?wait);??
if(havedata)??
mask|=?POLLIN?|?POLLRDNORM;//返回可讀掩碼??
up(&dev->sem);//釋放信號量??
returnmask;??
}??
static?const?struct?file_operationsmem_fops?=?{??
.owner=?THIS_MODULE,??
.open=?mem_open,??
.write=?mem_write,??
.read=?mem_read,??
.release=?mem_release,??
.llseek=?mem_llseek,??
.ioctl=?mem_ioctl,??
.poll=?mem_poll,??
};??
static?int?__init?memdev_init(void)??
{??
intresult;??
interr;??
inti;??
structclass?*memdev_class;??
//申請設備號??
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(result0){??
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("add?cdev?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
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);//初始化互斥鎖??
//初始化等待隊列??
init_waitqueue_head(&mem_devp[i].inque);??
}??
//自動創建設備文件??
memdev_class=?class_create(THIS_MODULE,?"memdev_driver");??
device_create(memdev_class,NULL,?MKDEV(mem_major,?0),?NULL,?"memdev0");??
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");??
?
評論
查看更多