KFiFo淺析

#1.前言
這段時間在寫一個基於PCIE實現FPGA與上位機通信,利用多線程實現讀寫同步,本次使用到了KFIFO無鎖隊列,以下是本人自己對kfifo無鎖隊列原理的理解。
#2.概述
提供一個無邊界的字節流服務,最重要的一點是,它使用並行無鎖編程技術,即當它用於只有一個入隊線程和一個出隊線程的場情時,兩個線程可以併發操作,而不需要任何加鎖行爲。當然,若是採用其他消費模式自然是要加鎖了。對應kfifo結構體如下:


struct kfifo {
    unsigned char *buffer;	/* the begaining address for the buffer holding the data, not changed */
    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) */
};

buffer:用於存放數據緩存;size:buffer空間大小,初始化時將其向上擴展爲2的冪;in:指buffer隊頭;out:指隊尾。其中由buffer、in、out實現循環隊列。
#3.kfifo功能描述及實現
##3.1kfifo_alloc 分配kfifo內存和初始化工作
kfifo中尤爲重要的一個是對size(buffer空間大小)向上取2的冪。對於設置的size,我們首先要做的是判斷這個數是不是2的次冪,關於這個問題,參考這個鏈接,這個鏈接也含了kfifo的原理:關於2的次冪問題
下面是roundup_pow_of_two()函數代碼:

//kfifo內存分配
struct kfifo *KFiFo::kfifo_alloc(unsigned int size)
{
    unsigned char *buffer = NULL;
    struct kfifo *fifo = NULL;
    /*
     * round up to the next power of 2, since our 'let the indices
     * wrap' tachnique works only in this case.
     */
    if (size & (size - 1)) {
        if(size > 0x80000000)
            return NULL;
        size = roundup_pow_of_two(size);
    }
    fifo = (struct kfifo *) malloc(sizeof(struct kfifo));
    if (!fifo)
        return NULL;
    buffer = (unsigned char *)malloc(size);
    if (!buffer)
    {
        free(fifo);
        return NULL;
    }
    fifo->buffer = buffer;
    fifo->size = size;
    fifo->in = fifo->out = 0;
    return fifo;
}

unsigned int KFiFo::roundup_pow_of_two(const unsigned int x)
{
    if (x == 0){ return 0; }
    if (x == 1){ return 2; }
    unsigned int ret = 1;
    while (ret < x)
    {
        ret = ret << 1;
    }
    return ret;
}

##3.2kfifo中的入隊與出隊
kfifo的入隊操作:kfifo_in,它首先將數據放入buffer中,然後改變kfifo->in的值。同樣的對於kfifo的出隊操作:kfifo_out,也是先將數據從buffer中取出,再改變的kfifo->out的值。那麼kfifo的入隊與出隊是怎麼樣實現隊列的循環的呢?下面將結合下圖進行解釋,
kfifo在進行初始化時,定義的kfifo的地址是*buffer,該緩存空間大小爲size,隊頭爲in,隊尾爲out。此時,如下圖所示fifo->in=fifo->out=0(在操作過程中,只要fifo->in=fifo->out即代表隊列爲空):
圖1 fifo初始化後示意圖
假設,在第一次放入一些數據後,此時的fifo空間如下圖所示,我們可以看到放入一定長度的數據後,fifo-in的值在數據放入buffer後隨之修改,由於沒有取數據,所以fifo->out的值並沒有發生改變。
此時,buffer中數據的長度:data=fifo->in-fifo->out。buffer剩餘空閒空間長度爲:fifo->size-(fifo->in-fifo->out)。
圖2 fifo當前緩存示意圖
在以上的基礎上取出一定長度數據後,如下圖所示,可見,此時fifo->out的值發生了變化。
圖3 取出一定長度數據後示意圖
對於kfifo_in()函數,對應代碼如下:

/**
 * kfifo_in - puts some data into the FIFO queue
 * @from: the data to be pushed in the queue head.
 * @len: the length of the data wanted to be pushed in.
 * return: number of bytes coied into the queue.
 *
 * This function copies at most @len bytes from the @from buffer into
 * the FIFO queue depending on the free space, and returns the number of
 * bytes copied. If there is no lefted space in the FIFO queue, no bytes
 * will be copied.
 *
 */
unsigned int KFiFo::kfifo_in(const void *from, unsigned int len)
{
    /*lefted is the avaiable size in the fifo buffer*/
    unsigned int lefted = fifo->size - fifo->in + fifo->out;

    /*get the min value between the lefted data size in the buffer and the wanted size */
    len = min(len, lefted);

    /*off is the length from fifo->buffer, the buffer beginning to the queue head*/
    unsigned int off = fifo->in & (fifo->size - 1);

    /* first put l data starting from the queue head to the buffer end */
    unsigned int l = min(len, fifo->size - off);
    memcpy(fifo->buffer + off, from, l);
    
    /* then put the rest (if any) at fifo->buffer, the beginning of the buffer */
    memcpy(fifo->buffer, (unsigned char *)from + l, len - l);
    
    fifo->in += len;
    return len;
}

那麼在下一次向buffer中放入數據之前,需要考慮由in到buffer_end的空閒空間長度是否足夠存放這一組數據。從上面的kfifo_in()函數, lefted = fifo->size - fifo->in + fifo->out計算出整個fifo剩餘的空閒空間,由min()判斷剩餘的空閒空間能否完全存儲這一組數據。再計算出buffer的初始位置到隊列頭部in這一段的長度off(off = fifo->in & (fifo->size - 1)),最後將數據由in至buffer_end放入數據,若還有剩餘數據未放入隊列中,則將剩餘數據從buffer初始位置開始放入buffer中。此過程示意圖如下:
圖4 下一次放入數據前示意圖
圖5 放入數據過程示意圖
對於kfifo_out()函數,與kfifo_in()函數類似,此處不詳解,代碼如下:

/**
 * kfifo_out - gets some data from the FIFO queue
 * @to: where the data copied.
 * @len: the size of the buffer wanted to be copied.
 * return: number of bytes copied out of the queue.
 *
 * This function copies at most @len bytes from the FIFO into the
 * @to buffer and returns the number of copied bytes.
 * If there is no lefted bytes in the FIFO queue, no bytes will be copied out. *
 */
unsigned int KFiFo::kfifo_out(void *to, unsigned int len)
{
    /*buffered is the length of the queue*/
    unsigned int buffered = fifo->in - fifo->out;

    /*get the min value between the buffered size and the wanted size */
    len = min(len, buffered);

    /*off is the length from the buffer beginning to the queue tail*/
    unsigned int off = fifo->out & (fifo->size - 1);

    /* first get the data from (fifo->buffer + off), the queue tail to the end of the buffer */
    unsigned int l = min(len, fifo->size - off);;
    memcpy(to, fifo->buffer + off, l);

    /* then get the rest (if any) from fifo->buffer, the beginning of the buffer */
    memcpy((unsigned char *)to + l, fifo->buffer, len - l);
    
    fifo->out += len;
    return len;
}

當前只是實現了單生產者和單消費者模式的無鎖隊列,那麼對於環形隊列和多生產和多消費者模式下的應用又該如何實現。

附測試代碼鏈接:KFiFo_test

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章