Linux驱动学习(四)——高级字符设备驱动程序

来源:互联网 发布:visio 软件下载最新 编辑:程序博客网 时间:2024/06/10 06:51

LDD第二章到第六章的综合复习。

用循环缓冲区实现一个FIFO,支持多个reader和writer,利用信号量在竞态下保护数据区域,并且在无数据的时候阻塞读,数据满的时候阻塞写,可以通过ioctl返回FIFO状态。

需要的技术:

1、信号量:竞争与锁的机制。

2、等待队列:进程的休眠与唤醒。用两种方式实现读写阻塞。

3、poll:返回文件可读或者可写的状态,为select调用。

4、ioctl:返回或者传入特定结构体,在本例中为FIFO的状态。

 

其实无论驱动程序逻辑多么复杂,基本的框架都是一些操作函数的实现,一个字符设备的大致框架如下:

将cdev嵌入自己的结构体里,可以支持更多的属性,而不是利用全局变量(等待队列、信号量等)进行管理
(一)初始化工作
1、请求设备号(指定或动态分配主设备号,请求子设备号)
2、分配结构体内存并初始化内部关键变量(申请buffer,初始化信号量等)
3、注册设备,步骤cdev_alloc --> cdev_init --> cdev_add,其中我们的字符设备是嵌套在fifo struct里面的,所以alloc工作在第二步已经做好。

(二)清理工作
1、注销设备,cdev_del
2、释放fifo struct内存,包括申请的buffer内存
3、回收设备号
执行完rmmod之后可以检查cat /proc/devices是否将我们申请的设备号释放完毕

(三)文件操作

1、open:包括必要的初始化和统计工作,以及与权限相关的操作

2、release:资源回收

3、read&write:对于数据的操作,在普通文件中主要是数据的读写,f_pos的移动;fifo中不支持lseek,但是需要在特定条件下阻塞读和写,在满足条件时唤醒,并且维护wp和rp以保证循环buffer的正常。

4、ioctl:用于对设备的控制,返回设备的状态,比如fifo当前的使用情况,或者是设备中每个变量的状态,如天线数据的子帧号等;也可以用作小数据量的传输。

5、mmap:用于用户空间和设备之间的内存映射。

6、poll:用于返回可读写状态等;

7、llseek:用于移动当前读写位置。

(四)调试支持

/proc文件,可以用来传输小数据量的数据,辅助调试。利用proc文件传输大量数据可能会导致设备驱动程序组织的比较乱,不建议使用。

 

代码还有bug,write函数无法写入,暂时先贴在下面,请各位高手指点一下小弟~

 

