[Q/A] socket, read, EINTR

QnA 2009. 9. 16. 13:08

신호에 대해서 궁금한게 있어서 질문을 드립니다

select나 poll에 대한 wrapper 클래스가 있었으면 좋겠다는 생각을 했는데

그냥 제가 만들기로 했습니다 ㅎㅎ

문제는 그 클레스에 신호를 사용할 예정에 있는데

예를들면

int n;

again

if ( ( n = read(소켓번호,버퍼,버퍼 사이즈) ) < 0 )
{
if(errno == EINTR)
goto again;
........
........ 기타등등

}

이렇게 있다면

read에서 데이터를 읽을라고 하는 찰라나 또는 읽고 있을때 신호가 발생해

블럭이 풀린다면 ..... 데이터는 소켓 버퍼에 그대로 남아 있는지

아니라면 어떻게 처리해 줘야 하는지 궁금합니다

답변 부탁드립니다

====================================================================

select를 사용하신다면 non-blocking모드로 작성하는 수순이

select 를 사용하신다면 non-blocking모드로 작성하는 수순이 뒤따를것 같은데, read()에서 non-block으로 하시지 않고,blocking mode로 작성하시고, 님께서 제시하신 '예제' 처럼 로직이 되어있다면, select를 쓰기는 어려운 모습인듯합니다만. 저 같으면, read에서 블록킹하지 말고 select에서 블록킹하는 로직으로 작성하겠습니다.
그리고, 시그널이 뜬다면 데이터는 전부 남아있을것으로 보입니다.

isinji의 이미지

보통 시스템 콜은 interrupt되지 않지만 read(), write(

보통 시스템 콜은 interrupt되지 않지만 read(), write(), wait() ... 등의 자원을 기다릴 수 있는 몇개의 시스템 콜들은 signal에 의해 interrupt될 수 있습니다.

interrupt된 시스템 콜이 signal handling routine이 완료된 후 재수행되는 지의 여부는 어느 계열에서 파생된 signal 함수를 사용하는 지에 따라 달라집니다. 계열에 무관하게 interrupt 되었던 system call을 재 수행하려면 signal()이 아닌, sigaction 구조체의 sa_flags를 SA_RESTART로 지정하여
sigaction을 사용하면 됩니다.

mastercho의 이미지

문제는

SA_RESTART 에 관한 내용을 봤는데

인용:
만약 이값을 설정하고 신호를 받으면 , 신호 처리 루틴에 의해 방해 받은 시스템 호출은 신호 처리 륀이 리턴되었을때 재시작된다
시스템 호출은 재시작에 실패하면 errno에 EINTR을 넣고 리턴한다

이렇게 나와 있습니다

문제는

recv할때 읽어 들일 양이 많아서 블럭킹이 되고 있을때입니다

다 읽을때까지 블럭킹 되지 않습니까?

거기서 다 읽지도 못했는데 신호를 받아 버리면 그동안 읽었던 내용이

재 시작할때 버퍼에 계속 남아있어주냐가 문제가 되는거 같습니다

재시작이 문제가 아니라 recv 할때 읽었던 내용이 그대로 버퍼에 남아 있느냐죠

그게 궁금한겁니다

isinji의 이미지

그렇다면 read()를 호출하기 전에 발생 가능한 시그널을 block시켰

그렇다면 read()를 호출하기 전에 발생 가능한 시그널을 block시켰다가 read()에서 return되면 block을 풀면 되지 않을까요 :?:

mastercho의 이미지

것도 방법이 되겠지만 ..

것도 방법이 되겠지만... recv 할때마다 신호를 컸다 켰다...

오버헤드가 큰거 같습니다

쩝 이럴때는 recv가 신호를 받을때 어떻게 동작하는지

커널 소스를 까보고 싶네요

mastercho의 이미지

뿐만 아니라

뿐만 아니라 멀티 쓰레드 프로그래밍이 되기 때문에

신호를 발생 시키는 코드와 무시하는 코드 사이에 임계영역

즉 뮤텍스까지 두어서 동기화를 시켜야 합니다

오버헤드가 너무 크죠

정보가 필요합니다 recv에 대한 신호 처리가 T_T

익명 사용자의 이미지

1 byte만 와도 select는 걸리죠.읽으려고 목적한 크기가 1바

1 byte만 와도 select는 걸리죠.
읽으려고 목적한 크기가 1바이트이상일경우 read에서 약간의 문제가 있을수
있을거 같다는 주장입니다.
select후 FIONREAD를 검사하여
원하는 양이 차지 않으면 잠시 delay를 갇도록 하면
read에서느 한번에 원하는 양을 얻을수 있을거 같습니다.

signal처리가 좀더 안정적으로 되지 않을까 생각하는 바입니다.

moonzoo의 이미지

처리할 signal을 명확히..

먼저 read 중에 signal이 발생했을 경우..

상대편에게 데이타를 다시 쏴달라고 요청하시면 됩니다.

경우에 따라서는 해당 프로세스를 죽이고 다시 띄울 필요도 있겠고요.

그러고 싶지않은 signal등은 IGNORE 하시고요

중요한것은 막연히 read중에 시그날 처리를 어떻게 하느냐가 아니라

read중에 A signal은 이렇게, B signal은 이렇게 처리하겠다는

방침을 정해야 할것입니다.

단지 read뿐이 아닌 프로그램 전반에 걸친 signal정책을

고려하셔야 할듯.

다시 말씀드리지만...굳이 버퍼에 남은것을 신경쓰기보다는

다시 받는게 명확해 보입니다.

stoneshim의 이미지

[quote] EINTR The receive was int

인용:
EINTR The receive was interrupted by delivery of a signal
before any data were available.

위의 man page와 같이 read(2)나 recv(2) 가 -1을 return하고 errno에 EINTR에 세팅되는 경우는 socket buffer에서 하나도 읽지 않은 경우입니다.

제가 알기로는 blocking socket에 대한 read라 해도 socket 버퍼에 있는 데이터가 파라미터로 명시한 size보다 적은 경우라면 그만큼만 socket 버퍼에서 복사한 뒤 return 합니다.

인용:
POSIX allows a read that is interrupted after
reading some data to return -1 (with errno set to EINTR)
or to return the number of bytes already read

recv(2)나 read(2)가 read를 하던 중간에 interrupt가 발생한 경우에 대해POSIX는 위와 같이 규정하며,
recv(2) 호출시 flag에 MSG_WAITALL 을 설정한 경우라면, signal 발생시 linux의 경우 그때까지 읽은 데이터 크기를 return 합니다.

잘못된 부분이 있다면 지적해 주시길...

mastercho의 이미지

그렇다면...

멀티 쓰레드 환경에서도

read 로 읽을때 신호가 와도 안전하다는 의미로 받아들여도 될런지요

제가 우려했던건...read하고 나서 소켓의 버퍼에서 데이터가 삭제 될때

read가 데이터를 읽은 내용의 양을 리턴하기 바로 전 신호가 발생해

-1 로 리턴하는 경우 입니다

이미 데이터는 읽어서 소켓 버퍼에서는 삭제 되었는데

신호가 발생해서 read에서는 -1 리턴된다면......

데이터가 제대로 읽어졌는지 확인할 길이 없지 않겠습니까?

이게 우려스러웠던것인데........

음....

어째튼 답변 감사 드리고요

아기가 귀엽네여 )

