Наши партнеры

UnixForum






Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

Исследуем процесс загрузки Linux

(C) В.А.Костромин, 2007
(версия файла от 21.10.2007 г.)


Назад Оглавление Вперед

Этап 10: Выполнение команд пользователя

На первый взгляд кажется, что описание процесса начальной загрузки Linux можно закончить в той точке, где мы остановились в предыдущем разделе. Однако, это, пожалуй, было бы не совсем правильно. Даже если не говорить об особых вариантах загрузки, типа однопользовательского или графического режимов, необходимо рассмотреть еще вопрос о том, что такое окружение, в каком окружении оказался пользователь, как это окружение меняется в процессе его работы с системой.

Что такое окружение?

Чтобы понять, что такое окружение, нам придется начать с самых основ, а именно, с понятия процесса. В самом первом приближении процесс - это выполняющаяся в памяти программа. Но Линукс - система многопользовательская и многозадачная. Как известно, реализация многозадачности (и, как следствие, одновременная работа многих пользователй в системе) достигается методами разделения во времени: разбиением времени работы процессора на отдельные кванты, в каждом из которых процессор выполняет только один процесс. Для переключения на выполнение другого процесса в следующий квант времени нужно уметь запоминать состояние текущего процесса и восстанавливать состояние процесса, который будет работать в течение следующего кванта.

Для хранения сведений о состоянии "отложенных" процессов, в памяти, выделенной для ядра, создается особая структура данных (структура задачи) типа task_struct (смотри [8]). Насколько я понимаю, именно эта структура в книге А.Робачевского [51] называется контекстом процесса. Контекст процесса хранит следующие данные:

  • идентификационная информация о процессе (PID процесса, PID родительского процесса, реальный и эффективный идентификаторы пользователя и реальный и эффективный идентификаторы группы);
  • статус процесса;
  • информация для планировщика;
  • информация для организации межпроцессорного взаимодействия;
  • ссылки и связи процесса;
  • информация о времени исполнения и таймеры;
  • информация об используемых процессом ресурсах файловой системы;
  • информация о выделенном процессу адресном пространстве;
  • информация о состоянии регистров процессора;
  • указатели на адресное пространство процесса;
  • указатели на каждый открытый процессом файл;
  • домашний (или корневой) каталог процесса и его текущий каталог; и так далее.

Поскольку оболочка тоже представляет собой процесс в системе, она тоже имеет контекст.

    Примечание. К сожалению, в разных источниках на русском языке имеется разнобой в терминологии, касающейся контекста. Судя по всему, контекст оболочки - это то же самое, что в руководстве man к оболочке bash называют средой выполнения. Впрочем, я не совсем уверен что это синонимы, поскольку мне не удалось найти в имеющихся источниках точных определений того и другого.

Среда выполнения командного интерпретатора, согласно man bash, содержит следующие компоненты:

  • открытые файлы, унаследованные командным интерпретатором при вызове, с учетом изменений, вызванных перенаправлениями, переданными встроенной команде exec
  • текущий рабочий каталог, установленный командами cd, pushd или popd, или унаследованный командным интерпретатором при вызове
  • маска режима создания файла, установленная с помощью команды umask или унаследованная от родительского процесса
  • текущие обработчики сигналов, установленные с помощью trap
  • параметры командного интерпретатора, установленные либо путем прямого присваивания значений переменным, либо заданные командой set, либо унаследованные из среды родительского процесса
  • функции командного интерпретатора, заданные в ходе выполнения или унаследованные из среды родительского процесса
  • опции, установленные при вызове (как стандартные, так и заданные явно в командной строке) или установленные с помощью комнады set
  • опции, установленные с помощью команды shopt
  • псевдонимы, заданные с помощью команды alias
  • идентификаторы различных процессов, в том числе, для фоновых заданий, значение параметра $$ и значение параметра $PPID

В этом списке надо обратить особое внимание на "параметры командного интерпретатора", другими словами - набор переменных с их значениями. В книге А.Робачевского при описании контекста процесса этот элемент контекста называется "окружением процесса". Этот термин часто применяется и в тех случаях, когда речь идет об оболочке bash. В англоязычной версии man-страницы применяется термин "environment". Его иногда переводят тоже как "среда", что вызывает некоторую путаницу. Чтобы в дальнейшем понимать все однозначно, давайте договоримся, что "среда исполнения" = "контекст оболочки", а набор переменных с их значениями будем называть "окружением".

