Linux中的循环缓冲区
在学习到 并发和竞态 时,其中的提到了缓冲区,用于实现免锁算法,这里转载的是大神有关循环缓冲区做的一些操作。
其中源代码在最下面的附件中,有关作者的讲解感觉很清晰,很好,不过这里说一下自己的见解:
-
/*
-
* Data management: read and write
-
*/
-
-
ssize_t scull_kfifo_read(struct file *filp, char __user *buf, size_t count,
-
loff_t *f_pos)
-
{
-
struct scull_kfifo *dev = filp->private_data;
-
ssize_t retval = 0;
-
-
-
if (down_interruptible(&dev->sem))
-
return -ERESTARTSYS;
-
-
// get the length of the fifo
-
while (!kfifo_len(dev->tekkamankfifo)) { /* nothing to read */
-
// release the lock
-
up(&dev->sem);
-
-
// check whether the filp can lock/sleep
-
if (filp->f_flags & O_NONBLOCK)
-
return -EAGAIN;
-
printk(""%s" reading: going to sleepn", current->comm);
-
-
// begin to sleep
-
if (wait_event_interruptible(dev->inq, kfifo_len(dev->tekkamankfifo)))
-
return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
-
-
// after the sleep, go on to protect the deal(pay more attention here)
-
/* otherwise loop, but first reacquire the lock */
-
if (down_interruptible(&dev->sem))
-
return -ERESTARTSYS;
-
}
-
-
if (count > kfifo_len(dev->tekkamankfifo))
-
count = kfifo_len(dev->tekkamankfifo);
-
-
// read the data from the fifo
-
count = kfifo_get(dev->tekkamankfifo,tekkaman, count);
-
-
if (copy_to_user(buf, tekkaman, count)) {
-
retval = -EFAULT;
-
goto out;
-
}
-
retval = count;
-
-
out:
-
up(&dev->sem);
-
wake_up_interruptible(&dev->outq);
-
if (printk_ratelimit()) printk(""%s" did read %li bytesn",current->comm, (long)retval);
-
return retval;
-
}
-
-
ssize_t scull_kfifo_write(struct file *filp, const char __user *buf, size_t count,
-
loff_t *f_pos)
-
{
-
struct scull_kfifo *dev = filp->private_data;
-
ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
-
-
if (down_interruptible(&dev->sem))
-
return -ERESTARTSYS;
-
-
if ( count>BUFSIZE ) count = BUFSIZE;
-
if (copy_from_user(tekkaman, buf, count)) {
-
up (&dev->sem);
-
return -EFAULT;
-
}
-
-
//write the data into the fifo
-
count = kfifo_put(dev->tekkamankfifo,tekkaman, count);
-
retval = count;
-
-
up(&dev->sem);
-
wake_up_interruptible(&dev->inq); /* blocked in read() and select() */
-
-
if (printk_ratelimit())
-
printk(""%s" did write %li bytesn",current->comm, (long)count);
-
return retval;
-
}
上面是修过修改的代码,做一下说明,其中最主要是在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] #
|
阅读(303) | 评论(0) | 转发(0) |