为什么使用 SIOCGIFCONF 的 ioctl 偶尔会执行缓慢?

问题描述 投票:0回答:1

当我在Linux上使用JDK8时,我发现

java.net.NetworkInterface#getNetworkInterfaces
方法偶尔需要几秒钟才能返回,因此我编写了以下Java程序来尝试重现该问题:

import java.net.NetworkInterface;
import java.net.SocketException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class NetworkTest {

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

    public static void main(String[] args) {
        while (true) {
            try {
                long startTime = System.currentTimeMillis();
                NetworkInterface.getNetworkInterfaces();
                long endTime = System.currentTimeMillis();

                long cost = endTime - startTime;
                if (cost > 200) {
                    System.out.println("time: " + LocalDateTime.now().format(DATE_TIME_FORMATTER) + ", cost: " + cost + "ms");
                }
            } catch (SocketException e) {
                throw new RuntimeException(e);
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

同时,我用

strace
跟踪程序,发现带有SIOCGIFCONF的ioctl偶尔会执行缓慢。以下是花了很长时间捕获的日志。

Java 输出:

time: 2023-12-26 20:51:22.453, cost: 1971ms

strace
输出:

20:51:20.482408 socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 8
20:51:20.482448 ioctl(8, SIOCGIFCONF, {1000 -> 80, NULL}) = 0
20:51:21.450974 ioctl(8, SIOCGIFCONF, {80, {{"lo", {AF_INET, inet_addr("127.0.0.1")}}, {"eth0", {AF_INET, inet_addr("10.0.16.15")}}}}) = 0
20:51:22.452894 ioctl(8, SIOCGIFFLAGS, {ifr_name="lo", ifr_flags=IFF_UP|IFF_LOOPBACK|IFF_RUNNING}) = 0
20:51:22.452960 ioctl(8, SIOCGIFNETMASK, {ifr_name="lo", ifr_netmask={AF_INET, inet_addr("255.0.0.0")}}) = 0
20:51:22.453023 ioctl(8, SIOCGIFINDEX, {ifr_name="lo", ifr_index=1}) = 0
20:51:22.453060 ioctl(8, SIOCGIFFLAGS, {ifr_name="eth0", ifr_flags=IFF_UP|IFF_BROADCAST|IFF_RUNNING|IFF_MULTICAST}) = 0
20:51:22.453093 ioctl(8, SIOCGIFBRDADDR, {ifr_name="eth0", ifr_broadaddr={AF_INET, inet_addr("10.0.16.15")}}) = 0
20:51:22.453127 ioctl(8, SIOCGIFNETMASK, {ifr_name="eth0", ifr_netmask={AF_INET, inet_addr("255.255.255.255")}}) = 0
20:51:22.453161 ioctl(8, SIOCGIFINDEX, {ifr_name="eth0", ifr_index=128}) = 0
20:51:22.453199 close(8)                = 0
20:51:22.453242 socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP) = 8
20:51:22.453297 open("/proc/net/if_inet6", O_RDONLY) = 9
20:51:22.453360 fstat(9, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
20:51:22.453410 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7cd648e000
20:51:22.453448 read(9, "fe80000000000000b8cef600741e59e0"..., 1024) = 108
20:51:22.453517 read(9, "", 1024)       = 0
20:51:22.453552 read(9, "", 1024)       = 0
20:51:22.453590 close(9)                = 0
20:51:22.453646 munmap(0x7f7cd648e000, 4096) = 0
20:51:22.453684 close(8)                = 0
20:51:22.453991 write(1, "time: 2023-12-26 20:51:22.453, c"..., 41) = 41
20:51:22.454055 write(1, "\n", 1)       = 1

从上面的输出中,我们可以观察到使用 SIOCGIFCONF 的 ioctl 花费了两倍的时间。可能的原因是什么?我该如何继续排查原因?

参考: java.net.NetworkInterface#getAll

更新

我尝试使用ftrace和systemtap进一步排查,但由于我的Linux系统是公司定制的,所以上述工具没有生效(缺乏相关调试信息)。还有其他解决问题的想法吗?

如果我长时间运行上面的NetworkTest,我可以观察到每隔半小时就会出现缓慢的请求,但我不知道原因。

time: 2024-01-03 11:51:20.459, cost: 769ms
time: 2024-01-03 11:51:22.458, cost: 999ms
time: 2024-01-03 12:21:21.451, cost: 550ms
time: 2024-01-03 12:51:21.453, cost: 558ms
time: 2024-01-03 13:06:20.456, cost: 780ms
time: 2024-01-03 13:06:23.453, cost: 1995ms
time: 2024-01-03 13:21:19.459, cost: 786ms
time: 2024-01-03 13:21:21.453, cost: 994ms
time: 2024-01-03 13:36:20.452, cost: 777ms
time: 2024-01-03 13:51:20.457, cost: 777ms
time: 2024-01-03 13:51:23.459, cost: 1002ms
time: 2024-01-03 14:06:20.458, cost: 775ms
time: 2024-01-03 14:06:22.458, cost: 1000ms
time: 2024-01-03 14:21:19.450, cost: 755ms
time: 2024-01-03 14:21:22.453, cost: 1002ms
time: 2024-01-03 14:36:20.451, cost: 777ms
time: 2024-01-03 14:51:22.452, cost: 1754ms
time: 2024-01-03 14:51:24.458, cost: 1006ms
time: 2024-01-03 15:06:21.454, cost: 1743ms
time: 2024-01-03 15:06:23.458, cost: 1004ms
time: 2024-01-03 15:21:21.456, cost: 728ms
time: 2024-01-03 15:21:23.458, cost: 1001ms
time: 2024-01-03 15:36:21.458, cost: 727ms
time: 2024-01-03 15:36:25.453, cost: 1994ms
time: 2024-01-03 15:51:22.460, cost: 732ms
time: 2024-01-03 15:51:25.452, cost: 1992ms
time: 2024-01-03 16:06:21.455, cost: 733ms
time: 2024-01-03 16:06:23.456, cost: 1001ms
time: 2024-01-03 16:21:22.454, cost: 727ms
time: 2024-01-03 16:36:21.458, cost: 735ms
time: 2024-01-03 16:36:24.451, cost: 1993ms
time: 2024-01-03 16:51:22.455, cost: 732ms
time: 2024-01-03 17:06:21.453, cost: 726ms
time: 2024-01-03 17:06:23.458, cost: 1005ms
time: 2024-01-03 17:21:21.455, cost: 726ms
time: 2024-01-03 17:36:20.458, cost: 732ms
time: 2024-01-03 17:36:22.457, cost: 999ms
time: 2024-01-03 17:51:21.456, cost: 730ms
time: 2024-01-03 18:06:21.451, cost: 724ms
time: 2024-01-03 18:21:21.457, cost: 731ms
time: 2024-01-03 18:36:20.457, cost: 725ms
time: 2024-01-03 18:36:23.455, cost: 1997ms
time: 2024-01-03 18:51:22.458, cost: 733ms
time: 2024-01-03 18:51:25.453, cost: 1994ms
time: 2024-01-03 19:06:23.451, cost: 1720ms
time: 2024-01-03 19:21:21.459, cost: 728ms
time: 2024-01-03 19:21:23.458, cost: 999ms
time: 2024-01-03 19:36:21.458, cost: 729ms
time: 2024-01-03 19:36:24.454, cost: 1995ms
time: 2024-01-03 19:51:23.451, cost: 1727ms
time: 2024-01-03 19:51:26.454, cost: 1002ms
time: 2024-01-03 20:06:23.455, cost: 729ms
time: 2024-01-03 20:21:22.453, cost: 1727ms
time: 2024-01-03 20:21:24.460, cost: 1006ms

内核版本:4.18.0-193.el8.#{company}.x86_64

java linux linux-kernel network-programming system-calls
1个回答
0
投票

ioctl
是针对设备特定输入/输出操作的系统调用...因此,在很多情况下,
ioctl
调用的执行可能会变慢,例如:

1- 您的系统(主机)在系统上有许多网络接口(逻辑/物理)。
因此

ioctl
需要迭代 each 接口来检索其配置信息。

2- 网络设备无法正常工作。例如损坏或配置错误的网络设备。
因此,在尝试从这些设备检索信息时,

ioctl
可能会遇到延迟甚至超时。

请注意,

NetworkInterface.getNetworkInterfaces()
确实是一种耗时的方法,因为它返回机器上网络接口的枚举。

© www.soinside.com 2019 - 2024. All rights reserved.