#include <linux/init.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/fs.h>// struct  file and file_operations#include <linux/types.h>// typedef dev_t, etc.#include <linux/kdev_t.h>#include <linux/poll.h>// interface poll_wait...#include <linux/kernel.h>// printk(), min()#include <linux/errno.h>// error codes#include <linux/cdev.h>// cdev_add()#include <linux/ioctl.h>// _IOW,etc.#include <linux/slab.h>// kmalloc()#include <linux/fcntl.h>#include <linux/sched.h>// schedule() and TASK_*#include <asm/uaccess.h>// interface copy_from_user and copy to user#include <asm/system.h>// cli(), *_flagsMODULE_LICENSE("GPL");struct cdev_fifo{wait_queue_head_t inq, outq;// read/write wait queuechar *buffer, *end;// loop buffer begin and endint bufsize;// loop buffer sizechar *rp, *wp;// read/write positionint nreaders, nwriters;// readers/writers counterstruct fasync_struct *async_queue;struct semaphore sem_buf;// mutexstruct cdev cdev;// char device};struct cdev_info{int bufsize;int nreaders;int nwriters;int spacefreesize;};#define CDEV_IOC_MAGIC 'k'#define CDEV_IOC_MAXNR 2#define CDEV_IOC_GETINFO _IOWR(CDEV_IOC_MAGIC, 0, struct cdev_info)static int cdev_fifo_major = 0;static int cdev_fifo_minor = 0;static int cdev_fifo_num = 1;static int cdev_fifo_size = 4000;module_param(cdev_fifo_major, int, 0);//insmod xxx.ko cdev_fifo_major = 254module_param(cdev_fifo_minor, int, 0);module_param(cdev_fifo_num, int, 0);  module_param(cdev_fifo_size, int, 0);static struct cdev_fifo *cdev_fifo_devices;static ssize_t cdev_open(struct inode *, struct file *);static ssize_t cdev_release(struct inode *, struct file *);static ssize_t cdev_read(struct file *, char *, size_t, loff_t*);static ssize_t cdev_write(struct file *, const char *, size_t, loff_t*);//no use of llseek static loff_t cdev_llseek(struct file *, loff_t, int);static unsigned int cdev_poll(struct file *, poll_table *);static int cdev_ioctl(struct inode *, struct file *, unsigned int, unsigned long);static int cdev_fasync(int, struct file *, int);struct file_operations cdev_fops = {.owner = THIS_MODULE,.open = cdev_open,.read = cdev_read,.write = cdev_write,.llseek = no_llseek,.ioctl = cdev_ioctl,.poll = cdev_poll,.fasync = cdev_fasync,.release = cdev_release,};static void cdev_fifo_setup_cdev(struct cdev_fifo *dev, int index){int err, devno = MKDEV(cdev_fifo_major, cdev_fifo_minor+index);// register char device// cdev_alloc --> cdev_init --> cdev_addcdev_init(&dev->cdev, &cdev_fops);dev->cdev.owner = THIS_MODULE;dev->cdev.ops = &cdev_fops;err = cdev_add(&dev->cdev, devno, 1);if (err)printk("Error %d adding cdev_fifo %d.\n", err, index);}static int __init cdev_module_init(void){int result, i;dev_t dev = 0;// ask a range of minor numbers to work withif (cdev_fifo_major)// for certain major number{dev = MKDEV(cdev_fifo_major, cdev_fifo_minor);result = register_chrdev_region(dev, cdev_fifo_num, "cdev_fifo");}else// for dynamic allocated major number{// allocate dev majorresult = alloc_chrdev_region(&dev, cdev_fifo_minor /*dev minor*/, cdev_fifo_num /*count*/, "cdev_fifo");cdev_fifo_major = MAJOR(dev);}if (result < 0){printk("cdev_fifo can't get major %d\n", cdev_fifo_major);return result;}// allocate fifo devices according to the number when module loadedcdev_fifo_devices = kmalloc(cdev_fifo_num * sizeof(struct cdev_fifo), GFP_KERNEL); if (!cdev_fifo_devices)// allocate failure{unregister_chrdev_region(dev, cdev_fifo_num);result = -ENOMEM;return result;}printk("kmalloc struct success.\n");// initialize device struct variablesmemset(cdev_fifo_devices, 0, cdev_fifo_num * sizeof(struct cdev_fifo));for (i = 0; i < cdev_fifo_num; ++i){init_waitqueue_head(&(cdev_fifo_devices[i].inq));init_waitqueue_head(&(cdev_fifo_devices[i].outq));sema_init(&(cdev_fifo_devices[i].sem_buf), 1);cdev_fifo_setup_cdev(cdev_fifo_devices+i, i);}printk("cdev_fifo register success.\n");return 0;}static void __exit cdev_module_exit(void){int i;dev_t devno = MKDEV(cdev_fifo_major, cdev_fifo_minor);if (cdev_fifo_devices){for (i = 0; i < cdev_fifo_num; ++i){cdev_del(&(cdev_fifo_devices[i].cdev));// remove deviceskfree(cdev_fifo_devices[i].buffer);}kfree(cdev_fifo_devices);// free memorycdev_fifo_devices = NULL;}unregister_chrdev_region(devno, cdev_fifo_num);// free device numberprintk("cdev_fifo unregister success.\n");}/* * file management: open and close*/static int cdev_open(struct inode *inode, struct file *filp){struct cdev_fifo *dev;dev = container_of(inode->i_cdev, struct cdev_fifo, cdev);filp->private_data = dev; //for read, write...if (down_interruptible(&dev->sem_buf))return -ERESTARTSYS;if (!dev->buffer){// allocate the bufferdev->buffer = kmalloc(cdev_fifo_size, GFP_KERNEL);if (!dev->buffer){up(&dev->sem_buf);return -ENOMEM;}}dev->bufsize = cdev_fifo_size;dev->end = dev->buffer + dev->bufsize;dev->rp = dev->wp = dev->buffer; // rd and wr from the beginning// count readers and writersif (filp->f_mode & FMODE_READ)dev->nreaders++;if (filp->f_mode & FMODE_WRITE)dev->nwriters++;up(&dev->sem_buf);printk("\"%s\" open file successful\n", current->comm);return nonseekable_open(inode, filp);}static int cdev_release(struct inode *inode, struct file *filp){struct cdev_fifo *dev = filp->private_data;cdev_fasync(-1, filp, 0);if (down_interruptible(&dev->sem_buf))return -ERESTARTSYS;if (filp->f_mode & FMODE_READ)dev->nreaders--;if (filp->f_mode & FMODE_WRITE)dev->nwriters--;if (dev->nreaders + dev->nwriters == 0){kfree(dev->buffer);// free memorydev->buffer = NULL;}up(&dev->sem_buf);return 0;}/* * data management: read and write*/static ssize_t cdev_read(struct file *filp, char *buf, size_t len, loff_t *f_pos){struct cdev_fifo *dev = filp->private_data;if (down_interruptible(&dev->sem_buf))return -ERESTARTSYS;// interruptedwhile (dev->rp == dev->wp)// fifo empty, read nothing{up(&dev->sem_buf);// release lockif (filp->f_flags & O_NONBLOCK)return -EAGAIN;printk("\"%s\" reading: going to sleep\n", current->comm);if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))return -ERESTARTSYS;// signal: tell fs layer handle itif (down_interruptible(&dev->sem_buf))return -ERESTARTSYS;}// fifo is not empty, return dataif (dev->wp > dev->rp)len = min(len, (size_t)(dev->wp - dev->rp));else// wp rrapped, return end-rplen = min(len, (size_t)(dev->end - dev->rp));if (copy_to_user(buf, dev->rp, len)){up(&dev->sem_buf);return -EFAULT;}dev->rp += len;if (dev->rp == dev->end)dev->rp = dev->buffer;up(&dev->sem_buf);// wake up writerswake_up_interruptible(&dev->outq);printk("\"%s\" did read %li bytes\n", current->comm, (long)len);return len;}// return free space sizestatic int spacefree(struct cdev_fifo *dev){if (dev->rp == dev->wp){return dev->bufsize - 1;}return ((dev->rp + dev->bufsize - dev->wp)) - 1;}// wait for free space for writingstatic int cdev_getwritespace(struct cdev_fifo *dev, struct file *filp){while (spacefree(dev) == 0) // fifo full {DEFINE_WAIT(wait);up(&dev->sem_buf);if (filp->f_flags & O_NONBLOCK)return -EAGAIN;printk("\"%s\" writing: going to sleep\n", current->comm);prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);if (spacefree(dev) == 0)// check condition!!!schedule();finish_wait(&dev->outq, &wait);if (signal_pending(current))return -ERESTARTSYS;//signal wake upif (down_interruptible(&dev->sem_buf))return -ERESTARTSYS;}return 0;}static ssize_t cdev_write(struct file *filp, const char *buf, size_t len, loff_t *f_pos){struct cdev_fifo *dev = filp->private_data;int result;printk("request semaphore\n");if (down_interruptible(&dev->sem_buf))return -ERESTARTSYS;printk("get semaphore, check space.\n");result = cdev_getwritespace(dev, filp);if (result)return result;printk("write space free.\n");// space is free, begin to writelen = min(len, (size_t)spacefree(dev));if (dev->wp >= dev->rp)len = min(len, (size_t)(dev->end - dev->wp));elselen = min(len, (size_t)(dev->rp - dev->wp -1));printk("Going to accept %li bytes to %p from %p\n", (long)len, dev->wp, buf);if (copy_from_user(dev->wp, buf, len)){up(&dev->sem_buf);return -EFAULT;}dev->wp += len;if (dev->wp == dev->end)dev->wp = dev->buffer;//wp wrappedup(&dev->sem_buf);wake_up_interruptible(&dev->inq);//wake up read and selectif (dev->async_queue)kill_fasync(&dev->async_queue, SIGIO, POLL_IN);printk("\"%s\" did write %li bytes.\n", current->comm, (long)len);return sizeof(int);}static int cdev_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg){int retval = 0;struct cdev_info curinfo;struct cdev_fifo *dev = filp->private_data;if (_IOC_TYPE(cmd) != CDEV_IOC_MAGIC)return -ENOTTY;if (_IOC_NR(cmd) > CDEV_IOC_MAXNR)return -ENOTTY;switch (cmd){case CDEV_IOC_GETINFO:curinfo.bufsize = dev->bufsize;curinfo.nreaders = dev->nreaders;curinfo.nwriters = dev->nwriters;curinfo.spacefreesize = spacefree(dev);if (copy_to_user((unsigned char __user *)arg, &curinfo, sizeof(curinfo))){printk("cdev_fifo ioctl error: copy_to_user\n");retval = -EFAULT;}break;default:return -ENOTTY;}return retval;}static unsigned int cdev_poll(struct file *filp, poll_table *wait){struct cdev_fifo *dev = filp->private_data;unsigned int mask = 0;if (down_interruptible(&dev->sem_buf))return -ERESTARTSYS;poll_wait(filp, &dev->inq, wait);poll_wait(filp, &dev->outq, wait);if (dev->rp != dev->wp)mask |= POLLIN | POLLRDNORM;// readableif (spacefree(dev))mask |= POLLOUT | POLLWRNORM;// writableup(&dev->sem_buf);return mask;}static int cdev_fasync(int fd, struct file *filp, int mode){struct cdev_fifo *dev = filp->private_data;return fasync_helper(fd, filp, mode, &dev->async_queue);}module_init(cdev_module_init);module_exit(cdev_module_exit);

 

