Linux驱动编程 step-by-step (九)

11 篇文章 0 订阅

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

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

测试代码1      测试代码2

设计思路

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

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

自定义一个结构

#define MAX_SIMPLE_LEN 1024	//buffer 数据长度
struct simple_dev{
    char *data;			//指向数据的头部
    char *datard;		//读指针
    char *datawr;		//写指针
    char *dataend;		//指向缓冲区的结尾
    wait_queue_head_t inq;	//读取等待队列头
    wait_queue_head_t outq;	//写入等待队列头
    struct cdev cdev;		//字符设备结构
    struct semaphore semp;	//结构体信号量
};

申请设备号

 之前有介绍,不再赘述

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

    D("alloc simple_dev struct \n");
    char7_dev = kmalloc(DEV_COUNT*sizeof(struct simple_dev), GFP_KERNEL);
    if( char7_dev == NULL)
    {
        printk(KERN_ERR "kmalloc simple_dev err no memory");
        unregister_chrdev_region(dev, DEV_COUNT);
        return -ENOMEM;
    } 

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

 memset(char7_dev, 0, DEV_COUNT*sizeof(struct simple_dev));

为缓冲区申请内存

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

 for( 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("kmalloc the data space OK! \n");
     }
     else{
        for( --index ; index >= 0 ; --index )
        {
             kfree(char7_dev[index].data);
        } 
        printk(KERN_ERR "kmalloc simple_dev data number err no memory");
        kfree(char7_dev);
        unregister_chrdev_region(dev, DEV_COUNT);
        return -ENOMEM;
    }
}

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

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

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

初始化数据指针

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

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

 for( index = 0 ; index < DEV_COUNT ; ++index )
    {                 
        /*init the data ptr*/ 
        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;                                

        /*init semaphore, waitqueue_head and so on*/
        sema_init(&(char7_dev[index].semp), 1);
        init_waitqueue_head(&(char7_dev[index].inq));
        init_waitqueue_head(&(char7_dev[index].outq));
    }

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

逐个设备初始化(注册)并添加(告知内核)
    for( 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 "add cdev err \n");
            goto error1;
        }
        else
        {   
            D( "add %d char dev OK!\n", index+1);
        }

    }


字符设备操作

struct file_operations simple_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方法,所以不在介绍了

打开及关闭操作

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

读写操作

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

 if(down_interruptible(&dev->semp) < 0)
    {
        printk(KERN_ERR "[%s]get the mutex lock error %d, %s",
                current->comm, __LINE__, __func__);
        return -ERESTARTSYS;
    }
    else
    {
        D("have get the mutex %d\n", __LINE__);
    }

读操作

对于读操作需要检测buffer中是否有数据
 if(dev->datawr == dev->datard) //empty buffer
     {              
  
        while(dev->datawr == dev->datard)//循环检测是否buffer中是否已经有数据
        {
            up(&dev->semp);//释放信号量 
            if(filp->f_flags & O_NONBLOCK) //检测用户是否是非阻塞打开
            {
                D("set NONBLOCK mask %d\n", __LINE__);
                return -EAGAIN;
            }
            D("[%s] reading going to sleep!", 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]get the mutex lock error %d, %s",
                        current->comm, __LINE__, __func__);
                return -ERESTARTSYS;
            }
            else
            {
                D("have get the mutex %d\n", __LINE__);
            }
        }
    }

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

计算buffer 剩余的数据

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

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

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

    if(data_remain < 0)
    {
        printk(KERN_ERR "the remain data we calculate is wrong check! %d \n", __LINE__);
    }
    else if(count > data_remain)
    {
        WAR("the data is less than the user want to read\n");
        D("we can only copy %d bytes to user\n", data_remain);
        count = data_remain;
    }
    else
    {
    }

