Linux的选择()和多个插槽的FIFO排序?(Linux select() and FIFO or

2019-10-18 08:48发布

有没有办法为Linux选择()呼叫中继事件排序?

我所看到的描述:

在一个机器,我写了一个简单的程序,它发送三个多播包,一个到每个三个不同的组播组。 这些数据包发送回到后端,在两者之间没有延迟。 即SENDTO(mcast_group1); SENDTO(mcast_group2); SENDTO(mcast_group3)。

在其他计算机上,我有一个接收程序。 该程序使用每多播组一个插座。 每个插座做了绑定()和IP_ADD_MEMBERSHIP(即加入/订阅)向侦听的地址。 然后程序会在三个插座一个select()。

当选择退货,所有三个插槽可供阅读。 但哪一个是先? 插座的准备就绪的阅读清单是一组,因此没有订单。 我想如果是select()方法返回的每个接收的数据包只有一次,为了(增加的开销是可以接受的在这里)。 或者是有一些其他类型的机制,我可以用它来确定数据包接收订单?

附加信息:

  • OS是CentOS 5的(有效的红帽企业版Linux)在x86_64
  • NIC硬件是英特尔82571EB
  • 我试过E1000E驱动程序版本1.3.10-K2和2.1.4-NAPI
  • 我试图牵制网卡的中断空载和孤立的CPU核心
  • 我已禁用硬件IRQ通过设置驱动器选项将InterruptThrottleRate = 0,并通过设置ethtool的RX-微秒(usecs)= 0合并
  • 我也尝试使用epoll的,并且它具有相同的行为

最后一句话:如果我只使用一个插座的分组排序被保留。 在这种情况下,我绑定到INADDR_ANY(0.0.0.0)和IP_ADD_MEMBERSHIP多次做同一个插座上。 但是,这并不为我们的应用程序的工作,因为我们需要通过结合实际的多播地址提供的过滤。 最终,会出现在同一台机器上的多个多播接收程序,与预订集可以彼此相交。 因此,也许一个替代解决办法是找到另一种方法来实现绑定(的过滤效果),但不结合()。

Answer 1:

您可以使用IP_PKTINFO将数据包被发送到组播组的地址-即使套接字认购一堆组播组。 在地方有了这个,你会为了和组地址进行过滤的能力得到了包。 看下面的例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>

#define PORT 1234
#define PPANIC(msg) perror(msg); exit(1);
#define STATS_PATCH 0

int main(int argc, char **argv)
{
    fd_set master;
    fd_set read_fds;
    struct sockaddr_in serveraddr;
    int sock;
    int opt = 1;
    size_t i;
    int rc;

    char *mcast_groups[] = {
        "226.0.0.1",
        "226.0.0.2",
        NULL
    };
#if STATS_PATCH 
    struct stat stat_buf;
#endif  

    struct ip_mreq imreq;

    FD_ZERO(&master);
    FD_ZERO(&read_fds);

    rc = sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(rc == -1)
    {
        PPANIC("socket() failed");
    }

    rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    if(rc == -1)
    {
        PPANIC("setsockopt(reuse) failed");
    }

    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

    rc = bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    if(rc == -1)
    {
        PPANIC("bind() failed");
    }

    rc = setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt));
    if(rc == -1)
    {
        PPANIC("setsockopt(IP_PKTINFO) failed");
    }

    for (i = 0; mcast_groups[i] != NULL; i++)
    {
        imreq.imr_multiaddr.s_addr = inet_addr(mcast_groups[i]);
        imreq.imr_interface.s_addr = INADDR_ANY;
        rc = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void *)&imreq, sizeof(struct ip_mreq));
        if (rc != 0)
        {
            PPANIC("joing mcast group failed");
        }
    }

    FD_SET(sock, &master);

    while(1)
    {
        read_fds = master;
        rc = select(sock + 1, &read_fds, NULL, NULL, NULL);

        if (rc == 0)
        {
            continue;
        }

        if(rc == -1)
        {
            PPANIC("select() failed");
        }

        if(FD_ISSET(sock, &read_fds))
        {
            char buf[1024];
            int inb;
            char ctrl_msg_buf[1024];
            struct iovec iov[1];
            iov[0].iov_base = buf;
            iov[0].iov_len = 1024;
            struct msghdr msg_hdr = {
                .msg_iov = iov,
                .msg_iovlen = 1,
                .msg_name = NULL,
                .msg_namelen = 0,
                .msg_control = ctrl_msg_buf,
                .msg_controllen = sizeof(ctrl_msg_buf),
            };
            struct cmsghdr *ctrl_msg_hdr;

            inb = recvmsg(sock, &msg_hdr, 0);
            if (inb < 0)
            {
                PPANIC("recvmsg() failed");
            }

            for (ctrl_msg_hdr = CMSG_FIRSTHDR(&msg_hdr); ctrl_msg_hdr != NULL; ctrl_msg_hdr = CMSG_NXTHDR(&msg_hdr, ctrl_msg_hdr))
            {
                if (ctrl_msg_hdr->cmsg_level == IPPROTO_IP && ctrl_msg_hdr->cmsg_type == IP_PKTINFO)
                {
                    struct in_pktinfo *pckt_info = (struct in_pktinfo *)CMSG_DATA(ctrl_msg_hdr);
                    printf("got data for mcast group: %s\n", inet_ntoa(pckt_info->ipi_addr));
                    break;
                }
            }

            printf("|");
            for (i = 0; i < inb; i++)
                printf("%c", isprint(buf[i])?buf[i]:'?');
            printf("|\n");
#if STATS_PATCH
            rc = fstat(sock, &stat_buf);
            if (rc == -1)
            {
                perror("fstat() failed");
            } else {
                printf("st_atime: %d\n", stat_buf.st_atime);
                printf("st_mtime: %d\n", stat_buf.st_mtime);
                printf("st_ctime: %d\n", stat_buf.st_ctime);
            }
#endif
        }
    }

    return 0;
}