Итак, окружение оболочки - это массив строк, содержащих пары имя-значение (name=value). Каждая такая пара задает некоторую переменную окружения, причем каждой переменой может быть придан еще некоторый набор атрибутов.

Имена переменных окружения могут формироваться как из заглавных, так и из строчных символов, однако обычно принято формировать их в верхнем регистре. Широко известными примерами переменных являются переменные PATH, HOME, PWD и так далее.

Значением переменной может быть либо строка симоволов, отличных от "=" и нулевого символа (ASCII NUL), либо пустое значение. Однако в справочной системе info рекомендуется использовать для задания значений переменным только символы подчеркивания, цифры и буквы, причем не начинать строку значения цифрой, поскольку оболочка не всегда корректно работает с переменными, не удовлетворяющими этим ограничениям.

Задать переменную окружения в оболочке можно просто выполнив команду вида

 
NAME="value"
При этом value может быть пустой строкой. В любом случае считается, что тем самым задана (установлена) переменная. После того, как переменная установлена, она может быть удалена только с помощью встроенной команды unset. Заметим, что задание для переменной пустого значения - это совсем не то же самое, что отмена задания переменной.

В предыдущих разделах мы видели, что окружение оболочки bash в случае запуска ее в качестве регистрационного shell, формируется именно таким образом в инициализационных скриптах и в скриптах /etc/profile, ~/bash_profile .

Для задания переменных может быть также использована встроенная в оболочку команда declare (синонимом этой команды является команда typeset, сохраняемая для совместимости со старыми терминалами). Формат запуска этой команды:

 
declare [-afFirtx] [-p] [name[=value]]
Команда declare позволяет не только объявить переменную, но и задать ей атрибуты. Для этого используются следующие опции команды:
-i Интерпретировать переменную name как число (целое).
-r Переменная name объявляется неизменяемой (readonly).
-t Установить для функции name атрибут trace. В этом случае для функции будет наследоваться обработчик сигнала DEBUG (см. описание встроенной команды trap). Атрибут trace не имеет смысла для переменных, не являющихся функциями.
-x Установить для переменной name атрибут экспортирования (наследования). Такие переменные передаются в составе окружения в дочерние процессы.

Если при задании опции вместо знака `-' использовать `+', то соответствующий атрибут будет сниматься с переменной.

Если в строке вызова команды не указано ни одного имени (name) и не заданы опции, то будет выведен список объявленных переменных с их значениями. Если вызвать эту команду с одной опцией `-p', будет выдан список всех переменных окружения, плюс определения функций. Чтобы увидеть только определения функций, можно использовать опцию `-f'. Команда declare -F выдает только имена функций окружения текущей оболочки.

Команду declare можно использовать внутри описания функции. При этом задаваемая ею переменная является локальной, то есть имеет смысл и может использоваться только внутри функции, как если бы к ней была применена команда local.

Перечень всех переменных и функций окружения можно получить также с помощью встроенной команды set без параметров. Формат запуска команды:

 
set [--abefhkmnptuvxBCHP] [-o option] [argument ...]
Если ее запустить без опций или аргументов, set выводит имена и значения всех переменных окружения и функций оболочки, отсортированные в соответствии с текущей локалью. Но основная функция этой команды - устанавливать или снимать атрибуты переменных и функций оболочки. Мы не будем подробнее разбирать использование этой команды, поскольку это заняло бы слишком много времени и места. В руководстве по bash этой команде посвящен отдельная секция, поскольку у этой команды имеется множество различных опций.

Наследование окружения

Как известно, в системе Linux каждый процесс кроме init имеет своего родителя. Другими словами, процессы не возникают из ничего, каждый процесс порожден каким-то другим процессом. Порождение дочернего процесса осуществляется системным вызовом fork, который создает в том числе контекст дочернего процесса. При этом значительная часть этого контекста наследуется из контекста процесса-родителя. Какие именно части контекста наследуются, показано в таблице 3.4 книги А.Робачевского. В том числе наследуется и окружение процесса. После создания контекста нового процесса запускается код программы процесса с помощью системного вызова exec, при этом часть контекста изменяется (смотри ту же таблицу в книге А.Робачевского).

