Когда процесс запускается из оболочки, почему оболочка разветвляется перед выполнением процесса?
Например, когда пользователь вводит данные grep blabla foo
, почему оболочка не может просто вызвать exec()
grep без дочерней оболочки?
Кроме того, когда оболочка разветвляется в эмуляторе терминала GUI, запускается ли другой эмулятор терминала? (например, pts/13
запуск pts/14
)
источник
exec grep blabla foo
. Конечно, в данном конкретном случае это будет не очень полезно (поскольку окно терминала будет закрываться сразу после завершения работы grep), но иногда это может быть удобно (например, если вы запускаете другую оболочку, возможно, через ssh) / sudo / screen, и не намерены возвращаться к исходному, или если процесс оболочки, на котором вы запускаете этот процесс, является вспомогательной оболочкой, которая в любом случае никогда не должна выполнять более одной команды).bash -c 'grep foo bar'
и при вызове exec существует форма оптимизации, которую bash выполняет автоматическиПо словам
pts
, проверьте сами: в оболочке, запуститьчтобы узнать ваш идентификатор процесса (PID), я, например,
Затем запустите, например,
sleep 60
а затем, в другом терминалеТак что нет, в целом у вас есть тот же tty, связанный с процессом. (Обратите внимание, что это ваша,
sleep
потому что она имеет вашу оболочку как родитель)источник
TL; DR : потому что это оптимальный метод для создания новых процессов и сохранения контроля в интерактивной оболочке
fork () необходим для процессов и труб
Чтобы ответить на конкретную часть этого вопроса, если
grep blabla foo
бы он вызывался черезexec()
напрямую в parent, родительский узел решил бы существовать, и его PID со всеми ресурсами был бы переданgrep blabla foo
.Впрочем, давайте в общем поговорим об
exec()
аfork()
. Основная причина такого поведения заключается в том, чтоfork()/exec()
это стандартный метод создания нового процесса в Unix / Linux, а это не специфическая для bash вещь; этот метод существует с самого начала и на него влияет тот же метод из уже существующих операционных систем того времени. Чтобы несколько перефразировать ответ Златовласки по связанному вопросу,fork()
создать новый процесс проще, поскольку ядру не нужно много работы для распределения ресурсов и множества свойств (таких как дескрипторы файлов, окружение и т. Д.) - все могут наследоваться от родительского процесса (в данном случае отbash
).Во-вторых, что касается интерактивных оболочек, вы не можете запустить внешнюю команду без разветвления. Чтобы запустить исполняемый файл, который живет на диске (например,
/bin/df -h
), вы должны вызвать одну изexec()
функций семейства, напримерexecve()
, которая заменит родительский процесс новым процессом, перехватит его PID и существующие файловые дескрипторы и т. Д. Для интерактивной оболочки вы хотите, чтобы элемент управления вернулся к пользователю и позволил родительской интерактивной оболочке продолжить работу. Таким образом, наилучшим способом является создание подпроцесса черезfork()
, и позволить этому процессу быть принятым черезexecve()
. Таким образом, интерактивная оболочка PID 1156 будет порождать дочерний процесс с помощьюfork()
PID 1157, а затем вызыватьexecve("/bin/df",["df","-h"],&environment)
, что приводит к/bin/df -h
запуску с PID 1157. Теперь оболочке остается только дождаться выхода процесса и вернуть ему управление.В случае, когда вам нужно создать канал между двумя или более командами, скажем
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()
.Одним словом, это необходимый и полезный механизм.
Недостатки разветвления и оптимизации
Теперь это отличается для неинтерактивных оболочек , таких как
bash -c '<simple command>'
. Несмотря на то,fork()/exec()
что это оптимальный метод, при котором вам нужно обрабатывать много команд, это пустая трата ресурсов, когда у вас есть только одна команда. Цитировать Стефана Шазеля из этого поста :Следовательно, многие оболочки (не только
bash
) используют,exec()
чтобы разрешить этуbash -c ''
задачу одной простой командой. И именно по причинам, указанным выше, минимизация конвейеров в сценариях оболочки лучше. Часто вы можете увидеть, как новички делают что-то вроде этого:Конечно, это будет
fork()
3 процесса. Это простой пример, но рассмотрим большой файл в диапазоне гигабайт. Было бы гораздо эффективнее с одним процессом:Потеря ресурсов на самом деле может быть формой атаки типа «отказ в обслуживании», и, в частности, бомбы- форки создаются с помощью функций оболочки, которые вызывают себя в конвейере, который разветвляется на несколько копий. В настоящее время это смягчается путем ограничения максимального числа процессов в cgroups на systemd , которое Ubuntu также использует начиная с версии 15.04.
Конечно, это не значит, что разветвление - это плохо. Это все еще полезный механизм, как обсуждалось ранее, но в случае, когда вы можете обходиться меньшим количеством процессов и, соответственно, меньшими ресурсами и, следовательно, более высокой производительностью, вам следует избегать,
fork()
если это возможно.Смотрите также
источник
Для каждой команды (пример: grep), которую вы вводите в приглашении bash, вы фактически намереваетесь запустить новый процесс и затем вернуться к приглашению bash после выполнения.
Если процесс оболочки (bash) вызывает exec () для запуска grep, процесс оболочки будет заменен на grep. Grep будет работать нормально, но после выполнения элемент управления не сможет вернуться в оболочку, поскольку процесс bash уже заменен.
По этой причине bash вызывает fork (), который не заменяет текущий процесс.
источник