자식 대신 부모의 파일 설명자를 복사합니다.

자식 대신 부모의 파일 설명자를 복사합니다.

리디렉션을 사용하는 방법을 배우고 있습니다. 일반적인 작업은 다음과 같습니다.

    command > file 2>&1

APUE 3.10과 3.12를 참고하면 핵심 시스템 호출 순서는 다음과 같다고 생각합니다.

    open(file) == 3
    dup2(3,1)
    dup2(1,2)

내 아이디어를 테스트하기 위해 쉘 스크립트를 생성하고 strace 명령을 사용하여 실행했습니다. 내 test.sh는 다음과 같습니다.

    #!/bin/sh
    echo 'hello'
    whatfuckis # For get a stderr

그런 다음 strace 명령을 사용하여 시스템 호출과 신호를 추적합니다.

    strace -y -o trace.log ./test.sh > tmp.txt 2>&1

tmp.txt는 예상대로 오류 메시지를 받습니다. Trace.log에서 읽기, 쓰기, 닫기, fcntl.. 등과 같은 많은 시스템 호출을 볼 수 있으며 파일 설명자 2(stderr)가 tmp.txt를 가리키는 결과도 볼 수 있습니다.

문제는 dup2와 같은 파일 설명자 복사에 대한 특정 작업이 표시되지 않는다는 것입니다. 이는 이상합니다. 파일 설명자 중복을 추적하는 방법은 무엇입니까? Trace.log의 주요 정보는 다음과 같습니다.

read(10</home/madhouse/Applications/Lab/test.sh>, "#!/bin/sh\n\necho 'hello'\nwhatfuck"..., 8192) = 54
write(1</home/madhouse/Applications/Lab/tmp.txt>, "hello\n", 6) = 6
stat("/home/MATLAB/R2016b/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/home/madhouse/Qt5.14.2/5.14.2/gcc_64//whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/home/madhouse/Qt5.14.2/Tools/QtCreator/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/lib/nodejs/node-v12.22.5-linux-x64/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/home/madhouse/.local/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/sbin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/games/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/games/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/snap/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
write(2</home/madhouse/Applications/Lab/tmp.txt>, "./test.sh: 4: ", 14) = 14
write(2</home/madhouse/Applications/Lab/tmp.txt>, "whatfuckis: not found", 21) = 21
write(2</home/madhouse/Applications/Lab/tmp.txt>, "\n", 1) = 1
read(10</home/madhouse/Applications/Lab/test.sh>, "", 8192) = 0
exit_group(127)                         = ?
+++ exited with 127 +++

아비로의 로그

167936 execve("/usr/bin/sh", ["sh", "-c", "./test.sh > tmp.txt 2>&1"], 0x7ffc000ac898 /* 76 vars */) = 0
167936 dup2(3</home/madhouse/Applications/Lab/tmp.txt>, 1) = 1</home/madhouse/Applications/Lab/tmp.txt>
167936 close(3</home/madhouse/Applications/Lab/tmp.txt>) = 0
167936 dup2(1</home/madhouse/Applications/Lab/tmp.txt>, 2) = 2</home/madhouse/Applications/Lab/tmp.txt>
167936 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f67e121d850) = 167937
167937 execve("./test.sh", ["./test.sh"], 0x55855ac8c2d8 /* 76 vars */) = 0
167937 write(1</home/madhouse/Applications/Lab/tmp.txt>, "hello\n", 6) = 6
167937 write(2</home/madhouse/Applications/Lab/tmp.txt>, "./test.sh: 4: ", 14) = 14
167937 write(2</home/madhouse/Applications/Lab/tmp.txt>, "whatfuckis: not found", 21) = 21
167937 write(2</home/madhouse/Applications/Lab/tmp.txt>, "\n", 1) = 1
167936 dup2(10</dev/pts/2>, 1</home/madhouse/Applications/Lab/tmp.txt>) = 1</dev/pts/2>
167936 dup2(11</dev/pts/2>, 2</home/madhouse/Applications/Lab/tmp.txt>) = 2</dev/pts/2>

답변1

두 가지 질문이 있습니다.

  1. 명령에서 리디렉션을 수행 중이므로 strace대화형 셸에서 리디렉션이 수행되고 strace관련 시스템 호출은 표시되지 않습니다.
  2. 시스템 dup2호출은 명령이 실행되기 전에 하위 프로세스 내에서 실행됩니다. 기본적으로 strace명령의 하위 항목은 따르지 않으므로 하위 항목 자체의 흔적은 볼 수 없습니다. 태그 가 하위 항목도 따라다니도록 하려면 -f마크업에 태그를 추가 해야 합니다 .strace

