Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1118945
  • 博文数量: 185
  • 博客积分: 495
  • 博客等级: 下士
  • 技术积分: 1418
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-02 15:12
个人简介

治肾虚不含糖,专注内核性能优化二十年。 https://github.com/KnightKu

文章分类

全部博文(185)

文章存档

2019年(1)

2018年(12)

2017年(5)

2016年(23)

2015年(1)

2014年(22)

2013年(82)

2012年(39)

分类: LINUX

2013-03-01 16:03:16

原文地址:Linux中的循环缓冲区 作者:xinyuwuxian

Linux中的循环缓冲区

在学习到 并发和竞态 时,其中的提到了缓冲区,用于实现免锁算法,这里转载的是大神有关循环缓冲区做的一些操作。

其中源代码在最下面的附件中,有关作者的讲解感觉很清晰,很好,不过这里说一下自己的见解:


点击(此处)折叠或打开

  1. /*
  2.  * Data management: read and write
  3.  */

  4. ssize_t scull_kfifo_read(struct file *filp, char __user *buf, size_t count,
  5.                 loff_t *f_pos)
  6. {
  7.     struct scull_kfifo *dev = filp->private_data;
  8.     ssize_t retval = 0;


  9.     if (down_interruptible(&dev->sem))
  10.         return -ERESTARTSYS;

  11.     // get the length of the fifo
  12.     while (!kfifo_len(dev->tekkamankfifo)) { /* nothing to read */
  13.         // release the lock
  14.         up(&dev->sem);

  15.         // check whether the filp can lock/sleep
  16.         if (filp->f_flags & O_NONBLOCK)
  17.             return -EAGAIN;
  18.         printk(""%s" reading: going to sleepn", current->comm);

  19.         // begin to sleep
  20.         if (wait_event_interruptible(dev->inq, kfifo_len(dev->tekkamankfifo)))
  21.             return -ERESTARTSYS; /* signal: tell the fs layer to handle it */

  22.         // after the sleep, go on to protect the deal(pay more attention here)
  23.         /* otherwise loop, but first reacquire the lock */
  24.         if (down_interruptible(&dev->sem))
  25.             return -ERESTARTSYS;
  26.     }

  27.     if (count > kfifo_len(dev->tekkamankfifo))
  28.         count = kfifo_len(dev->tekkamankfifo);

  29.     // read the data from the fifo
  30.     count = kfifo_get(dev->tekkamankfifo,tekkaman, count);

  31.     if (copy_to_user(buf, tekkaman, count)) {
  32.         retval = -EFAULT;
  33.         goto out;
  34.     }
  35.     retval = count;

  36.   out:
  37.     up(&dev->sem);
  38.     wake_up_interruptible(&dev->outq);
  39.     if (printk_ratelimit())     printk(""%s" did read %li bytesn",current->comm, (long)retval);
  40.     return retval;
  41. }

  42. ssize_t scull_kfifo_write(struct file *filp, const char __user *buf, size_t count,
  43.                 loff_t *f_pos)
  44. {
  45.     struct scull_kfifo *dev = filp->private_data;
  46.     ssize_t retval = -ENOMEM; /* value used in "goto out" statements */

  47.     if (down_interruptible(&dev->sem))
  48.         return -ERESTARTSYS;
  49.     
  50.     if ( count>BUFSIZE ) count = BUFSIZE;
  51.     if (copy_from_user(tekkaman, buf, count)) {
  52.         up (&dev->sem);
  53.         return -EFAULT;
  54.     }

  55.     //write the data into the fifo
  56.     count = kfifo_put(dev->tekkamankfifo,tekkaman, count);
  57.     retval = count;

  58.     up(&dev->sem);
  59.     wake_up_interruptible(&dev->inq); /* blocked in read() and select() */
  60.     
  61.     if (printk_ratelimit())
  62.         printk(""%s" did write %li bytesn",current->comm, (long)count);
  63.     return retval;
  64. }
上面是修过修改的代码,做一下说明,其中最主要是在read和write函数中加入的信号量作为保护这里, 循环缓冲区本身就是免锁算法,

