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

时间:2021-11-07 08:57:21

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

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

测试代码1      测试代码2

设计思路

用一个图来说明(可是画了很久哟)
Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序
简单说来就是一个进程写入缓冲区,另一个进程可以读出,读出后原buffer中的数据被置为无效值,

自定义一个结构

[cpp] view plaincopy
  1. #define MAX_SIMPLE_LEN 1024 //buffer 数据长度  
  2. struct simple_dev{  
  3.     char *data;         //指向数据的头部  
  4.     char *datard;       //读指针  
  5.     char *datawr;       //写指针  
  6.     char *dataend;      //指向缓冲区的结尾  
  7.     wait_queue_head_t inq;  //读取等待队列头  
  8.     wait_queue_head_t outq; //写入等待队列头  
  9.     struct cdev cdev;       //字符设备结构  
  10.     struct semaphore semp;  //结构体信号量  
  11. };  
Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序

申请设备号

 之前有介绍,不再赘述

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

[cpp] view plaincopy
  1. D("alloc simple_dev struct \n");  
  2. char7_dev = kmalloc(DEV_COUNT*sizeof(struct simple_dev), GFP_KERNEL);  
  3. if( char7_dev == NULL)  
  4. {  
  5.     printk(KERN_ERR "kmalloc simple_dev err no memory");  
  6.     unregister_chrdev_region(dev, DEV_COUNT);  
  7.     return -ENOMEM;  
  8. }   

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

[cpp] view plaincopy
  1. memset(char7_dev, 0, DEV_COUNT*sizeof(struct simple_dev));  

为缓冲区申请内存

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

[cpp] view plaincopy
  1.  for( index = 0 ; index < DEV_COUNT ; ++index )  
  2.  {  
  3.      //char7_dev[index].count = 0  
  4.      if(( char7_dev[index].data = kmalloc(MAX_SIMPLE_LEN, GFP_KERNEL) )!= NULL )  
  5.      {  
  6.          memset(char7_dev[index].data, 0, MAX_SIMPLE_LEN);  
  7.          D("kmalloc the data space OK! \n");  
  8.      }  
  9.      else{  
  10.         for( --index ; index >= 0 ; --index )  
  11.         {  
  12.              kfree(char7_dev[index].data);  
  13.         }   
  14.         printk(KERN_ERR "kmalloc simple_dev data number err no memory");  
  15.         kfree(char7_dev);  
  16.         unregister_chrdev_region(dev, DEV_COUNT);  
  17.         return -ENOMEM;  
  18.     }  
  19. }  

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

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

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

初始化数据指针

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

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

[cpp] view plaincopy
  1. for( index = 0 ; index < DEV_COUNT ; ++index )  
  2.    {                   
  3.        /*init the data ptr*/   
  4.        char7_dev[index].datard = char7_dev[index].data;  
  5.        char7_dev[index].datawr = char7_dev[index].data;  
  6.        char7_dev[index].dataend = char7_dev[index].data + MAX_SIMPLE_LEN;                                  
  7.   
  8.        /*init semaphore, waitqueue_head and so on*/  
  9.        sema_init(&(char7_dev[index].semp), 1);  
  10.        init_waitqueue_head(&(char7_dev[index].inq));  
  11.        init_waitqueue_head(&(char7_dev[index].outq));  
  12.    }  

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

逐个设备初始化(注册)并添加(告知内核)
[cpp] view plaincopy
  1. for( index = 0 ; index < DEV_COUNT ; ++index )  
  2. {  
  3.     cdev_init(&(char7_dev[index].cdev), &simple_fops);  
  4.     char7_dev[index].cdev.owner = THIS_MODULE;  
  5.   
  6.     err = cdev_add(&(char7_dev[index].cdev), dev, 1);  
  7.     if(err < 0)  
  8.     {  
  9.         printk(KERN_ERR "add cdev err \n");  
  10.         goto error1;  
  11.     }  
  12.     else  
  13.     {     
  14.         D( "add %d char dev OK!\n", index+1);  
  15.     }  
  16.   
  17. }  


字符设备操作

[cpp] view plaincopy
  1. struct file_operations simple_fops={  
  2.     .owner   = THIS_MODULE,  
  3.     .open    = simple_open,  
  4.     .release = simple_close,  
  5.     .read    = simple_read,  
  6.     .write   = simple_write,  
  7.     .llseek  = simple_llseek,  
  8. //  .ioctl   = simple_ioctl,  
  9.     .poll    = simple_poll,  
  10.     .mmap    = simple_mmap,  
  11. };  

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

打开及关闭操作

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

读写操作

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

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

