Я часто набираю такие вещи, как ls
и cat [filename]
, чтобы понять, где я нахожусь и что делаю, но пока я не закодировал свою собственную оболочку, я не переставал думать о том, как компьютер находит и выполняет эти команды. В этом посте я расскажу, что и где находятся команды, и как ваш компьютер их выполняет. Встроенные команды — это особый случай, который я не буду рассматривать.
Команды — это программы, которые оболочка вызывает и запускает, когда видит, что вы хотите что-то сделать. Если вы уже писали программы раньше, вы знаете, что при компиляции исходного кода результирующий исполняемый файл находится в файле в вашем текущем каталоге, и вы вызываете его, вводя ./
(что указывает на ваш текущий каталог) плюс имя исполняемого файла.
Когда мы вводим ./ls
, мы получаем сообщение об ошибке, поэтому очевидно, что ls
не находится в текущем каталоге.
Это связано с тем, что большинство пользовательских команд, таких как ls
, находятся в каталоге /bin
. Если мы перейдем к /bin
и введем ./ls
, команда сработает.
Так что же делает встроенные команды особенными? Почему отсутствие ./
позволяет нам запускать их?
Ответ кроется в переменной окружения PATH
. Переменная PATH
представляет собой строку каталогов, разделенных двоеточием. Когда вы вводите что-то в терминал, оболочка интерпретирует это как команду и ищет эту программу, проверяя каждую директорию в вашей переменной PATH
одну за другой, пока не найдет программу, чье имя совпадает с тем, что вы ввели. Чтобы увидеть свой путь переменная, введите $PATH
или env
, чтобы просмотреть все переменные окружения. Если мы вручную ищем программу, такую как ls
, в каждом каталоге по пути, мы видим, что в конечном итоге каталог /bin
будет содержать ls
.
Поскольку эти команды — такие же программы, как и любые другие, они могут принимать аргументы (ну, некоторые из них могут). Это распространяется как на флаги, так и на то, что мы считаем аргументами командной строки, например имена файлов. Например, когда вы вводите ls -l
, оболочка передает строку -l
программе ls
, а ls
решает, что делать, когда ей передается этот конкретный аргумент. В этом случае ls
видит, что ему был передан флаг для вывода содержимого каталога в длинной форме, поэтому он делает именно это, используя системные вызовы.
Системные вызовы — это функции, предоставляемые ядром операционной системы. Когда оболочке необходимо выполнить команду, системный вызов fork
используется для создания дочернего процесса, которому присваивается идентификатор процесса или PID
. Помимо PID
, этот дочерний процесс является дубликатом родительского процесса, которым в данном случае является программа оболочки. Затем дочерний процесс выполняет системный вызов execve
. execve
принимает имя файла программы, аргументы программы и среду. При успешном запуске исполняемая программа перезапишет текст, данные и вызывающий процесс. Программа наследует PID
дочернего процесса.
При создании каждого дочернего процесса предыдущий становится родительским. Этот родительский процесс должен прекратить работу, пока работает дочерний процесс, поэтому выполняется вызов wait
. Вызов wait
заблокирует текущий процесс до тех пор, пока дочерний процесс не будет завершен или не будет получен сигнал.
В приведенном выше примере мы выходим после использования execve
, потому что, если execve
завершается успешно, наш код перезаписывается, и дочерний процесс никогда не выполняет ничего в исходном коде после вызова execve
. Если это так, то мы знаем, что что-то пошло не так, и нам нужно вручную выйти из дочернего процесса.
Надеюсь, это демистифицирует механику оболочки. Удачного кодирования!
Первоначально опубликовано на medium.com 19 марта 2018 г.