Когда в оболочке запускается какая-то команда, не являющаяся встроенной или функцией командного интерпретатора, тоже порождается дочерний процесс. То есть команда вызывается в отдельной среде выполнения (контексте), которая наследуется от командного интерпретатора. Как сказано в man bash, в эту среду наследуются:

  • открытые файлы командного интерпретатора плюс любые изменения и дополнения, заданные для команды перенаправлениями;
  • текущий рабочий каталог;
  • маска режима создания файлов;
  • переменные командного интерпретатора, помеченные как экспортируемые, а также переменные, экспорт которых был задан при запуске команды;
  • наследуются установленные в родительском командном интерпретаторе обработчики сигналов, а сигналы, в нем игнорировавшиеся, также игнорируются.

Команды, результаты которых необходимо подставить, и асинхронные команды вызываются в средах порожденных командных интерпретаторов, которые совпадают со средой родительского, но обработчики сигналов устанавливаются такие, как были унаследованы родительским командным интерпретатором при вызове. Встроенные команды, вызываемые как часть конвейера, также выполняются в среде порожденного командного интерпретатора. Команда, вызванная в отдельной среде, не может повлиять на среду выполнения родительского командного интерпретатора. Изменения в среде порожденного командного интерпретатора не затрагивают среду выполнения родительского.

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

Из приведенного описания следует, что все переменные окружения делятся на две категории или группы. Первую группу можно назвать локальными переменными процесса, а вторую – наследуемыми или экспортируемыми. Разница между ними заключается в следующем. Когда текущий процесс порождает своего наследника, наследуемые переменные передаются последнему вместе с их значениями. А локальные переменные – не передаются. Как мы видели выше, при создании переменной окружения с помощью команды declare можно установить ей атрибут экспортирования, в противном случае она определяется как локальная. Впрочем, перевести ее в разряд экспортируемых можно и позже, для чего в оболочке имеется специальная встроенная команда export.

Как мы видели выше, установить для переменной атрибут "экспортируемая" можно если использовать специальную опцию команды declare. Если же переменная была задана простым присваиванием значения (то есть командой вида NAME="value"), то можно сделать ее экспортируемой с помощью специальной встроенной команды вида

export NAME
(причем в одной команде export можно задать атрибут экспортирования для нескольких переменных). Примеры использования этой команды тоже встречались в предыдущих разделах при анализе инициализационных скриптов.

Между прочим команда export может отмечать как наследуемые не только переменные окружения, но и функции среды выполнения. Для того, чтобы передать дочернему процессу функцию, эта команда запускается с опцией -f:

$ export -f имя_функции

Если имя функции задано не верно (функция не существует) будет возращен ненулевой статус выхода.

Команда export - n NAME снимает с переменной атрибут экспортируемости, команда export без параметров (или с параметром -p) возвращает список всех наследуемых (или экспортируемых) строковых переменных.

Как было сказано выше, для установки или снятия атрибутов можно также использовать команду set. Если export - команда для установки/снятия только одного вида атрибутов, то set работает с разными. Но нас это пока мало интересует, мы оставим команду set и кратко рассмотрим еще одну команду, имеющую отношение к наследованию окружения, а именно, команду env.

Команда env предназначена для запуска программ в модифицированном окружении. Формат запуска команды

$ env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]

Мы, конечно, не будем сейчас разбирать эту команду подробно, скажу только, что если команда COMMAND не задана, то в выводе вы увидите то окружение, которое должно получиться после выполнения команды env с учетом всех опций и пар "NAME=VALUE", которые будут заданы в строке ее вызова. Если выполнить эту команду вообще без аргументов, она должна дать ту часть окружения, которая будет наследована из окружения текущей оболочки. То есть результат должен быть таким же, как при выполнении команды export. Однако на практике это не всегда имеет место. Попробуем разобраться.

Результаты экспериментов

Из предыдущего рассмотрения вроде бы можно было сделать вывод о том, что текущее окружение оболочки можно вывести с помощью команд declare или set без параметров, а наследуемые или экспортируемые переменные - с помощью команд export или env, опять же без параметров. Однако простой эксперимент с запуском этих команд показал, что на деле не все так просто.

