首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SocketCAN select()和write()不阻塞

SocketCAN select()和write()不阻塞
EN

Stack Overflow用户
提问于 2017-11-22 02:29:37
回答 2查看 4.4K关注 0票数 2

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

我可以打开can设备("can0")作为BSD套接字,并发送带有“写”的帧。这一切都很好。

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

这意味着我必须等待任意一段时间,然后再尝试写,这似乎非常低效率(轮询!)。

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

代码语言:javascript
复制
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)以使缓冲区永远不被填充时,我会看到以下输出:

代码语言:javascript
复制
CAN Data Generator
...............................................................................
................................................................................
...etc

太好了!在500 uS,我得到54%的CAN总线利用率(根据canbusload实用程序)。

但是,当我尝试延迟0以使传输速率达到最大值时,我会看到:

代码语言:javascript
复制
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“就开始出现,这意味着”写“调用失败,错误105。

通过逻辑跟踪,这意味着"select“必须返回,而"FD_ISSET(skt &fds)”为真,尽管缓冲区已满!(或者说我错过了什么?)

SockedCAN的医生们只是说"可以使用写(2)系统调用进行类似的写入帧操作。

这个职位建议使用"select“。

这个职位建议“写”不会阻止CAN优先权仲裁,但不包括其他情况。

,那么“选择”是正确的方式吗?我的“写”应该阻止吗?我还可以使用哪些其他选项来避免轮询呢?

EN

回答 2

Stack Overflow用户

发布于 2018-03-10 15:18:40

在快速查看埋伏量:184之后,它似乎计算了效率(#data/#总线上的总位)。

另一方面,根据,对于8字节帧,CAN总线的最大效率约为57%,因此您似乎离这57%不远.我想说的是,你确实是在淹没公共汽车。

当设置500 bus延迟、500 here总线比特率、8字节帧时,它给出的(control+data)比特率为228 here,低于CAN总线的最大比特率,因此这里没有瓶颈。

而且,由于在本例中只监视了一个套接字,所以您实际上不需要pselect。您可以使用pselect和1套接字完成无需pselect和使用write的所有操作。

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

所以,回答你的问题:

  1. 是“选择”正确的方法吗?--好吧,你可以用两种方式来做,要么是(p)select,要么是write,因为你只在等待一个文件描述符,没有真正的区别。
  2. 应该是我的“写”块吗?--如果在发送缓冲区中没有可用的单个字节,它就应该这样做。
  3. 我还能用什么其他的选项来避免轮询呢?也许可以通过ioctl‘’ing SIOCOUTQ和getsockopt‘’ing SO_SNDBUF和substracting.你得亲自检查一下。或者,您也可以将发送缓冲区大小设置为sizeof(can_frame)的倍数,看看当可用的sizeof(can_frame)小于sizeof(can_frame)时,它是否会使您保持信令。

无论如何,如果您有兴趣有一个更精确的时间,您可以使用BCM套接字。在那里,您可以指示内核在特定的时间间隔发送特定的帧。一旦设置完毕,进程就在内核空间中运行,没有任何系统调用。这样就避免了用户内核缓冲区问题.我会测试不同的费率,直到canbusload显示总线利用率没有增加为止。

票数 3
EN

Stack Overflow用户

发布于 2021-12-29 15:29:40

选择和投票为我工作与SocketCan的权利。但是,需要谨慎的配置。

一些背景:

在用户应用程序和HW之间,有两个缓冲区:

  1. 套接字缓冲区,其大小(以字节为单位)由setsockopt的SO_SNDBUF选项控制
  2. 驱动程序的qdisc,其大小(在数据包中)由"ifconfig can0 txqueuelen 5“命令控制。数据路径是:用户应用程序“写”命令->套接字缓冲区->驱动程序的qdisc -> HW TX邮箱。

在这条道路上有2个流量控制点:

  1. 当没有免费的TX邮箱时,驱动程序冻结驱动程序的qdisc (__QUEUE_STATE_DRV_XOFF),以防止更多的数据包从驱动程序的qdisc中排到HW中。当TX邮箱空闲时,它将不冻结(在TX完成中断时)。
  2. 当套接字缓冲区超过其容量的一半时,轮询/选择块,直到socket缓冲区超过其容量的一半。

现在,假设套接字缓冲区有20个数据包的空间,而驱动程序的qdisc有5个数据包的空间。我们还假设HW有一个TX邮箱。

  1. 投票/选择让用户写入多达10个数据包。
  2. 这些数据包被移动到套接字缓冲区。
  3. 其中5个包继续并填充驱动程序的qdisc。
  4. 驱动程序从驱动程序的qdisc中排出第一个数据包,将其放入HW邮箱并冻结驱动程序的qdisc (=不再排队列)。现在驱动器的qdisc中有一个包的空间
  5. 第6包成功地从套接字缓冲器移动到驱动程序的qdisc。
  6. 第7包从套接字缓冲区向下移动到驱动程序的qdisc,但是由于没有空间--它被丢弃并产生错误105 (“没有可用的缓冲区空间”)。

解决办法是什么?在上述假设中,让我们为8个数据包配置套接字缓冲区。在这种情况下,轮询/选择将阻止用户应用程序后的4个包,确保在驱动器的qdisc中有空间为所有这4个包。

但是,套接字缓冲区被配置为字节,而不是数据包。翻译应按以下方式进行:每个数据包都可以在套接字缓冲区占据~704字节(其中大多数用于套接字结构)。因此,要将套接字缓冲区配置为8个数据包,以字节为单位的大小应该是8*704:

代码语言:javascript
复制
int size = 8*704;
setsockopt(s, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47425873

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档