700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序

Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序

时间:2019-03-20 20:37:42

相关推荐

Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序

字符设备模拟pipe的驱动程序

让我们用一个”pipe“的设备驱动去结束简单字符设备吧(这里所说的pipe并非标准的pipe只是模拟了一个从一端写入从另一端写入的设备)

测试代码1 测试代码2

设计思路

用一个图来说明(可是画了很久哟)

简单说来就是一个进程写入缓冲区,另一个进程可以读出,读出后原buffer中的数据被置为无效值,

自定义一个结构

[cpp]view plaincopy#defineMAX_SIMPLE_LEN1024//buffer数据长度 structsimple_dev{ char*data;//指向数据的头部 char*datard;//读指针 char*datawr;//写指针 char*dataend;//指向缓冲区的结尾 wait_queue_head_tinq;//读取等待队列头 wait_queue_head_toutq;//写入等待队列头 structcdevcdev;//字符设备结构 structsemaphoresemp;//结构体信号量 };

申请设备号

之前有介绍,不再赘述

为自定义结构体分配区内存

[cpp]view plaincopyD("allocsimple_devstruct\n"); char7_dev=kmalloc(DEV_COUNT*sizeof(structsimple_dev),GFP_KERNEL); if(char7_dev==NULL) { printk(KERN_ERR"kmallocsimple_deverrnomemory"); unregister_chrdev_region(dev,DEV_COUNT); return-ENOMEM; }

使用kmalloc 给设备结构他分配内存,因为系统可能因为暂时不能分配到内存(虽然很少见),所以在分配到内存后需要检测是否分配成功,在检测到成功分配内存后还需要将所分配的内存区清零,以免以后出现奇怪的错误难以调试(一定要记住)

[cpp]view plaincopymemset(char7_dev,0,DEV_COUNT*sizeof(structsimple_dev));

为缓冲区申请内存

在为自定义结构他申请好内存后,我们需要为每个结构体内的buffer申请内存,使用相同的方法,分配并清零

