신호가 걸렸을 때 시스템 호출 중단


29

read()write()통화 매뉴얼 페이지를 읽음으로써 이러한 통화는 차단 여부와 상관없이 신호에 의해 중단 된 것으로 보입니다.

특히, 가정

  • 프로세스는 일부 신호에 대한 핸들러를 설정합니다.
  • 설정 O_NONBLOCK 되지 않은 상태 로 장치가 열린 경우 (예 : 터미널) (예 : 차단 모드에서 작동)
  • 그러면 프로세스 read()는 장치에서 시스템 호출을 읽어 커널 공간에서 커널 제어 경로를 실행합니다.
  • 선행 작업이 read()커널 공간에서 실행 되는 동안 핸들러가 이전에 설치된 신호가 해당 프로세스로 전달되고 해당 신호 핸들러가 호출됩니다.

SUSv3 'XSH (System Interfaces Volume)' 매뉴얼 페이지와 해당 섹션을 읽으면 다음과 같은 사실을 알 수 있습니다.

나는. read()데이터를 읽기 전에 신호에 의해 a 가 중단 되면 (즉, 사용 가능한 데이터가 없기 때문에 차단해야 함) errno[EINTR] 로 설정하여 -1을 반환합니다 .

ii. a read()가 일부 데이터를 성공적으로 읽은 후 신호에 의해 중단 된 경우 (즉, 즉시 요청 서비스를 시작할 수있는 경우) 읽은 바이트 수를 반환합니다.

질문 A) : 어느 경우이든 (블록 / 블록 없음) 신호의 전달 및 처리가 완전히 투명하지 않다고 가정하는 것이 옳은가 read()?

사례 i. 블로킹 read()은 일반적으로 프로세스가 TASK_INTERRUPTIBLE상태에 놓이게되므로 신호가 전달 될 때 커널이 프로세스를 TASK_RUNNING상태에 놓기 때문에 이해할 수있는 것 같습니다 .

그러나 read()차단 할 필요가 없으며 (case ii.) 커널 공간에서 요청을 처리 할 때 신호 도착 및 처리가 HW의 도착 및 적절한 처리와 같이 투명 할 것이라고 생각했을 것입니다 방해가 될 것입니다. 특히 신호를 전달할 때 프로세스가 일시적으로 사용자 모드설정 되어 신호 처리기를 실행하여 결국 인터럽트 된 read()(커널 공간에서) 처리를 끝내기 위해 반환되는 신호 처리기를 read()실행합니다. 그 후 프로세스는 read()(사용자 공간에서) 호출 직후의 지점으로 되돌아 가고 결과적으로 사용 가능한 모든 바이트가 읽 힙니다.

그러나 ii. 는 read()데이터를 즉시 사용할 수 있기 때문에가 중단 되었음을 의미하는 것으로 보이지만 일부 데이터 만 반환합니다 (모두 대신).

이것은 나의 두 번째 (마지막) 질문으로 연결됩니다.

질문 B) : A)의 가정이 맞다 read()면 요청을 즉시 충족시키기 위해 사용할 수있는 데이터가 있기 때문에 차단할 필요가 없는데 왜 중단이 발생합니까? 다시 말해, read()신호 처리기를 실행 한 후 왜 재개되지 않는가? 결과적으로 사용 가능한 모든 데이터 (결국 사용 가능)가 반환되는 이유는 무엇입니까?

답변:


29

요약 : 신호 수신이 투명하지 않은 것이 맞습니다. i (아무 것도 읽지 않고 중단 된 경우) 나 ii (부분 읽은 후에 중단 된 경우)가 아닙니다. 그렇지 않으면 운영 체제 아키텍처와 응용 프로그램 아키텍처를 근본적으로 변경해야합니다.

OS 구현보기

신호에 의해 시스템 호출이 중단되면 어떻게되는지 고려하십시오. 신호 처리기는 사용자 모드 코드를 실행합니다. 그러나 syscall 핸들러는 커널 코드이며 사용자 모드 코드를 신뢰하지 않습니다. syscall 핸들러에 대한 선택 사항을 살펴 보자.

  • 시스템 호출을 종료하십시오. 사용자 코드에 얼마나 많은 일이 있었는지보고하십시오. 원하는 경우 어떤 방식 으로든 시스템 호출을 다시 시작하는 것은 응용 프로그램 코드에 달려 있습니다. 이것이 유닉스 작동 방식입니다.
  • 시스템 호출 상태를 저장하고 사용자 코드가 호출을 재개하도록 허용하십시오. 여러 가지 이유로 문제가 있습니다.
    • 사용자 코드가 실행되는 동안 저장된 상태가 무효화 될 수 있습니다. 예를 들어 파일에서 읽을 경우 파일이 잘릴 수 있습니다. 따라서 커널 코드는 이러한 경우를 처리하기 위해 많은 논리가 필요합니다.
    • 사용자 코드가 syscall을 다시 시작한다는 보장이 없으므로 잠금 상태는 영구적으로 유지되므로 저장된 상태는 잠금을 유지할 수 없습니다.
    • 커널은 syscall을 시작하기위한 일반 인터페이스 외에 진행중인 syscall을 재개하거나 취소하기 위해 새 인터페이스를 노출해야합니다. 이것은 드문 경우에 많은 합병증입니다.
    • 저장된 상태는 리소스 (적어도 메모리)를 사용해야합니다. 이러한 리소스는 커널이 할당하고 보유해야하지만 프로세스 할당량에 따라 계산됩니다. 이것은 극복 할 수 없지만 합병증입니다.
      • 신호 처리기는 시스템 호출 자체가 중단 될 수 있습니다. 따라서 가능한 모든 시스템 콜을 다루는 정적 리소스 할당을 가질 수는 없습니다.
      • 리소스를 할당 할 수 없으면 어떻게됩니까? 그러면 syscall은 실패 할 것입니다. 즉,이 경우를 처리하기 위해 응용 프로그램에 코드가 있어야하므로이 디자인은 응용 프로그램 코드를 단순화하지 않습니다.
  • 진행 중이지만 일시 중단 된 상태로 신호 처리기의 새 스레드를 만듭니다. 이것은 다시 문제가됩니다.
    • 초기 유닉스 구현에는 프로세스 당 단일 스레드가있었습니다.
    • 신호 처리기는 syscall의 신발을 너무 밟을 위험이 있습니다. 이것은 어쨌든 문제이지만 현재 유닉스 디자인에는 포함되어 있습니다.
    • 새 스레드에 리소스를 할당해야합니다. 위 참조.

