쉘이 왜 fork ()를 호출합니까?


32

프로세스가 쉘에서 시작될 때 프로세스를 실행하기 전에 쉘이 자체적으로 분기되는 이유는 무엇입니까?

예를 들어, 사용자가 입력 할 때 grep blabla foo왜 쉘 exec()이 자식 쉘없이 grep을 호출 할 수 없습니까?

또한 쉘이 GUI 터미널 에뮬레이터 내에서 포크 할 때 다른 터미널 에뮬레이터를 시작합니까? ( pts/13시작 과 같은 pts/14)

답변:


34

exec패밀리 메소드 를 호출하면 새 프로세스가 작성되지 않고 대신 exec현재 프로세스 메모리 및 명령어 세트 등을 실행하려는 프로세스로 바꿉니다.

예를 들어, grepexec를 사용하여 실행하려고합니다 . bash프로세스 (별도의 메모리, 주소 공간이 있음)입니다. exec(grep)이제을 호출 하면 exec는 현재 프로세스의 메모리, 주소 공간, 명령어 세트 등을 grep's데이터 로 바꿉니다. 이는 bash프로세스가 더 이상 존재하지 않음을 의미 합니다. 결과적으로 grep명령 을 완료 한 후 터미널로 돌아갈 수 없습니다 . exec 패밀리 메소드가 절대 리턴되지 않는 이유입니다. exec 후에는 코드를 실행할 수 없습니다. 도달 할 수 없습니다.


거의 괜찮습니다 --- 터미널을 bash로 대체했습니다. ;-)
Rmano

2
BTW, bash 명령을 사용하여 bash가 먼저 포크하지 않고 grep을 실행하도록 할 수 있습니다exec grep blabla foo . 물론,이 특정한 경우에, 그레프가 끝나 자마자 터미널 창이 닫히기 때문에 매우 유용하지는 않지만, 때때로 편리 할 수 ​​있습니다 (예 : ssh를 통해 다른 쉘을 시작하는 경우) / sudo / screen으로 돌아가거나 원래 프로세스로 돌아 가지 않을 것입니다.이 프로세스를 실행하는 쉘 프로세스가 둘 이상의 명령을 실행하지 않는 하위 쉘인 경우).
Ilmari Karonen

7
명령어 세트 는 매우 특정한 의미를 갖습니다. 그리고 그것은 당신이 그것을 사용하는 의미가 아닙니다.
Andrew Savinykh

@IlmariKaronen 명령에 대한 인수와 환경을 준비하려는 랩퍼 스크립트에서 유용합니다. 그리고 bash는 사실의 하나 개 이상의 명령을 실행하는 데 의미하지 않는다 당신이 언급 한 경우, bash -c 'grep foo bar'및 호출 간부 최적화 강타의 양식을 자동으로 수행이
세르지 Kolodyazhnyy

3

에 따라 pts직접 확인하십시오. 쉘에서 실행하십시오.

echo $$ 

프로세스 ID (PID)를 알기 위해 예를 들어

echo $$
29296

그런 다음 예를 들어 sleep 60다른 터미널에서 실행하십시오.

(0)samsung-romano:~% ps -edao pid,ppid,tty,command | grep 29296 | grep -v grep
29296  2343 pts/11   zsh
29499 29296 pts/11   sleep 60

따라서 아니요, 일반적으로 프로세스와 관련된 동일한 tty가 있습니다. (이것은 sleep당신의 쉘이 부모이기 때문에 당신의 것입니다).


2

TL; DR : 새 프로세스를 작성하고 대화식 쉘에서 제어를 유지하기위한 최적의 방법이므로

프로세스와 파이프에 fork ()가 필요합니다

이 질문의 특정 부분에 대답 하기 위해 부모에서 직접 grep blabla foo호출 exec()해야하는 경우 부모는 존재하여 모든 리소스가있는 PID를 인수합니다 grep blabla foo.

그러나 일반적으로 exec()및 에 대해 이야기 해 보겠습니다 fork(). 이러한 동작의 주요 이유는 fork()/exec()유닉스 / 리눅스에서 새로운 프로세스를 생성하는 표준 방법 이기 때문 입니다. 이것은 배쉬 전용이 아닙니다. 이 방법은 처음부터 시행되었으며 기존의 기존 운영 체제와 동일한 방법으로 영향을받습니다. 관련 질문에 대한 goldilocks의 대답 을 다소 말로 바꾸면fork() 커널이 리소스를 할당하는 한 할 일이 적고 많은 속성 (예 : 파일 설명자, 환경 등)이 있기 때문에 새로운 프로세스를 만드는 것이 더 쉽습니다. 부모 프로세스에서 상속받습니다 (이 경우 bash).

