Как execvp запускает команду?
Я знаю, что execvp
может использоваться для выполнения простых команд следующим образом:
char* arg[] = {"ls", "-l", NULL};
execvp(arg[0],arg);
Я хочу знать, что происходит здесь, когда я запускаю execvp
. В man-странице говорится: execvp
заменяет изображение образа процесса новым. Однако здесь я выполняю команду, а не исполняемый файл.
Чтобы быть конкретным, скажем, есть команда, которая специально требует ввода, например. Кот. Если у меня есть текстовый файл text.txt, который содержит имя файла, ожидаемое для cat, и я перенаправляю stdin к файловому потоку файла, будет вывод execle("cat","cat",NULL)
или execvp("cat", arg)
(очевидно, где arg хранит "cat"
и NULL
) приведет к выводу в консоли, как cat /filename
? Моя интуиция заключается в том, что я должен прочитать файл и, возможно, проанализировать его для хранения аргументов в arg. Однако я хочу убедиться.
Спасибо заранее!
Ответы
Ответ 1
Здесь что происходит в вызове execvp
:
- Ваша реализация libc ищет в
PATH
, если применимо, файл, который должен быть выполнен. Большинство, если не все, команд в UNIX-подобных системах являются исполняемыми. Что произойдет, если это не так? Попробуй. Посмотрите как glibc делает это.
- Как правило, если исполняемый файл найден, будет выполнен вызов
execve
. Части execve
могут быть реализованы в libc, или это может быть системный вызов (например, в Linux).
- Linux готовит программу, выделяя для нее память, открывая ее, планируя ее на выполнение, инициализирует структуры памяти, устанавливает свои аргументы и среду из предоставленных аргументов в вызов
execvp
, находит обработчик, подходящий для загрузки двоичного кода, и устанавливает текущую задачу (вызывающий execvp
) как не выполняемый. Вы можете найти его реализацию здесь.
Все шаги выше соответствуют требованиям, установленным POSIX, которые описаны в соответствующих страницах руководства.
Ответ 2
Относительно ваших вопросов:
В man-странице говорится: execvp
заменяет изображение образа процесса с новым. Однако здесь я выполняю команду не исполняемый файл.
Долгое время назад оболочка была очень ограниченной, и почти все команды UNIX были автономными исполняемыми файлами. Теперь, главным образом для целей скорости, некоторые подмножества команд UNIX реализуются внутри самой оболочки, эти команды называются builtins
. Вы можете проверить, какая команда реализована в вашей оболочке как встроенная или не с помощью команды type
:
λ ~/ type echo
echo is a shell builtin
(Полный список встроенных функций с описаниями можно найти на страницах man
для вашей оболочки, например, man bash-builtins
или man builtin
.)
Но у большинства команд все еще есть свой исполняемый-дубликат:
λ ~/ whereis echo
/bin/echo
Итак, в вашем конкретном случае, когда вы работаете:
char* arg[] = {"ls", "-l", NULL};
execvp(arg[0],arg);
Фактически вы заменяете адресное пространство текущего процесса адресным пространством (скорее всего) /bin/ls
.
Моя интуиция заключается в том, что я должен прочитать файл и, возможно, проанализировать его для хранения аргументы в arg.
Действительно, у вас есть. Но вы также можете использовать некоторые встроенные функции для этого aka "shebang":
Вместо того, чтобы помещать имя файла в отдельный файл, добавьте так называемую shebang в качестве первой строки файла, который вы хотите для cat:
#!/bin/cat
И добавьте chmod +x
к нему. Затем вы можете запустить его как исполняемый файл (через любую из функций exec
или оболочку):
λ ~/tmp/ printf '#!/bin/cat\nTEST\n' > cat_me
λ ~/tmp/ chmod +x cat_me
λ ~/tmp/ ./cat_me
#!/bin/cat
TEST
Из-за этого у него есть недостаток печати shebang
сам с файлом, но все же это интересно сделать in-kernel =)
BTW. Проблема, которую вы описали, если так распространено, что существует специальный исполняемый файл с именем xargs
, который (в очень упрощенном объяснении) выполняет данную программу в списке аргументов, переданных через stdin. Для получения дополнительной информации обратитесь к man xargs
.
Для легкого запоминания exec
-семейства я часто использую следующую таблицу:
Figure 8.14. Differences among the six exec functions
+----------+----------+----------+----------+--------+---------+--------+
| Function | pathname | filename | agr list | argv[] | environ | envp[] |
+----------+----------+----------+----------+--------+---------+--------+
| execl | * | | * | | * | |
+----------+----------+----------+----------+--------+---------+--------+
| execlp | | * | * | | * | |
+----------+----------+----------+----------+--------+---------+--------+
| execle | * | | * | | | * |
+----------+----------+----------+----------+--------+---------+--------+
| execv | * | | | * | * | |
+----------+----------+----------+----------+--------+---------+--------+
| execvp | | * | | * | * | |
+----------+----------+----------+----------+--------+---------+--------+
| execve | * | | | * | | * |
+----------+----------+----------+----------+--------+---------+--------+
| letter | | p | l | v | | e |
+----------+----------+----------+----------+--------+---------+--------+
Итак, в вашем случае execvp
принимает имя файла, argv (v) и environment ( e).
Затем он пытается "угадать" путь (aka полный путь), добавив filename
(в вашем случае cat
) к каждому компоненту пути в PATH
, пока не найдет путь с исполняемым filename
.
Гораздо больше информации о том, что происходит с капотом exec
(включая материал наследования), можно найти в Расширенное программирование в среде UNIX (второе издание) W. Richard Stevens и Стивен А. Раго, а также APUE2.
Если вас интересует внутренняя часть UNIX, вы, вероятно, должны ее прочитать.
Ответ 3
"ls" - это не просто команда, это на самом деле программа (большинство команд есть). Когда вы запустите execvp, он будет уничтожать всю вашу программу, ее память, стек, кучу и т.д.... концептуально "очистить" и передать его "ls" , чтобы он мог использовать его для собственного стека, куча и т.д.
Короче говоря, execvp уничтожит вашу программу и заменит ее другой программой, в данном случае "ls" .
Ответ 4
Моя интуиция заключается в том, что я должен прочитать файл и, возможно, проанализировать его для хранения аргументов в arg. Однако я хочу убедиться.
Ваша интуиция во многом правильна. Утилита cat
, которую вы используете в качестве примера, имеет два отдельных пути кода:
- Если имена файлов указаны как аргументы, они будут открывать и читать каждый по очереди.
- Если указанных файлов не указано, они будут считываться со стандартного ввода.
Такое поведение специально реализовано в утилите cat
- оно не реализовано ни на одном более низком уровне. В частности, он определенно не является частью системного вызова exec
. Системные вызовы exec
вообще не "рассматривают" аргументы; они просто передают их прямо на новый процесс в argv
, и этот процесс позволяет обрабатывать их, но он считает нужным.