'포크', 하위 프로세스 및 '하위 쉘' 정보

'포크', 하위 프로세스 및 '하위 쉘' 정보

이 기사는 기본적으로 기사의 후속 기사입니다.이전 질문내 거.

fork이 질문에 대한 답변을 통해 나는 "서브쉘"의 전체 개념을 잘 이해하지 못했을 뿐만 아니라 더 일반적으로 -ing과 하위 프로세스 간의 관계를 이해하지 못했다는 것을 깨달았습니다 .

나는 프로세스가 Xa를 실행할 때 fork,새로운Y생성된 프로세스의 상위 프로세스는 이지만 X, 이 질문에 대한 답변에 따르면,

[a] 서브셸은 완전히 새로운 프로세스가 아니라 기존 프로세스의 포크입니다.

여기서 의미하는 바는 "포킹"이 "완전히 새로운 프로세스"가 아니거나 발생하지 않는다는 것입니다.

나는 지금 혼란스러워요. 사실 내 혼란을 직접적으로 해결해줄 일관된 질문을 하기에는 너무 혼란스럽습니다.

하지만 간접적으로 깨달을 수 있는 질문을 던질 수는 있습니다.

source 에 따르면 새 인스턴스가 시작될 때마다 가져오기 때문에 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. #1위의 플래그가 지정된 명령 #2과 계정에 플래그를 지정하는 동작의 차이점 #3은 무엇입니까 ?

  2. 에서 생성된 쉘을 "서브쉘"이라고 #1한다면 #2를 호출하여 생성된 쉘과 유사한 쉘은 무엇입니까 #3?

  3. Unix 프로세스의 "이론"(더 나은 단어가 없음) 측면에서 위의 경험적/일화적 결과를 합리화(및 일반화)하는 것이 가능합니까?

마지막 질문의 동기는 다음을 결정할 수 있다는 것입니다.리드타임(즉, 실험에 의지하지 않음) 어떤 명령이 포함되면 무한 회귀로 이어질 수 있습니까 $ZDOTDIR/.zshenv?


1위의 다양한 예에서 사용한 명령의 구체적인 순서 date; printenv; echo $$ 그다지 중요하지 않습니다. 그것은 내 "실험"의 결과를 설명하는 데 도움이 될 수 있는 출력을 제공하는 명령입니다. (그러나 나는 다음과 같은 이유로 이러한 시퀀스에 여러 명령이 포함되기를 원합니다.여기.)

답변1

zshall(1)에 따르면 zsh의 새 인스턴스가 시작될 때마다 $ZDOTDIR/.zshenv가 획득됩니다.

"시작"이라는 단어에 집중하면 더 나아질 것입니다. 이 기능은 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서브루틴에서 프로그램을 생성합니다. 이는 쉘 내부 또는 외부 어디에서나 프로세스에 적용되는 기본 이론입니다.

서브쉘은 forks 이고 실행하는 모든 비내장 명령은 및 fork입니다 exec.


확장 은 $$상위 쉘의 PID입니다.POSIX 호환 셸에서, 따라서 예상한 결과를 얻지 못할 수도 있습니다. 또한 zsh는 어쨌든 서브쉘 실행을 적극적으로 최적화하며 일반적으로 exec마지막 명령이 되거나 서브쉘 없이 모든 명령이 안전한 경우 서브쉘을 전혀 생성하지 않습니다.

직관을 테스트하는 데 유용한 명령은 다음과 같습니다.

strace -e trace=process -f $SHELL -c ' ... '

...그러면 새 셸에서 실행하는 명령에 대한 모든 프로세스 관련 이벤트(다른 이벤트 제외)가 표준 오류로 인쇄됩니다. 새 프로세스에서 무엇이 실행되고 있지 않은지, 그리고 exec그것이 발생하는 위치를 확인할 수 있습니다.

