linux 下调用recv函数,死循环在recv函数里面,啥原因?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux 下调用recv函数,死循环在recv函数里面,啥原因?相关的知识,希望对你有一定的参考价值。

linux服务器,在连接只有1000个时候,一切正常,到连接到2000以上的时候,就死循环在recv函数里面,cpu占用率很高,因为recv是系统函数,也不知道他在里面干什么。程序是epoll实现的。
有谁碰到过类似问题么?或者是能指导一个努力的方向?
回1楼:不是建立连接的时候死循环,而是跑了一段时间后,在调用recv函数的时候。
我这里是服务器,接受连接, 是一个进程多个线程。每个线程管理多个连接。每个线程使用epoll方式响应网络事件,当网络可读的时候,就去调用recv函数读取数据。
在进行测试的时候,发现1000个连接是好了,如果是3000个连接,跑一个晚上后,就有好几个线程死循环在系统的recv函数了。

回2楼:调试出问题的程序后,发现几个线程都停留在recv函数里面,n单步执行后,直接跳出这个线程。
之所以说是死循环,因为在这种情况下这几个相关线程的cpu占用都很高。
如果是因为socket已经非法,那么我该怎么提前规避处理?
回3楼:socket都是非阻塞的。

建议你用strace看那几个线程确切是卡在哪里

而且你描述的是,死循环。 recv函数怎么会死循环?

还有,当你的系统压力变大的时候, 会出现epoll提示某socket可用,但是等你去读的时候该socket已经被关闭的情况,你看看这种情况会不会对你的程序造成影响。
----------------------------
man recv
RETURN VALUE
These calls return the number of bytes received, or -1 if an error occurred. The return value will be 0 when the peer has performed an
orderly shutdown.

你可以看到,当对端关闭socket的时候recv返回值是0。 那么作为你的程序,你又没有判断这种情况呢? 你默认的如果是使用EPOLLET模式, 你肯定不停的读socket直到EAGAIN出现,但是如果返回值0的话,并不会出现EAGAIN。

建议你还是多用strace来查询问题所在,有时候比gdb更能直接找出原因。

还有再纠正一点,recv是一个linux系统调用,要么是阻塞要么是返回,不存在死循环的问题的, 死循环肯定是出在你的程序代码中。 如果你觉得recv本身不退出又占用大量cpu,那就是linux库出bug或者是内核bug了。
参考技术A 给recv() 设个timeout, 过了这个时间就返回超时的错误,不要RECV一直阻塞在那里.

timeout 可以自己做一个。

下面是 google 弄得一段例子。
struct timeval tv; /* timeval and timeout stuff added by davekw7x */
int timeouts = 0;
tv.tv_sec = 3;
tv.tv_usec = 0;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof tv))

perror("setsockopt");
return -1;


if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof their_addr) == -1)
perror("connect");
exit(1);