둘째, 대화식 쉘에 관한 한, 포크없이 외부 명령을 실행할 수 없습니다. 디스크에 상주하는 실행 파일을 시작하려면 (예 :) 상위 프로세스를 새 프로세스로 바꾸고 PID 및 기존 파일 디스크립터 등을 대신하는 패밀리 기능 /bin/df -h중 하나를 호출해야합니다 . 대화식 쉘의 경우, 제어가 사용자에게 리턴되어 상위 대화식 쉘이 계속되도록 할 수 있습니다. 따라서 가장 좋은 방법은을 통해 하위 프로세스를 만들고 해당 프로세스를 통해 처리하는 것 입니다. 따라서 대화식 쉘 PID 1156은 PID 1157을 통해 자식을 생성 한 다음을 호출하여 PID 1157 로 실행합니다. 이제 쉘은 프로세스가 종료되고 제어를 리턴 할 때까지 기다려야합니다.exec()execve()fork()execve()fork()execve("/bin/df",["df","-h"],&environment)/bin/df -h

두 개 이상의 명령 사이에 파이프를 만들어야하는 경우 df | grep(예 : pipe()syscall 에서 오는 파이프 끝을 읽고 쓰는) 두 개의 파일 설명자를 만드는 방법이 필요 합니다. 두 가지 새로운 프로세스가 상속합니다. 새로운 프로세스를 포기한 다음 dup2()호출을 통해 파이프의 쓰기 끝을 stdout일명 fd 1 에 복사하여 작성합니다 (따라서 쓰기 끝이 fd 4이면 dup2(4,1)). 때 exec()산란에 df자식 프로세스가 아무것도 생각하지 않습니다 발생 stdout출력이 실제로 파이프를가는 것을 (그것을하지 않는 한 적극적으로 확인) 의식하지 않고 그와 쓰기. 같은 과정이 일어나는 grep, 우리를 제외하고 fork(), FD 3 및 파이프의 읽기 끝을 dup(3,0)산란 전에 grep함께exec(). 이 모든 시간 동안 부모 프로세스는 여전히 존재하며 파이프 라인이 완료되면 제어권을 다시 얻기 위해 대기합니다.

내장 명령의 경우 일반적으로 명령 fork()을 제외하고 쉘은 그렇지 않습니다 source. 서브 쉘에는이 필요합니다 fork().

요컨대, 이것은 필요하고 유용한 메커니즘입니다.

포크 및 최적화의 단점

자,이은 (non-interactive) 형 모드 쉘에 대해 서로 다른 등, bash -c '<simple command>'. fork()/exec()많은 명령을 처리해야하는 최적의 방법 임에도 불구하고 단일 명령이 하나만 있으면 리소스가 낭비됩니다. 이 게시물 에서 Stéphane Chazelas 를 인용하려면 :

포크는 CPU 시간, 메모리, 할당 된 파일 디스크립터가 비싸다. 종료하기 전에 다른 프로세스를 기다리는 쉘 프로세스를 갖는 것은 리소스 낭비 일 뿐이다. 또한 명령을 실행하는 별도의 프로세스 (예 : 프로세스가 종료 된 경우)의 종료 상태를 올바르게보고하기가 어렵습니다.

따라서 많은 단순한 쉘은 단순한 단일 명령으로이를 대체 bash하기 exec()위해 사용합니다 bash -c ''. 위에서 언급 한 이유로 쉘 스크립트에서 파이프 라인을 최소화하는 것이 좋습니다. 종종 초보자가 다음과 같은 작업을 수행하는 것을 볼 수 있습니다.

cat /etc/passwd | cut -d ':' -f 6 | grep '/home'

물론 이것은 fork()3 가지 과정 이 될 것 입니다. 이것은 간단한 예이지만 기가 바이트 범위의 큰 파일을 고려하십시오. 하나의 프로세스로 훨씬 더 효율적입니다.

awk -F':' '$6~"/home"{print $6}' /etc/passwd

자원 낭비는 실제로 서비스 거부 공격의 한 형태 일 수 있으며, 특히 포크 폭탄 은 파이프 라인에서 자신을 호출하는 쉘 기능을 통해 생성되어 여러 복사본을 포크합니다. 요즘은 systemd의 cgroup에서 최대 프로세스 수를 제한하여 완화됩니다. Ubuntu는 버전 15.04부터 사용합니다.

물론 그것은 포크가 나쁜 것을 의미하지는 않습니다. 이전에 설명한 것처럼 여전히 유용한 메커니즘이지만 더 적은 프로세스, 적은 리소스 및 더 나은 성능으로 벗어날 수있는 fork()경우 가능한 경우 피해야 합니다.

참조


1

bash 프롬프트에서 실행하는 각 명령 (예 : grep)에 대해 실제로 새 프로세스를 시작한 다음 실행 후 bash 프롬프트로 돌아갑니다.

쉘 프로세스 (bash)가 exec ()를 호출하여 grep을 실행하면 쉘 프로세스가 grep으로 바뀝니다. Grep은 제대로 작동하지만 실행 후에는 bash 프로세스가 이미 교체되었으므로 제어가 쉘로 돌아갈 수 없습니다.

이러한 이유로 bash는 fork ()를 호출하는데, 현재 프로세스를 대체하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.