套接字可以选择()和写入()不阻止 [英] SocketCAN select() and write() don't block
问题描述
我正在使用SocketCAN在嵌入式设备(SOC/ARM CORE/Linux)上测试CAN接口,我希望使用高效的代码尽快发送数据进行测试。
我可以将CAN设备("can0")作为BSD套接字打开,并使用"WRITE"发送帧。这一切都运行良好。
我的桌面生成帧的速度明显快于CAN传输速率(我使用的是500000 bps)。为了高效地发送,我尝试在套接字文件描述符上使用"SELECT"来等待它准备就绪,然后使用"WRITE"。然而,无论发送缓冲区的状态如何,"SELECT"似乎都会立即返回,而"WRITE"也不会阻塞。这意味着当缓冲区填满时,我从"WRITE"(返回值-1)得到一个错误,并将errno设置为105("No Buffer Space Available")。
这意味着我必须等待任意时间,然后再次尝试写入,这似乎非常低效(轮询!)。
以下是我的代码(C,为简洁起见进行了编辑):
printf("CAN Data Generator
");
int skt; // CAN raw socket
struct sockaddr_can addr;
struct canfd_frame frame;
const int WAIT_TIME = 500;
// Create socket:
skt = socket(PF_CAN, SOCK_RAW, CAN_RAW);
// Get the index of the supplied interface name:
unsigned int if_index = if_nametoindex(argv[1]);
// Bind CAN device to socket created above:
addr.can_family = AF_CAN;
addr.can_ifindex = if_index;
bind(skt, (struct sockaddr *)&addr, sizeof(addr));
// Generate example CAN data: 8 bytes; 0x11,0x22,0x33,...
// ...[Omitted]
// Send CAN frames:
fd_set fds;
const struct timeval timeout = { .tv_sec=2, .tv_usec=0 };
struct timeval this_timeout;
int ret;
ssize_t bytes_writ;
while (1)
{
// Use 'select' to wait for socket to be ready for writing:
FD_ZERO(&fds);
FD_SET(skt, &fds);
this_timeout = timeout;
ret = select(skt+1, NULL, &fds, NULL, &this_timeout);
if (ret < 0)
{
printf("'select' error (%d)
", errno);
return 1;
}
else if (ret == 0)
{
// Timeout waiting for buffer to be free
printf("ERROR - Timeout waiting for buffer to clear.
");
return 1;
}
else
{
if (FD_ISSET(skt, &fds))
{
// Ready to write!
bytes_writ = write(skt, &frame, CAN_MTU);
if (bytes_writ != CAN_MTU)
{
if (errno == 105)
{
// Buffer full!
printf("X"); fflush(stdout);
usleep(20); // Wait for buffer to clear
}
else
{
printf("FAIL - Error writing CAN frame (%d)
", errno);
return 1;
}
}
else
{
printf("."); fflush(stdout);
}
}
else
{
printf("-"); fflush(stdout);
}
}
usleep(WAIT_TIME);
}
当我将每帧的WAIT_TIME设置为一个较高的值(例如500 us)以使缓冲区永远不会填满时,我看到以下输出:
CAN Data Generator
...............................................................................
................................................................................
...etc
这很好!在500 us时,我获得了54%的CAN总线利用率(根据canbus加载实用程序)。
但是,当我尝试延迟0以最大限度提高传输速率时,我看到:
CAN Data Generator
................................................................................
............................................................X.XX..X.X.X.X.XXX.X.
X.XX..XX.XX.X.XX.X.XX.X.X.X.XX..X.X.X.XX..X.X.X.XX.X.XX...XX.X.X.X.X.XXX.X.XX.X.
X.X.XXX.X.XX.X.X.X.XXX.X.X.X.XX.X.X.X.X.XX..X..X.XX.X..XX.X.X.X.XX.X..X..X..X.X.
.X.X.XX.X.XX.X.X.X.X.X.XX.X.X.XXX.X.X.X.X..XX.....XXX..XX.X.X.X.XXX.X.XX.XX.XX.X
.X.X.XX.XX.XX.X.X.X.X.XX.X.X.X.X.XX.XX.X.XXX...XX.X.X.X.XX..X.XX.X.XX.X.X.X.X.X.
起始点"。显示缓冲区已满;一旦缓冲区已满,"X"开始出现,这意味着"WRITE"调用失败并返回错误105。
通过逻辑跟踪,这意味着"SELECT"必须已返回,并且"fd_isset(skt,&;fds)"为真,尽管缓冲区已满!(还是我错过了什么?)。SockedCAN文档只写了"Writing CAN frames can be done similarly, with the write(2) system call"
This post建议使用"选择"。
This post建议"WRITE"不会阻止CAN优先级仲裁,但不包括其他情况。
那么"选择"是正确的方式吗?我的"WRITE"应该阻止吗?我还可以使用哪些其他选项来避免轮询?推荐答案
快速查看canbusload:184后,它似乎计算出了效率(数据数量/总线上的总位数)。
另一方面,根据this的数据,对于8字节帧,CAN总线的最高效率约为57%,因此您似乎离这57%并不遥远.我会说你确实把公交车弄得水泄不通。当设置500uS延迟、500Kbps总线码率、8字节帧时,(控制+数据)码率为228kbps,低于CAN总线的最大码率,因此,这里没有瓶颈。
此外,由于在这种情况下只监视一个套接字,因此您实际上不需要pselect
。您可以使用pselect
和1个插座执行的所有操作都可以在不使用pselect
和使用write
的情况下完成。
(解密者:以下仅为猜测,目前无法测试,抱歉。)
至于为什么pselect
的行为,认为缓冲区可能有字节语义,所以它告诉您还有空间可以容纳更多的字节(至少1),而不一定是更多的CAN_Frame。所以,当返回时,pselect
不会通知您可以发送整个CAN帧。我猜您可以通过使用SIOCOUTQ
和Rx缓冲区的最大大小SO_SNDBUF
来解决这个问题,但不确定它是否适用于CAN套接字(最好的做法是使用SO_SNDLOWAT
标志,但在Linux的实现中它是不可更改的)。
所以,为了回答您的问题:
- "选择"是否正确?
您可以同时使用
(p)select
和write
两种方法,因为您只需要等待一个文件描述符,所以没有什么真正的区别。 - 我的"写入"应该阻止吗?如果发送缓冲区中没有可用的单个字节,则应该阻止。
- 我还可以使用哪些其他选项来避免轮询?可以通过ioctl
SIOCOUTQ
和getsockoptSO_SNDBUF
和减法...来避免轮询。您需要自己检查这一点。或者,您也可以将发送缓冲区大小设置为sizeof(can_frame)
的倍数,并查看当可用缓冲区大小小于sizeof(can_frame)
时,它是否会让您继续发送信号。
无论如何,如果您对更精确的计时感兴趣,您可以使用BCM套接字。在那里,您可以指示内核以特定的间隔发送特定的帧。一旦设置好,进程就会在内核空间中运行,而不需要任何系统调用。通过这种方式,避免了用户内核缓冲区问题。我会测试不同的速率,直到canbusload
显示总线利用率没有上升。
这篇关于套接字可以选择()和写入()不阻止的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!