답변:
글쎄, 그것에 많은 측면이 있습니다.
파일 기술자
각 프로세스에 대해 커널은 열린 파일 테이블을 유지 관리합니다 (잘 다르게 구현 될 수 있지만 어쨌든 볼 수 없으므로 간단한 테이블이라고 가정 할 수 있습니다). 이 테이블에는 파일 위치, 파일 위치, 파일을 연 모드, 현재 읽고 쓰는 위치 및 해당 파일에 대한 I / O 작업을 실제로 수행하는 데 필요한 기타 정보가 포함되어 있습니다. 이제 프로세스는 해당 테이블을 읽거나 쓰지 않습니다. 프로세스가 파일을 열면 소위 파일 디스크립터를 다시 가져옵니다. 이것은 단순히 테이블에 대한 인덱스입니다.
디렉토리 /dev/fd
와 그 내용
Linux dev/fd
에서 실제로는에 대한 심볼릭 링크 /proc/self/fd
입니다. /proc
커널은 파일 API로 액세스 할 여러 내부 데이터 구조를 커널이 맵핑하는 의사 파일 시스템입니다 (따라서 일반 파일 / 디렉토리 / 프로그램에 대한 심볼릭 링크처럼 보입니다). 특히 모든 프로세스에 대한 정보가 있습니다 (이름을 부여한 것임). 기호 링크는 /proc/self
항상 현재 실행중인 프로세스 (즉, 프로세스를 요청하는 프로세스와 연관되어 있으므로 다른 프로세스는 다른 값을 볼 수 있음)와 관련된 디렉토리를 나타냅니다. 프로세스의 디렉토리에는 하위 디렉토리가 있습니다fd
열려있는 각 파일에 대해 이름은 파일 디스크립터의 십진 표시 (프로세스 파일 테이블의 색인, 이전 섹션 참조)이고 대상이 해당 파일 인 파일의 기호 링크를 포함합니다.
자식 프로세스를 만들 때의 파일 디스크립터
자식 프로세스는에 의해 생성됩니다 fork
. A fork
는 파일 디스크립터의 사본을 작성하는데, 이는 작성된 하위 프로세스가 상위 프로세스와 동일한 열린 파일 목록을 가지고 있음을 의미합니다. 따라서 열린 파일 중 하나가 자식에 의해 닫히지 않는 한 자식의 상속 된 파일 설명자에 액세스하면 부모 프로세스의 원래 파일 설명자에 액세스하는 것과 동일한 파일에 액세스합니다.
포크 후에는 처음에 포크 호출과의 리턴 값 만 다른 동일한 프로세스의 두 사본이 있습니다 (부모는 자식의 PID를 가져오고 자식은 0을 얻습니다). 일반적으로 포크 다음에 exec
는 사본 중 하나를 다른 실행 파일로 대체합니다. 열린 파일 디스크립터는 그 실행 후에도 유지됩니다. 또한 실행 전에는 프로세스가 다른 조작을 수행 할 수 있습니다 (예 : 새 프로세스가 가져 오지 않아야하는 파일 닫기 또는 다른 파일 열기).
명명되지 않은 파이프
명명되지 않은 파이프는 커널이 요청하면 생성 된 파일 디스크립터 쌍이므로 첫 번째 파일 디스크립터에 기록 된 모든 것이 두 번째 파일 디스크로 전달됩니다. 가장 일반적인 용도는의 파이프 구성 foo | bar
에 사용되며 bash
, 여기서 표준 출력은 foo
파이프의 쓰기 부분으로 대체되고 표준 입력은 읽기 부분으로 대체됩니다. 표준 입력 및 표준 출력은 파일 테이블의 처음 두 항목 (항목 0 및 1; 2는 표준 오류 임)이므로 해당 테이블 항목을 다른 파일 디스크립터에 해당하는 데이터 (다시 말해서 실제 구현은 다를 수 있습니다). 프로세스가 테이블에 직접 액세스 할 수 없으므로이를 수행하는 커널 함수가 있습니다.
공정 대체
이제 우리는 프로세스 대체가 어떻게 작동하는지 이해하기 위해 모든 것을 갖추고 있습니다.
echo
프로세스를 위한 배쉬 포크 . 하위 프로세스 (원래 bash
프로세스 의 정확한 사본 )는 파이프의 읽기 끝을 닫고 자체 표준 출력을 파이프의 쓰기 끝으로 바꿉니다. 그것이 echo
쉘 내장 이라고 가정 bash
하면 exec
호출 자체를 아낄 수는 있지만 어쨌든 중요하지 않습니다 (쉘 내장 기능도 비활성화 될 수 있습니다 /bin/echo
. 이 경우 execs ).<(echo 1)
하여 의사 파일 링크로 표현식 을 대체합니다 /dev/fd
./dev/fd/
. 해당 파일 디스크립터가 여전히 열려 있기 때문에 여전히 파이프의 읽기 끝에 해당합니다. 따라서 PHP 프로그램이 주어진 파일을 읽기 위해 열면 실제로 수행하는 것은 second
명명되지 않은 파이프의 읽기 끝을위한 파일 디스크립터 를 작성하는 것 입니다. 그러나 그것은 문제가되지 않습니다. 어느 쪽이든 읽을 수 있습니다.echo
동일한 파이프의 쓰기 끝으로 이동 하는 명령 의 표준 출력을 수신합니다 .php
시나리오에 대해 이야기하고 있지만 php
파이프를 잘 처리하지 못합니다 . 또한 명령을 고려할 cat <(echo test)
때 이상한 점은 bash
포크는 한 번 cat
, 두 번은 포크 입니다 echo test
.
celtschk
대답 에서 빌려온 /dev/fd
것은에 대한 상징적 인 링크 /proc/self/fd
입니다. 또한 /proc
의사 파일 시스템으로, 프로세스 및 기타 시스템 정보에 대한 정보를 계층 적 파일 구조로 제공합니다. 파일은 파일에 /dev/fd
해당하며 프로세스에 의해 열리고 파일 디스크립터는 이름으로, 파일 자체는 대상으로합니다. 파일을 여는 것은 /dev/fd/N
설명자를 복제 N
한다고 가정합니다 (설명자가 N
열려 있다고 가정 ).
그리고 그것이 어떻게 작동하는지에 대한 나의 조사 결과는 다음과 같습니다 ( strace
출력은 불필요한 세부 사항을 제거하고 현재 상황을 더 잘 표현하도록 수정되었습니다).
$ cat 1.c
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
char buf[100];
int fd;
fd = open(argv[1], O_RDONLY);
read(fd, buf, 100);
write(STDOUT_FILENO, buf, n_read);
return 0;
}
$ gcc 1.c -o 1.out
$ cat 2.c
#include <unistd.h>
#include <string.h>
int main(void)
{
char *p = "hello, world\n";
write(STDOUT_FILENO, p, strlen(p));
return 0;
}
$ gcc 2.c -o 2.out
$ strace -f -e pipe,fcntl,dup2,close,clone,close,execve,wait4,read,open,write bash -c './1.out <(./2.out)'
[bash] pipe([3, 4]) = 0
[bash] dup2(3, 63) = 63
[bash] close(3) = 0
[bash] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p2
Process p2 attached
[bash] close(4) = 0
[bash] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p1
Process p1 attached
[bash] close(63) = 0
[p2] dup2(4, 1) = 1
[p2] close(4) = 0
[p2] close(63) = 0
[bash] wait4(-1, <unfinished ...>
Process bash suspended
[p1] execve("/home/yuri/_/1.out", ["/home/yuri/_/1.out", "/dev/fd/63"], [/* 31 vars */]) = 0
[p2] clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7c211fb9d0) = p22
Process p22 attached
[p22] execve("/home/yuri/_/2.out", ["/home/yuri/_/2.out"], [/* 31 vars */]) = 0
[p2] wait4(-1, <unfinished ...>
Process p2 suspended
[p1] open("/dev/fd/63", O_RDONLY) = 3
[p1] read(3, <unfinished ...>
[p22] write(1, "hello, world\n", 13) = 13
[p1] <... read resumed> "hello, world\n", 100) = 13
Process p2 resumed
Process p22 detached
[p1] write(1, "hello, world\n", 13) = 13
hello, world
[p2] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = p22
[p2] --- SIGCHLD (Child exited) @ 0 (0) ---
[p2] wait4(-1, 0x7fff190f289c, WNOHANG, NULL) = -1 ECHILD (No child processes)
Process bash resumed
Process p1 detached
[bash] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = p1
[bash] --- SIGCHLD (Child exited) @ 0 (0) ---
Process p2 detached
[bash] wait4(-1, 0x7fff190f2bdc, WNOHANG, NULL) = 0
--- SIGCHLD (Child exited) @ 0 (0) ---
[bash] wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG, NULL) = p2
[bash] wait4(-1, 0x7fff190f299c, WNOHANG, NULL) = -1 ECHILD (No child processes)
기본적으로 bash
파이프를 작성하고 그 끝을 파일 설명 자로 하위에 전달합니다 (end를 읽고, end를 1.out
쓰는 것 2.out
). 그리고 명령 행 매개 변수로 read end를 1.out
( /dev/fd/63
)에 전달합니다. 이 방법 1.out
은 열 수 있습니다 /dev/fd/63
.