인터럽트와의 주요 차이점은 인터럽트 코드가 신뢰할 수 있고 제약이 크다는 것입니다. 일반적으로 리소스를 할당하거나 영원히 실행하거나 잠금을 해제하여 해제하거나 다른 종류의 불쾌한 일을하는 것은 허용되지 않습니다. 인터럽트 핸들러는 OS 구현자가 직접 작성했기 때문에 아무 것도하지 않는다는 것을 알고 있습니다. 반면에 응용 프로그램 코드는 무엇이든 할 수 있습니다.

응용 프로그램 디자인 뷰

시스템 호출 도중에 응용 프로그램이 중단 된 경우 syscall을 계속 완료해야합니까? 항상 그런 것은 아닙니다. 예를 들어, 터미널에서 한 줄을 읽는 쉘과 같은 프로그램을 고려하고 사용자가를 누르면 Ctrl+CSIGINT가 트리거됩니다. 읽기가 완료되어서는 안됩니다. 신호가 전부입니다. 이 예제는 read바이트를 아직 읽지 않아도 syscall을 인터럽트 할 수 있어야 함을 보여줍니다 .

따라서 애플리케이션이 커널에게 시스템 호출을 취소하도록 지시 할 수있는 방법이 있어야합니다. 유닉스 디자인에서는 자동으로 발생합니다. 신호는 syscall을 반환합니다. 다른 설계에서는 애플리케이션이 syscall을 재개하거나 재개 할 수있는 방법이 필요합니다.

read시스템 호출은 운영 체제의 일반적인 디자인 주어진 의미가 그 원시적 때문입니다 방법입니다. 의미하는 바는 대략 "한도 (버퍼 크기)까지 가능한 한 많이 읽지 만 다른 일이 발생하면 멈추는 것"입니다. 실제로 전체 버퍼를 읽으려면 read가능한 한 많은 바이트를 읽을 때까지 루프에서 실행 해야합니다. 이것은 더 높은 수준의 함수 fread(3)입니다. read(2)어떤 시스템 호출 과 달리 fread라이브러리 함수는 사용자 공간에서 구현됩니다 read. 파일을 읽거나 시도하는 응용 프로그램에 적합합니다. 커넥션을 깨끗하게 조절해야하는 네트워크 프로그램이나 명령 줄 인터프리터 나 동시 연결이 있고 스레드를 사용하지 않는 네트워크 프로그램에는 적합하지 않습니다.

루프에서 읽는 예제는 Robert Love의 Linux 시스템 프로그래밍에서 제공됩니다.

ssize_t ret;
while (len != 0 && (ret = read (fd, buf, len)) != 0) {
  if (ret == -1) {
    if (errno == EINTR)
      continue;
    perror ("read");
    break;
  }
  len -= ret;
  buf += ret;
}

그것은 처리 소요 case icase ii및 몇 가지 더 있습니다.


UNIX 디자인 철학에 대한 기사에서 유사한 견해를 뒷받침하는 매우 간결하고 명확한 답변을 주신 Gilles에게 감사드립니다. syscall 인터럽트 동작은 기술적 제약이나 장애보다는 UNIX 디자인 철학과 관련이 있다는 것을 매우 확신합니다.
darbehdar

@darbehdar 유닉스 디자인 철학 (여기서는 주로 프로세스가 커널보다 신뢰도가 낮고 임의의 코드를 실행할 수 있으며 프로세스와 스레드가 암시 적으로 생성되지 않음), 기술적 제약 (자원 할당에 대한) 및 응용 프로그램 디자인 (있는 경우) 신호가 syscall을 취소해야하는 경우).
Gilles 'SO- 악마 그만해'

2

질문 A에 대답하려면 :

예. 신호 전달 및 처리가 완전히 투명하지는 않습니다 read().

read()이 신호에 의해 중단하는 동안에 실행을 반쯤 일부 리소스를 점유 할 수있다. 그리고 신호의 신호 처리기는 다른 read()(또는 다른 비동기 신호 안전 시스템 호출)도 호출 할 수 있습니다. 따라서 read()사용하는 리소스를 해제하려면 먼저 신호에 의해 중단 된 신호를 중지해야합니다. 그렇지 않으면 read()신호 처리기에서 호출 된 리소스가 동일한 리소스에 액세스하여 재진입 문제가 발생합니다.

read()신호 처리기에서 호출 할 수있는 것 이외의 시스템 호출 도 마찬가지이므로 동일한 리소스 집합을 차지할 수도 있습니다 read(). 위의 재진입 문제를 피하기 위해 가장 단순하고 안전한 설계는 read()신호가 실행되는 동안 매번 인터럽트를 중지하는 것 입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.