stoneshim의 이미지

[quote]제가 우려했던건...read하고 나서 소켓의 버퍼에서 데이터

인용:
제가 우려했던건...read하고 나서 소켓의 버퍼에서 데이터가 삭제 될때

read가 데이터를 읽은 내용의 양을 리턴하기 바로 전 신호가 발생해

-1 로 리턴하는 경우 입니다

제 생각에는 리눅스 환경이라면 이러한 상황에서 -1을 return하지 않고, 읽은 크기만큼 리턴할 것입니다.

이러한 상황(다 읽고 return하기 직전)이 아니고 읽고 있는 도중이라도 signal이 발생하면 그때까지 읽은 크기만큼 리턴할 것입니다.( 리눅스라면요.. )

다른 implementation이라면 테스트를 해보시는게 어떨까요?

그리고... 멀티 스레드 환경과 지금 논의되는 내용은 별 상관이 없을듯 한데요.. 문제의 소지가 될법한것이... 있나요?

제 아이를 귀엽게 봐주시니 감사합니다. ^_____^

mastercho의 이미지

리눅스라면...

그렇다면 다른 유닉스에서는 위험할수 있다는 이야기가 되겠네여 --;

음... 참 그리고 멀티 쓰레드는 생각해보니 아무런 상관이 없네요

사실 신호 발생을 다른 쓰레드에서 발생 시키는 방법으로 구현하다보니 ^^;

착각했습니다

그럼 )

isinji의 이미지

우려하시는 부분에 대한 답변이 될 것 같습니다.[quote]T

우려하시는 부분에 대한 답변이 될 것 같습니다.

인용:

There is one situation where resumption never happens no matter which choice you make: when a data-transfer function such as read or write is interrupted by a signal after transferring part of the data. In this case, the function returns the number of bytes already transferred, indicating partial success.

This might at first appear to cause unreliable behavior on record-oriented devices (including datagram sockets; see Datagrams), where splitting one read or write into two would read or write two records. Actually, there is no problem, because interruption after a partial transfer cannot happen on such devices; they always transfer an entire record in one burst, with no waiting once data transfer has started.

인용한 글은 gnu c library manual 입니다.

http://www.gnu.org/manual/glibc-2.2.5/html_node/Interrupted-Primitives.h...


WRITTEN BY
RootFriend
개인적으로... 나쁜 기억력에 도움되라고 만들게되었습니다.

,