read()
및 호출에 대한 매뉴얼 페이지를 읽어 보면 write()
이러한 호출은 차단 여부에 관계없이 신호에 의해 중단되는 것처럼 보입니다.
특히, 가정해보자
- 프로세스는 신호에 대한 핸들러를 생성합니다.
- 다음 명령을 사용하여 장치(예: 터미널)를 엽니다.
O_NONBLOCK
아니요설정(예: 차단 모드에서 실행) - 그런 다음 프로세스는
read()
시스템 호출을 통해 장치에서 데이터를 읽고 커널 공간에서 커널 제어 경로를 실행합니다. - 프로세스가
read()
커널 공간에서 실행되면 이전에 설치된 핸들러의 신호가 프로세스에 전달되고 해당 신호 핸들러가 호출됩니다.
매뉴얼 페이지와 해당 섹션을 읽어보세요.SUSv3 "시스템 인터페이스 볼륨(XSH)", 찾은 사람:
나. 데이터를 읽기 전에 a가 read()
신호에 의해 중단되면(즉, 사용할 수 있는 데이터가 없기 때문에 차단해야 함), -1이 반환되고 errno
[EINTR]로 설정됩니다.
2. read()
일부 데이터를 성공적으로 읽은 후 신호에 의해 a가 중단되면(즉, 요청 서비스를 즉시 시작할 수 있음) 읽은 바이트 수를 반환합니다.
질문 1):
두 경우 모두(차단/차단하지 않음) 신호 전달 및 처리가 완전히 투명하지 않다고 가정하는 것이 맞습니까 read()
?
첫 번째 사례입니다. 차단은 read()
일반적으로 프로세스를 해당 TASK_INTERRUPTIBLE
상태로 설정하여 신호가 전달될 때 커널이 프로세스를 해당 상태로 설정하므로 이해할 수 있는 것 같습니다 TASK_RUNNING
.
그러나 read()
차단이 필요하지 않고(케이스 2) 요청이 커널 공간에서 처리되는 경우 하드웨어 인터럽트의 도착과 적절한 처리가 투명할 수 있는 것처럼 신호의 도착과 처리가 투명할 것이라고 가정합니다. . 특히 신호를 보낸 후 프로세스가 일시적으로 실행될 것이라고 가정합니다.사용자 모드신호 핸들러를 실행하고 결국 해당 핸들러에서 돌아와 인터럽트 처리를 완료합니다 read()
(커널 공간에서). 그러면 프로세스는 read()
완료될 때까지 프로세스를 실행합니다. 그런 다음 프로세스는 호출 후 지점 read()
(사용자 공간에서)으로 돌아가고 결과는 다음과 같습니다. 사용 가능한 모든 바이트를 가져옵니다.
그러나 ii. 데이터를 즉시 사용할 수 있으므로 중단되었음을 의미하는 것처럼 보이지만 read()
데이터의 전부는 아닌 일부만 반환합니다.
이는 두 번째(그리고 마지막) 질문으로 이어집니다.
질문 B):
A)의 가정이 맞다면 read()
요청을 즉시 만족시킬 데이터가 있는데 차단할 필요가 없는데 왜 중단됩니까? 즉, read()
신호 처리기를 실행한 후 복구가 이루어지지 않아 결국 사용 가능한 모든 데이터(결국 사용 가능)가 반환되는 이유는 무엇입니까?
답변1
요약: 맞습니다. i(아무 것도 읽지 않고 중단)의 경우나 ii(부분 읽기 후 중단)의 경우 신호 수신이 투명하지 않습니다. 그렇지 않으면 운영 체제 아키텍처와 애플리케이션 아키텍처를 근본적으로 변경해야 합니다.
운영 체제 구현 보기
시스템 호출이 신호에 의해 중단되면 어떤 일이 발생하는지 생각해 보세요. 신호 처리기는 사용자 모드 코드를 실행합니다. 그러나 시스템 호출 처리기는 커널 코드이며 사용자 모드 코드를 신뢰하지 않습니다. 이제 시스템 호출 처리기 옵션을 살펴보겠습니다.
- 시스템 호출 종료. 사용자 코드에서 수행된 작업 수를 보고합니다. 필요한 경우 시스템 호출을 다시 시작하는 것은 애플리케이션 코드에 달려 있습니다. 이것이 유닉스가 작동하는 방식입니다.
- 시스템 호출 상태를 저장하고 사용자 코드가 호출을 재개할 수 있도록 합니다. 이는 다음과 같은 여러 가지 이유로 문제가 됩니다.
- 사용자 코드가 실행되는 동안 저장된 상태가 무효화되는 일이 발생할 수 있습니다. 예를 들어, 파일에서 읽는 경우 파일이 잘릴 수 있습니다. 따라서 커널 코드에는 이러한 상황을 처리하기 위해 많은 논리가 필요합니다.
- 사용자 코드가 시스템 호출을 재개하고 잠금이 영원히 유지된다는 보장이 없기 때문에 저장된 상태는 잠금을 보유하는 것이 허용되지 않습니다.
- 커널은 시스템 호출을 시작하기 위한 일반 인터페이스 외에도 진행 중인 시스템 호출을 재개하거나 취소하기 위한 새로운 인터페이스를 노출해야 합니다. 드문 경우지만 이는 심각한 합병증입니다.
- 저장된 상태에는 리소스(적어도 메모리)를 사용해야 합니다. 이러한 리소스는 커널에서 할당하고 보유해야 하지만 프로세스 할당에 포함됩니다. 이것은 극복할 수 없는 것은 아니지만 복잡한 문제입니다.
- 신호 처리기는 자체적으로 중단될 수 있는 시스템 호출을 수행할 수 있으므로 가능한 모든 시스템 호출을 포괄하는 정적 리소스 할당을 가질 수는 없습니다.
- 자원을 할당할 수 없으면 어떻게 되나요? 그러면 시스템 호출은 어쨌든 실패합니다. 즉, 애플리케이션에는 이 상황을 처리하기 위한 코드가 있어야 하므로 이 디자인은 애플리케이션 코드를 단순화하지 않습니다.
- 계속 진행하지만(일시 중지) 신호 처리기에 대한 새 스레드를 만듭니다. 이번에도 문제가 있습니다.
- 초기 UNIX 구현에는 프로세스당 하나의 스레드만 있었습니다.
- 신호 처리기는 시스템 호출을 무시할 위험이 있습니다. 어쨌든 이것이 문제가 되지만 현재 UNIX 설계에는 이미 포함되어 있습니다.
- 새 스레드에 리소스를 할당해야 합니다. 위를 참조하세요.
인터럽트와의 주요 차이점은 인터럽트 코드가 신뢰할 수 있고 매우 제한적이라는 것입니다. 일반적으로 리소스를 할당하거나, 영원히 실행하거나, 잠금을 해제하지 않고 잠금을 획득하거나, 기타 불쾌한 작업을 수행하는 것은 허용되지 않습니다. 인터럽트 핸들러는 운영 체제 구현자가 직접 작성하므로 나쁜 일을 하지 않을 것임을 알고 있습니다. . 반면에 애플리케이션 코드는 무엇이든 할 수 있습니다.
애플리케이션 디자인 보기
시스템 호출 중에 애플리케이션이 중단되면 시스템 호출이 계속해서 완료되어야 합니까? 항상 그런 것은 아닙니다. 예를 들어, 터미널에서 한 줄을 읽고 사용자가 이를 눌러 SIGINT를 트리거하는 쉘형 프로그램을 생각해 보세요 Ctrl+C
. 읽기가 완료되어서는 안 됩니다. 이것이 바로 신호의 목적입니다. 이 예에서는 read
바이트를 읽지 않은 경우에도 시스템 호출이 중단 가능해야 함을 보여줍니다.
따라서 애플리케이션에는 커널에 시스템 호출을 취소하라고 지시하는 방법이 있어야 합니다. Unix 설계에서는 이것이 자동으로 발생합니다. 신호로 인해 시스템 호출이 반환됩니다. 다른 디자인에서는 애플리케이션이 마음대로 시스템 호출을 재개하거나 취소할 수 있는 방법이 필요합니다.
시스템 read
호출은 운영 체제의 전반적인 디자인을 고려할 때 의미가 있는 기본 요소이기 때문에 그 이름이 됩니다. 대략적으로 말하면 "한도(버퍼 크기)에 도달할 때까지 최대한 읽고, 다른 일이 발생하면 중지한다"는 의미입니다. 실제로 전체 버퍼를 읽으려면 read
가능한 한 많은 바이트를 읽을 때까지 반복해야 합니다. 이는 더 높은 수준의 기능입니다.fread(3)
. 같지 않은read(2)
fread
이는 NET의 라이브러리 함수인 시스템 호출입니다 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 i
처리 및 case ii
기타 몇 가지 작업을 처리합니다 .
답변2
질문 A에 답하세요:
예, 신호 전달 및 처리는 read()
.
도중에 실행하면 read()
일부 리소스를 차지할 수 있으며 신호로 인해 중단될 수 있습니다. 해당 신호의 신호 처리기는 다른 신호 read()
(또는 다른 신호 처리기)를 호출할 수 있습니다.비동기 신호 안전 시스템 호출) 역시 마찬가지다. 따라서 read()
신호에 의해 중단된 신호는 사용하는 리소스를 해제하기 위해 먼저 중지되어야 합니다. 그렇지 않으면 read()
신호 처리기에서 호출된 신호가 동일한 리소스에 액세스하여 재진입 문제를 일으킬 것입니다.
신호 처리기에서 호출할 수 있는 시스템 호출 외에도 read()
동일한 리소스 집합을 차지할 수 있기 때문입니다 read()
. 위에서 언급한 재진입 문제를 피하기 위해 가장 간단하고 안전한 설계는 read()
작동 중 신호가 발생할 때마다 중단을 중지하는 것입니다.