Первый раз я выполнил такой эксперимент в окне терминала из графической оболочки KDE. И получил совершенно обескуражившие меня результаты - вывод команд в этих парах получился существенно различный. Вначале я подумал, что это различие можно объяснить тем, что между запусками этих команд я выполнял какие-то другие операции как текущей в оболочке, так и в KDE. И надо сказать, что это предположение в значительной степени верно: когда BASH запускается из графического окружения, он наследует в свое окружение какие-то переменные от предыдущих процессов. А цепочка предыдущих процессов в этом варианте запуска достаточно сложна. На следующем рисунке вы видите фрагмент вывода команды pstree

init--acpid
     |
     |
    ...
     |--prefdm--gdm-binary--gdm-binary----X
    ...                                |
                                       |--tvm--ssh-agent
                                       |--xterm--bash--pstree
То есть в этом случае окружение BASH формируется не только инициализационными скриптами системы и файлами типа /etc/profile, но и менеджером дисплея, терминальной программой (xterm) и самим bash (точнее, в тех скриптах, которые он вызывает для своего запуска).

Но даже если я в одном и том же окне вызываю несколько раз одну и ту же команду, то результат может оказаться разным. Тем более, если между двумя последовательными вызовами одной и той же команды вызывались какие-то другие. Я заметил этот эффект при запуске команд set и declare. Разница заключалась в том, что изменялось значение переменой "$_". Как показало чтение man и результаты экспериментов, когда bash запускает на выполнение внешнюю команду, в переменной `$_' запоминается полный путь к файлу команды, а для встроенных команд правило формирования значений этой переменной мне понять не удалось - похоже, что в ней оказывается имя предыдущей встроенной команды. Так что если перед вызовом команд set и declare вызывались какие-то другие команды, то и значение переменой "$_" для них, соответственно, различно.

Выходит, что получить полностью идентичные результаты выполнения даже для одной и той же команды из набора (declare, set, export, env) можно только в том случае, если каждый раз перезапускать компьютер и первой выполнять эту команду, записывая результат в файл. В таком случае после двух запусков получим идентичные файлы. Одинаковыми при таком варианте получения окружения должны оказаться выводы команд declare и set, так как обе они встроенные. Эксперимент показал, что дело обстоит почти так, отличие в выводе этих двух команд заключалось только в идентификационном номере родительского процесса (PPID). А вывод команд export и env отличался в двух строках, а именно, в переменных "$_" (эта переменная присутствует только в выводе команды env и равна "/bin/env", что понятно, так как это внешняя команда) и "$OLDPWD" (эта переменная имеет пустое значение, присутствует только в выводе команды export и вот это я объяснить уже не могу).

-----------------------------------------

Для каждого процесса в файловой системе proc имеется специальный файл environ, который содержит окружение процесса. Записи в этом файле отделяются друг от друга символами с кодом 0; 0 может также стоять в конце окружения. Чтобы узнать содержимое окружения процесса 1065, надо выполнить следующую команду:

(cat /proc/1065/environ; echo) | tr "\000" "\n"

В программах на C доступ к переменным окружения можно получить (см. [54]) с помощью функций getenv(), putenv(), setenv(), unsetenv(), clearenv(), объявленным в файле /usr/include/stdlib.h.

6.2 Bash Startup Files

This section describs how Bash executes its startup files. If any of the files exist but cannot be read, Bash reports an error. Tildes are expanded in file names as described above under Tilde Expansion (see section 3.5.2 Tilde Expansion).

Interactive shells are described in 6.3 Interactive Shells.

Invoked as an interactive login shell, or with `--login'

When Bash is invoked as an interactive login shell, or as a non-interactive shell with the `--login' option, it first reads and executes commands from the file `/etc/profile', if that file exists. After reading that file, it looks for `~/.bash_profile', `~/.bash_login', and `~/.profile', in that order, and reads and executes commands from the first one that exists and is readable. The `--noprofile' option may be used when the shell is started to inhibit this behavior.

When a login shell exits, Bash reads and executes commands from the file `~/.bash_logout', if it exists.

Invoked as an interactive non-login shell

When an interactive shell that is not a login shell is started, Bash reads and executes commands from `~/.bashrc', if that file exists. This may be inhibited by using the `--norc' option. The `--rcfile file' option will force Bash to read and execute commands from file instead of `~/.bashrc'.