while (((numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) && (++timeouts < 1000)) /* loop to retry in case it timed out; added by davekw7x */
perror("recv");
printf("After timeout #%d, trying again:\n", timeouts);

printf("numbytes = %d\n", numbytes);

buf[numbytes] = '\0';

printf("Received: %s",buf);
参考技术B 不会是程序设计问题导致函数互相锁死吧?

我没研究过服务器以及变成,纯粹猜测。
参考技术C send后,操作系统会决定是不是马山发包还是继续等待看看还有别的send,然后把他们一起打成一个包来发送。所以你recv是不知道要接收几个包的。

测量linux内核中函数的执行时间

【中文标题】测量linux内核中函数的执行时间【英文标题】:Measuring execution time of a function inside linux kernel 【发布时间】:2011-01-11 08:45:08 【问题描述】:

我正在使用 Linux 安全模块挂钩向 recv() 系统调用添加一些自定义功能。与原始的 recv() 相比,我想测量此功能的开销。我编写了一个简单的 tcp 服务器,可以在有和没有模块的情况下运行。此 tcp 服务器调用 recv() 函数“N”次。它测量每个 recv 所花费的时间,例如:

clock_gettime(before);
recv()
clock_gettime(after);
global_time += after - before.

最后,我用“global_time/N”打印单个recv() 的平均时间。让我们将此时间称为“user_space_avg_recv”时间。

在我的模块中,我想放置时间测量函数来计算我的钩子的确切执行时间。我尝试了 3 种方法。

    我使用 jiffies 如下:

    sj = jiffies;
    my_hook();
    ej = jiffies;
    current->total_oh = ej - sj;
    

    但我发现 sj 和 ej 值之间没有区别。因此 total_oh 没有改变。

    我使用 current_kernel_time() 因为我认为它以纳秒为单位返回时间。但是,再一次,前后时间没有区别。

    我使用了 get_cycles。我打印进程退出时的总周期。但是,当我将总周期值转换为毫秒时,它会远大于 “user_space_avg_recv”值。这没有意义,因为内核内部的测量值总是小于从用户空间测量的时间值。这可能意味着我要么没有使用正确的 API 进行测量,要么在将值从周期转换为毫秒时出错。

我基本上使用以下公式将周期转换为毫秒:

avg overhead of my hook in milliseconds = 
             (((cycles / 2.99) / 10^6) / N)

2.99 因为我的时钟频率是 2.99Ghz

几点:

我的用户空间程序使用 set affinity 绑定到单个内核。

我使用的是内核 2.6.22.14

为了阻止内核在我的钩子中切换上下文,我使用了 preempt_disable() 和 preempt_enable()。因此它不会计算其他内核线程的执行时间。即使这样,由于我的钩子使用了一些 I/O,我的线程可能会自愿释放控制,或者可能会发生一些中断,这可能会增加总周期数。

问题: 如何在内核中准确测量函数执行时间?

【问题讨论】:

尝试在 BIOS 中禁用频率更改并按周期重新测量 你试过ktime_get吗? 【参考方案1】:

您可以使用function tracer API 来跟踪所有函数调用和返回,并带有高精度时间戳。这包括中断事件和上下文切换。然后,您可以分析用户空间中生成的跟踪,以准确了解您的函数需要多长时间运行。

如果您不能使用函数跟踪器 API,您可以调用 do_gettimeofday() 来获取微秒分辨率的时间戳,或调用 getnstimeofday() 来获取纳秒分辨率。这些与用户空间 gettimeofday() 调用在内部使用的功能相同。当然,对于非常快速的函数,这可能不够准确;任何比这更快的精度,您可能需要深入研究计时器代码以查看它如何实现周期转换。另请注意,仅仅因为它们具有高分辨率并不意味着它们具有那么高的准确性 - 但它们应该对基准测试有用。

请注意,任何形式的跟踪都会导致额外的延迟 - do_gettimeofday() 需要许多原子比较和交换操作,并且 ftrace 将日志代码放在每个单个函数的前置和后置码时间>。在解释结果时应考虑到这一点。

【讨论】:

那不是为 2.6.28 以后写的吗?我正在使用 2.6.22.14。 @Methos:用另一种方法更新。 @bdonlan,感谢您的更新。但是,do_gettimeofday() 在内部从“xtime”读取值。我在数字 2 中提到的 current_kernel_time() 函数也有同样的作用(它在同一个文件 kernel/time.c 中)。正如我在第 2 点中所说,我没有看到在我的钩子之前和之后返回的值之间有任何时间差。 实际上,它们是不同的——xtime 会定期更新,因此精度有限。 do_gettimeofday 根据 CPU 周期计数应用偏移量,以获得更高的结果精度。比较我链接到的do_gettimeofday() 的代码和current_kernel_time 的代码(lxr.linux.no/linux+v2.6.22.14/kernel/time.c#L219) 我想你说的是从 do_gettimeofday() 中的 time_interpolator_get_offset() 获得的偏移量。我不知道这是否确实计算了 CPU 周期(但我会相信你的话)。在任何情况下,do_gettimeofday() 在“struct timeval”中返回值,其精度为秒和微秒。我需要 struct timespec,它的精度为 sec 和 nanosec。我想我必须添加我自己的 my_gettimeofday(),它基本上不会将 nsec 转换为 microsec。我将对内核进行更改,编译并测试您的理论。再次感谢。【参考方案2】:

我不确定你会得到你想要的结果,但我们使用以下代码来获得微秒。

double Microsecs()

   static struct timeval _t;  
   static struct timezone tz;  
   gettimeofday(&_t, &tz);  
   return   (double)_t.tv_sec + (double)_t.tv_usec/(1000*1000);

比你在你想要的调用之前和之后调用它,看看它有多少次。 我们一直在使用这种方法来评估 IO 时间监控读/写/寻道操作,以优化性能,并且取得了不错的效果。

HTH。

【讨论】:

为什么是“静态结构”而不是普通结构?对我来说,这似乎是一种竞争条件...... 你可能是对的。在单线程代码中减少了堆栈上的活动量,特别是当您在测试中调用它超过 10^6 次并执行非常多的非常小的时间片总和时。但是,无论如何,你可能是对的。我 9 年前做过,我实际上不记得是否检查过性能差异。【参考方案3】:

您尝试过使用 OProfile 吗?

【讨论】:

我想过。但是我想分别分析每个进程的钩子。我不知道如何使用 Oprofile 来做到这一点,我认为这种方法会更容易。关于如何使用 Oprofile 执行此操作的任何指示? @Dan:这不是答案。您可以通过发表评论来澄清这些事情。

以上是关于linux 下调用recv函数,死循环在recv函数里面,啥原因?的主要内容,如果未能解决你的问题,请参考以下文章

循环时 recv() 函数如何工作?

测量linux内核中函数的执行时间

recv() 函数太慢

如何在 linux C 中解除对 recv() 或 recvfrm() 函数的阻塞

linux中read,write和recv,send的区别

奇怪的 Winsock recv() 减速