下面的代码不会解决有机磷农药的问题,但可以指导人们处理类似要求

(编辑)一个人不应该做这样的事情在深夜......即使与解决方案,您将只能获得FD被选择处理的顺序-这会给你没有关于帧到达的时间指示。

至于说在这里 ,它是目前无法获取的插座或他们改变所需要的回调并不套接字索引节点设置的时间戳的顺序。 但是,如果你能修补你的内核,你可以通过设置选择系统调用中的时间解决问题。

下面的补丁可以给你一个想法:

diff --git a/fs/select.c b/fs/select.c
index 467bb1c..3f2927e 100644
--- a/fs/select.c
+++ b/fs/select.c
@@ -435,6 +435,9 @@ int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
        for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
            unsigned long in, out, ex, all_bits, bit = 1, mask, j;
            unsigned long res_in = 0, res_out = 0, res_ex = 0;
+           struct timeval tv;
+           
+           do_gettimeofday(&tv);

            in = *inp++; out = *outp++; ex = *exp++;
            all_bits = in | out | ex;
@@ -452,6 +455,16 @@ int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
                f = fdget(i);
                if (f.file) {
                    const struct file_operations *f_op;
+                   struct kstat stat;
+                   
+                   int ret;
+                   u8 is_sock = 0;
+
+                   ret = vfs_getattr(&f.file->f_path, &stat);
+                   if(ret == 0 && S_ISSOCK(stat.mode)) {
+                       is_sock = 1;
+                   }
+                   
                    f_op = f.file->f_op;
                    mask = DEFAULT_POLLMASK;
                    if (f_op->poll) {
@@ -464,16 +477,22 @@ int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
                        res_in |= bit;
                        retval++;
                        wait->_qproc = NULL;
+                       if(is_sock && f.file->f_inode)
+                           f.file->f_inode->i_ctime.tv_sec = tv.tv_sec;
                    }
                    if ((mask & POLLOUT_SET) && (out & bit)) {
                        res_out |= bit;
                        retval++;
                        wait->_qproc = NULL;
+                       if(is_sock && f.file->f_inode)
+                           f.file->f_inode->i_ctime.tv_sec = tv.tv_sec;
                    }
                    if ((mask & POLLEX_SET) && (ex & bit)) {
                        res_ex |= bit;
                        retval++;
                        wait->_qproc = NULL;
+                       if(is_sock && f.file->f_inode)
+                           f.file->f_inode->i_ctime.tv_sec = tv.tv_sec;
                    }
                    /* got something, stop busy polling */
                    if (retval) {

笔记:

  1. 这是...只为你:) - 不要指望它在主线

  2. 每个相关fd被测试之前 do_gettimeofday()被调用。 以获得更高的粒度这应该在每次迭代中进行(且仅当需要)。 由于STAT接口只提供一秒钟可能的粒度(!太丑了!)的使用剩余时间属性,第二的分数映射到这些领域。

  3. 这是使用内核3.16.0做,没有得到很好的测试。 请不要在飞船或医疗设备使用它。 如果您想尝试一下,得到一个文件系统映像(如https://people.debian.org/~aurel32/qemu/amd64/debian_wheezy_amd64_standard.qcow2并用QEMU来测试它):

    须藤QEMU系统-x86_64的-kernel拱/ 86 /引导/ bzImage的-hda debian_wheezy_amd64_standard.qcow2 -append “根=的/ dev / SDA1”



Answer 2:

如果选择()返回> 1的事件一定是如此接近,以使订货毫无意义的问题。



Answer 3:

您可以在获得其使用FSTAT文件描述符成为准备时间戳。

欲了解更多信息阅读http://pubs.opengroup.org/onlinepubs/009695399/functions/fstat.html



文章来源: Linux select() and FIFO ordering of multiple sockets?