So, typically, your `~/.bash_profile' contains the line
 
if [ -f ~/.bashrc ]; then . ~/.bashrc; fi
after (or before) any login-specific initializations.

Invoked non-interactively

When Bash is started non-interactively, to run a shell script, for example, it looks for the variable BASH_ENV in the environment, expands its value if it appears there, and uses the expanded value as the name of a file to read and execute. Bash behaves as if the following command were executed:
 
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
but the value of the PATH variable is not used to search for the file name.

As noted above, if a non-interactive shell is invoked with the `--login' option, Bash attempts to read and execute commands from the login shell startup files.

Invoked with name sh

If Bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well.

When invoked as an interactive login shell, or as a non-interactive shell with the `--login' option, it first attempts to read and execute commands from `/etc/profile' and `~/.profile', in that order. The `--noprofile' option may be used to inhibit this behavior. When invoked as an interactive shell with the name sh, Bash looks for the variable ENV, expands its value if it is defined, and uses the expanded value as the name of a file to read and execute. Since a shell invoked as sh does not attempt to read and execute commands from any other startup files, the `--rcfile' option has no effect. A non-interactive shell invoked with the name sh does not attempt to read any other startup files.

When invoked as sh, Bash enters POSIX mode after the startup files are read.

Invoked in POSIX mode

When Bash is started in POSIX mode, as with the `--posix' command line option, it follows the POSIX standard for startup files. In this mode, interactive shells expand the ENV variable and commands are read and executed from the file whose name is the expanded value. No other startup files are read.

Invoked by remote shell daemon

Bash attempts to determine when it is being run by the remote shell daemon, usually rshd. If Bash determines it is being run by rshd, it reads and executes commands from `~/.bashrc', if that file exists and is readable. It will not do this if invoked as sh. The `--norc' option may be used to inhibit this behavior, and the `--rcfile' option may be used to force another file to be read, but rshd does not generally invoke the shell with those options or allow them to be specified.

Invoked with unequal effective and real UID/GIDs

If Bash is started with the effective user (group) id not equal to the real user (group) id, and the -p option is not supplied, no startup files are read, shell functions are not inherited from the environment, the SHELLOPTS variable, if it appears in the environment, is ignored, and the effective user id is set to the real user id. If the -p option is supplied at invocation, the startup behavior is the same, but the effective user id is not reset.



Команда su

Оболочка - это интерпретатор команд, который может использоваться для запуска, приостановки, остановки и даже написания программ. Оболочка - неотъемлемая часть системы Linux, а также часть проекта Linux и Unix. Если представить себе ядро Linux как центральную часть сферы, то оболочка предстанет в виде внешнего слоя, окружающего ядро. Когда вы передаете команду из оболочки или других программ в Linux, ядро соответствующим образом реагирует. Дальше в руководстве приводится перечень оболочек, доступных в Linux, а также некоторые их отличительные особенности. С какой оболочкой вы работаете, можно определить при регистрации в Linux, либо просмотрев содержимое файла /etc/passwd, либо посредством поиска в этом файле вашего имени пользователя. Как это выглядит, демонстрирует следующий пример:

# fgrep bball /etc/passwd

bball:x:100:100:William H. Ball,,,,:/home/bball:/bin/bash

Имя оболочки, которой вы пользуетесь, выводится в конце содержимого вашего файла passwd.

Файл Назначение или содержимое

/etc/issue.net
/etc/services
/etc/shells
/etc/inputrc
/etc/skel
\etc\bash_completion.d\ \etc\bash_completion.d\net-tools
\etc\bash_completion.d\netprofile
\etc\bash_completion.d\urpmi
\etc\bash_completion.d\strace
\etc\bash_completion.d\ooffice2.1.sh

\etc\skel\tmp\.bash_logout
\etc\skel\tmp\.bash_profile
\etc\skel\tmp\.bashrc
\etc\skel\tmp\.screenrc

\etc\sysconfig\alsa
\etc\sysconfig\msec
\etc\sysconfig\udev_net
\etc\sysconfig\bootsplash
\etc\sysconfig\installkernel
\etc\sysconfig\tmpwatch
\etc\sysconfig\xinetd
\etc\sysconfig\rawdevices
\etc\sysconfig\userdrake
\etc\sysconfig\firstboot

\etc\sysconfig\harddrake2\kernel
\etc\sysconfig\harddrake2\service.conf
\etc\sysconfig\harddrake2\previous_hw

/etc/sysconfig/xinetd
# Add extra options here
EXTRAOPTIONS=""

/etc/sysconfig/rawdevices
/dev/raw/raw1 /dev/dvd

Назад Оглавление Вперед