dpdk rte_ring无锁队列 及 内核kfifo

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_38679007/article/details/80582812
kfifo是内核里面的一个FIFO数据结构,采用环形循环队列的数据结构来实现;它提供一个无边界的字节流服务,最重要的一点是,它使用并行无锁编程技术,即当它用于只有一个入队线程和一个出队线程的场情时,两个线程可以并发操作,而不需要任何加锁行为,就可以保证kfifo的线程安全。
kfifo代码既然肩负着这么多特性,那我们先一敝它的代码:
struct kfifo {
    unsigned char *buffer;    /* the buffer holding the data */
    unsigned int size;    /* the size of the allocated buffer */
    unsigned int in;    /* data is added at offset (in % size) */
    unsigned int out;    /* data is extracted from off. (out % size) */
    spinlock_t *lock;    /* protects concurrent modifications */ //紧用在多生产者或多消费者环境。
};
+--------------------------------------------------------------+
|            |<----------data---------->|                      |
+--------------------------------------------------------------+
             ^                          ^                      ^
             |                          |                      |
            out                        in                     size

struct kfifo *kfifo_alloc(unsigned int size, gfp_t gfp_mask, spinlock_t *lock)
{
    unsigned char *buffer;
    struct kfifo *ret;

    /*
     * round up to the next power of 2, since our 'let the indices
     * wrap' tachnique works only in this case.
     */
    if (size & (size - 1)) {
        BUG_ON(size > 0x80000000);
        size = roundup_pow_of_two(size);
    }

    buffer = kmalloc(size, gfp_mask);
    if (!buffer)
        return ERR_PTR(-ENOMEM);

    ret = kfifo_init(buffer, size, gfp_mask, lock);

    if (IS_ERR(ret))
        kfree(buffer);

    return ret;
}
unsigned int __kfifo_put(struct kfifo *fifo,
             unsigned char *buffer, unsigned int len)
{
    unsigned int l;
    /* 本次存放的数据量,当数据量大于剩余空间时取len */
    len = min(len, fifo->size - fifo->in + fifo->out);

    smp_mb();
    /* 计算fifo->in后面还有多少可用空间 */
    l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
    memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);

    memcpy(fifo->buffer, buffer + l, len - l);

    smp_wmb();

    fifo->in += len;

    return len;
}

unsigned int __kfifo_get(struct kfifo *fifo,
             unsigned char *buffer, unsigned int len)
{
    unsigned int l;
     /* 本次取的数据量,当len大于kinfo->buf数据量时时取len */
    len = min(len, fifo->in - fifo->out);

    smp_rmb();

   /* 计算fifo->out后面有多少数据可以取 */
    l = min(len, fifo->size - (fifo->out & (fifo->size - 1)));
    memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l);
    memcpy(buffer + l, fifo->buffer, len - l);

    smp_mb();

    fifo->out += len;

    return len;
}

保持一个原则:先存取数据然后增加in/out.
空闲的空间:fifo->size - fifo->in + fifo->out
已占用的空间:fifo->in - fifo->out
队列空:fifo->in == fifo->out
队列满:(fifo->in - fifo->out)&(fifo->size-1) == 0

smp_mp:内存屏障用来告诉CPU保顺执行。



展开阅读全文

内核中的kfifo 浅析

07-26

