
이 게시물은 기본적으로 다음 게시물의 후속 게시물입니다.이전 질문내 것.
fork
그 질문에 대한 대답을 통해 나는 "하위 쉘"의 전체 개념을 잘 이해하지 못할 뿐만 아니라 더 일반적으로 -ing 프로세스와 하위 프로세스 간의 관계를 이해하지 못한다는 것을 깨달았습니다 .
나는 프로세스가 X
a를 실행할 때 fork
,새로운Y
부모가 인 프로세스가 생성되지만 X
해당 질문에 대한 답변에 따르면
[a] 서브셸은 완전히 새로운 프로세스가 아니라 기존 프로세스의 포크입니다.
여기서 의미하는 바는 "포크"가 "완전히 새로운 프로세스"가 아니라는 것입니다.
나는 지금 매우 혼란스럽습니다. 사실 내 혼란을 직접적으로 해소하기 위한 일관된 질문을 공식화하기에는 너무 혼란스럽습니다.
그러나 나는 간접적으로 깨달음으로 이어질 수 있는 질문을 공식화할 수 있습니다.
에 따르면 는 zshall(1)
새로운 $ZDOTDIR/.zshenv
인스턴스가 zsh
시작될 때마다 소싱되므로 "완전히 새로운 [zsh] 프로세스"를 생성하는 명령은 $ZDOTDIR/.zshenv
무한 회귀를 초래합니다. 반면에 파일에 다음 줄 중 하나를 포함 $ZDOTDIR/.zshenv
하면~ 아니다결과적으로 무한 회귀가 발생합니다.
echo $(date; printenv; echo $$) > /dev/null #1
(date; printenv; echo $$) #2
위에서 설명한 메커니즘으로 무한 회귀를 유도하는 유일한 방법은 파일 에 다음 1$ZDOTDIR/.zshenv
과 같은 줄을 포함하는 것이었습니다 .
$SHELL -c 'date; printenv; echo $$' #3
내 질문은 다음과 같습니다
위에 표시된 명령과 이러한 동작 차이로 인해 계정으로 표시된 명령 사이에는 어떤 차이점
#1
이 있습니까#2
?#3
생성되고
#1
"#2
서브쉘"이라고 불리는 쉘이 호출된 것과 같은 쉘은 무엇입니까#3
?Unix 프로세스의 "이론"(더 나은 단어가 없음) 측면에서 위에서 설명한 경험적/일화적 결과를 합리화(및 일반화)하는 것이 가능합니까?
마지막 질문의 동기는 다음을 결정할 수 있다는 것입니다.미리(즉, 실험에 의지하지 않고) 어떤 명령이 에 포함되면 무한 회귀로 이어질 수 있습니까 $ZDOTDIR/.zshenv
?
1date; printenv; echo $$
위의 다양한 예에서 사용한 특정 명령 순서 는 그다지 중요하지 않습니다. 이는 내 "실험" 결과를 해석하는 데 잠재적으로 도움이 되는 출력을 제공하는 명령입니다. (그러나 나는 이러한 시퀀스가 하나 이상의 명령으로 구성되기를 원했습니다.여기.)
답변1
zshall(1)에 따르면 $ZDOTDIR/.zshenv는 zsh의 새 인스턴스가 시작될 때마다 소스가 됩니다.
여기서 "시작"이라는 단어에 집중하면 더 나은 시간을 보낼 수 있습니다. 의 효과는 fork()
또 다른 프로세스를 생성하는 것입니다.현재 프로세스가 이미 있는 곳에서 정확히 시작됩니다.. 의 반환 값만 다를 뿐 기존 프로세스를 복제합니다 fork
. 문서에서는 "시작"을 사용하여 처음부터 프로그램에 들어가는 것을 의미합니다.
예제 #3이 실행되어 $SHELL -c 'date; printenv; echo $$'
처음부터 완전히 새로운 프로세스를 시작합니다. 일반적인 시작 동작을 거칩니다. 예를 들어 다른 셸에서 교체하여 이를 설명할 수 있습니다. bash -c ' ... '
대신 zsh -c ' ... '
. 이곳을 이용하는데 특별한 것은 없습니다 $SHELL
.
예제 #1과 #2는 서브쉘을 실행합니다. 쉘 fork
은 자체적으로 해당 하위 프로세스 내에서 명령을 실행한 다음 하위 프로세스가 완료되면 자체 실행을 계속합니다.
질문 #1에 대한 대답은 위와 같습니다. 예제 3은 처음부터 완전히 새로운 쉘을 실행하는 반면, 다른 두 개는 하위 쉘을 실행합니다. 시작 동작에는 로드가 포함됩니다 .zshenv
.
그들이 이 동작을 구체적으로 부르는 이유는(다른 파일과 달리) 이 파일이 대화형 및 비대화형 쉘 모두에 로드되기 때문일 것입니다.
귀하의 질문 #2에:
#1과 #2에서 생성된 쉘을 "서브쉘"이라고 부르면 #3에서 생성된 것과 같은 쉘은 무엇이라고 합니까?
이름을 원하면 "하위 쉘"이라고 부를 수 있지만 실제로는 아무것도 아닙니다. 이는 동일한 셸이든, 다른 셸이든, 셸에서 시작하는 다른 프로세스와 다르지 않습니다 cat
.
귀하의 질문 #3에:
Unix 프로세스의 "이론"(더 나은 단어가 없음) 측면에서 위에서 설명한 경험적/일화적 결과를 합리화(및 일반화)하는 것이 가능합니까?
fork
새로운 PID를 사용하여 이 프로세스가 중단된 지점부터 병렬로 실행되기 시작하는 새로운 프로세스를 만듭니다.exec
현재 실행 중인 코드를 어딘가에서 로드되어 처음부터 실행되는 새 프로그램으로 바꿉니다. 새로운 프로그램을 생성할 때 먼저 fork
자신을 생성한 다음 exec
자식 프로그램을 생성합니다. 이것이 껍질 내부와 외부 어디에서나 적용되는 과정의 기본 이론입니다.
서브셸은 fork
s이며, 실행하는 모든 비내장 명령은 a fork
및 로 이어집니다 exec
.
$$
상위 쉘의 PID로 확장됩니다 .모든 POSIX 호환 쉘에서, 따라서 예상한 출력을 얻지 못할 수도 있습니다. 또한 zsh는 어쨌든 서브셸 실행을 적극적으로 최적화하며 일반적으로 exec
마지막 명령이거나 서브셸 없이 모든 명령이 안전한 경우 서브셸을 전혀 생성하지 않습니다.
직관을 테스트하는 데 유용한 명령 중 하나는 다음과 같습니다.
strace -e trace=process -f $SHELL -c ' ... '
...
그러면 새 셸에서 실행하는 명령에 대한 모든 프로세스 관련 이벤트(다른 이벤트 제외)가 표준 오류로 인쇄됩니다 . 새 프로세스에서 무엇이 실행되고 실행되지 않는지, 그리고 어디서 exec
발생하는지 확인할 수 있습니다.
또 다른 유용한 명령은 pstree -h
현재 프로세스의 상위 프로세스 트리를 인쇄하고 강조 표시하는 명령입니다. 출력에 얼마나 많은 레이어가 있는지 확인할 수 있습니다.
답변2
매뉴얼에 있는 명령이 .zshenv
"소스 제공"되었다고 말하면 해당 명령을 실행하는 셸 내에서 실행된다는 의미입니다. 에 대한 호출을 발생시키지 않으므로 fork()
서브쉘을 생성하지 않습니다. 세 번째 예제는 서브셸을 명시적으로 실행하여 에 대한 호출을 호출하므로 fork()
무한히 반복됩니다. 나는 이것이 귀하의 첫 번째 질문에 (적어도 부분적으로) 대답되어야 한다고 믿습니다.
명령 1과 2에는 "생성된" 항목이 없으므로 아무 것도 호출할 수 없습니다. 해당 명령은 소싱 셸의 컨텍스트 내에서 실행됩니다.
일반화는 쉘 루틴이나 프로그램을 "호출"하는 것과 쉘 루틴이나 프로그램을 "소싱"하는 것의 차이입니다. 후자는 일반적으로 외부 프로그램이 아닌 쉘 명령/스크립트에만 적용 가능합니다. 쉘 스크립트의 "소싱"은 일반적으로 소싱 지시문의 시작 부분에 있는 "점-공간" 순서를 참고하여
. <scriptname>
수행./<scriptname>
됩니다 . 소싱은 셸 내부 명령을 사용/full/path/to/script
하여 호출할 수도 있습니다 .source <scriptname>
source
답변3
fork
, 모든 것이 잘 진행된다고 가정하면 두 번 반환됩니다. 하나의 반환은 상위 프로세스(원래 프로세스 ID가 있음)에 있고 다른 하나는 새 하위 프로세스(다른 프로세스 ID이지만 상위 프로세스와 많은 공통점을 공유함)에 있습니다. 이 시점에서 하위 항목은 exec(3)
일부 "새" 바이너리가 해당 프로세스에 로드되도록 하는 작업을 수행할 수 있지만 하위 항목은 그렇게 할 필요가 없으며 상위 프로세스(예: zsh 함수)를 통해 이미 로드된 다른 코드를 실행할 수 있습니다. . 따라서 fork
"완전히 새로운" 프로세스가 시스템 호출을 통해 로드된 것을 의미하는 경우 a는 "완전히 새로운" 프로세스로 이어질 수도 있고 그렇지 않을 수도 있습니다 exec(3)
.
어떤 명령이 무한 회귀를 일으키는지 미리 추측하는 것은 까다롭습니다. 포크 호출 포크 사례(일명 "포크폭탄") 외에 또 다른 쉬운 방법은 일부 명령 주위의 순진한 함수 래퍼를 사용하는 것입니다.
function ssh() {
ssh -o UseRoaming=no "$@"
}
대신에 아마도 다음과 같이 작성되어야 할 것입니다.
function ssh() {
=ssh -o UseRoaming=no "$@"
}
또는 호출하는 함수를 호출하는 함수 command ssh ...
의 무한한 함수 호출을 피하기 위해 ... 함수 호출은 ZSH 프로세스 내부에 있기 때문에 이것은 전혀 포함되지 않지만 해당 단일에 의해 일부 제한에 부딪힐 때까지 즐겁게 무한대로 발생합니다. ZSH 프로세스.ssh
ssh
fork
strace
는 항상 그렇듯이 모든 명령(특히 여기 fork
및 일부 exec
호출)에 어떤 시스템 호출이 포함되어 있는지 정확하게 표시하는 데 유용합니다. 쉘은 -x
쉘이 내부적으로 수행하는 작업(예: 함수 호출)을 보여주는 것과 유사한 것으로 디버깅될 수 있습니다 . 더 많은 내용을 보려면 Stevens의 "Unix 환경의 고급 프로그래밍"에서 새로운 프로세스의 생성 및 처리와 관련된 몇 가지 장을 참조하세요.