信号中断阻塞模式下的发送方法

Posted

技术标签:

【中文标题】信号中断阻塞模式下的发送方法【英文标题】:A signal interrupts the send method in blocking mode 【发布时间】:2015-09-18 09:03:15 【问题描述】:

我正在使用处于阻塞模式的套接字进行编程,我对send 方法有疑问。

send 方法的手册页中,它说:

[EINTR] 在传输任何数据之前,一个信号会中断系统调用。

这意味着如果在传输任何数据之前有信号中断系统调用,send 将返回 -1,errno 将设置为 EINTR

我的问题是,如果在信号中断系统调用时已经传输了一部分数据,会返回什么。它似乎不应该返回 -1,因为它已经发送了一些数据。我认为它会返回已传输的数据数量,这意味着阻塞模式下的send 方法可能返回的数据数量可能少于您作为第三个参数传递的数据数量。

ssize_t send(int socket, const void *buffer, size_t length, int flags);

【问题讨论】:

这个question和我的问题很相似,比较有用。 【参考方案1】:

[EINTR] 在传输任何数据之前,一个信号会中断系统调用。

这意味着如果send()开始传输数据,它不会被任何信号中断。因此,传输将阻止信号的接收,直到它完成。 send() 可能返回的数据字节数少于您作为第三个参数传递的数据字节的情况通常是由于网络问题,例如丢包。

【讨论】:

您有参考资料来支持您的回答吗?我确信 TCP 将处理丢失的数据包而不使 send 方法返回。 您可以查看设备驱动程序如何实现其write() 函数。 send()write() 很熟悉,但与网络相关的部分。在write() 的实现中,你会发现这样的东西:if (signal_pending(current)) return -ERESTARTSYS; 如果某些信号未决,它将中断传输。 makelinux.net/ldd3/chp-6-sect-2 或许能帮到你。 谢谢,不过我们说的是socket,实现好像是文件系统IO,而不是网络。尽管它们具有相同的接口,但它们的实现方式不同。 socket 是一个特殊的 I/O,read()send() 几乎没有什么不同。您可以查看***.com/questions/1790750/…。另外,如果你真正了解Linux操作系统的底层,所有这些操作都是基于Virtual Filesystem层的,无论是网络socket还是spin disk上的文件。 对“... [the] 关于返回的更少字节的声明...”有什么感觉?【参考方案2】:

documentation 很清楚。

返回值

成功时,这些调用会返回发送的字节数。出错时,返回 -1,并正确设置 errno。

[...]

EINTR在任何数据传输之前出现信号;

send() 要么返回

发送的字节数 或-1

如果返回-1,则通过errno 的值指示原因。

如果errno 等于EINTR,则信号中断send()到目前为止还没有收到任何数据

根据以上信息,我们可以安全地得出结论,如果收到数据,send() 函数将不会返回-1,无论是否收到信号与否。

【讨论】:

我和你有同样的想法,但我不确定。那是因为我不明白为什么“在传输任何数据之前发生信号”时会出错,如果“发生信号”导致错误,为什么“在传输某些数据后发生信号”是成功的。 @KudoCC:如果您面临不同的行为,那么您的程序或send() 的实现都有问题。 @KudoCC:被信号打断不应被视为错误。至少在 Linux 上,您还可以使用信号属性 SA_RESTART 在信号基础上关闭此行为。 谢谢,我想要的是你确保这一点:) @KudoCC:这不是“从以上信息中可以安全地得出结论......”确实够了吗?【参考方案3】:

其他答案很清楚,但是在阅读了您的一些 cmets 之后,我想补充一些进一步的信息。

首先,您理解EINTR 背后的想法是错误的。被系统调用中的信号打断不应被视为错误。 EINTR 在慢速系统调用中背后的基本原理(慢速系统调用是那些可以永远阻塞的系统调用,例如某些文件类型上的 open(2) - 例如终端设备 - 某些设备上的 accept(2)read(2)write(2) - 包括套接字- 等等)是,如果您的程序在系统调用中被阻塞并且信号被捕获(同时仍然被阻塞),那么信号处理程序很可能(但不是强制性)改变了您程序中的状态并且事情有所不同,所以电话会以EINTR 提前返回,让您有机会做任何您想做的事情。这不是像 EINVALEBADF 或其他“真实”错误之类的错误 - 它只是内核告诉您信号被捕获的方式。

如果您不想做任何事情,那么在设置信号处理程序时(这会导致系统调用自动重新启动),或者在struct sigactionsa_flags 字段上设置SA_RESTART 标志,或者显式设置当 errno 设置为 EINTR 返回 -1 时再次调用 send(2)

最重要的是,内核没有固有的限制,当信号被捕获时强制它返回用户空间。相反,EINTR 只是一种方便的行为,开发人员可能会觉得有用。

如果内核正在传输数据并发出信号,这没什么大不了的:如果正在传输数据,则系统调用正在进行。内核在进程上下文中代表调用它的程序执行系统调用,因此从技术上讲,该进程不再处于休眠状态。如果一个信号到达,它将处于挂起状态,直到内核决定是时候将其传递给进程了——这很可能发生在send(2) 返回时。

【讨论】:

感谢您承担为此提供大量背景信息的负担! :-) 1+ 顺便说一句 感谢您的回答,我注意到它有一段时间并试图理解它,但我发现除非对内核/信号等有一些背景知识,否则我很难理解。 @KudoCC 很高兴我能帮上忙!也感谢您提出的好问题:)

以上是关于信号中断阻塞模式下的发送方法的主要内容,如果未能解决你的问题,请参考以下文章

在阻塞 boost c++ 方法中,如何在 Python 中捕获中断信号?

非阻塞系统调用可以中断吗?

Linux驱动开发异步通知

Linux驱动开发异步通知

低速系统调用的信号中断

ucos-iii串口用信号量及环形队列中断发送,用内建消息队列中断接收