다음( sh
존재하는 /bin/dash
)을 고려하십시오.
$ strace -e trace=process sh -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
execve("/bin/sh", ["sh", "-c", "grep \"^Pid:\" /proc/self/status /"...], [/* 47 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7fcc8b661540) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fcc8b661810) = 24865
wait4(-1, /proc/self/status:Pid: 24865
/proc/24864/status:Pid: 24864
[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 24865
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=24865, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
exit_group(0) = ?
+++ exited with 0 +++
특이한 점은 없습니다. grep
포크된 프로세스(여기에서 수행됨)가 메인 셸 프로세스에서 대체됩니다. clone()
여태까지는 그런대로 잘됐다.
이제 bash 4.4를 사용합니다.
$ strace -e trace=process bash -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
execve("/bin/bash", ["bash", "-c", "grep \"^Pid:\" /proc/self/status /"...], [/* 47 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7f8416b88740) = 0
execve("/bin/grep", ["grep", "^Pid:", "/proc/self/status", "/proc/25798/status"], [/* 47 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7f8113358b80) = 0
/proc/self/status:Pid: 25798
/proc/25798/status:Pid: 25798
exit_group(0) = ?
+++ exited with 0 +++
여기서 분명한 것은 grep
쉘 프로세스의 pid를 가정하고 명시적인 호출 fork()
은 없다는 것입니다 clone()
. 그렇다면 문제는 어떻게 bash
전화 없이 그러한 곡예를 달성할 수 있느냐는 것입니다.
그러나 clone()
명령에 쉘 리디렉션이 포함되어 있으면 시스템 호출이 발생합니다.df > /dev/null
답변1
sh -c 'command line'
일반적으로 system("command line")
, ssh host 'command line'
, vi
의 !
, 및 더 일반적으로 명령줄을 해석하는 데 사용되는 모든 항목에 사용 되므로 cron
최대한 효율적으로 만드는 것이 중요합니다.
포크는 CPU 시간, 메모리, 할당된 파일 설명자 측면에서 비용이 많이 듭니다. 하나의 쉘 프로세스가 종료되기 전에 다른 프로세스를 기다리게 하는 것은 리소스 낭비일 뿐입니다. 또한 명령을 실행할 개별 프로세스의 종료 상태(예: 프로세스가 종료된 경우)를 올바르게 보고하기 어렵게 만듭니다.
많은 쉘은 종종 최적화를 위해 포크 수를 최소화하려고 시도합니다. 최적화되지 않은 쉘이라도 bash
or 케이스에서 이 작업을 수행하는 것을 좋아합니다. ksh 또는 zsh와 달리 또는에서는 이 작업을 수행하지 않습니다(서브쉘에서도 동일). ksh93은 가장 포크에 강한 쉘입니다.sh -c cmd
(cmd in subshell)
bash -c 'cmd > redir'
bash -c 'cmd1; cmd2'
다음과 같은 경우에는 최적화가 불가능합니다.
sh < file
sh
해당 명령이 실행되는 동안 스크립트에 더 많은 텍스트가 추가될 수 있으므로 마지막 명령의 분기를 건너뛸 수 있는 방법이 없습니다 . 검색할 수 없는 파일의 경우 파일의 끝을 감지할 수 없습니다. 이는 파일에서 너무 많은 내용을 너무 일찍 읽는 것을 의미할 수 있기 때문입니다.
또는:
sh -c 'trap "echo Ouch" INT; cmd'
"마지막" 명령을 실행한 후 쉘은 추가 명령을 실행해야 할 수도 있습니다.
답변2
Bash 소스 코드를 연구하면서 파이프나 리디렉션이 없으면 Bash는 실제로 포크를 무시한다는 사실을 발견했습니다.Execute_cmd.c의 1601번째 줄:
/* If this is a simple command, tell execute_disk_command that it
might be able to get away without forking and simply exec.
This means things like ( sleep 10 ) will only cause one fork.
If we're timing the command or inverting its return value, however,
we cannot do this optimization. */
if ((user_subshell || user_coproc) && (tcom->type == cm_simple || tcom->type == cm_subshell) &&
((tcom->flags & CMD_TIME_PIPELINE) == 0) &&
((tcom->flags & CMD_INVERT_RETURN) == 0))
{
tcom->flags |= CMD_NO_FORK;
if (tcom->type == cm_simple)
tcom->value.Simple->flags |= CMD_NO_FORK;
}
나중에 깃발이 execute_disk_command()
작동하기 시작했고, 이는 설정되었습니다.노퍽정수 변수, 나중에포크하기 전에 확인하세요. 실제 명령 자체는 execve()
래퍼에 의해 실행됩니다.함수 shell_execve()분기된 프로세스 또는 상위 프로세스(이 경우 실제 상위 프로세스)에서.
이 메커니즘의 이유는스티븐의 대답.
참고 사항은 이 질문의 범위를 벗어납니다. 쉘이 대화형인지 실행되는지는 분명히 중요합니다 -c
. 명령을 실행하기 전에 포크가 발생합니다. 이는 strace
대화형 셸( )에서 실행 하고 출력 파일을 검사 하면 strace -e trace=process -f -o test.trace bash
분명해집니다 .
19607 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_t
idptr=0x7f2d35e93a10) = 19628
19607 wait4(-1, <unfinished ...>
19628 execve("/bin/true", ["/bin/true"], [/* 47 vars */]) = 0
당신은 또한 볼 수 있습니다bash가 간단한 명령에 대해 하위 쉘을 생성하지 않는 이유는 무엇입니까?