linux device driver —— 环形缓冲区的实现

还是没有接触到怎么控制硬件,但是在书里看到了一个挺巧妙的环形缓冲区实现。

此环形缓冲区实际为一个大小为bufsize的一维数组,有一个rp的读指针,一个wp的写指针。

在数据满时写进程会等待读进程读取数据,数据为空时读进程会等待写进程写入数据。


在上次代码上改的,所以名字还是ioctldemo

ioctldemo.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/stat.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/semaphore.h>
#include <asm-generic/uaccess.h>
#include <asm-generic/ioctl.h>
#include <asm-generic/current.h>

#define IOCTLDEMO_MAJOR 0
#define MODULE_NAME "ioctldemo"

#define DEMO_MAGIC 'm'
#define DEMO_SIZE  int
#define DEMO_NR_MAX    1
#define MY_IOCTL_READ _IOR(DEMO_MAGIC,1,DEMO_SIZE);

static int ioctldemo_major = IOCTLDEMO_MAJOR;

void ioctldemo_exit(void);
int  ioctldemo_init(void);
long my_unlocked_ioctl(struct file*, unsigned int, unsigned long);
int  my_cdev_open(struct inode*, struct file*);
int  my_cdev_release(struct inode*,struct file*);

ssize_t my_cdev_read (struct file*, char __user*, size_t, loff_t *);
ssize_t my_cdev_write(struct file*, const char __user*, size_t, loff_t *);

MODULE_LICENSE("Dual BSD/GPL");
module_param(ioctldemo_major,int,S_IRUGO);
module_init(ioctldemo_init);
module_exit(ioctldemo_exit);
struct dev_driver
{
    wait_queue_head_t inq,outq; //read and write queues
    char *buffer, *end; //begin of buff, end of buff
    int  bufsize;//为了方便取余,必须为2的n次幂
    char *rp,*wp;
    struct semaphore sem;
    struct cdev my_cdev;
}my_driver;

static struct file_operations cdev_ops =
{
    .owner            = THIS_MODULE,
    .open            = my_cdev_open,
    .release        = my_cdev_release,
    .read           = my_cdev_read,
    .write          = my_cdev_write,
};

int __init ioctldemo_init(void)
{
    int ret;
    dev_t devno;
    printk(KERN_NOTICE "=== ioctldemo_init start\n");
    devno = MKDEV(ioctldemo_major,);
    if(ioctldemo_major)
    {
        printk(KERN_NOTICE "=== ioctldemo_init try register\n");
        ret = register_chrdev_region(devno,,MODULE_NAME);
    }else
    {
        printk(KERN_NOTICE "=== ioctldemo_init auto register\n");
        ret = alloc_chrdev_region(&devno,,,MODULE_NAME);
        ioctldemo_major = MAJOR(devno);
    }
    )
    {
        printk(KERN_NOTICE "=== ioctldemo_init register fail\n");
        return ret;
    }

    cdev_init(&my_driver.my_cdev,&cdev_ops);
    my_driver.my_cdev.owner = THIS_MODULE;
    ret = cdev_add(&my_driver.my_cdev,MKDEV(ioctldemo_major,),);

    )
    {
        printk(KERN_NOTICE "=== ioctldemo_init add cdev fail\n");
        return ret;
    }
    //init buffer
    my_driver.bufsize= <<;
    my_driver.buffer = (char*)kmalloc(my_driver.bufsize,GFP_KERNEL);
    my_driver.end    = my_driver.buffer  + my_driver.bufsize;
    my_driver.rp     = my_driver.wp = my_driver.buffer;
    printk(KERN_DEBUG "ioctldemo buf->%p, end->%p",my_driver.buffer,my_driver.end);
    //init semaphore
    sema_init(&my_driver.sem,);
    //init wait queue
    init_waitqueue_head(&my_driver.inq);
    init_waitqueue_head(&my_driver.outq);

    printk(KERN_NOTICE "=== ioctldemo_init finish\n");
    ;
}

void __exit ioctldemo_exit(void)
{
    printk (KERN_NOTICE "=== ioctldemo_exit");
    kfree(my_driver.buffer);
    cdev_del(&my_driver.my_cdev);
    unregister_chrdev_region(MKDEV(ioctldemo_major,),);
}