[cpp] view plaincopy
  1. if(down_interruptible(&dev->semp) < 0)  
  2.    {  
  3.        printk(KERN_ERR "[%s]get the mutex lock error %d, %s",  
  4.                current->comm, __LINE__, __func__);  
  5.        return -ERESTARTSYS;  
  6.    }  
  7.    else  
  8.    {  
  9.        D("have get the mutex %d\n", __LINE__);  
  10.    }  

读操作

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

[cpp] view plaincopy
  1.  if(dev->datawr == dev->datard) //empty buffer  
  2.      {                
  3.     
  4.         while(dev->datawr == dev->datard)//循环检测是否buffer中是否已经有数据  
  5.         {  
  6.             up(&dev->semp);//释放信号量   
  7.             if(filp->f_flags & O_NONBLOCK) //检测用户是否是非阻塞打开  
  8.             {  
  9.                 D("set NONBLOCK mask %d\n", __LINE__);  
  10.                 return -EAGAIN;  
  11.             }  
  12.             D("[%s] reading going to sleep!", current->comm);   
  13.             /*将当调用进程加到写等待队列*/  
  14.             if(wait_event_interruptible(dev->inq, dev->datard != dev->datawr))  
  15.             {  
  16.                 return -ERESTARTSYS;  
  17.             }               
  18.             if(down_interruptible(&dev->semp) < 0)//wait_wvent_interrupt返回,获取信号量  
  19.             {  
  20.                 printk(KERN_ERR "[%s]get the mutex lock error %d, %s",  
  21.                         current->comm, __LINE__, __func__);  
  22.                 return -ERESTARTSYS;  
  23.             }  
  24.             else  
  25.             {  
  26.                 D("have get the mutex %d\n", __LINE__);  
  27.             }  
  28.         }  
  29.     }  

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

计算buffer 剩余的数据

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

[cpp] view plaincopy
  1. if(dev->datawr < dev->datard)  
  2. {  
  3.     data_remain = (dev->dataend - dev->datard)  
  4.                 + (dev->datawr-dev->data);  
  5. }  
  6. else  
  7. {  
  8.     data_remain = dev->datawr - dev->datard;  
  9. }  
如果有一些不理解 你可以参照下边的示意图,应该很容易就理解上述计算buffer内数据长度的
Linux驱动编程 step-by-step (九)字符设备模拟pipe的驱动程序

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

[cpp] view plaincopy
  1. if(data_remain < 0)  
  2. {  
  3.     printk(KERN_ERR "the remain data we calculate is wrong check! %d \n", __LINE__);  
  4. }  
  5. else if(count > data_remain)  
  6. {  
  7.     WAR("the data is less than the user want to read\n");  
  8.     D("we can only copy %d bytes to user\n", data_remain);  
  9.     count = data_remain;  
  10. }  
  11. else  
  12. {  
  13. }  

向用户空间传入数据

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

[cpp] view plaincopy
  1.     if(( dev->datawr > dev->datard ) || (dev->datard + count <= dev->dataend))  
  2.     {  
  3.         err = copy_to_user(userstr, dev->datard, count);  
  4.         if(err != 0)  
  5.         {  
  6.             printk(KERN_ERR "an error occured when copy data to user:%d\n", __LINE__);  
  7.             up(&dev->semp);  
  8.             return err;  
  9.         }  
  10.         else  
  11.         {  
  12.             D("data copy to user OK\n");  
  13.             dev->datard = dev->datard + count;              
  14.             if(dev->datard == dataend)  
  15.                  dev->datard = dev->data;  
  16.             wake_up_interruptible(&dev->outq);  
  17.             up(&dev->semp);  
  18.             return count;  
  19.         }  
  20.     }  


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

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

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

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

[cpp] view plaincopy
  1.         else  
  2.         {  
  3.             data_remain= (dev->dataend -dev->datard );   
  4.   
  5.             /*读取从当前位置到buffer结尾的数据长度*/  
  6.             err = copy_to_user(userstr, dev->datard+1, data_remain);  
  7.             if(err != 0)  
  8.             {  
  9.                 printk(KERN_ERR "an error occured when copy data to user:%d\n", __LINE__);  
  10.                 up(&dev->semp);  
  11.                 return err;  
  12.             }  
  13.             else  
  14.             {  
  15.                 D("data copy to user OK\n");  
  16.             //  up(&dev->semp);  
  17.             }             
  18.                
  19.             
  20.             /*从buffer头部读取剩余的长度*/         
  21.             err = copy_to_user(userstr+data_remain, dev->data, count-data_remain);  
  22.             if(err != 0)  
  23.             {  
  24.                 printk(KERN_ERR "an error occured when copy data to user:%d\n", __LINE__);  
  25.                 up(&dev->semp);  
  26.                 return err;  
  27.             }  
  28.             else  
  29.             {  
  30.                 D("data copy to user OK\n");  
  31.                 dev->datard = dev->data+(count-data_remain);  
  32.                 wake_up_interruptible(&dev->outq);  
  33.                 up(&dev->semp);  
  34.                 return count;  
  35.             }  
  36.         }  

