博客 / 詳情

返回

linux中kfifo的無鎖隊列實現解讀

簡介

kfifo是linux內核中的一個模塊。
在單消費者,單生產者情況下,可以達到不加鎖也能保證線程安全的效果。

路徑

linux/lib/kfifo.c,鏈接直達:https://github.com/torvalds/linux/blob/master/lib/kfifo.c
linux/include/linux/kfifo.h,鏈接直達:https://github.com/torvalds/linux/blob/master/include/linux/kfifo.h

數據結構

struct __kfifo {
    unsigned int    in; //寫指針
    unsigned int    out;//讀指針
    unsigned int    mask; //size-1
    unsigned int    esize;//單個元素的大小
    void        *data; //保存數據
};

接口函數

//創建
kfifo_alloc(fifo, size, gfp_mask)
//初始化
kfifo_init(fifo, buffer, size)
//釋放
kfifo_free(fifo)
//寫數組
kfifo_put(fifo, val)
//讀數組
kfifo_get(fifo, val)
//讀數組但不移除
kfifo_peek(fifo, val)
//寫多個數據
kfifo_in(fifo, buf, n)
//讀多個數據
kfifo_out(fifo, buf, n)
//讀多個數據但不移除
kfifo_out_peek(fifo, buf, n)
//fifo大小
kfifo_size(fifo)
//fifo數據長度
kfifo_len(fifo) 
//fifo是否為空
kfifo_is_empty(fifo)

實現

無鎖隊列核心

在改變讀寫指針的時候,應該是原子操作。這要求僅通過讀寫指針的關係來判斷剩餘空間長度,而不是用一個變量來記錄已使用長度。(更詳細解釋,可以看這個博主的文章:https://airekans.github.io/c/2015/10/12/linux-kernel-data-str...)
限制長度在2的整次冪就能支撐這種方式,而不僅僅是比求模效率高。

計算剩餘空間

static inline unsigned int kfifo_unused(struct __kfifo *fifo)
{
    return (fifo->mask + 1) - (fifo->in - fifo->out);
}

未利用size為2的整次冪這特性。

寫數據

static void kfifo_copy_in(struct __kfifo *fifo, const void *src,
        unsigned int len, unsigned int off)
{
    unsigned int size = fifo->mask + 1;
    unsigned int esize = fifo->esize;
    unsigned int l;

    off &= fifo->mask;
    if (esize != 1) {
        off *= esize;
        size *= esize;
        len *= esize;
    }
    l = min(len, size - off);

    memcpy(fifo->data + off, src, l);
    memcpy(fifo->data, src + l, len - l);
    /*
     * make sure that the data in the fifo is up to date before
     * incrementing the fifo->in index counter
     */
    smp_wmb();
}

unsigned int __kfifo_in(struct __kfifo *fifo,
        const void *buf, unsigned int len)
{
    unsigned int l;

    l = kfifo_unused(fifo);
    if (len > l)
        len = l;

    kfifo_copy_in(fifo, buf, len, fifo->in);
    fifo->in += len;
    return len;
}

off傳進來的時候,是fifo->in。
off &= fifo->mask;
這個操作有效利用size為2的整次冪,因為這個操作,後續的fifo->in增加,是不用考慮長度判斷的。
fifo->in += len;

讀數據

static void kfifo_copy_out(struct __kfifo *fifo, void *dst,
        unsigned int len, unsigned int off)
{
    unsigned int size = fifo->mask + 1;
    unsigned int esize = fifo->esize;
    unsigned int l;

    off &= fifo->mask;
    if (esize != 1) {
        off *= esize;
        size *= esize;
        len *= esize;
    }
    l = min(len, size - off);

    memcpy(dst, fifo->data + off, l);
    memcpy(dst + l, fifo->data, len - l);
    /*
     * make sure that the data is copied before
     * incrementing the fifo->out index counter
     */
    smp_wmb();
}

unsigned int __kfifo_out_peek(struct __kfifo *fifo,
        void *buf, unsigned int len)
{
    unsigned int l;

    l = fifo->in - fifo->out;
    if (len > l)
        len = l;

    kfifo_copy_out(fifo, buf, len, fifo->out);
    return len;
}

unsigned int __kfifo_out(struct __kfifo *fifo,
        void *buf, unsigned int len)
{
    len = __kfifo_out_peek(fifo, buf, len);
    fifo->out += len;
    return len;
}

讀寫的指針的操作是一樣的,兩者記錄了內存的位置。僅僅在計算剩餘空間時候有交叉比較。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.