使用C中的指针的循环缓冲区

时间:2013-07-16 00:29:15

标签: c pointers queue circular-buffer

我有一个队列结构,我尝试使用循环缓冲区实现,我在网络应用程序中使用它。我正在寻找一些指导和反馈。首先,让我提出相关的代码。

typedef struct nwk_packet_type
{
    uint8_t dest_address[NRF24_ADDR_LEN];
    uint8_t data[32];
    uint8_t data_len;
}nwk_packet_t;

/* The circular fifo on which outgoing packets are stored */
nwk_packet_t nwk_send_queue[NWK_QUEUE_SIZE];
nwk_packet_t* send_queue_in; /* pointer to queue head */
nwk_packet_t* send_queue_out; /* pointer to queue tail */

static nwk_packet_t* nwk_tx_pkt_allocate(void)
{
    /* Make sure the send queue is not full */
    if(send_queue_in == (send_queue_out - 1 + NWK_QUEUE_SIZE) % NWK_QUEUE_SIZE)
        return 0;

    /* return pointer to the next add and increment the tracker */
    return send_queue_in++;//TODO: it's not just ++, it has to be modular by packet size
}

/* External facing function for application layer to send network data */
// simply adds the packet to the network queue if there is space
// returns an appropriate error code if anything goes wrong
uint8_t nwk_send(uint8_t* address, uint8_t* data, uint8_t len)
{
    /* First check all the parameters */
    if(!address)
        return NWK_BAD_ADDRESS;
    if(!data)
        return NWK_BAD_DATA_PTR;
    if(!len || len > 32)
        return NWK_BAD_DATA_LEN;

    //TODO: PROBABLY NEED TO START BLOCKING HERE
    /* Allocate the packet on the queue */
    nwk_packet_t* packet;
    if(!( packet = nwk_tx_pkt_allocate() ))
        return NWK_QUEUE_FULL;

    /* Build the packet */
    memcpy(packet->dest_address, address, NRF24_ADDR_LEN);
    memcpy(packet->data, data, len);
    packet->data_len = len;
    //TODO: PROBABLY SAFE TO STOP BLOCKING HERE

    return NWK_SUCCESS;
}

/* Only called during NWK_IDLE, pushes the next item on the send queue out to the chip's "MAC" layer over SPI */
void nwk_transmit_pkt(void)
{
    nwk_packet_t tx_pkt = nwk_send_queue[send_queue_out];
    nrf24_send(tx_pkt->data, tx_pkt->data_len);
}

/* The callback for transceiver interrupt when a sent packet is either completed or ran out of retries */
void nwk_tx_result_cb(bool completed)
{
    if( (completed) && (nwk_tx_state == NWK_SENDING))
        send_queue_out++;//TODO: it's not just ++, it has to be modular by packet size with in the buffer

}

好的,现在快速解释,然后我的问题。所以基本的想法是我有这个队列用于发送到网络上的数据。函数nwk_send()可以从应用程序代码中的任何地方调用,这可以是一个小型的基于先发制人的任务操作系统(FreeRTOS),因此可以在代码中的许多地方发生并被中断操作系统中断。

既然该函数正在修改指向全局队列的指针,我知道它在执行此操作时需要阻塞。我在关于我应该阻止的位置(即禁用中断)的代码的评论中是否正确?使用全局布尔变量或其他东西而不是仅仅禁用中断也更聪明一点?

此外,我认为当事情被排除在队列之外时,我应该阻止第二个位置,但我不确定究竟是哪一个。它在nwk_transmit_pkt()中我实际上是将数据从队列中复制到本地ram变量吗?

最后一个问题,如何在数组中的指针上实现模数运算?我觉得它应该看起来像:

send_queue_in = ((send_queue_in + 1) % (NWK_QUEUE_SIZE*sizeof(nwk_packet_t))) + nwk_send_queue;

非常感谢任何反馈,谢谢。

2 个答案:

答案 0 :(得分:1)

关于锁定,最好使用您使用的操作系统中的某些现有互斥原语。我不熟悉FreeRTOS,但它应该有内置的原语来锁定中断和用户上下文。

对于循环缓冲区,您可以使用以下内容:

检查空队列

send_queue_in == send_queue_out

检查完整队列

(send_queue_in + 1) % NWK_QUEUE_SIZE == send_queue_out

推送元素[伪代码]

if (queue is full)
    return error;
queue[send_queue_in] = new element;
send_queue_in = (send_queue_in + 1) % NWK_QUEUE_SIZE;

pop元素[伪代码]

if (queue is empty)
   return error;
element = queue[send_queue_out];
send_queue_out = (send_queue_out + 1) % NWK_QUEUE_SIZE;

看起来您在发送之前复制并且不仅仅引用数据包数据。这意味着您可以保持锁定,直到复制完成。

答案 1 :(得分:0)

没有用于开发的整体驱动程序框架,以及在uC上与中断状态通信时,您需要非常小心。

您无法使用OS同步原语与中断状态进行通信。如果这样做会导致操作系统崩溃,因为中断处理程序无法阻止。

应避免复制实际批量数据。

在8位uC上,我建议将索引排队到缓冲区数组池,其中缓冲区的数量<256。这意味着只需要排队一个字节,因此,在更新内部字节大小索引之前使用适当的队列类存储该值,可以安全地将缓冲区传送到tx处理程序,而不会过多地中断禁用。

对池数组的访问应该是线程安全的,并且'插入/删除'应该很快 - 我在每个缓冲区结构中都有'succ / pred'字节字段,因此形成一个双链表,访问受保护互斥。除了I / O之外,我还为所有线程间通信使用此缓冲池。

对于tx,从teh池获取缓冲区结构,填充数据,将索引推送到tx队列,禁用中断的时间足够长,以确定tx中断是否需要'primimg'。如果需要启动,则在重新启用中断之前,请先填充FIFO数据。

当tx中断处理程序发送缓冲区时,它可以将'used'索引推回到'scavenge'队列并发信号通知信号量以使处理程序线程运行。然后,该线程可以从清除队列中获取该条目并将其返回到池中。

此方案仅在中断处理程序不使用相同的缓冲方案重新启用更高优先级的中断时才有效。