也就是说本身已经加上锁了,不知道这里大神为什么还是另外加入了一个信号量,无疑浪费了循环缓冲区尽量减少鎖機制的使用这个目的,

循环缓冲区采用生产者/消费者模式很好的实现了免锁算法,

以上为学习ldd3自己的见解.

内核里有一个通用的循环缓冲区的实现在 

使用的数据结构如下:

struct kfifo { 
    unsigned char * buffer;     /* 使用的缓冲区头指针 */ 
    unsigned int size;     /* 缓冲区总大小 */ 
    unsigned int in;     /* 已写入缓冲区的数据总量,当前缓冲区写指针的偏移量:(in % size) */
    unsigned int out;     /* 已读出缓冲区的数据总量,当前缓冲区读指针的偏移量:(out % size) */ 
    spinlock_t * lock;     /* 为避免竞态的自旋锁 */ 
} ; /*当in==out时,缓冲区为空;当(in-out)==size时,缓冲区已满*/

kfifo 提供的循环缓冲的部分函数分为2类:

(1)以双下划线开头,没有使用自旋锁函数;

(2)没有双下划线开头,需要额外加锁的情况下使用的函数。

其实第二类只是在第一类的基础上进行加锁后,实际的代码如下:

    unsigned long flags; 
    spin_lock_irqsave( fifo- > lock, flags) ; 
    /*第一类函数*/ 
    spin_unlock_irqrestore( fifo- > lock, flags) ;


以下我按使用的顺序介绍每个函数的使用,部分函数源码在kernel/kfifo.c 中定义,这些接口是经过精心构造的,可以小心地避免一些边界情况,原理其实很简单,建议去看源码弄清楚实现的原理,可以学到一些编程技巧。

(0)声明循环缓冲数据结构指针

struct kfifo *tekkamanfifo;


(1)初始化循环缓冲结构体

struct kfifo * kfifo_init( unsigned char * buffer, unsigned int size, 
                gfp_t gfp_mask, spinlock_t * lock) ; 
/*调用kfifo_init必须保证size是2的整数次幂,而且buffer 只接受一个已分配好空间的指针。也就是说之前要使用kmalloc分配好空间,将返回的指针传递到buffer*/ 
struct kfifo * kfifo_alloc( unsigned int size, gfp_t gfp_mask, 
                 spinlock_t * lock) ; 
