参考资料:《Linux内核中的循环缓冲区》作者:西邮 王聪 严重感谢**作者! 但是(可能是源码版本问题)有些结论并不正确: “而kfifo_init只会接受一个已分配好空间的fifo->buffer,不能和kfifo_free搭配,用kfifo_init分配的kfifo只能用kfree释放。” 阅读源码可以得出这样的结论:kfifo_init和kfifo_alloc分配的kfifo都能用kfree释放。已经用实验证实。<br />原文链接地址: http://www.kerneltravel.net/jiaoliu/kern-kfifo.html<br /><br />--------------------------------------------------------------------------------<br /><br />在学习到第十章 中断处理 时,其中的中断驱动的I/O需要使用缓冲区,我觉得与其自己实现一个缓冲区,不如利用内核已经写好的fifo。内核里有一个通用的循环缓冲区的实现在 。<br />使用的数据结构如下:<br />struct kfifo {<br /> unsigned char *buffer; /* 使用的缓冲区头指针 */<br /> unsigned int size; /* 缓冲区总大小 */<br /> unsigned int in; /* 已写入缓冲区的数据总量,当前缓冲区写指针的偏移量:(in % size) */<br /> unsigned int out; /* 已读出缓冲区的数据总量,当前缓冲区读指针的偏移量:(out % size) */<br /> spinlock_t *lock; /* 为避免竞态的自旋锁 */<br />};/*当in==out时,缓冲区为空;当(in-out)==size时,缓冲区已满*/<br /><br /><br />kfifo提供的循环缓冲的部分函数分为2类:<br /><br />(1)以双下划线开头,没有使用自旋锁函数;<br /><br />(2)没有双下划线开头,需要额外加锁的情况下使用的函数。<br /><br />其实第二类只是在第一类的基础上进行加锁后,实际的代码如下: unsigned long flags;<br /> spin_lock_irqsave(fifo->lock, flags);<br /> /*第一类函数*/<br /> spin_unlock_irqrestore(fifo->lock, flags);<br /><br /><br /><br />以下我按使用的顺序介绍每个函数的使用,部分函数源码在kernel/kfifo.c中定义,这些接口是经过精心构造的,可以小心地避免一些边界情况,原理其实很简单,建议去看源码弄清楚实现的原理,可以学到一些编程技巧。<br /><br />(0)声明循环缓冲数据结构指针 struct kfifo *tekkamanfifo;<br /><br /><br /><br />(1)初始化循环缓冲结构体 struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size,<br /> gfp_t gfp_mask, spinlock_t *lock);<br />/*调用kfifo_init必须保证size是2的整数次幂,而且buffer只接受一个已分配好空间的指针。也就是说之前要使用kmalloc分配好空间,将返回的指针传递到buffer*/<br />struct kfifo *kfifo_alloc(unsigned int size, gfp_t gfp_mask,<br /> spinlock_t *lock);<br />/*调用kfifo_alloc不必保证size是2的幂,它内部会把size向上调整到2的整数次幂。空间分配的内部实现使用kmalloc。函数内部调用kfifo_init/<br /><br /><br /><br />buffer:之前要使用kmalloc分配好的空间指针;<br /><br />size:循环缓冲空间大小;<br /><br />gfp_mask:和kmalloc使用的分配标志(flags)一样。(参阅Linux设备驱动程序学习(8)-分配内存)<br /><br />lock:是事先声明并初始化好的自旋锁结构体指针;<br /><br />返回值 为初始化好的循环缓冲数据结构指针 。<br /><br />(2) 向缓冲区里写入数据 unsigned int kfifo_put(struct kfifo *fifo,unsigned char *buffer, unsigned int len);<br />unsigned int __kfifo_put(struct kfifo *fifo,unsigned char *buffer, unsigned int len);<br /><br /><br /><br />fifo:要写入数据的缓冲区结构体指针;<br /><br />buffer:要写入的数据指针,指向内核空间。如需要用户空间数据,之前要用copy_from_user复制数据到内核空间;<br /><br />len:要写入的数据大小;<br /><br />返回值 为写入缓冲区的数据字节数。<br /><br />(3)从缓冲区里读出数据 unsigned int kfifo_get(struct kfifo *fifo, unsigned char *buffer, unsigned int len);<br />unsigned int __kfifo_get(struct kfifo *fifo, unsigned char *buffer, unsigned int len);<br /><br /><br /><br />参数定义和kfifo_put类似。<br /><br />返回值 为从缓冲区读出的数据字节数。<br /><br />(4)得到缓冲区已有的数据字节数 unsigned int kfifo_len(struct kfifo *fifo);<br />unsigned int __kfifo_len(struct kfifo *fifo);<br /><br /><br /><br />fifo:要操作的缓冲区结构体指针;<br /><br />函数返回缓冲区实际已有的数据字节数,内部实现十分简单,就是in - out;<br /><br />返回值 为缓冲区已有的数据字节数。<br /><br />(5)清空缓冲区 void __kfifo_reset(struct kfifo *fifo);<br />void kfifo_reset(struct kfifo *fifo);<br /><br /><br /><br />内部实现十分简单,就是in = out = 0。<br /><br />(6)使用结束,释放缓冲区。 void kfifo_free(struct kfifo *fifo);<br /><br /><br /><br />所有的kfifo提供的循环缓冲的函数就是这些。在理解内部实现原理的基础上才能更好的使用它,所以再次建议阅读源码,因为源码很简单,但是很精巧。 <br /><br /><br />--------------------------------------------------------------------------------<br /><br /><br />ARM9开发板实验<br /><br />实验模块源码:scull-kfifo<br /><br />测试程序源码:scull-kfifo-test<br /><br />实验现象: [Tekkaman2440@SBC2440V4]#cd /lib/modules/<br />[Tekkaman2440@SBC2440V4]#insmod scull_kfifo.ko<br />[Tekkaman2440@SBC2440V4]#cat /proc/devices<br />Character devices:<br /> 1 mem<br /> 2 pty<br /> 3 ttyp<br /> 4 /dev/vc/0<br /> 4 tty<br /> 4 ttyS<br /> 5 /dev/tty<br /> 5 /dev/console<br /> 5 /dev/ptmx<br /> 7 vcs<br />10 misc<br />13 input<br />14 sound<br />81 video4linux<br />89 i2c<br />90 mtd<br />116 alsa<br />128 ptm<br />136 pts<br />153 spi<br />180 usb<br />189 usb_device<br />204 s3c2410_serial<br />252 scull_kfifo<br />253 usb_endpoint<br />254 rtc<br /><br />Block devices:<br /> 1 ramdisk<br />256 rfd<br /> 7 loop<br />31 mtdblock<br />93 nftl<br />96 inftl<br />179 mmc<br />[Tekkaman2440@SBC2440V4]#mknod -m 666 /dev/scull_kfifo c 252 0<br />[Tekkaman2440@SBC2440V4]#echo 1234567890 > /dev/scull_kfifo<br />'sh' did write 11 bytes<br />[Tekkaman2440@SBC2440V4]#/tmp/scull_kfifo_test<br />scull_kfifo: the module can not lseek!<br />please input the command :1<br />scull_kfifo: ioctl SCULL_KFIFO_SIZE len=11<br />please input the command :2<br />scull_kfifo: SCULL_KFIFO_RESET code=0<br />please input the command :1<br />scull_kfifo: ioctl SCULL_KFIFO_SIZE len=0<br />please input the command :q<br />[Tekkaman2440@SBC2440V4]#echo 123456789012345678901234567890 > /dev/scull_kfifo <br /><br />'sh' did write 31 bytes<br />[Tekkaman2440@SBC2440V4]#echo 123456789012345678901234567890 > /dev/scull_kfifo <br /><br />'sh' did write 31 bytes<br />[Tekkaman2440@SBC2440V4]#echo 1234567890 > /dev/scull_kfifo<br /><br />'sh' did write 2 bytes<br />'sh' did write 0 bytes<br />'sh' did write 0 bytes<br />'sh' did write 0 bytes<br />'sh' did write 0 bytes<br />'sh' did write 0 bytes<br />'sh' did write 0 bytes<br />'sh' did write 0 bytes<br />'sh' did write 0 bytes<br />printk: 204310 messages suppressed.<br />'sh' did write 0 bytes<br /><br />1234567890<br />[Tekkaman2440@SBC2440V4]#/tmp/scull_kfifo_test<br />scull_kfifo: the module can not lseek!<br />please input the command :1<br />scull_kfifo: ioctl SCULL_KFIFO_SIZE len=64<br />please input the command :q<br />[Tekkaman2440@SBC2440V4]#cat /dev/scull_kfifo<br />printk: 1493677 messages suppressed.<br />'cat' did read 64 bytes<br />1234'cat' reading: going to sleep<br />56789012345678901234567890<br />123456789012345678901234567890<br />12<br />[Tekkaman2440@SBC2440V4]#/tmp/scull_kfifo_test<br />scull_kfifo: the module can not lseek!<br />please input the command :2<br />scull_kfifo: SCULL_KFIFO_RESET code=0<br />please input the command :q<br />[Tekkaman2440@SBC2440V4]#rmmod scull_kfifo<br />[Tekkaman2440@SBC2440V4]#lsmod<br />Module Size Used by Not tainted<br />[Tekkaman2440@SBC2440V4]#<br /> <br /> <br /> |
|