套接字可以选择()和写入()不阻止 [英] SocketCAN select() and write() don't block

查看:19
本文介绍了套接字可以选择()和写入()不阻止的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用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的实现中它是不可更改的)。

所以,为了回答您的问题:

  1. "选择"是否正确? 您可以同时使用(p)selectwrite两种方法,因为您只需要等待一个文件描述符,所以没有什么真正的区别。
  2. 我的"写入"应该阻止吗?如果发送缓冲区中没有可用的单个字节,则应该阻止。
  3. 我还可以使用哪些其他选项来避免轮询?可以通过ioctlSIOCOUTQgetsockoptSO_SNDBUF和减法...来避免轮询。您需要自己检查这一点。或者,您也可以将发送缓冲区大小设置为sizeof(can_frame)的倍数,并查看当可用缓冲区大小小于sizeof(can_frame)时,它是否会让您继续发送信号。

无论如何,如果您对更精确的计时感兴趣,您可以使用BCM套接字。在那里,您可以指示内核以特定的间隔发送特定的帧。一旦设置好,进程就会在内核空间中运行,而不需要任何系统调用。通过这种方式,避免了用户内核缓冲区问题。我会测试不同的速率,直到canbusload显示总线利用率没有上升。

这篇关于套接字可以选择()和写入()不阻止的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