Linux高級字符設備驅動
設備Ioctl控制
[日期:2012-05-17] | 來源:Linux社區 作者:yinjiabin | [字體:大 中 小] |
1. Ioctl 用來做什麼?
大部分驅動除了需要具備讀寫設備的能力外,還需要具備對硬件控制的能力。例如,要求設備報告錯誤信息,改變波特率,這些操作常常通過ioctl方法來實現。
1.1 用戶使用方法
在用戶空間,使用ioctl 系統調用來控制設備,原型如下:
int ioctl(int fd,unsigned long cmd,...)
原型中的點表示這是一個可選的參數,存在與否依賴於控制命令(第2 個參數)是否涉及到與設備的數據交互。
1.2 驅動ioctl方法
ioctl 驅動方法有和用戶空間版本不同的原型:
int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
cmd參數從用戶空間傳下來,可選的參數arg 以一個unsigned long 的形式傳遞,不管它是一個整數或一個指針。如果cmd命令不涉及數據傳輸,則第3 個參數arg的值無任何意義。
2. Ioctl實現
2.1 實現Ioctl方法的步驟:
1) 定義命令
2.) 實現命令
2.2 定義命令
在編寫ioctl代碼之前,首先需要定義命令。爲了防止對錯誤的設備使用正確的命令,命令號應該在系統範圍內是唯一的。ioctl 命令編碼被劃分爲幾個位段,include/asm/ioctl.h中定義了這些位字段:類型(幻數),序號,傳送方向,參數的大小。Documentation/ioctl-number.txt文件中羅列了在內核中已經使用了的幻數。
定義ioctl 命令的正確方法是使用4 個位段, 這個列表中介紹的符號定義在<linux/ioctl.h>中:
1) Type
幻數(類型): 表明哪個設備的命令,在參考了ioctlnumber.txt之後選出,8 位寬。
2) Number
序號,表明設備命令中的第幾個,8 位寬
3) Direction
數據傳送的方向,可能的值是_IOC_NONE(沒有數據傳輸),_IOC_READ, _IOC_WRITE。數據傳送是從應用程序的觀點來看待的,_IOC_READ 意思是從設備讀。
4) Size
用戶數據的大小。(13/14位寬,視處理器而定)
內核提供了下列宏來幫助定義命令:
1) _IO(type,nr)
沒有參數的命令
2) _IOR(type,nr,datatype)
從驅動中讀數據
3) _IOW(type,nr,datatype)
寫數據到驅動
4) _IOWR(type,nr,datatype)
雙向傳送,type 和number 成員作爲參數被傳遞。
定義命令(範例)
#define MEM_IOC_MAGIC ‘m’ //定義幻數
#define MEM_IOCSET
_IOW(MEM_IOC_MAGIC, 0, int)
#define MEM_IOCGQSET
_IOR(MEM_IOC_MAGIC, 1, int)
2.3 Ioctl函數實現
定義好了命令,下一步就是要實現Ioctl函數了,Ioctl函數的實現包括如下3個技術環節:
1) 返回值
2) 參數使用
3) 命令操作
2.3.1 Ioctl函數實現(返回值)
Ioctl函數的實現通常是根據命令執行的一個switch語句。但是,當命令號不能匹配任何一個設備所支持的命令時,通常返回-EINVAL(“非法參數”)。
2.3..2 Ioctl函數實現(參數)
如何使用Ioctl中的參數?
如果是一個整數,可以直接使用。如果是指針,我們必須確保這個用戶地址是有效的,因此使用前需進行正確的檢查。
2.3.3 Ioctl函數實現(參數檢查)
不需要檢測:
1) copy_from_user
2) copy_to_user
3) get_user
4) put_user
需要檢測:
1) __get_user
2) __put_user
int access_ok(int type, const void *addr, unsigned long size)
第一個參數是VERIFY_READ 或者VERIFY_WRITE,用來表明是讀用戶內存還是寫用戶內存。addr 參數是要操作的用戶內存地址,size 是操作的長度。如果ioctl 需要從用戶空間讀一個整數,那麼size參數等於sizeof(int)。access_ok 返回一個布爾值: 1 是成功(存取沒問題)和0 是失敗(存取有問題),如果該函數返回失敗, 則Ioctl應當返回–EFAULT 。
3. Ioctl函數實現範例
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg,_IOC_SIZE(cmd)); //why _IOC_READ 對應VERIFY_WRITE ???
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg,_IOC_SIZE(cmd));
if (err)
return -EFAULT;
switch(cmd)
{
case MEM_IOCSQUANTUM: /* Set: arg points to the value */
retval = __get_user(scull_quantum, (int *)arg);
break;
case MEM_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int *)arg);
break;
default:
return –EINVAL;
}
在Linux驅動程序設計中,可以使用等待隊列來實現進程的阻塞,等待隊列可看作保存進程的容器,在阻塞進程時,將進程放入等待隊列,當喚醒進程時,從等待等列中取出進程。
Linux 2.6內核提供瞭如下關於等待隊列的操作:
1、定義等待隊列
wait_queue_head_t my_queue
2、初始化等待隊列
init_waitqueue_head(&my_queue)
3、定義並初始化等待隊列
DECLARE_WAIT_QUEUE_HEAD(my_queue)
4、有條件睡眠
1)wait_event(queue,condition)
當condition(一個布爾表達式)爲真時,立即返回;否則讓進程進入TASK_UNINTERRUPTIBLE模式的睡眠,並掛在queue參數所指定的等待隊列上。
2)wait_event_interruptible(queue,condition)
當condition(一個布爾表達式)爲真時,立即返回;否則讓進程進入TASK_INTERRUPTIBLE的睡眠,並掛在queue參數所指定的等待隊列上。
3)int wait_event_killable(wait_queue_t queue, condition)
當condition(一個布爾表達式)爲真時,立即返回;否則讓進程進入TASK_KILLABLE的睡眠,並掛在queue參數所指定的等待隊列上。
5、無條件睡眠(老版本,建議不再使用)
1)sleep_on(wait_queue_head_t *q)
讓進程進入不可中斷的睡眠,並把它放入等待隊列q。
2)interruptible_sleep_on(wait_queue_head_t *q)
讓進程進入可中斷的睡眠,並把它放入等待隊列q。
6、從等待隊列中喚醒進程
1) wake_up(wait_queue_t *q)
從等待隊列q中喚醒狀態爲TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE 的所有進程。
2) wake_up_interruptible(wait_queue_t *q)
從等待隊列q中喚醒狀態爲TASK_INTERRUPTIBLE 的進程。
1、阻塞型字符設備驅動的功能
當一個設備無法立刻滿足用戶的讀寫請求時應當如何處理? 例如:調用read時沒有數據可讀, 但以後可能會有;或者一個進程試圖向設備寫入數據,但是設備暫時沒有準備好接收數據。應用程序通常不關心這種問題,應用程序只是調用 read 或 write 並得到返回值。驅動程序應當(缺省地)阻塞進程,使它進入睡眠,直到請求可以得到滿足。
2、阻塞方式
1)在阻塞型驅動程序中,Read實現方式如下:如果進程調用read,但設備沒有數據或數據不足,進程阻塞。當新數據到達後,喚醒被阻塞進程。
2)在阻塞型驅動程序中,Write實現方式如下:如果進程調用了write,但設備沒有足夠的空間供其寫入數據,進程阻塞。當設備中的數據被讀走後,緩衝區中空出部分空間,則喚醒進程。
3、非阻塞方式
阻塞方式是文件讀寫操作的默認方式,但應用程序員可通過使用O_NONBLOCK標誌來人爲的設置讀寫操作爲非阻塞方式(該標誌定義在<linux/fcntl.h>中,在打開文件時指定)。
如果設置了O_NONBLOCK標誌,read和write的行爲是不同的。如果進程在沒有數據就緒時調用了read,或者在緩衝區沒有空間時調用了write,系統只是簡單地返回-EAGAIN,而不會阻塞進程。
4、實例分析
程序實現的功能當進程讀文件時,沒有數據可讀,則該進程阻塞。
1)memdev.h源代碼
#ifndef _MEMDEV_H_
#define _MEMDEV_H_
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0 /*預設的mem的主設備號*/
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 /*設備數*/
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif
/*mem設備描述結構體*/
struct mem_dev
{
char *data;
unsigned long size;
wait_queue_head_t inq;
};
#endif /* _MEMDEV_H_ */
2)阻塞型字符驅動memdev.c
#include <linux/module.h>1、什麼是Poll方法,功能是什麼?
#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>
#include "memdev.h"
static mem_major = MEMDEV_MAJOR;
bool have_data = false; /*表明設備有足夠的數據可供讀*/
module_param(mem_major, int, S_IRUGO);
struct mem_dev *mem_devp; /*設備結構體制針*/
struct cdev cdev;
/*文件打開函數*/
int mem_open(struct inode *inode, struct file *filp)
{
struct mem_dev *dev;
/*獲取次設備號*/
int num = MINOR(inode->i_rdev);
if (num >= MEMDEV_NR_DEVS)
return -ENODEV;
dev = &mem_devp[num];
/*將設備描述結構指針賦值給文件私有數據指針*/
filp->private_data = dev;
return 0;
}
/*release函數*/
int mem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*讀函數*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
/*判斷讀位置是否有效*/
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE - p)
count = MEMDEV_SIZE - p;
while (!have_data) /* 沒有數據可讀 ,考慮爲什麼不用if,而用while。答:爲了排除由於中斷喚醒等待隊列,但此時並沒有數據可讀,故次用while和interruptible配合的原因*/
{
/*判斷用戶是否設置了非阻塞方式*/
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN; /*設置了非阻塞方式*/
/*當設置了阻塞方式*/
wait_event_interruptible(dev->inq,have_data);/**當have_data爲真時,立即返回,否則讓進程進入TASK_KILL
的睡眠 並掛在dev->inq隊列上*/
}
/*讀數據到用戶空間*/
if (copy_to_user(buf, (void*)(dev->data + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}
have_data = false; /* 表明不再有數據可讀 */
return ret;
}
/*寫函數*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
/*分析和獲取有效的寫長度*/
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE - p)
count = MEMDEV_SIZE - p;
/*從用戶空間寫數據*/
if (copy_from_user(dev->data + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
have_data = true; /* 有新的數據可讀 */
/* 喚醒讀進程*/
wake_up(&(dev->inq));
return ret;
}
/* seek函數 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{
loff_t newpos;
switch(whence) {
case 0: /* SEEK_SET */
newpos = offset;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + offset;
break;
case 2: /* SEEK_END */
newpos = MEMDEV_SIZE -1 + offset;
break;
default: /* can't happen */
return -EINVAL;
}
if ((newpos<0) || (newpos>MEMDEV_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
/*?文件操作結構體*/
static const struct file_operations mem_fops =
{
.owner = THIS_MODULE,
.llseek = mem_llseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
};
/*設備驅動模塊加載函數*/
static int memdev_init(void)
{
int result;
int i;
dev_t devno = MKDEV(mem_major, 0);
/* 靜態申請設備號*/
if (mem_major)
result = register_chrdev_region(devno, 2, "memdev");
else /* 動態分配設備號 */
{
result = alloc_chrdev_region(&devno, 0, 2, "memdev");
mem_major = MAJOR(devno);
}
if (result < 0)
return result;
/*初始化cdev結構*/
cdev_init(&cdev, &mem_fops);
cdev.owner = THIS_MODULE;
cdev.ops = &mem_fops;
/* 註冊字符設備*/
cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
/* 爲設備描述結構分配內存*/
mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
if (!mem_devp) /*申請失敗*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(mem_devp, 0, sizeof(struct mem_dev));
/*爲設備分配內存*/
for (i=0; i < MEMDEV_NR_DEVS; 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_waitqueue_head(&(mem_devp[i].inq));
}
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return result;
}
/*模塊卸載函數*/
static void memdev_exit(void)
{
cdev_del(&cdev); /*註銷設備*/
kfree(mem_devp); /*釋放設備結構體內存*/
unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/
}
MODULE_AUTHOR("yinjiabin");
MODULE_LICENSE("GPL");
module_init(memdev_init);module_exit(memdev_exit);
3)測試程序源碼app-read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
int main()
{
int fd;
fd_set rds;
int ret;
char Buf[128];
/*初始化Buf*/
strcpy(Buf,"memdev is char dev!");
printf("BUF: %s\n",Buf);
/*打開設備文件*/
fd = open("/dev/memdev0",O_RDWR);
FD_ZERO(&rds);
FD_SET(fd, &rds);
/*清除Buf*/
strcpy(Buf,"Buf is NULL!");
printf("Read BUF1: %s\n",Buf);
ret = select(fd + 1, &rds, NULL, NULL, NULL);
if (ret < 0)
{
printf("select error!\n");
exit(1);
}
if (FD_ISSET(fd, &rds))
read(fd, Buf, sizeof(Buf));
/*檢測結果*/
printf("Read BUF2: %s\n",Buf);
close(fd);
return 0;
}
2、Select系統調用(功能)
Select系統調用用於多路監控,當沒有一個文件滿足要求時,select將阻塞調用進程。
int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set *exceptfds, const struct timeval *timeout)
Select系統調用(參數)
1)Maxfd:
文件描述符的範圍,比待檢測的最大文件描述符大1
2)Readfds:
被讀監控的文件描述符集
3)Writefds:
被寫監控的文件描述符集
4)Exceptfds:
被異常監控的文件描述符集;
5)Timeout:
定時器,Timeout取不同的值,該調用有不同的表現:
1>Timeout值爲0,不管是否有文件滿足要求,都立刻返回,無文件滿足要求返回0,有文件滿足要求返回一個正值。2>Timeout爲NULL,select將阻塞進程,直到某個文件滿足要求
3>Timeout 值 爲 正 整 數 , 就 是 等 待 的 最 長 時 間 , 即select在timeout時間內阻塞進程。
3、Select系統調用(返回值)
Select調用返回時,返回值有如下情況:
1)正常情況下返回滿足要求的文件描述符個數;
2)經過了timeout等待後仍無文件滿足要求,返回值爲0;
3)如果select被某個信號中斷,它將返回-1並設置errno爲EINTR。
4)如果出錯,返回-1並設置相應的errno。
4、Select系統調用(使用方法)
1)將要監控的文件添加到文件描述符集
2)調用Select開始監控
3)判斷文件是否發生變化
系統提供了4個宏對描述符集進行操作:
#include <sys/select.h>
void FD_SET(int fd, fd_set *fdset)
void FD_CLR(int fd, fd_set *fdset)
void FD_ZERO(fd_set *fdset)
void FD_ISSET(int fd, fd_set *fdset)
宏FD_SET將文件描述符fd添加到文件描述符集fdset中;
宏FD_CLR從文件描述符集fdset中清除文件描述符fd;
宏FD_ZERO清空文件描述符集fdset;
在調用select後使用FD_ISSET來檢測文件描述符集fdset中的文件fd發生了變化。
FD_ZERO(&fds); //清空集合
FD_SET(fd1,&fds); //設置描述符
FD_SET(fd2,&fds); //設置描述符
maxfdp=fd1+1; //描述符最大值加1,假設fd1>fd2
switch(select(maxfdp,&fds,NULL,NULL,&timeout))
case -1: exit(-1);break; //select錯誤,退出程序
case 0:break;
default:
if(FD_ISSET(fd1,&fds)) //測試fd1是否可讀
5、poll方法
應用程序常常使用select系統調用,它可能會阻塞進程。這個調用由驅動的 poll 方法實現,原型爲:unsigned int (*poll)(struct file *filp,poll_table *wait)
Poll設備方法負責完成:1)使用poll_wait將等待隊列添加到poll_table中。
2)返回描述設備是否可讀或可寫的掩碼。
位掩碼
1>POLLIN 設備可讀
2>POLLRDNORM數據可讀
3>POLLOUT\設備可寫
4>POLLWRNORM數據可寫
設備可讀通常返回(POLLIN|POLLRDNORM )
設備可寫通常返回(POLLOUT|POLLWRNORM )
6、範例
static unsigned int mem_poll(struct file *filp,poll_table *wait)7、工作原理
{struct scull_pipe *dev =filp->private_data;}
unsigned int mask =0;
/* 把等待隊列添加到poll_table */
poll_wait(filp,&dev->inq,wait);
/*返回掩碼*/
if (有數據可讀)
mask = POLLIN |POLLRDNORM;/*設備可讀*/
return mask;
Poll方法只是做一個登記,真正的阻塞發生在select.c 中的 do_select函數。
8、實例分析
1)poll型設備驅動memdev.h源碼
#ifndef _MEMDEV_H_
#define _MEMDEV_H_
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0 /*預設的mem的主設備號*/
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 /*設備數*/
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif
/*mem設備描述結構體*/
struct mem_dev
{
char *data;
unsigned long size;wait_queue_head_t inq;
};
#endif /* _MEMDEV_H_ */
2)Poll型設備驅動memdev.c源碼
#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>
#include <linux/poll.h>
#include "memdev.h"
static mem_major = MEMDEV_MAJOR;
bool have_data = false; /*表明設備有足夠數據可供讀*/
module_param(mem_major, int, S_IRUGO);
struct mem_dev *mem_devp; /*設備結構體指針*/
struct cdev cdev;
/*文件打開函數*/
int mem_open(struct inode *inode, struct file *filp)
{
struct mem_dev *dev;
/*獲取次設備號*/
int num = MINOR(inode->i_rdev);
if (num >= MEMDEV_NR_DEVS)
return -ENODEV;
dev = &mem_devp[num];
/*將設備描述結構指針賦值給文件私有數據指針*/
filp->private_data = dev;
return 0;
}
/*文件釋放函數*/
int mem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*讀函數*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
/*判斷讀位置是否有效*/
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE - p)
count = MEMDEV_SIZE - p;
while (!have_data) /* 沒有數據可讀,考慮爲什麼不用if,而用while */
{
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
wait_event_interruptible(dev->inq,have_data);
}
/*讀數據到用戶空間*/
if (copy_to_user(buf, (void*)(dev->data + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}
have_data = false; /* 表明不再有數據可讀 */
/* 喚醒寫進程 */
return ret;
}
/*寫函數*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
/*分析和獲取有效的寫長度*/
if (p >= MEMDEV_SIZE)
return 0;
if (count > MEMDEV_SIZE - p)
count = MEMDEV_SIZE - p;
/*從用戶空間寫入數據*/
if (copy_from_user(dev->data + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
have_data = true; /* 有新的數據可讀 */
/* 喚醒讀進程 */
wake_up(&(dev->inq));
return ret;
}
/* seek文件定位函數 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{
loff_t newpos;
switch(whence) {
case 0: /* SEEK_SET */
newpos = offset;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + offset;
break;
case 2: /* SEEK_END */
newpos = MEMDEV_SIZE -1 + offset;
break;
default: /* can't happen */
return -EINVAL;
}
if ((newpos<0) || (newpos>MEMDEV_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
unsigned int mem_poll(struct file *filp, poll_table *wait)
{
struct mem_dev *dev = filp->private_data;
unsigned int mask = 0;
/*將等待隊列添加到poll_table表中 */
poll_wait(filp, &dev->inq, wait);
if (have_data)
mask |= POLLIN | POLLRDNORM; /* readable */
return mask;
}
/*文件操作結構體*/
static const struct file_operations mem_fops =
{
.owner = THIS_MODULE,
.llseek = mem_llseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
.poll = mem_poll,
};
/*設備驅動模塊加載函數*/
static int memdev_init(void)
{
int result;
int i;
dev_t devno = MKDEV(mem_major, 0);
/* 靜態申請設備號*/
if (mem_major)
result = register_chrdev_region(devno, 2, "memdev");
else /* 動態分配設備號 */
{
result = alloc_chrdev_region(&devno, 0, 2, "memdev");
mem_major = MAJOR(devno);
}
if (result < 0)
return result;
/*初始化cdev結構*/
cdev_init(&cdev, &mem_fops);
cdev.owner = THIS_MODULE;
cdev.ops = &mem_fops;
/* 註冊字符設備 */
cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
/* 爲設備描述結構分配內存*/
mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
if (!mem_devp) /*申請失敗*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(mem_devp, 0, sizeof(struct mem_dev));
/*爲設備分配內存*/
for (i=0; i < MEMDEV_NR_DEVS; 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_waitqueue_head(&(mem_devp[i].inq));
//init_waitqueue_head(&(mem_devp[i].outq));
}
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return result;
}
/*模塊卸載函數*/
static void memdev_exit(void)
{
cdev_del(&cdev); /*註銷設備*/
kfree(mem_devp); /*釋放設備結構體內存*/
unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/
}
MODULE_AUTHOR("David Xie");
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);
3)測試程序app-read.c源碼
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
int main()
{
int fd;
fd_set rds; //聲明描述符集合
int ret;
char Buf[128];
/*初始化Buf*/
strcpy(Buf,"memdev is char dev!");
printf("BUF: %s\n",Buf);
/*打開設備文件*/
fd = open("/dev/memdev0",O_RDWR);
FD_ZERO(&rds); //清空描述符集合
FD_SET(fd, &rds); //設置描述符集合
/*清除Buf*/
strcpy(Buf,"Buf is NULL!");
printf("Read BUF1: %s\n",Buf);
ret = select(fd + 1, &rds, NULL, NULL, NULL);//調用select()監控函數
if (ret < 0)
{
printf("select error!\n");
exit(1);
}
if (FD_ISSET(fd, &rds)) //測試fd1是否可讀
read(fd, Buf, sizeof(Buf));
/*檢測結果*/
printf("Read BUF2: %s\n",Buf);
close(fd);
return 0;
}