О `fork`, дочерних процессах и« подоболочках »

Для тех, кто ищет, почему они не могут редактировать файл с "@" или "+" в конце в mac osx, причина в вероятно, связано с метаданными, например, файл был скопирован из резервной копии Time Machine через терминал, а не через проводник.

Два шага позволят удалить метаданные и снова сделать их доступными для записи в MacOS:

# Remove the metadata attributes
xattr -c <some file>

# Remove the file ACL(s)
chmod -N <some file>
5
23.05.2017, 15:40
3 ответа

Поскольку, согласно 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 эту программу в дочерней. Это фундаментальная теория процессов, которая применима везде, внутри и вне оболочек.

Под-оболочки - это вилки, и каждая не встроенная команда, которую вы выполняете, приводит как к вилке, так и к выполнению.


Обратите внимание, что $$ расширяется до PID родительской оболочки в любой POSIX-совместимой оболочке, так что вы можете не получить ожидаемый вывод. Обратите внимание, что zsh в любом случае оптимизирует выполнение подоболочек, и обычно выполняетпоследнюю команду, или вообще не порождает подоболочку, если все команды безопасны без нее.

Одна из полезных команд для проверки интуиции:

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

Это выведет в стандартную ошибку все связанные с процессом события (и никакие другие) для команды .... , запущенной в новой оболочке. Вы можете увидеть, что выполняется и не выполняется в новом процессе, и где происходят execы.

Еще одна, возможно, полезная команда - pstree -h, которая распечатает и выделит дерево родительских процессов текущего процесса. В выводе можно увидеть, на сколько уровней вглубь вы находитесь.

8
27.01.2020, 20:36

Когда в руководстве говорится, что команды в .zshenv являются "исходными", это означает, что они выполняются внутри оболочки, которая их запускает. Они не вызывают вызов fork(), поэтому они не порождают подпрограмму. Ваш третий пример явно запускает подоболочку, вызывая вызов fork(), и таким образом бесконечно повторяется. Это, я полагаю, должно (по крайней мере, частично) ответить на ваш первый вопрос.

  1. В командах 1 и 2 нет ничего "созданного", поэтому и называть их никак нельзя - эти команды выполняются в контексте оболочки-сорсера.

  2. Обобщение - это разница между "вызовом" процедуры или программы оболочки и "сорсингом" процедуры или программы оболочки - причем последнее обычно применимо только к командам / скриптам оболочки, а не к внешним программам. "Вызов" сценария оболочки обычно выполняется через . в отличие от ./ или /full/path/to/script - обратите внимание на последовательность "точка-пробел" в начале директивы сорсинга. Sourcing также можно вызвать с помощью source , при этом команда source является внутренней командой оболочки.

2
27.01.2020, 20:36

fork, если все идет хорошо, возвращается дважды. Один возврат происходит в родительском процессе (который имеет исходный ID процесса), а второй - в новом дочернем процессе (с другим ID процесса, но имеющим много общего с родительским процессом). В этот момент дочерний процесс может exec(3) что-то сделать, что приведет к загрузке "нового" двоичного файла в этот процесс, хотя дочерний процесс не обязательно должен это делать, он может выполнять другой код, уже загруженный через родительский процесс (функции zsh, например). Следовательно, fork может привести или не привести к "совершенно новому" процессу, если под "совершенно новым" понимать что-то загруженное через exec(3) системный вызов.

Угадать заранее, какие команды вызывают бесконечный регресс, непросто; кроме случая с вилкой-вызовом-вилкой (он же "вилочная бомба"), другой легкий вариант - это наивная функциональная обертка вокруг некоторой команды

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

которую вместо этого, вероятно, следует записать как

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

или command ssh ... , чтобы избежать бесконечных вызовов функции ssh, вызывающей функцию ssh, вызывающую функцию .... Это никак не связано с fork, так как вызовы функций являются внутренними для процесса ZSH, но будут весело уходить в бесконечность до тех пор, пока этот единственный процесс ZSH не наткнется на какой-то предел.

strace, как всегда, удобен для выявления точных системных вызовов для любой команды (в частности, здесь fork и, возможно, некоторые exec вызовы); оболочки могут быть отлажены с помощью -x или аналогичных, которые показывают, что оболочка делает внутри (например, вызовы функций). Для дополнительного чтения у Стивенса в "Advanced Programming in the Unix Environment" есть несколько глав, связанных с созданием и обработкой новых процессов.

1
27.01.2020, 20:36

Теги

Похожие вопросы