유용할 수 있는 또 다른 명령은 pstree -h현재 프로세스의 상위 프로세스 트리를 인쇄하고 강조 표시하는 것입니다. 출력에서 레이어 수를 볼 수 있습니다.

답변2

매뉴얼에서 명령이 .zshenv"소스화"되었다고 말하면 해당 명령이 실행되는 셸에서 실행된다는 의미입니다. 를 호출하지 않으므로 fork()서브쉘을 생성하지 않습니다. 세 번째 예는 명시적으로 서브셸을 실행하여 call 을 호출 fork()하므로 무한히 반복됩니다. 나는 이것이 귀하의 첫 번째 질문에 (적어도 부분적으로) 답변되어야 한다고 믿습니다.

  1. 명령 1과 2에서는 아무것도 "생성"되지 않으므로 아무 것도 호출할 수 없습니다. 이러한 명령은 소싱 셸의 컨텍스트에서 실행됩니다.

  2. 간단히 말해서, 쉘 루틴이나 프로그램을 "호출"하는 것과 쉘 루틴이나 프로그램을 "소싱"하는 것의 차이점은 일반적으로 후자는 외부 프로그램이 아닌 쉘 명령/스크립트에만 적용됩니다. 쉘 스크립트 "가져오기"는 일반적으로 . <scriptname>not 또는 get 지시어 시작 부분의 "점 공백" 시퀀스를 통해 ./<scriptname>수행 됩니다. 쉘 내부 명령 인 /full/path/to/scriptSourcing 을 사용할 수도 있습니다 .source <scriptname>source

답변3

fork, 모든 것이 잘 진행된다고 가정하면 두 번 반환됩니다. 하나는 원래 프로세스 ID를 사용하여 상위 프로세스에서 반환되고 다른 하나는 새 하위 프로세스에서 반환됩니다(프로세스 ID는 다르지만 상위 프로세스와 많은 공통점을 공유함). 이 시점에서 하위 프로세스는 exec(3)일부 "새" 바이너리가 프로세스에 로드되도록 하는 특정 작업을 수행할 수 있습니다. 그러나 하위 프로세스는 이를 수행할 필요가 없으며 상위 프로세스를 통해 로드된 다른 코드를 실행할 수 있습니다( zsh 기능과 같은). 따라서 fork"완전히 새로운"이 시스템 호출을 통해 로드된 것을 의미하는 경우 exec(3)"완전히 새로운" 프로세스가 발생할 수도 있고 그렇지 않을 수도 있습니다.

어떤 명령이 무한 회귀를 발생시킬지 미리 추측하는 것은 까다롭습니다. 포크 호출 포크 사례(일명 "forkbomb") 외에도 또 다른 간단한 사례는 일부 명령을 통한 간단한 함수 래퍼입니다.

function ssh() {
   ssh -o UseRoaming=no "$@"
}

대신 아마도 다음과 같이 작성되어야 할 것입니다.

function ssh() {
  =ssh -o UseRoaming=no "$@"
}

또는 함수를 호출하는 함수 에 대한 무한 함수 호출을 command ssh ...피하십시오 . 함수 호출은 ZSH 프로세스 내부이므로 이는 결코 관련되지 않지만 해당 단일 함수가 ZSH 프로세스의 일부 제한에 도달할 때까지 행복하게 무한대로 발생합니다.sshsshfork

strace항상 그렇듯이, 명령과 관련된 시스템 호출이 무엇인지 정확히 밝히는 것이 편리합니다(특히 여기 fork, 그리고 아마도 몇 가지 exec더). 쉘은 또는 유사한 것을 사용하여 디버깅할 수 있으며 -x, 쉘 내에서 내부적으로 수행되는 작업(예: 함수 호출)을 보여줍니다. 더 자세히 읽어보려면 Stevens의 Unix 환경에서의 고급 프로그래밍에는 새로운 프로세스의 생성 및 처리와 관련된 여러 장이 있습니다.

관련 정보