linux内核数据结构之kfifornrn1、前言rnrn  最近项目中用到一个环形缓冲区(ring buffer),代码是由linux内核的kfifo改过来的。缓冲区在文件系统中经常用到,通过缓冲区缓解cpu读写内存和读写磁盘的速度。例如一个进程A产生数据发给另外一个进程B,进程B需要对进程A传的数据进行处理并写入文件,如果B没有处理完,则A要延迟发送。为了保证进程A减少等待时间,可以在A和B之间采用一个缓冲区,A每次将数据存放在缓冲区中,B每次冲缓冲区中取。这是典型的生产者和消费者模型,缓冲区中数据满足FIFO特性,因此可以采用队列进行实现。Linux内核的kfifo正好是一个环形队列,可以用来当作环形缓冲区。生产者与消费者使用缓冲区如下图所示:rnrn  环形缓冲区的详细介绍及实现方法可以参考http://en.wikipedia.org/wiki/Circular_buffer,介绍的非常详细,列举了实现环形队列的几种方法。环形队列的不便之处在于如何判断队列是空还是满。维基百科上给三种实现方法。rnrn2、linux 内核kfifornrn  kfifo设计的非常巧妙,代码很精简,对于入队和出对处理的出人意料。首先看一下kfifo的数据结构:rn复制代码rnrnstruct kfifo rn unsigned char *buffer; /* the buffer holding the data */rn unsigned int size; /* the size of the allocated buffer */rn unsigned int in; /* data is added at offset (in % size) */rn unsigned int out; /* data is extracted from off. (out % size) */rn spinlock_t *lock; /* protects concurrent modifications */rn;rnrn复制代码rnrnkfifo提供的方法有:rn复制代码rnrn 1 //根据给定buffer创建一个kfiforn 2 struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size,rn 3 gfp_t gfp_mask, spinlock_t *lock);rn 4 //给定size分配buffer和kfiforn 5 struct kfifo *kfifo_alloc(unsigned int size, gfp_t gfp_mask,rn 6 spinlock_t *lock);rn 7 //释放kfifo空间rn 8 void kfifo_free(struct kfifo *fifo)rn 9 //向kfifo中添加数据rn10 unsigned int kfifo_put(struct kfifo *fifo,rn11 const unsigned char *buffer, unsigned int len)rn12 //从kfifo中取数据rn13 unsigned int kfifo_put(struct kfifo *fifo,rn14 const unsigned char *buffer, unsigned int len)rn15 //获取kfifo中有数据的buffer大小rn16 unsigned int kfifo_len(struct kfifo *fifo)rnrn复制代码rnrn 定义自旋锁的目的为了防止多进程/线程并发使用kfifo。因为in和out在每次get和out时,发生改变。初始化和创建kfifo的源代码如下:rn复制代码rnrn 1 struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size,rn 2 gfp_t gfp_mask, spinlock_t *lock)rn 3 rn 4 struct kfifo *fifo;rn 6 /* size must be a power of 2 */rn 7 BUG_ON(!is_power_of_2(size));rn 9 fifo = kmalloc(sizeof(struct kfifo), gfp_mask);rn10 if (!fifo)rn11 return ERR_PTR(-ENOMEM);rn13 fifo->buffer = buffer;rn14 fifo->size = size;rn15 fifo->in = fifo->out = 0;rn16 fifo->lock = lock;rn17 rn18 return fifo;rn19 rn20 struct kfifo *kfifo_alloc(unsigned int size, gfp_t gfp_mask, spinlock_t *lock)rn21 rn22 unsigned char *buffer;rn23 struct kfifo *ret;rn29 if (!is_power_of_2(size)) rn30 BUG_ON(size > 0x80000000);rn31 size = roundup_pow_of_two(size);rn32 rn34 buffer = kmalloc(size, gfp_mask);rn35 if (!buffer)rn36 return ERR_PTR(-ENOMEM);rn38 ret = kfifo_init(buffer, size, gfp_mask, lock);rn39 rn40 if (IS_ERR(ret))rn41 kfree(buffer);rn43 return ret;rn44 rnrn复制代码rnrn  在kfifo_init和kfifo_calloc中,kfifo->size的值总是在调用者传进来的size参数的基础上向2的幂扩展,这是内核一贯的做法。这样的好处不言而喻--对kfifo->size取模运算可以转化为与运算,如:kfifo->in % kfifo->size 可以转化为 kfifo->in & (kfifo->size – 1)rnrn kfifo的巧妙之处在于in和out定义为无符号类型,在put和get时,in和out都是增加,当达到最大值时,产生溢出,使得从0开始,进行循环使用。put和get代码如下所示:rn复制代码rnrn 1 static inline unsigned int kfifo_put(struct kfifo *fifo,rn 2 const unsigned char *buffer, unsigned int len)rn 3 rn 4 unsigned long flags;rn 5 unsigned int ret;rn 6 spin_lock_irqsave(fifo->lock, flags);rn 7 ret = __kfifo_put(fifo, buffer, len);rn 8 spin_unlock_irqrestore(fifo->lock, flags);rn 9 return ret;rn10 rn11 rn12 static inline unsigned int kfifo_get(struct kfifo *fifo,rn13 unsigned char *buffer, unsigned int len)rn14 rn15 unsigned long flags;rn16 unsigned int ret;rn17 spin_lock_irqsave(fifo->lock, flags);rn18 ret = __kfifo_get(fifo, buffer, len);rn19 //当fifo->in == fifo->out时,buufer为空rn20 if (fifo->in == fifo->out)rn21 fifo->in = fifo->out = 0;rn22 spin_unlock_irqrestore(fifo->lock, flags);rn23 return ret;rn24 rn25 rn26 rn27 unsigned int __kfifo_put(struct kfifo *fifo,rn28 const unsigned char *buffer, unsigned int len)rn29 rn30 unsigned int l;rn31 //buffer中空的长度rn32 len = min(len, fifo->size - fifo->in + fifo->out);rn34 /*rn35 * Ensure that we sample the fifo->out index -before- wern36 * start putting bytes into the kfifo.rn37 */rn39 smp_mb();rn41 /* first put the data starting from fifo->in to buffer end */rn42 l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));rn43 memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);rn45 /* then put the rest (if any) at the beginning of the buffer */rn46 memcpy(fifo->buffer, buffer + l, len - l);rn47 rn48 /*rn49 * Ensure that we add the bytes to the kfifo -before-rn50 * we update the fifo->in index.rn51 */rn53 smp_wmb();rn55 fifo->in += len; //每次累加,到达最大值后溢出,自动转为0rn57 return len;rn58 rn59 rn60 unsigned int __kfifo_get(struct kfifo *fifo,rn61 unsigned char *buffer, unsigned int len)rn62 rn63 unsigned int l;rn64 //有数据的缓冲区的长度rn65 len = min(len, fifo->in - fifo->out);rn67 /*rn68 * Ensure that we sample the fifo->in index -before- wern69 * start removing bytes from the kfifo.rn70 */rn72 smp_rmb();rn74 /* first get the data from fifo->out until the end of the buffer */rn75 l = min(len, fifo->size - (fifo->out & (fifo->size - 1)));rn76 memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l);rn78 /* then get the rest (if any) from the beginning of the buffer */rn79 memcpy(buffer + l, fifo->buffer, len - l);rn81 /*rn82 * Ensure that we remove the bytes from the kfifo -before-rn83 * we update the fifo->out index.rn84 */rn86 smp_mb();rn88 fifo->out += len; //每次累加,到达最大值后溢出,自动转为0rn90 return len;rn91 rnrn复制代码rnrn  put和get在调用__put和__get过程都进行加锁,防止并发。从代码中可以看出put和get都调用两次memcpy,这针对的是边界条件。例如下图:蓝色表示空闲,红色表示占用。rnrn(1)空的kfifo,rnrn(2)put一个buffer后rnrn(3)get一个buffer后rnrn(4)当此时put的buffer长度超出in到末尾长度时,则将剩下的移到头部去rnrn3、测试程序rnrn 仿照kfifo编写一个ring_buffer,现有线程互斥量进行并发控制。设计的ring_buffer如下所示:rn复制代码rnrn 1 /**@brief 仿照linux kfifo写的ring bufferrn 2 *@atuher Anker date:2013-12-18rn 3 * ring_buffer.hrn 4 * */rn 5 rn 6 #ifndef KFIFO_HEADER_H rn 7 #define KFIFO_HEADER_Hrn 8 rn 9 #include rn 10 #include rn 11 #include rn 12 #include rn 13 #include rn 14 #include rn 15 rn 16 //判断x是否是2的次方rn 17 #define is_power_of_2(x) ((x) != 0 && (((x) & ((x) - 1)) == 0))rn 18 //取a和b中最小值rn 19 #define min(a, b) (((a) < (b)) ? (a) : (b))rn 20 rn 21 struct ring_bufferrn 22 rn 23 void *buffer; //缓冲区rn 24 uint32_t size; //大小rn 25 uint32_t in; //入口位置rn 26 uint32_t out; //出口位置rn 27 pthread_mutex_t *f_lock; //互斥锁rn 28 ;rn 29 //初始化缓冲区rn 30 struct ring_buffer* ring_buffer_init(void *buffer, uint32_t size, pthread_mutex_t *f_lock)rn 31 rn 32 assert(buffer);rn 33 struct ring_buffer *ring_buf = NULL;rn 34 if (!is_power_of_2(size))rn 35 rn 36 fprintf(stderr,"size must be power of 2.\n");rn 37 return ring_buf;rn 38 rn 39 ring_buf = (struct ring_buffer *)malloc(sizeof(struct ring_buffer));rn 40 if (!ring_buf)rn 41 rn 42 fprintf(stderr,"Failed to malloc memory,errno:%u,reason:%s",rn 43 errno, strerror(errno));rn 44 return ring_buf;rn 45 rn 46 memset(ring_buf, 0, sizeof(struct ring_buffer));rn 47 ring_buf->buffer = buffer;rn 48 ring_buf->size = size;rn 49 ring_buf->in = 0;rn 50 ring_buf->out = 0;rn 51 ring_buf->f_lock = f_lock;rn 52 return ring_buf;rn 53 rn 54 //释放缓冲区rn 55 void ring_buffer_free(struct ring_buffer *ring_buf)rn 56 rn 57 if (ring_buf)rn 58 rn 59 if (ring_buf->buffer)rn 60 rn 61 free(ring_buf->buffer);rn 62 ring_buf->buffer = NULL;rn 63 rn 64 free(ring_buf);rn 65 ring_buf = NULL;rn 66 rn 67 rn 68 rn 69 //缓冲区的长度rn 70 uint32_t __ring_buffer_len(const struct ring_buffer *ring_buf)rn 71 rn 72 return (ring_buf->in - ring_buf->out);rn 73 rn 74 rn 75 //从缓冲区中取数据rn 76 uint32_t __ring_buffer_get(struct ring_buffer *ring_buf, void * buffer, uint32_t size)rn 77 rn 78 assert(ring_buf || buffer);rn 79 uint32_t len = 0;rn 80 size = min(size, ring_buf->in - ring_buf->out); rn 81 /* first get the data from fifo->out until the end of the buffer */rn 82 len = min(size, ring_buf->size - (ring_buf->out & (ring_buf->size - 1)));rn 83 memcpy(buffer, ring_buf->buffer + (ring_buf->out & (ring_buf->size - 1)), len);rn 84 /* then get the rest (if any) from the beginning of the buffer */rn 85 memcpy(buffer + len, ring_buf->buffer, size - len);rn 86 ring_buf->out += size;rn 87 return size;rn 88 rn 89 //向缓冲区中存放数据rn 90 uint32_t __ring_buffer_put(struct ring_buffer *ring_buf, void *buffer, uint32_t size)rn 91 rn 92 assert(ring_buf || buffer);rn 93 uint32_t len = 0;rn 94 size = min(size, ring_buf->size - ring_buf->in + ring_buf->out);rn 95 /* first put the data starting from fifo->in to buffer end */rn 96 len = min(size, ring_buf->size - (ring_buf->in & (ring_buf->size - 1)));rn 97 memcpy(ring_buf->buffer + (ring_buf->in & (ring_buf->size - 1)), buffer, len);rn 98 /* then put the rest (if any) at the beginning of the buffer */rn 99 memcpy(ring_buf->buffer, buffer + len, size - len);rn100 ring_buf->in += size;rn101 return size;rn102 rn103 rn104 uint32_t ring_buffer_len(const struct ring_buffer *ring_buf)rn105 rn106 uint32_t len = 0;rn107 pthread_mutex_lock(ring_buf->f_lock);rn108 len = __ring_buffer_len(ring_buf);rn109 pthread_mutex_unlock(ring_buf->f_lock);rn110 return len;rn111 rn112 rn113 uint32_t ring_buffer_get(struct ring_buffer *ring_buf, void *buffer, uint32_t size)rn114 rn115 uint32_t ret;rn116 pthread_mutex_lock(ring_buf->f_lock);rn117 ret = __ring_buffer_get(ring_buf, buffer, size);rn118 //buffer中没有数据rn119 if (ring_buf->in == ring_buf->out)rn120 ring_buf->in = ring_buf->out = 0;rn121 pthread_mutex_unlock(ring_buf->f_lock);rn122 return ret;rn123 rn124 rn125 uint32_t ring_buffer_put(struct ring_buff 论坛

没有更多推荐了,返回首页