写操作

与读操作类似

获取即将写入的位置

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

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

判断buffer是否已满

当即将写入的位置 正好是读指针指向的位置则表示buffer已满需要等待读进程 [cpp] view plaincopy
  1. if( next_ptr == dev->datard )  
  2. {  
  3.     while(next_ptr == dev->datard)  
  4.     {  
  5.         up(&dev->semp);  
  6.         if(filp->f_flags & O_NONBLOCK)  
  7.         {  
  8.             D("set NONBLOCK mask %d\n", __LINE__);  
  9.             return -EAGAIN;  
  10.         }  
  11.         D("[%s] writing going to sleep!", current->comm);  
  12.   
  13.         if(wait_event_interruptible(dev->outq, next_ptr != dev->datard))  
  14.         {  
  15.             return -ERESTARTSYS;  
  16.         }  
  17.   
  18.         if(down_interruptible(&dev->semp) < 0)  
  19.         {  
  20.             printk(KERN_ERR "[%s]get the mutex lock error %d, %s",  
  21.                     current->comm, __LINE__, __func__);  
  22.             return -ERESTARTSYS;  
  23.         }  
  24.         else  
  25.         {  
  26.             D("have get the mutex %d\n", __LINE__);  
  27.         }  
  28.     }  
  29. }  

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

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

[cpp] view plaincopy
  1. if(dev->datawr >= dev->datard)  
  2. {  
  3.     remain_space = (dev->dataend - dev->datawr-1)  
  4.                 + (dev->datard - dev->data);  
  5. }  
  6. else  
  7. {  
  8.     remain_space = dev->datard - dev->datawr - 1;  
  9. }  

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

[cpp] view plaincopy
  1.     if( (dev->datawr < dev->datard) || (dev->datawr + count < dev->dataend) )  
  2.     {  
  3.         err = copy_from_user(dev->datawr, userstr, count);  
  4.         if(err != 0)  
  5.         {  
  6.             printk(KERN_ERR "error occured when copy data from user %d\n", __LINE__);  
  7.             up(&dev->semp);  
  8.             return err;  
  9.         }  
  10.         else  
  11.         {  
  12.             D("data copy from user OK\n");  
  13.             dev->datawr = dev->datawr + count ;  
  14.             wake_up_interruptible(&dev->inq);  
  15.             up(&dev->semp);  
  16.             return count;  
  17.         }  
  18.     }  
  19.   
  20.     else  
  21.     {  
  22.         remain_space = dev->dataend - dev->datawr ;  
  23.         err = copy_from_user(dev->datawr, userstr, remain_space);  
  24.         if(err != 0)  
  25.         {  
  26.             printk(KERN_ERR "error occured when copy data from user %d\n", __LINE__);  
  27.             up(&dev->semp);  
  28.             return err;  
  29.         }  
  30.         else   
  31.         {  
  32.            D("copy part of the data from user\n");  
  33.         }  
  34.         err = copy_from_user(dev->data, userstr+remain_space, count-remain_space);  
  35.         if(err != 0)  
  36.         {  
  37.             printk(KERN_ERR "error occured when copy data from user %d\n", __LINE__);  
  38.             up(&dev->semp);  
  39.             return err;  
  40.        }  
  41.        else{  
  42.            D("data copy from user OK\n");  
  43.            dev->datawr = dev->data + (count-remain_space);  
  44.            wake_up_interruptible(&dev->inq);  
  45.            up(&dev->semp);  
  46.            return count;  
  47.       }  
  48.   }  

poll方法

在等待队列上调用poll_wait

[cpp] view plaincopy
  1. poll_wait(filp, &dev->inq, wait);  
  2. poll_wait(filp, &dev->outq, wait);  

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

[cpp] view plaincopy
  1. if(dev->datard != dev->datawr)  
  2. {  
  3.     mask |= POLLIN | POLLRDNORM;   //can be read  
  4. }  
  5.   
  6. if(dev->datawr+1 == dev->dataend)  
  7.     next_ptr = dev->data;  
  8. else  
  9.     next_ptr = dev->datawr+1;  
  10.   
  11. if(next_ptr != dev->datard)  
  12. {  
  13.     mask |= POLLOUT | POLLWRNORM; //can be write  
  14. }