向用户空间传入数据

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

    if(( dev->datawr > dev->datard ) || (dev->datard + count <= dev->dataend))
    {
        err = copy_to_user(userstr, dev->datard, count);
        if(err != 0)
        {
            printk(KERN_ERR "an error occured when copy data to user:%d\n", __LINE__);
            up(&dev->semp);
            return err;
        }
        else
        {
            D("data copy to user OK\n");
            dev->datard = dev->datard + count;            
            if(dev->datard == dataend)
                 dev->datard = dev->data;
            wake_up_interruptible(&dev->outq);
            up(&dev->semp);
            return count;
        }
    }


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

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

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

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

        else
        {
            data_remain= (dev->dataend -dev->datard ); 

            /*读取从当前位置到buffer结尾的数据长度*/
            err = copy_to_user(userstr, dev->datard+1, data_remain);
            if(err != 0)
            {
                printk(KERN_ERR "an error occured when copy data to user:%d\n", __LINE__);
                up(&dev->semp);
                return err;
            }
            else
            {
                D("data copy to user OK\n");
            //  up(&dev->semp);
            }           
             
          
            /*从buffer头部读取剩余的长度*/       
            err = copy_to_user(userstr+data_remain, dev->data, count-data_remain);
            if(err != 0)
            {
                printk(KERN_ERR "an error occured when copy data to user:%d\n", __LINE__);
                up(&dev->semp);
                return err;
            }
            else
            {
                D("data copy to user OK\n");
                dev->datard = dev->data+(count-data_remain);
                wake_up_interruptible(&dev->outq);
                up(&dev->semp);
                return count;
            }
        }

写操作

与读操作类似

获取即将写入的位置

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

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

判断buffer是否已满
当即将写入的位置 正好是读指针指向的位置则表示buffer已满需要等待读进程

    if( next_ptr == dev->datard )
    {
        while(next_ptr == dev->datard)
        {
            up(&dev->semp);
            if(filp->f_flags & O_NONBLOCK)
            {
                D("set NONBLOCK mask %d\n", __LINE__);
                return -EAGAIN;
            }
            D("[%s] writing going to sleep!", current->comm);

            if(wait_event_interruptible(dev->outq, next_ptr != dev->datard))
            {
                return -ERESTARTSYS;
            }

            if(down_interruptible(&dev->semp) < 0)
            {
                printk(KERN_ERR "[%s]get the mutex lock error %d, %s",
                        current->comm, __LINE__, __func__);
                return -ERESTARTSYS;
            }
            else
            {
                D("have get the mutex %d\n", __LINE__);
            }
        }
    }

计算buffer剩余长度(可以写入的数据的长度)
同样需要分两种情况,可以参照上图

    if(dev->datawr >= dev->datard)
    {
        remain_space = (dev->dataend - dev->datawr-1)
                    + (dev->datard - dev->data);
    }
    else
    {
        remain_space = dev->datard - dev->datawr - 1;
    }

向buffer写入数据调整读写指针
    if( (dev->datawr < dev->datard) || (dev->datawr + count < dev->dataend) )
    {
        err = copy_from_user(dev->datawr, userstr, count);
        if(err != 0)
        {
            printk(KERN_ERR "error occured when copy data from user %d\n", __LINE__);
            up(&dev->semp);
            return err;
        }
        else
        {
            D("data copy from user OK\n");
            dev->datawr = dev->datawr + count ;
            wake_up_interruptible(&dev->inq);
            up(&dev->semp);
            return count;
        }
    }

    else
    {
        remain_space = dev->dataend - dev->datawr ;
        err = copy_from_user(dev->datawr, userstr, remain_space);
        if(err != 0)
        {
            printk(KERN_ERR "error occured when copy data from user %d\n", __LINE__);
            up(&dev->semp);
            return err;
        }
        else 
        {
           D("copy part of the data from user\n");
        }
        err = copy_from_user(dev->data, userstr+remain_space, count-remain_space);
        if(err != 0)
        {
            printk(KERN_ERR "error occured when copy data from user %d\n", __LINE__);
            up(&dev->semp);
            return err;
       }
       else{
           D("data copy from user OK\n");
           dev->datawr = dev->data + (count-remain_space);
           wake_up_interruptible(&dev->inq);
           up(&dev->semp);
           return count;
      }
  }

poll方法

在等待队列上调用poll_wait

    poll_wait(filp, &dev->inq, wait);
    poll_wait(filp, &dev->outq, wait);

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

    if(dev->datard != dev->datawr)
    {
        mask |= POLLIN | POLLRDNORM;   //can be read
    }

    if(dev->datawr+1 == dev->dataend)
        next_ptr = dev->data;
    else
        next_ptr = dev->datawr+1;

    if(next_ptr != dev->datard)
    {
        mask |= POLLOUT | POLLWRNORM; //can be write
    }

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值