가장 중요한 것은 예상된 검사를 수행하려면 다음을 실행해야 한다는 것입니다.

strace -yf -o trace.log sh -c './test.sh > tmp.txt 2>&1'

그러면 예상한 순서가 표시됩니다.

$ grep -E 'dup2\(|clone\(|execve\(|(open|write|close)\(.*tmp.txt' trace.log 
31769 execve("/usr/bin/sh", ["sh", "-c", "./test.sh > tmp.txt 2>&1"], [/* 124 vars */]) = 0
31769 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ffff7fd19d0) = 31770
31770 open("tmp.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3</tmp/tmp.txt>
31770 dup2(3</tmp/tmp.txt>, 1</dev/pts/445>) = 1</tmp/tmp.txt>
31770 close(3</tmp/tmp.txt>)            = 0
31770 dup2(1</tmp/tmp.txt>, 2</dev/pts/445>) = 2</tmp/tmp.txt>
31770 execve("./test.sh", ["./test.sh"], [/* 123 vars */]) = 0
31770 dup2(3</tmp/test.sh>, 255)        = 255</tmp/test.sh>
31770 write(1</tmp/tmp.txt>, "hello\n", 6) = 6
31770 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ffff7fd19d0) = 31771
31771 write(2</tmp/tmp.txt>, "./test.sh: line 3: whatfuckis: c"..., 49) = 49

및 시스템 호출은 실제 실행 전에 open명령의 하위 명령 pid dup2내에서 실행 됩니다.31770sh -c...test.sh

자식 대신 부모의 파일 설명자를 복사합니다.

(셸 구현에 따라) 실행이 가능합니다 clone. 이 경우 부모는 원본을 저장해야 합니다.표준 출력그리고표준 오류 오류율파일 설명자를 삭제하고 하위 프로세스가 종료된 후 복원합니다.

$ grep -E 'dup2\(|clone\(|execve\(|(open|write|close|openat)\(.*tmp.txt|F_DUPFD|exit_group' trace.log
1094  execve("/usr/bin/sh", ["sh", "-c", "./test.sh > tmp.txt 2>&1"], 0x7fffd0324118 /* 20 vars */) = 0
1094  openat(AT_FDCWD, "tmp.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3</tmp/tmp.txt>
1094  fcntl(1</dev/pts/0>, F_DUPFD, 10) = 10</dev/pts/0>
1094  dup2(3</tmp/tmp.txt>, 1)          = 1</tmp/tmp.txt>
1094  close(3</tmp/tmp.txt>)            = 0
1094  fcntl(2</dev/pts/0>, F_DUPFD, 10) = 11</dev/pts/0>
1094  dup2(1</tmp/tmp.txt>, 2)          = 2</tmp/tmp.txt>
1094  clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f844aa71690) = 1095
1095  execve("./test.sh", ["./test.sh"], 0x7f844aa9dc08 /* 20 vars */) = 0
1095  fcntl(3</tmp/test.sh>, F_DUPFD, 10) = 10</tmp/test.sh>
1095  write(1</tmp/tmp.txt>, "hello\n", 6) = 6
1095  write(2</tmp/tmp.txt>, "./test.sh: 3: ", 14) = 14
1095  write(2</tmp/tmp.txt>, "whatfuckis: not found", 21) = 21
1095  write(2</tmp/tmp.txt>, "\n", 1)   = 1
1095  exit_group(127)                   = ?
1094  dup2(10</dev/pts/0>, 1</tmp/tmp.txt>) = 1</dev/pts/0>
1094  dup2(11</dev/pts/0>, 2</tmp/tmp.txt>) = 2</dev/pts/0>
1094  exit_group(127) 

dup2각각의 실행을 시작하기 전에 1094상위 프로세스(pid)가 어떻게 파일 설명자 1과 2를 파일 설명자 10과 11에 각각 복사하는지( fcntl시스템 호출과 F_DUPFD명령을 사용하여) 확인하세요 .

그런 다음 하위 프로세스(pid 1095)가 완료된 후 상위 프로세스는 파일 설명자 10 및 11(원본 파일 설명자)을 복원합니다.표준 출력그리고표준 오류 오류율) 파일 설명자 1과 2로 돌아갑니다.

관련 정보