SocketCAN select()和write()不要阻止

时间:2017-11-22 02:29:38

标签: c embedded-linux can-bus socketcan

我正在使用SocketCAN测试嵌入式设备(SOC / ARM内核/ Linux)上的CAN接口,我希望使用高效代码尽可能快地发送数据进行测试。

我可以打开CAN设备(" can0")作为BSD套接字,并发送#34;写"帧。这一切都运作良好。

我的桌面显然可以比CAN传输速率更快地生成帧(我使用500000 bps)。为了有效发送,我尝试使用" select"在套接字文件描述符上等待它准备就绪,然后是"写"。然而,"选择"似乎无论发送缓冲区的状态如何都会立即返回,并且"写"也没有阻止。这意味着当缓冲区填满时,我会收到来自"写"的错误。 (返回值-1),并且errno设置为105("没有可用的缓冲区空间")。

这意味着我必须等待任意数量的时间,然后再次尝试写入,这看起来非常低效(轮询!)。

这是我的代码(C,为简洁而编辑):

printf("CAN Data Generator\n");

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)\n", errno);
        return 1;
    }
    else if (ret == 0)
    {
        // Timeout waiting for buffer to be free
        printf("ERROR - Timeout waiting for buffer to clear.\n");
        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)\n", 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总线利用率(根据canbusload实用程序)。

但是,当我尝试延迟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.

最初的点&#34;。&#34;显示缓冲区填满;一旦缓冲区已满,&#34; X&#34;开始出现意味着&#34;写&#34;呼叫失败,错误105.

追踪逻辑,这意味着&#34;选择&#34;必须已经退回并且&#34; FD_ISSET(skt,&amp; fds)&#34;是真的,虽然缓冲区已满! (或者我错过了什么?)。

SockedCAN文档只是说&#34; Writing CAN frames can be done similarly, with the write(2) system call&#34;

This post建议使用&#34;选择&#34;。

This post建议&#34;写&#34;不会阻止CAN优先权仲裁,但不包括其他情况。

所以&#34;选择&#34;正确的方法吗?我应该写#34;块?我可以使用哪些其他选项来避免轮询?

1 个答案:

答案 0 :(得分:0)

快速查看canbusload:184后,它似乎计算效率(#data /#总线上的总位数)。

另一方面,根据this,对于8字节帧,CAN总线的最大效率约为57%,因此您似乎距离57%不远......我会说你确实充斥着公共汽车。

当设置500uS延迟,500kbps总线比特率,8字节帧时,它会给你一个228kbps的(控制+数据)比特率,低于CAN总线的最大比特率,因此,这里没有瓶颈。

此外,由于在这种情况下只监控1个插槽,因此您真的不需要pselect。您可以使用pselect和1个套接字执行所有操作,而无需pselect并使用write

Disclamer:在下文中,这只是猜测因为我现在无法测试,抱歉。) 至于为什么pselect的行为,认为缓冲区可能有字节语义,所以它告诉你仍然有更多字节(至少1)的空间,不一定是更多<强> can_frame 秒。因此,在返回时,pselect不会通知您可以发送整个CAN帧。我想你可以通过使用SIOCOUTQ和Rx缓冲区SO_SNDBUF的最大大小来解决这个问题,但不确定它是否适用于CAN套接字(好的方法是使用SO_SNDLOWAT标志,但它在Linux的实现中不可改变。

所以,回答你的问题:

  1. 是&#34;选择&#34;正确的方法吗? 好吧,你可以两种方式,(p)selectwrite,因为你只等待一个文件描述符,没有真正的区别。
  2. 我的&#34;写&#34;阻止?如果发送缓冲区中没有可用的单字节,则应该这样做。
  3. 我还可以使用哪些其他选项来避免投票?可能是通过 ioctl &#39} SIOCOUTQ获取& #39; ing SO_SNDBUF和减去...你需要自己检查一下。或者,也许您可​​以将发送缓冲区大小设置为sizeof(can_frame)的倍数,并查看它是否在低于sizeof(can_frame)时保持信号发送。
  4. 无论如何,如果您对更精确的计时感兴趣,可以使用BCM套接字。在那里,您可以指示内核以特定间隔发送特定帧。设置后,进程在内核空间中运行,无需任何系统调用。这样就避免了用户内核缓冲区问题。我会测试不同的费率,直到canbusload显示总线利用率没有上升。