shell/init는 어떻게 stdio 스트림을 생성합니까?

shell/init는 어떻게 stdio 스트림을 생성합니까?

나는 MIT의 소스를 읽고 있습니다.xv6 OS. 이 스니펫은 다음의 시작 부분에 있습니다 sh.c.

// Ensure that three file descriptors are open.
while((fd = open("console", O_RDWR)) >= 0){
    if(fd >= 3){
      close(fd);
      break;
    }
}

나는 이것이 다음과 같은지 확인한다는 것을 이해합니다.적어도새로 할당된 파일 설명자가 3보다 크거나 같은지 확인하여 3개의 파일 설명자가 열려 있습니다(아마도 stdin, stdout 및 stderr용).

open1) 동일한 프로세스에서 동일한 장치를 여러 번 사용하고 다른 파일 설명자를 기대하는 것이 어떻게 가능합니까 ?

2) 이를 이해하기 위해 호스트 컴퓨터(x86_64 Linux 4.6.0.1)에서 유사한 코드 조각을 실행했습니다. 테스트 프로그램은 open다른 fd를 기대할 수 있는지 확인하기 위해 루프에서 텍스트 파일을 반복적으로 편집했지만 항상 동일한 파일 설명자를 생성했습니다. 이것으로부터 나는 xv6의 조각이 분명히 작동하기 때문에 (Qemu에서 테스트됨) open실제 파일과 장치(예: )가 어떻게든 다르다는 결론을 내렸습니다. /dev/console차이점은 정확히 무엇입니까?

#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>

int main(void)
{
    int fd;
    int cnt = 0;

    while ((fd = open("sample.txt", O_RDWR) > 0)) {
        if (cnt != 10) {
            cnt++;
            printf("File descriptor opened: %d\n", fd);
        } else {
            break;
        }
    }

    return 0;
}

실행 결과는 다음과 같습니다.

$ ./a.out
File descriptor opened: 1
File descriptor opened: 1
[snip]
File descriptor opened: 1
File descriptor opened: 1

편집하다답변 중 하나를 기반으로 strace실행 파일을 실행하여 다음을 발견했습니다.open 물론여러 파일 설명자를 반환하지만 어떤 이유로 인해 모두 인쇄되지 않습니다. 왜 그럴까요?

3) 다소 관련이 없지만 fds 0-2에서 stdio 스트림을 사용하는 규칙은 단지 규칙이 아닙니까? 예를 들어 초기화 시퀀스가 ​​입력/출력 파일 설명자를 다른 항목에 할당한 경우 해당 하위 항목이 I/O를 수행하는 방식에 어떻게든 영향을 미칠 수 있습니까?

답변1

실제로는 3가지 질문입니다. 프로그램이 잘못되었으므로 #2를 즉시 폐기하십시오.

    while ((fd = open("sample.txt", O_RDWR) > 0)) {

아마 당신은 의미

    while ((fd = open("sample.txt", O_RDWR)) > 0) {

부적절하게 배치된 괄호를 사용하면 가 fd0보다 큰지만 테스트하는 것입니다(파일 설명자 0, 1, 2가 열려 있으므로 이는 아마도 좋은 가정일 것입니다).

#1의 경우:open호출(성공한 경우)은 고유한 파일 설명자를 반환하도록 정의됩니다. 장치를 다시 열 수 없으면 open가 반환됩니다 -1.

#3의 경우: 물론이죠.협약, 뿐만 아니라POSIX기준. 다른 시스템에서는 모든 프로그램에 대해 네 번째 오픈 스트림을 갖는 것을 포함하여 다른 규칙을 사용했습니다.

추가 자료:Aegis 환경 사용(1988년 7월)
Apollo Domain/OS에 오류가 있다고 나와 있는 6-9페이지를 참조하세요.입력그리고 **출력*

답변2

아니요, 코드는 그렇지 않습니다.확인하다설명자는 실제로 열립니다. 아직 설명자가 없습니다. 열 때마다 새로운 파일 설명자(예: 0,1,2,3)가 제공됩니다. fd 3에 도달하면 코드가 중단되어 0에서 2까지 열린 상태로 유지됩니다.

각 파일 설명자는 단순히 일부 파일의 특정 위치에 대한 포인터입니다. 따라서 동일한 파일에 대해 둘 이상의 설명자를 갖는 것은 문제가 되지 않습니다.

테스트 프로그램이 서로 다른 공개 호출에 대해 동일한 fd를 제공한다면 버그가 있는 것입니다. 코드를 보여주세요.

예, fd 0에서 2까지에는 강력한 규칙이 있습니다. 일부 코드가 stdout에 인쇄하려는 경우 실제로는 fd 1에 인쇄됩니다. stdout을 다른 것으로 "매핑"할 방법이 없습니다.

답변3

1) 시스템이 동시에 여러 프로세스에서 동일한 파일을 열 수 있도록 지원한다면, 하나의 프로세스도 여러 번 열 수 있도록 허용하는 것은 어떨까요? 파일 설명자는 상속되기 때문에 어쨌든 동일한 프로세스에서 동일한 파일을 두 번 가질 수 있습니다. 즉, 한 번 상속되고 프로세스 자체에서 한 번 열린 경우입니다. 프로세스가 어떤 파일을 열었는지 확인하고 이전 파일에 대한 참조를 반환하는 것은 추가 작업입니다.

또한 프로세스의 여러 부분이 동일한 파일을 동시에 사용하는 경우에도 문제가 됩니다. 라이브러리가 기본 프로그램에서 일부 구성 파일을 사용하는 동시에 일부 구성 파일을 사용한다고 가정해 보겠습니다. 파일 설명자는 액세스 모드 및 파일 포인터의 위치에 연결되어 있습니다. 복사본이 하나만 있다면 이상한 일이 일어날 것입니다. 또한 파일이 (동일한 fd에) 두 번 열린 경우 닫힐 때 어떤 일이 발생해야 합니까? 언제를 결정하기 위해 또 다른 참조 카운팅 계층이 있을 수 있습니다.정말파일을 닫아도 다른 문제에는 도움이 되지 않습니다.


2) 코드에 따라 다릅니다. C가 아닌 스마트 언어는 열려 있는 파일을 보유하는 변수를 다시 계산하고 파일을 다시 열기 직전에 닫을 수 있습니다. 코드를 보지 않고서는 말하기 어렵습니다.