[cpp]view plaincopyfor(index=0;index<DEV_COUNT;++index) { //char7_dev[index].count=0 if((char7_dev[index].data=kmalloc(MAX_SIMPLE_LEN,GFP_KERNEL))!=NULL) { memset(char7_dev[index].data,0,MAX_SIMPLE_LEN); D("kmallocthedataspaceOK!\n"); } else{ for(--index;index>=0;--index) { kfree(char7_dev[index].data); } printk(KERN_ERR"kmallocsimple_devdatanumbererrnomemory"); kfree(char7_dev); unregister_chrdev_region(dev,DEV_COUNT); return-ENOMEM; } }

这里需要注意的一点是 在申请某个buffer缓冲区失败时候,需要将已经成功申请的内存释放掉(else中做了这个工作)

初始化结构体中的各数据指针、信号量、等待队列头

在申请好设备及各设备buffer内存后,我们需要对结构中的一些变量进行初始化,

初始化数据指针

将data指向buffer起始位置, datawr,datard 初始化也指向起始位置(表示空buffer),dataend 指向buffer末尾一个无效位置,用于判断读写位置是否合法

初始化信号量以及读写队列头

[cpp]view plaincopyfor(index=0;index<DEV_COUNT;++index) { /*initthedataptr*/ char7_dev[index].datard=char7_dev[index].data; char7_dev[index].datawr=char7_dev[index].data; char7_dev[index].dataend=char7_dev[index].data+MAX_SIMPLE_LEN; /*initsemaphore,waitqueue_headandsoon*/ sema_init(&(char7_dev[index].semp),1); init_waitqueue_head(&(char7_dev[index].inq)); init_waitqueue_head(&(char7_dev[index].outq)); }

初始化字符设备添加字符设备

逐个设备初始化(注册)并添加(告知内核)

[cpp]view plaincopyfor(index=0;index<DEV_COUNT;++index) { cdev_init(&(char7_dev[index].cdev),&simple_fops); char7_dev[index].cdev.owner=THIS_MODULE; err=cdev_add(&(char7_dev[index].cdev),dev,1); if(err<0) { printk(KERN_ERR"addcdeverr\n"); gotoerror1; } else { D("add%dchardevOK!\n",index+1); } }

字符设备操作

[cpp]view plaincopystructfile_operationssimple_fops={ .owner=THIS_MODULE, .open=simple_open, .release=simple_close, .read=simple_read, .write=simple_write, .llseek=simple_llseek, //.ioctl=simple_ioctl, .poll=simple_poll, .mmap=simple_mmap, };

因为2.6.35之后文件操作已经没有ioctl方法,所以不在介绍了

打开及关闭操作

打开关闭函数于之前的设备驱动没有差异,故不叙述

读写操作

首先获取信号量(读写操作都一样)

[cpp]view plaincopyif(down_interruptible(&dev->semp)<0) { printk(KERN_ERR"[%s]getthemutexlockerror%d,%s", current->comm,__LINE__,__func__); return-ERESTARTSYS; } else { D("havegetthemutex%d\n",__LINE__); }

读操作

对于读操作需要检测buffer中是否有数据

[cpp]view plaincopyif(dev->datawr==dev->datard)//emptybuffer { while(dev->datawr==dev->datard)//循环检测是否buffer中是否已经有数据 { up(&dev->semp);//释放信号量 if(filp->f_flags&O_NONBLOCK)//检测用户是否是非阻塞打开 { D("setNONBLOCKmask%d\n",__LINE__); return-EAGAIN; } D("[%s]readinggoingtosleep!",current->comm); /*将当调用进程加到写等待队列*/ if(wait_event_interruptible(dev->inq,dev->datard!=dev->datawr)) { return-ERESTARTSYS; } if(down_interruptible(&dev->semp)<0)//wait_wvent_interrupt返回,获取信号量 { printk(KERN_ERR"[%s]getthemutexlockerror%d,%s", current->comm,__LINE__,__func__); return-ERESTARTSYS; } else { D("havegetthemutex%d\n",__LINE__); } } }

看到这里你可能已经知道上边的流程图有一些错误(up 和 down 操作应该在while循环中去做,而不是在整个if 中)由于时间问题上图就不做修改了

计算buffer 剩余的数据

如果读指针在写指针之后(datard > datawr)则buffer中的数据就从读位置到buffer结尾,又buffer开头转到写位置

[cpp]view plaincopyif(dev->datawr<dev->datard) { data_remain=(dev->dataend-dev->datard) +(dev->datawr-dev->data); } else { data_remain=dev->datawr-dev->datard; } 如果有一些不理解 你可以参照下边的示意图,应该很容易就理解上述计算buffer内数据长度的

判断数据长度的合法性并计算能够写入用户空间的长度

[cpp]view plaincopyif(data_remain<0) { printk(KERN_ERR"theremaindatawecalculateiswrongcheck!%d\n",__LINE__); } elseif(count>data_remain) { WAR("thedataislessthantheuserwanttoread\n"); D("wecanonlycopy%dbytestouser\n",data_remain); count=data_remain; } else { }

向用户空间传入数据

1、当读取操作不会读到buffer尾部时候,直接将数据copy给用户,调整读指针, 唤醒睡眠在写队列上的进程,释放信号量,相用户返回已经读取的数据长度

[cpp]view plaincopyif((dev->datawr>dev->datard)||(dev->datard+count<=dev->dataend)) { err=copy_to_user(userstr,dev->datard,count); if(err!=0) { printk(KERN_ERR"anerroroccuredwhencopydatatouser:%d\n",__LINE__); up(&dev->semp); returnerr; } else { D("datacopytouserOK\n"); dev->datard=dev->datard+count; if(dev->datard==dataend) dev->datard=dev->data; wake_up_interruptible(&dev->outq); up(&dev->semp); returncount; } }

2、如果读到buffer末尾还需要绕回来从数据头部再读取

则先读取read 指针到buffer末尾的数据

然后再从头部读取相应长度的数据

同样在成功读取后需要唤醒写等待队列, 调整读指针, 释放信号量

[cpp]view plaincopyelse { data_remain=(dev->dataend-dev->datard); /*读取从当前位置到buffer结尾的数据长度*/ err=copy_to_user(userstr,dev->datard+1,data_remain); if(err!=0) { printk(KERN_ERR"anerroroccuredwhencopydatatouser:%d\n",__LINE__); up(&dev->semp); returnerr; } else { D("datacopytouserOK\n"); //up(&dev->semp); } /*从buffer头部读取剩余的长度*/ err=copy_to_user(userstr+data_remain,dev->data,count-data_remain); if(err!=0) { printk(KERN_ERR"anerroroccuredwhencopydatatouser:%d\n",__LINE__); up(&dev->semp); returnerr; } else { D("datacopytouserOK\n"); dev->datard=dev->data+(count-data_remain); wake_up_interruptible(&dev->outq); up(&dev->semp); returncount; } }

写操作

与读操作类似

获取即将写入的位置

检测buffer是否已经满需要检测下即将写入的地址是否有效(是否已经到了尾部位置),如果数据已经写到buffer的结尾则需要调整写

[cpp]view plaincopyif(dev->datawr+1==dev->dataend)//即将写入的位置是buffer尾部 next_ptr=dev->data;//调整写入指针的指向 else next_ptr=dev->datawr+1;

判断buffer是否已满

当即将写入的位置 正好是读指针指向的位置则表示buffer已满需要等待读进程[cpp]view plaincopyif(next_ptr==dev->datard) { while(next_ptr==dev->datard) { up(&dev->semp); if(filp->f_flags&O_NONBLOCK) { D("setNONBLOCKmask%d\n",__LINE__); return-EAGAIN; } D("[%s]writinggoingtosleep!",current->comm); if(wait_event_interruptible(dev->outq,next_ptr!=dev->datard)) { return-ERESTARTSYS; } if(down_interruptible(&dev->semp)<0) { printk(KERN_ERR"[%s]getthemutexlockerror%d,%s", current->comm,__LINE__,__func__); return-ERESTARTSYS; } else { D("havegetthemutex%d\n",__LINE__); } } }

计算buffer剩余长度(可以写入的数据的长度)

同样需要分两种情况,可以参照上图

[cpp]view plaincopyif(dev->datawr>=dev->datard) { remain_space=(dev->dataend-dev->datawr-1) +(dev->datard-dev->data); } else { remain_space=dev->datard-dev->datawr-1; }

向buffer写入数据调整读写指针

[cpp]view plaincopyif((dev->datawr<dev->datard)||(dev->datawr+count<dev->dataend)) { err=copy_from_user(dev->datawr,userstr,count); if(err!=0) { printk(KERN_ERR"erroroccuredwhencopydatafromuser%d\n",__LINE__); up(&dev->semp); returnerr; } else { D("datacopyfromuserOK\n"); dev->datawr=dev->datawr+count; wake_up_interruptible(&dev->inq); up(&dev->semp); returncount; } } else { remain_space=dev->dataend-dev->datawr; err=copy_from_user(dev->datawr,userstr,remain_space); if(err!=0) { printk(KERN_ERR"erroroccuredwhencopydatafromuser%d\n",__LINE__); up(&dev->semp); returnerr; } else { D("copypartofthedatafromuser\n"); } err=copy_from_user(dev->data,userstr+remain_space,count-remain_space); if(err!=0) { printk(KERN_ERR"erroroccuredwhencopydatafromuser%d\n",__LINE__); up(&dev->semp); returnerr; } else{ D("datacopyfromuserOK\n"); dev->datawr=dev->data+(count-remain_space); wake_up_interruptible(&dev->inq); up(&dev->semp); returncount; } }

poll方法

在等待队列上调用poll_wait

[cpp]view plaincopypoll_wait(filp,&dev->inq,wait); poll_wait(filp,&dev->outq,wait);

检测文件是否可读或者可写

[cpp]view plaincopyif(dev->datard!=dev->datawr) { mask|=POLLIN|POLLRDNORM;//canberead } if(dev->datawr+1==dev->dataend) next_ptr=dev->data; else next_ptr=dev->datawr+1; if(next_ptr!=dev->datard) { mask|=POLLOUT|POLLWRNORM;//canbewrite }

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。