int my_cdev_open(struct inode *node, struct file *filp)
{
    ;
}

int my_cdev_release(struct inode *node, struct file *filp)
{
    ;
}

/*一个环形缓冲区的不覆盖读写实现。
 *对于 read函数: 写指针和读指针重合时视为数据为空,等待输入数据。每次读取最多读到缓冲区数组尾部。
 *对于write函数: 每次写入最多写入到(读指针-1)位置或缓冲区数组尾部,如果写指针在读指针的前一项(相对环形来说)视为队列已满,等待读取数据。
 */

ssize_t my_cdev_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
    if(down_interruptible(&my_driver.sem))
        return -ERESTARTSYS;
    while(my_driver.rp == my_driver.wp)
    {
        up(&my_driver.sem);
        if(filp->f_flags == O_NONBLOCK)
            return -EAGAIN;
        printk(KERN_NOTICE "%s :reading, go to sleep\n",current->comm);
        if(wait_event_interruptible(my_driver.inq,(my_driver.rp != my_driver.wp)))
            return -ERESTARTSYS;
        if(down_interruptible(&my_driver.sem))
            return -ERESTARTSYS;
        printk(KERN_DEBUG "%s :read awoken from waiting,rp:%p, rp:%p\n",current->comm,my_driver.rp,my_driver.wp);
    }
    if(my_driver.wp > my_driver.rp)
        count = min(count,(size_t)(my_driver.wp - my_driver.rp));
    else
        count = min(count,(size_t)(my_driver.end - my_driver.rp));
    if(copy_to_user(buf,my_driver.rp,count))
    {
        up(&my_driver.sem);
        return -EFAULT;
    }
    my_driver.rp += count;
    if(my_driver.rp == my_driver.end)
    {
        my_driver.rp = my_driver.buffer;
        printk(KERN_DEBUG "ioctldemo convert rp");
    }
    up(&my_driver.sem);
    wake_up_interruptible(&my_driver.outq);
    printk(KERN_NOTICE "%s did read %ld bytes\n",current->comm,count);
    return count;
}

//get max space that is free now
size_t my_cdev_getfree(struct dev_driver *drp)
{
    if(drp->rp == drp->wp)
        ;
    )) - ;
}

ssize_t my_cdev_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
    if(down_interruptible(&my_driver.sem))
        return -ERESTARTSYS;
    )
    {
        up(&my_driver.sem);
        if(filp->f_flags == O_NONBLOCK)
            return -EAGAIN;
        printk(KERN_NOTICE "%s :write go to sleep\n",current->comm);
        )))
            return -ERESTARTSYS;
        if(down_interruptible(&my_driver.sem))
            return -ERESTARTSYS;
        printk(KERN_NOTICE "%s :write awoken from sleep\n",current->comm);
    }
    count = min(count,my_cdev_getfree(&my_driver));
    if(my_driver.wp >= my_driver.rp)
        count = min(count,(size_t)(my_driver.end - my_driver.wp));
    else// writer pointer has wrapped, fill up to rp-1
        count = min(count,(size_t)(my_driver.rp - my_driver.wp -));
    if(copy_from_user(my_driver.wp,buf,count))
    {
        up(&my_driver.sem);
        return -EFAULT;
    }
    my_driver.wp += count;
    if(my_driver.wp == my_driver.end)
    {
        my_driver.wp = my_driver.buffer;
        printk(KERN_DEBUG "ioctldemo convert wp");
    }
    up(&my_driver.sem);
    wake_up_interruptible(&my_driver.inq);
    printk(KERN_NOTICE "%s did write %ld bytes\n",current->comm,count);
    return count;
}

编译,安装,卸载脚本见

测试:

例如我把驱动挂载到了/dev/ioctldemo

可以执行以下命令

cat /dev/ioctldemo

此时缓冲区因为没有数据,所以等待输入。
再打开另外一个终端,输入命令

ls > /dev/ioctldemo

可以看到之前的窗口显示出来了数据。

上一篇:《LeetBook》leetcode题解(13):Roman to Integer[E]


下一篇:<> Chapter 4:Debugging Techniques