/**
* Author:hasen
* 参考 :《linux设备驱动开发详解》
* 简介:android小菜鸟的linux
* 设备驱动开发学习之旅
* 主题:支持阻塞的设备驱动
* Date:2014-11-05
*/
/*重点已在注释前加上了@@,使用搜索"@@"可以查看重点代码部分*/
/**
* 现在实现这样一个设备,globalfifo,只有当FIFO中有数据的时候(即有进程把数据写入了FIFO
* 而且没有被读进程读空),读进程才能把数据读出,而且读出的数据慧聪globalfifo的全局内存中
* 拿掉;只有当FIFO非满时(即还有一些空间未被写,或写满后被读进程从这个FIFO中读出了数据),
* 写进程才能往这个FIFO中写入数据。
*/
/*globalfifo中,读FIFO将会唤醒写FIFO,写FIFO将会唤醒读FIFO,
所以需要两个等待队列头, 分别对应读和写*/
struct globalfifo_dev{
struct cdev cdev ;/*cdev结构体*/
unsigned int current_len ;/*当前fifo的有效长度*/
unsigned char mem[GLOBALFIFO_SIZE] ;/*全局内存*/
struct semaphore sem ;/*并发控制用的信号量*/
wait_queue_head_t r_wait ;/*@@阻塞读用的等待队列头*/
wait_queue_head_t w_wait ;/*@@阻塞写用的等待队列头*/
}
globalfifo_dev *globalfifo_devp ;
int gobalfifo_init(void)
{
int ret ;
dev_t devno = MKDEV(globalfifo_major,0) ;
/*申请设备号*/
if(globalfifo_major)
ret = register_chrdev_region(devno,1,"globalfifo") ;
else{
ret = alloc_chrdev_region(&devno,0,1,"globalfifo") ;
globalfifo_major = MAJOR(devno) ;
}
if(dev < 0)
return ret ;
/*动态申请设备结构体内存*/
globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev),GFP_KERNEL) ;
if(globalfifo_devp){ /*申请失败*/
ret = -ENOMEM ;
goto fail_malloc ;
}
memset(globalfifo_devp,0,sizeof(struct globalfifo_dev)) ;
globalfifo_setup_dev(globalfifo_devp ,0) ;
init_MUTEX(&globalfifo_devp->sem) ;/*初始化信号量*/
init_waitqueue_head(&globalfifo_devp->r_wait) ;/*@@初始化读等待队列头*/
init_waitqueue_head(&globalfifo_devp->w_wait) ;/*@@初始化写等待队列头*/
return 0 ;
fail_malloc :unregister_chrdev_region(devno,1) ;
return ret ;
}
/*globalfifo读函数*/
static ssize_t globalfifo_read(struct file *filp,char __user *buf,
size_t count ,loff_t *ppos)
{
int ret ;
struct globalfifo_dev *dev = filp->private_data ;/*获得设备结构体指针*/
DECLARE_WAITQUEUE(wait,current) ; /*@@定义等待队列*/
down(&dev->sem) ; /*获得信号量*/
add_wait_queue(&dev->r_wait,&wait) ;/*@@进入读等待队列头*/
/*@@等待FIFO非空*/
while(dev->current_len == 0){
if(filp->f_flags & O_NONBLOCK){
ret = -EAGAIN ;
goto out ;
}
__set_current_state(TASK_INTERRUPTIBLE) ;/*@@改变进程状态为睡眠*/
up(&dev->sem) ;
schedule() ;/*@@调度其他进程执行*/
if(signal_pending(current)){ /*如果是信号唤醒的*/
ret = -ERESTARTSYS ;
goto out2 ;
}
down(&dev->sem) ;
}
/*拷贝到用户空间*/
if(count >dev->current_len)
count = dev->current_len ;
if(copy_to_user(buf,dev->mem,count)){
ret = -EFAULT ;
goto out ;
}else{
memcpy(dev->mem,dev->mem + count,dev->current_len - count);/*fifo数据前移*/
dev->current_len -= count ; /*有效数据长度减少*/
printk(KERN_INFO "read %d bytes,current_len:%d\n",count,dev->current_len) ;
wake_up_interruptible(&dev->w_wait) ;/*@@唤醒写等待程序*/
ret = count ;
}
out :up(&dev->sem) ; /*释放信号量*/
out2: remove_wait_queue(&dev->r_wait,&wait) ; /*@@移除等待队列*/
set_current_state(TASK_RUNNING) ; /*@@切换当前进程状态*/
return ret ;
}
/*globalfifo写函数*/
static ssize_t globalfifo_write(static file *filp,const char __user *buf,
size_t count,loff_t *ppos)
{
struct globalfifo_dev *dev = filp->private_data ; /*获得设备结构体指针*/
int ret ;
DECLARE_WAITQUEUE(wait,current) ; /*@@定义等待队列*/
down(&dev->sem) ;/*获取信号量*/
add_wait_queue(&dev->w_wait,&wait) ;/*@@进入写等待队列头*/
/*@@等待FIFO非满*/
while(dev->current_len == GLOBALFIFO_SIZE){
if(filp->f_flags & O_NONBLOCK){
/*如果是非阻塞访问*/
ret = -EAGAIN ;
goto out ;
}
__set_current_state(TASK_INTERRUPTIBLE) ;/*@@改变进程状态为睡眠*/
up(&dev->sem) ;
schedule() ;/*@@调度其他进程执行*/
if(signal_pending(current)){
/*如果是信号唤醒*/
ret = -ERESTARTSYS ;
goto out2;
}
down(&dev->sem) ; /*获得信号量*/
}
/*从用户空间拷贝到内核空间*/
if(count > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len ;
if(copy_from_user(dev->mem + dev->current_len,buf,count)){
ret = -EFAULT ;
goto out ;
}else{
dev->current_len += count ;
printk(KERN_INFO "write %d bytes ,current_len:%d\n",count,dev->current_len) ;
wake_up_interruptible(&dev->r_wait) ; /*@@唤醒读等待队列*/
ret = count ;
}
out : up(&dev->sem) ; /*释放信号量*/
out2: remove_wait_queue(&dev->w_wait,&wait) ;/*@@移除等待队列*/
set_current_state(TASK_RUNNING) ;/*改变进程状态*/
return ret ;
}
在用户空间验证globalfifo的读写
1、在globalfifo所在文件夹下运行make命令编译得到globalfifo.ko。接着insmod模块:
hasen@hasen-pc$ sudo su
hasen@hasen-pc# insmod globalfifo.ko
2、创建设备文件节点"/dev/globalfifo"
hasen@hasen-pc# mknod /dev/globalfifo c 249 0
3、启动两个进程,一个进程"cat /dev/globalfifo &"在后台执行,一个进程"echo 字符串 /dev/globalfifo"在前台执行
hasen@hasen-pc# cat /dev/globalfifo &
hasen@hasen-pc# echo 'hello world!' /dev/globalfifo
会看到:每当echo进程向/dev/globalfifo写入一串数据,cat进程就立即将该串数据显示出来。