我有一个想要实现的 USB 协议,但我对实现它的最佳方法有点迷失。
USB 协议涉及来回交换数据和确认数据包,如下所示:
Device: data
Host: ACK
Host: reply
Device: ACK
Run Code Online (Sandbox Code Playgroud)
但有时,数据包可能会像这样异步进入:
Device: data #1
Device: data #2
Host: ACK #1
...
Run Code Online (Sandbox Code Playgroud)
我想要一个 API,它可以抽象出 USB 的所有细节,让程序只处理实际数据,而不必担心数据包标头或确认数据包或类似的事情。理想情况下,有一个write_to_device函数会阻塞直到设备确认数据包,一个函数read_from_device会阻塞直到收到数据包,还有一个is_data_available函数会立即返回队列上是否有任何数据。
我正在考虑运行一个单独的线程来处理 USB 事件。该线程将处理所有数据封装和确认。
当数据包进入时,处理线程将发送一个 ACK 数据包,然后提取原始数据并将其写入管道。该read_from_device函数(从主线程调用)将简单地从此管道中读取并自然阻塞,直到有数据为止。但是如果我使用这个方案,我将没有一个干净的方法来实现一个is_data_available函数 - 没有办法在不读取数据的情况下检查管道中是否有数据。
像这样的东西:
[ Main thread ][ Processing thread ]
| Read from pipe || |
| || USB packet comes in |
| || Send ACK packet |
| || Extract data |
| || Write data to pipe |
| Read succeeds || |
| Return data || |
Run Code Online (Sandbox Code Playgroud)
真正的问题是实现一个write_to_device功能。
[ Main thread ][ Processing thread ]
| Somehow signal write || |
| Wait for write to complete || |
| || Send the data |
| || Wait for ACK packet |
| || Somehow signal that write completed
| Return || |
Run Code Online (Sandbox Code Playgroud)
如何干净地实现一种发送数据包、等待确认数据包然后返回的方法?
我建议您创建一个自定义管道类或结构或其他东西。为此,您定义一个 write 方法,该方法还等待信号量触发。如果您使用的是 Linux,sem_wait(来自信号量函数系列sem_*)就是您想要查看的内容。
然后,写入函数会将数据写入 FIFO 并等待信号量被标记。但是,写入线程如何知道所有数据何时通过您想要发送的管道到达?如果线程必须阻塞读取,这里可能会出现问题。
因此,我建议您在从主线程到处理线程的管道内使用微格式,发送一个整数大小,该整数大小定义您要写入的字节数。然后,处理线程将读取该数量的字节,将其转发到设备,并在标记所有数据后立即标记信号量。该write函数将等待信号量,因此处于非忙阻塞状态,直到处理线程完成。
这是自定义管道结构和概述的写入函数的起草方式:
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
typedef struct {
int write_to_pipe, read_from_pipe;
sem_t *write_sem;
} MyPipe;
MyPipe *pipe_new() {
int fds[2];
if (pipe(fds)) {
// handle error here
return NULL;
}
sem_t *sem = NULL;
if (sem_init(sem, 0, 0)) {
// handle error here
close(fds[0]);
close(fds[1]);
return NULL;
}
MyPipe *result = malloc(sizeof(MyPipe));
result->write_to_pipe = fds[1];
result->read_from_pipe = fds[0];
result->write_sem = sem;
return result;
}
void pipe_write(MyPipe *pipe, const unsigned char *buf, const int size) {
write(pipe->write_to_pipe, &size, sizeof(int));
write(pipe->write_to_pipe, buf, size);
sem_wait(pipe->write_sem);
}
Run Code Online (Sandbox Code Playgroud)
处理线程将知道该MyPipe实例并read_from_pipe在需要时进行读取。它首先读取主线程写入管道的字节数,然后读取任意块的所有字节。当所有数据发送到设备并被其确认后,它可以发送sem_post信号量,以便pipe_write返回。
或者,可以添加另一个信号量,该pipe_write信号量会发出信号以使处理线程仅在实际有可用数据时才读取数据。
免责声明:尚未测试代码,仅检查其是否可以编译。需要用 来构建-pthread,才能sem_*可用。