그러나 약간의 테스트를 통해 Perl의 동일한 변수에 대해 동일한 파일을 두 번 열면 동일한 FD 번호가 생성됩니다.

perl -e 'open F, "test.txt"; printf "%d ", fileno(F); open F, "test.txt"; printf "%d\n", fileno(F)'
3 3

이를 실행하면 strace파일이 다시 열리기 직전에 닫혀 있음을 알 수 있습니다.

두 개의 서로 다른 변수를 사용하면 두 개의 FD 번호를 얻습니다.

perl -e 'open F, "test.txt"; printf "%d ", fileno(F); open G, "test.txt"; printf "%d\n", fileno(G)'
3 4

3) 기술적으로 표준 파일 번호는 관례라고 말할 수 있습니다. 에서 성문화된 협약POSIX 및 ISO C 표준:

프로그램 시작 시 표준 입력(기존 입력 읽기용), 표준 출력(기존 출력 쓰기용), 표준 오류(진단 출력 쓰기용)의 세 가지 스트림이 미리 정의되어 있으며 명시적으로 열 필요가 없습니다.

그러나 어쨌든 커널에 신경 쓰지 않고 프로그램을 실행하는 것이 가능할 수도 있다는 점에서 관례입니다. 아니면 그렇지 않을 수도 있습니다. 호출 사양을 읽어보면 exec다음과 같은 것 같습니다.당신을 위해 무언가를 열 수 있는 구현이 허용되었습니다.:

exec 함수 계열 중 하나를 성공적으로 호출한 후 파일 설명자 0, 1 또는 2가 닫히는 경우 구현 시 새 프로세스 이미지에서 파일 설명자에 대해 지정되지 않은 파일을 열 수 있습니다.

(물론 프로그램 자체에서 닫을 수도 있습니다.)

시작할 때 이를 사용하지 않도록 설정하면 호환성이 사라집니다.

파일 설명자 0이 읽기용으로 열리지 않거나 파일 설명자 1 또는 2가 쓰기용으로 열리지 않은 상태에서 표준 유틸리티 또는 호환 응용 프로그램이 실행되는 경우 유틸리티 또는 응용 프로그램이 실행되는 환경은 부적합으로 간주됩니다. 유틸리티나 애플리케이션은 이 표준에 설명된 대로 작동하지 않을 수 있습니다.

그만큼Linux 매뉴얼 페이지에서는 다소 실용적으로 설명합니다.:

일반적인 원칙에 따르면, 권한이 있든 없든 어떤 이식 가능한 프로그램도 이 세 가지 파일 설명자가 execve()를 통해 닫힌 상태로 유지될 것이라고 가정할 수 없습니다.

관련 정보