1

我有一个这样的功能:

static int
rcv_kern(int sock, void *buf, int len, struct sockaddr *addr,
     socklen_t *addrlen)
{
    struct timeval timeout = {1, 0};
    fd_set set;
    int status;

    FD_SET(sock, &set);
    if ((status = select(sock + 1, &set, NULL, NULL, &timeout)) == 0) {
        FD_ZERO(&set);
        fprintf(stderr, 
            "timeout while receiving answer from kernel\n");
        exit(1);
    } else if (status == -1) {
        FD_ZERO(&set);
        perror("recvfrom failed");
        exit(1);
    }
    FD_ZERO(&set);
    return recvfrom(sock, buf, len, 0, addr, addrlen);
}

它用于使用 netlink 从内核空间接收消息。但是当我运行它时,结果总是显示“从内核接收答案时超时”的消息,从源代码中,这是由于“选择”方法总是返回“0”的原因。不知道原因,谁能给我一些建议,谢谢。

4

5 回答 5

4

查理,
有几件事:

1)您可能应该循环您的 select() 调用,并且仅当 FD_ISSET() 在您的文件描述符上返回 true 时才调用 recvfrom。
2) 确保在 netlink 套接字上发送的实际驱动程序或内核代码实际上正在向其写入/发送数据。如果没有,那么如果您的函数在 1 秒内没有接收到数据,那么您的函数将超时。(这就是您设置的超时时间)。

一些一般性的评论... 在 Linux 中,当使用 select() 系统调用时。每次调用后超时数据结构都会重置,因此如果您将代码更改为围绕选择循环,您可能应该这样做..您必须为循环中的每次迭代重置超时值。

此外,如果 select 超时,这并不一定意味着它是一个错误。请记住,select 是一个非阻塞调用。它只会在给定的“超时”期间在套接字上等待并返回。如果您无论如何都想从文件描述符中读取......这意味着您希望您的 recv_kern() 函数阻塞,直到有数据返回,然后不要费心使用 select()。只需直接在文件描述符上调用 recvfrom() 即可。这样,您的 recv_kernel() 函数将阻塞并且仅在读取内核发送的数据后才返回。


如果不了解有关如何使用此代码的上下文的更多信息,很难在这里提供更具体的帮助。我假设这是您编写的自定义内核模块,它将数据发送到用户空间,对吗?
尝试将您的 recv_kern() 函数更改为阻塞(取出选择代码并调用 recvfrom())。这种方式应该能够判断您的内核驱动程序是否实际上正在正确地将数据发送到用户空间。如果您在 recvfrom() 上阻塞并且没有任何东西返回..那么您的内核驱动程序也可能有问题。

希望有帮助。

于 2009-05-05T04:07:48.173 回答
2

您应该像这样重写函数:

static int
rcv_kern(int sock, void *buf, int len, struct sockaddr *addr,
     socklen_t *addrlen)
{
    struct timeval timeout = {1, 0};
    fd_set set;
    int status;

    FD_ZERO(&set);
    FD_SET(sock, &set);
    if ((status = select(sock + 1, &set, NULL, NULL, &timeout)) == 0) {
        fprintf(stderr, 
                "timeout while receiving answer from kernel\n");
        exit(1);
    } else if (status < 0) {
        perror("recvfrom failed");
        exit(1);
    }
    if ((status = recvfrom(sock, buf, len, 0, addr, addrlen)) < 0) {
        perror("recvfrom error");
        exit(1);
    }
    if (status == 0) {
        fprintf(stderr, "kernel closed socket\n");
        exit(1);
    }
    return status;
}

就像其他人说的那样,您需要在调用 select 之前调用 FD_ZERO。对 FD_ZERO 的其他调用是多余的。此外,您需要进行完整的错误检查。

于 2009-06-17T10:40:14.977 回答
1

与超时无关,但需要在 FD_SET(sock, &set) 之前先 FD_ZERO(&set),否则 fd_set 将未初始化并且可能包含许多设置位。此外,退出之前的 FD_ZERO() 是毫无意义的。

于 2009-05-05T02:16:05.353 回答
0

我在内核空间研究了我的代码,我知道内核无法使用“skb_dequeue(&sk->sk_receive_queue)”方法从客户端接收消息。我不知道它是怎么发生的。

于 2009-05-05T02:17:28.000 回答
0

strerror(errno)对于初学者,您可以通过在超时发生时打印出来(打印 errno 也是明智的)来找出实际错误是什么。

至于在没有 errno 的情况下猜测问题可能是什么,请注意,不能保证有什么可以读;即使您通过accept(2) 获得了套接字,它也可能只是一个已建立的连接,但客户端没有开始写入。通常你不会只做一次 select(2); 您希望有一个主循环一直调用 select(2) 直到程序想要退出,因为几乎任何时候都可能由于任何原因发生超时。

其他可能的问题:

  • 客户端无法连接。
  • 您未能正确绑定套接字。
  • 在调用 bind(2) 之后,您忘记在服务器的套接字上调用 listen(2)。

如果您使用 IP 套接字,您可以使用 Wireshark 查看您的网络流量,以查看客户端是否按照您的预期执行。

于 2009-05-05T03:02:14.913 回答