/*调用kfifo_alloc不必保证size是2的幂,它内部会把size向上调整到2的整数次幂。空间分配的内部实现使用kmalloc。函数内部调用kfifo_init/


buffer:之前要使用kmalloc分配好的空间指针;

size:循环缓冲空间大小;

gfp_mask:和kmalloc使用的分配标志(flags)一样。(参阅Linux设备驱动程序学习(8)-分配内存 

lock:是事先声明并初始化好的 自旋锁结构体指针;

返回值 为初始化好的 循环缓冲数据结构指针 。

(2) 向缓冲区里写入数据

unsigned int kfifo_put( struct kfifo * fifo, unsigned char * buffer, unsigned int len); 
unsigned int __kfifo_put( struct kfifo * fifo, unsigned char * buffer, unsigned int len) ;


fifo:要写入数据的缓冲区结构体指针;

buffer:要写入的数据指针,指向内核空间。如需要用户空间数据,之前要用copy_from_user复制数据到内核空间;

len:要写入的数据大小;

返回值 为写入缓冲区的数据字节数。

(3)从缓冲区里读出数据

unsigned int kfifo_get( struct kfifo * fifo, unsigned char * buffer, unsigned int len) ; 
unsigned int __kfifo_get( struct kfifo * fifo, unsigned char * buffer, unsigned int len) ;


参数定义和kfifo_put类似。

返回值 为从缓冲区读出的数据字节数。

(4)得到缓冲区已有的数据字节数

unsigned int kfifo_len( struct kfifo * fifo) ; 
unsigned int __kfifo_len( struct kfifo * fifo) ;


fifo:要操作的缓冲区结构体指针;

函数返回缓冲区实际已有的数据字节数,内部实现十分简单,就是in - out;

返回值 为缓冲区已有的数据字节数。

(5)清空缓冲区

void __kfifo_reset( struct kfifo * fifo) ; 
void kfifo_reset( struct kfifo * fifo) ;


内部实现十分简单,就是in = out = 0。

(6)使用结束,释放缓冲区。

void kfifo_free( struct kfifo * fifo) ;


所有的kfifo 提供的循环缓冲的函数就是这些。在理解内部实现原理的基础上才能更好的使用它,所以再次建议阅读源码,因为源码很简单,但是很精巧。



ARM9开发板实验

实验模块源码:scull-kfifo

测试程序源码:scull-kfifo-test

实验现象:

[ Tekkaman2440@SBC2440V4] # cd / lib/ modules/ 
[ Tekkaman2440@SBC2440V4] # insmod scull_kfifo. ko
[ Tekkaman2440@SBC2440V4] # cat / proc/ devices
Character devices: 
  1 mem
  2 pty
  3 ttyp
  4 / dev/ vc/ 0
  4 tty
  4 ttyS
  5 / dev/ tty
  5 / dev/ console
  5 / dev/ ptmx
  7 vcs
 10 misc
 13 input
 14 sound
 81 video4linux
 89 i2c
 90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
204 s3c2410_serial
252 scull_kfifo 
253 usb_endpoint
254 rtc

Block devices: 
  1 ramdisk
256 rfd
  7 loop
 31 mtdblock
 93 nftl
 96 inftl
179 mmc
[ Tekkaman2440@SBC2440V4] # mknod - m 666 / dev/ scull_kfifo c 252 0
[ Tekkaman2440@SBC2440V4] # echo 1234567890 > / dev/ scull_kfifo
"sh" did write 11 bytes
[ Tekkaman2440@SBC2440V4] # / tmp/ scull_kfifo_test
scull_kfifo: the module can not lseek!
please input the command : 1
scull_kfifo: ioctl SCULL_KFIFO_SIZE len= 11
please input the command : 2
scull_kfifo: SCULL_KFIFO_RESET code= 0
please input the command : 1
scull_kfifo: ioctl SCULL_KFIFO_SIZE len= 0
please input the command : q
[ Tekkaman2440@SBC2440V4] # echo 123456789012345678901234567890 > / dev/ scull_kfifo

"sh" did write 31 bytes
[ Tekkaman2440@SBC2440V4] # echo 123456789012345678901234567890 > / dev/ scull_kfifo

"sh" did write 31 bytes
[ Tekkaman2440@SBC2440V4] # echo 1234567890 > / dev/ scull_kfifo

 "sh" did write 2 bytes
"sh" did write 0 bytes
"sh" did write 0 bytes
"sh" did write 0 bytes
"sh" did write 0 bytes
"sh" did write 0 bytes
"sh" did write 0 bytes
"sh" did write 0 bytes
"sh" did write 0 bytes
printk: 204310 messages suppressed. 
"sh" did write 0 bytes

1234567890
[ Tekkaman2440@SBC2440V4] # / tmp/ scull_kfifo_test
scull_kfifo: the module can not lseek!
please input the command : 1
scull_kfifo: ioctl SCULL_KFIFO_SIZE len= 64
please input the command : q
[ Tekkaman2440@SBC2440V4] # cat / dev/ scull_kfifo
printk: 1493677 messages suppressed. 
"cat" did read 64 bytes
1234"cat" reading: going to sleep 
56789012345678901234567890
123456789012345678901234567890
12
[ Tekkaman2440@SBC2440V4] # / tmp/ scull_kfifo_test
scull_kfifo: the module can not lseek!
please input the command : 2
scull_kfifo: SCULL_KFIFO_RESET code= 0
please input the command : q
[ Tekkaman2440@SBC2440V4] # rmmod scull_kfifo
[ Tekkaman2440@SBC2440V4] # lsmod
Module Size Used by Not tainted
[ Tekkaman2440@SBC2440V4] #

阅读(524) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~