今天在看linux内核时候,发现内核中的循环缓冲区的设计与实现非常的完美。让后就想自己仿照着也写一个,用于加深理解。
linux内核中对struct kfifo结构体的各种操作的源代码存放在:
/kernel/kfifo.c 和 /include/linux/kfifo.h这两个文件之中。
struct kfifo
{
unsigned char *buffer;
unsigned int size; // 用于记录缓存区的大小;
unsigned int in; // 用于记录下一到达缓存区的数据的存放位置;
unsigned int out; // 用于记录下一次从缓存区提取的数据的位置;
spinlock_t *lock; // 用于防止并发的对这个缓存区进行读操作或写操作;
};
通过这个struct kfifo结构体来对一个循环缓存区进行各种信息的统计;
先介绍几个在对这个缓存区进行各种操作时,需要用到的几个非常简单的函数,当然这几个函数是我自己在写循环缓存队列时用到的。
1. 测试一个数是否是 2的幂数
int my_is_power_of_2(int n)
{
return ( n!=0 && ( n & (n - 1) ) == 0 );
}
2. 如果一个数不是2的幂数的话,找出大于这个数的最近的2的幂数
int my_power_of_2(int n)
{
unsigned int i = 0;
if(n)
{
while(n != 0)
{
n = n >> 1;
i++;
}
return (1 << i);
}
}
此函数的用法: 如果 n = 12 ,不是2的幂数,my_power_of_2(n)返回 16;
n = 17 ,也不是2的幂数, my_power_of_2(n)返回 32;
3.这是一个宏定义,用于取两个数中最小的数;
#define min(m, n) (m) < (n) ? (m) : (n)
min(2,3) min()返回的是 2;
min(10, 4) min()返回的是 4;
开始对循环缓存队列进行各种操作的函数:
1.循环缓存队列的初始化:
struct kfifo * fifo_init(unsigned char *buffer, gfp_t gfp_mask, unsigned int len,
spinlock_t *spinlock)
{
struct kfifo *fifo = NULL;
fifo = kmalloc(sizeof(struct kfifo), gfp_mask);
if( fifo == NULL )
return -ENOMEM;
fifo->buffer = buffer;
fifo->size = len;
fifo->in = 0;
fifo->out = 0;
fifo->lock = spinlock;
return fifo;
}
kfifo_init()函数主要做的工作是:分配了一个struct kfifo结构体,然后对其进行初始赋值;
2.给struct kfifo结构体分配内存,用于存取缓存的数据
struct kfifo * kfifo_alloc(gfp_t gfp_mask, unsigned int len, spinlock_t *spinlock)
{
struct kfifo *ret = NULL;
unsigned char *buffer = NULL;
// 用于判断len是否是 2的幂数,如果是的话直接使用就可以了
// 如果不是的话,使用my_power_of_2()将其调整为2的幂数
if(!my_is_power_of_2(len))
len = my_power_of_2(len);
buffer = kmalloc(len, gfp_mask);
if( buffer == NULL )
return -ENOMEM;
memset(buffer, 0, len);
ret = kfifio_init(buffer, gfp_mask, len, spinlock);
if( IS_ERR(ret) ) // IS_ERR()函数主要用于判断返回值是否是像-ENOMEM这类的返回值;
kfree(buffer);
return ret;
}
3.释放循环缓存队列中的缓存;
void kfifo_free(struct kfifo *fifo)
{
kfree(fifo->buffer);
kfree(fifo);
}
4.给循环缓存队列中添加数据的操作(未加锁的版本)
int __kfifo_put(unsigned char *buffer, unsigned int len, struct kfifo *fifo)
{
unsigned int length ;
//fifo->size - fifo->in + fifo->out 这个计算出来的结果值是循环缓存队列中的未用的
//空间字节数;
len = min(len, fifo->size - fifo->in + fifo->out);
// fifo->size - (fifo->in & (fifo->size -1))计算出来的结果是fifo->in到缓存数组
//末尾的空间大小
length = min(len, fifo->size - ( filo->in & (fifo->size - 1) ) );
memcpy(fifo->buffer + (fifo->in & (fifo->size -1)),buffer, length );
memcpy(fifo->buffer, buffer+length, len-length);
fifo->in +=len;
return len;
}
__kfifo_put()函数返回的是写入循环队列中的数据的字节数;因为有可能存在,当要写入的字节数大于循环队列中的空闲的空间大小。
5.从循环缓存队列中取走数据的操作(为加锁版本)
int __kfifo_get(unsigned char *buffer, int len, struct kfifo *fifo)
{
unsigned int length;
// fifo->in - fifo->out 计算出来的是缓存队列中存放的字节数;
len = min(len, fifo->in - fifo->out);
length = min(len, fifo->size - (fifo->out & (fifo->size - 1))) ;
memcpy( buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), length );
memcpy( buffer+length, fifo->buffer, len - length);
fifo->out += len;
return len;
}
__kfifo_get()返回的是从循环队列中提取的数据的字节数;
6.循环缓存队列的重置(为加锁):
inline void __kfifo_reset(struct kfifo *fifo)
{
fifo->in = 0;
fifo->out = 0;
}
7. 循环缓存队列中的数据长度(为加锁):
inline int __kfifo_len(struct kfifo *fifo)
{
return fifo->in - fifo->out;
}
以上的这几个函数涉及到了对循环缓存队列的读写操作,而在系统中如果同时存在多个进程对这个缓存队列进行读写操作的话,会存在数据的紊乱问题。所以这个循环缓存队列可以看成是一个临界区资源,每次只能有一个进程对其进行读写操作,不允许并发的读写操作。
所以就有了以上几个函数的加锁版本了:
1. int kfifo_put(unsigned char *buffer, unsigned int len, struct kfifo *fifo)
{
unsigned int flags;
unsigned int ret;
spin_lock_irqsave(fifo->lock, flags);
ret = __kfifo_put(buffer, len, fifo);
spin_unlock_irqstore(fifo->lock, flags);
return ret;
}
2. int kfifo_get(unsigned char *buffer, unsigned int len, struct kfifo *fifo)
{
unsigned int flags ;
unsigned int ret;
spin_lock_irqsave(fifo->lock, flags);
ret = __kfifo_get(buffer, len, fifo);
spin_unlock_irqstore(fifo->lock, flags);
return ret;
}
3.int kfifo_len(struct kfifo *fifo)
{
unsigned int flags;
unsigned int ret;
spin_lock_irqsave(fifo->lock, flags);
ret = __kfifo_len(fifo);
spin_unlock_irqstore(fifo->lock, flags);
return ret;
}
4. void kfifo_reset(struct kfifo *fifo)
{
unsigned int flags;
unsigned int ret;
spin_lock_irqsave(fifo->lock, flags);
__kfifo_reset(fifo);
spin_unlock_irqstore(fifo->lock, flags);
}
看完内核对struct kfifo 结构体的各种操作,感觉在内核代中,每个函数只完成一个功能,并且对循环缓存队列的读操作、写操作、重置操作都非常的简单,代码的逻辑也是非常的清晰。
以上代码是我对模仿内核代码写的,当然处理的肯定没有内核代码那么完美,比如在内核代码中对循环队列缓存区的读写操作是要加入内存屏障的。
多看、多想内核代码,对编程水平帮助很大!