测试程序:

测试读

 

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(){char buf[256];int readlen = 0, retlen;FILE* fd;fd = fopen("/dev/fifocdev", "r");if (fd != -1){while(1){printf("enter read len=");scanf("%d",&readlen);retlen = fread((void*)buf, sizeof(char), readlen, fd);buf[retlen] = '\0';printf("read size= %d, content=%s.\n", retlen, buf);}fclose(fd);}elseprintf("open error.\n");return 0;}

 

测试写

 

#include <stdio.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(){char buf[256];int readlen = 0, retlen;FILE* fd;memset(buf,0,sizeof(buf));fd = fopen("/dev/fifocdev", "w");if (fd != -1){while(1){printf("enter write string=");scanf("%s",(char*)&buf);printf("strlen=%d\n",strlen(buf));retlen = fwrite((char*)&buf, sizeof(char), strlen(buf)+1, fd);printf("write size= %d, content=%s.\n", strlen(buf)+1, buf);}fclose(fd);}elseprintf("open error.\n");return 0;}

 

ioctl

 

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/ioctl.h>#include <fcntl.h>#define DEVICE_FILENAME "/dev/fifocdev"struct cdev_info{int bufsize;int nreaders;int nwriters;int spacefreesize;};#define CDEV_IOC_MAGIC 'k'#define CDEV_IOC_GETINFO _IOWR(CDEV_IOC_MAGIC, 0, struct cdev_info)int main(){struct cdev_info info;int dev;dev = open(DEVICE_FILENAME, O_RDWR | O_NDELAY);if (dev >= 0){printf("open dev success.\n");ioctl(dev, CDEV_IOC_GETINFO, &info);printf("bufsize=%d.\n", info.bufsize);printf("nreaders=%d.\n", info.nreaders);printf("nwriters=%d.\n", info.nwriters);printf("spacefreesize=%d.\n", info.spacefreesize);close(dev);}elseprintf("open device error.\n");return 0;}

 

可以用ioctl和dmesg辅助调试。

在insmod之后,根据/proc/devices里面的主次设备号建立字符设备文件

sudo mknod c 251 0

 

问题:

直接运行./testwritefifo,会出现段错误,fwrite的地方报错

利用sudo ./testwritefifo,返回写入成功,但是dmesg并没有看到pringk 出来的写入操作(没进去write函数),ioctl也发现FIFO的freesize并没有变化,但是nwriter确实是+1了,说明测试写FIFO程序打开文件成功了。(从dmesg也可以看到writefifo open file successful)。

原创粉丝点击