Текст книги "Основы программирования в Linux"
Автор книги: Нейл Мэтью
Соавторы: Ричард Стоунс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 42 (всего у книги 67 страниц)
Далее приведено несколько процессов, выполнявшихся в другой системе Linux. Вывод был сокращен для облегчения понимания. В следующих примерах вы увидите, как определить состояние или статус процесса. Вывод командой ps
столбца STAT
предоставляет коды текущего состояния процесса. Самые широко распространенные коды перечислены в табл. 11.1. Смысл некоторых из них станет понятен чуть позже в этой главе. Другие же не рассматриваются в данной книге и их можно спокойно игнорировать.
Таблица 11.1
S | Спящий. Обычно ждет появления события, такого как сигнал или активизация ввода |
R | Выполняющийся. Строго говоря «работоспособный», т.е. в очереди на выполнение, либо выполняющийся, либо готовый к выполнению |
D | Непрерывно спящий (ожидающий). Обычно ждущий завершения ввода или вывода |
T | Остановленный. Обычно остановленный системой управления заданиями командной оболочки или находящийся под контролем отладчика |
Z | Умерший или процесс-зомби |
N | Задача с низким приоритетом, «nice» |
W | Разбитый на страницы (не используется в Linux с ядром версии 2.6 и последующих версий) |
S | Ведущий процесс сеанса |
+ | Процесс в группе фоновых процессов |
l | Многопотоковый процесс |
< | Задача с высоким приоритетом |
$ ps ах
PID TTY STAT TIME COMMAND
1 ? Ss 0:03 init [5]
2 ? S 0:00 [migration/0]
3 ? SN 0:00 [ksoftirqd/0]
4 ? S< 0:05 [events/0]
5 ? S< 0:00 [khelper]
6 ? S< 0:00 [kthread]
840 ? S< 2:52 [kjournald]
888 ? S
3069 ? Ss 0:00 /sbin/acpid
3098 ? Ss 0:11 /usr/sbin/hald –daemon=yes
3099 ? S 0:00 hald-runner
8357 ? Ss 0:03 /sbin/syslog-ng
8677 ? Ss 0:00 /opt/kde3/bin/kdm
9119 ? S 0:11 konsole [kdeinit]
9120 pts/2 Ss 0:00 /bin/bash
9151 ? Ss 0:00 /usr/sbin/cupsd
9457 ? Ss 0:00 /usr/sbin/cron
9479 ? Ss 0:00 /usr/sbin/sshd -o PidFile=/var/run/sshd.init.pid
9618 tty1 Ss+ 0:00 /sbin/mingetty –noclear tty1
9619 tty2 Ss+ 0:00 /sbin/mingetty tty2
9621 tty3 Ss+ 0:00 /sbin/mingetty tty3
9622 tty4 Ss+ 0:00 /sbin/mingetty tty4
9623 tty5 Ss+ 0:00 /sbin/mingetty tty5
9638 tty6 Ss+ 0:00 /sbin/mingetty tty6
10359 tty1 Ss+ 10:05 /usr/bin/Xorg -br -nolisten tcp :0 vt7 -auth
10360 ? S 0:00 -:0
10381 ? Ss 0:00 /bin/sh /usr/bin/kde
10438 ? Ss 0:00 /usr/bin/ssh-agent /bin/bash /etc/X11/xinit/xinitrc
10478 ? S 0:00 start_kdeinit –new-startup +kcminit_startup
10479 ? Ss 0:00 kdeinit Running...
10500 ? S 0:53 kdesktop [kdeinit]
10502 ? S 1:54 kicker [kdeinit]
10524 ? Sl 0:47 beagled /usr/lib/beagle/BeagleDaemon.exe –bg
10530 ? S 0:02 opensuseupdater
10539 ? S 0:02 kpowersave [kdeinit]
10541 ? S 0:03 klipper [kdeinit]
10555 ? S 0:01 kio_uiserver [kdeinit]
10688 ? S 0:53 konsole [kdeinit]
10689 pts/1 Ss+ 0:07 /bin/bash
10784 ? S 0:00 /opt/kde3/bin/kdesud
11052 ? S 0:01 [pdflush]
19996 ? SN1 0:20 beagled-helper /usr/lib/beagle/IndexHelper.exe
20254 ? S 0:00 qmgr -1 -t fifo -u
21192 ? Ss 0:00 /usr/sbin/ntpd -p /var/run/ntp/ntpd.pid -u ntp -i /v
21198 ? S 0:00 pickup -1 -t fifo -u
21475 pts/2 R+ 0:00 ps ax
Здесь вы видите на самом деле очень важный процесс
1 ? Ss 0:03 init [5]
В основном каждый процесс запускается другим процессом, называемым родительским или порождающим процессом. Подобным образом запущенный процесс называют дочерним или порожденным. Когда стартует ОС Linux, она выполняет единственную программу, первого предка и процесс с номером 1, init
. Это, если хотите, диспетчер процессов операционной системы и прародитель всех процессов. Другие системные процессы, с которыми вы вскоре встретитесь, запускаются процессом init
или другим процессом, запущенным процессом init
.
Один из таких примеров – процедура регистрации. Процесс init
запускает программу getty
для каждого последовательного терминала или модема коммутируемой линии передачи, которые можно применять для регистрации. Эти процессы отображены в следующем выводе команды ps
:
9619 tty2 Ss+ 0:00 /sbin/mingetty tty2
Процессы getty
ждут работы на терминале, приглашая пользователя зарегистрироваться хорошо всем знакомой строкой, и затем передают управление программе регистрации, которая устанавливает окружение пользователя и в конце запускает сеанс командной оболочки. Когда пользовательский сеанс командной оболочки завершается, процесс init
запускает новый процесс getty
.
Как видите, способность запускать новые процессы и ждать их окончания – одна из основных характеристик системы. Позже в этой главе вы узнаете, как выполнять аналогичные задачи в ваших собственных программах с помощью системных вызовов fork
, exec
и wait
.
В следующем примере вывода команды ps
приведен элемент списка для самой команды ps
.
21475 pts/2 R+ 0:00 ps ax
Эта строка означает, что процесс 21475
находится в состоянии выполнения (R
) и выполняет он команду ps ах
. Таким образом, процесс описан в своем собственном выводе! Индикатор состояния показывает только то, что программа готова к выполнению, а не то, что она обязательно выполняется в данный момент. На однопроцессорном компьютере в каждый момент времени может выполняться только один процесс, в то время как другие процессы ждут своего рабочего периода. Эти периоды, называемые квантами времени, очень короткие и создают впечатление одновременного выполнения программ. Опция R+
просто показывает, что данная программа – фоновая задача, не ждущая завершения других процессов или окончания ввода или вывода данных. Именно поэтому можно увидеть два таких процесса, приведенные в списке вывода команды
ps. (Другой, часто встречающийся процесс, помечаемый как выполняющийся, – дисплейный сервер системы X.)
Ядро Linux применяет планировщик процессов для того, чтобы решить, какой процесс получит следующий квант времени. Решение принимается исходя из приоритета процесса (мы обсуждали приоритеты процессов в главе 4). Процессы с высоким приоритетом выполняются чаще, а другие, такие как низкоприоритетные фоновые задачи, – реже. В ОС Linux процессы не могут превысить выделенный им квант времени. Они преимущественно относятся к разным задачам, поэтому приостанавливаются и возобновляются без взаимодействия друг с другом. В более старых системах, например Windows 3.х, как правило, для возобновления других процессов требовалось явное согласие процесса.
В многозадачных системах, таких как Linux, несколько программ могут претендовать на один и тот же ресурс, поэтому программы с короткими рабочими циклами, прерывающиеся для ввода, считаются лучше ведущими себя, чем программы, прибирающие к рукам процессор для продолжительного вычисления какого-либо значения или непрерывных запросов к системе, касающихся готовности ввода данных. Хорошо ведущие себя программы называют nice-программами (привлекательными программами) и в известном смысле эту "привлекательность" можно измерить. Операционная система определяет приоритет процесса на основе значения "nice", по умолчанию равного 0, и поведения программы. Программы, выполняющиеся без пауз в течение долгих периодов, как правило, получают более низкие приоритеты. Программы, делающие паузы время от времени, например в ожидании ввода, получают награду. Это помогает сохранить отзывчивость программы, взаимодействующей с пользователем; пока она ждет какого-либо ввода от пользователя, система увеличивает ее приоритет, чтобы, когда программа будет готова возобновить выполнение, у нее был высокий приоритет. Задать значение nice
для процесса можно с помощью команды nice
, а изменить его – с помощью команды renice
. Команда nice
увеличивает на 10 значение nice
процесса, присваивая ему более низкий приоритет. Просмотреть значения nice
активных процессов можно с помощью опций -l
или -f
(для полного вывода) команды ps
. Интересующие вас значения представлены в столбце NI
(nice).
$ ps -l
F S UID PID PPID С PRI NI ADDR SZ WCHAN TTY TIME CMD
000 S 500 1259 1254 0 75 0 – 710 wait4 pts/2 00:00:00 bash
000 S 500 1262 1251 0 75 0 – 714 wait4 pts/1 00:00:00 bash
000 S 500 1313 1262 0 75 0 – 2762 schedu pts/1 00:00:00 emacs
000 S 500 1362 1262 2 80 0 – 789 schedu pts/1 00:00:00 oclook
000 R 500 1363 1262 0 81 0 – 782 – pts/1 00:00:00 ps
Как видно из списка, программа oclock
выполняется (как процесс 1362) со значением nice
по умолчанию. Если бы она была запущена командой
$ nice oclock &
то получила бы значение nice
+10. Если вы откорректируете это значение командой
$ renice 10 1362
1362: old priority 0, new priority 10
программа oclock
будет выполняться реже. Увидеть измененное значение nice можно снова с помощью команды ps
:
$ ps -l
F S UID PID PPID С PRI NI ADDR SZ WCHAN TTY TIME CMD
000 S 500 1259 1254 0 75 0 – 710 wait4 pts/2 00:00:00 bash
000 S 500 1262 1251 0 75 0 – 714 wait4 pts/1 00:00:00 bash
000 S 500 1313 1262 0 75 0 – 2762 schedu pts/1 00:00:00 emacs
000 S 500 1362 1262 0 90 10 – 789 schedu pts/1 00:00:00 oclock
000 R 500 1365 1262 0 81 0 – 782 – pts/1 00:00:00 ps
Столбец состояния теперь также содержит N
, указывая на то, что значение nice
было изменено по сравнению с принятым по умолчанию:
$ ps х
PID TTY STAT TIME COMMAND
1362 pts/1 SN 0:00 oclock
Поле PPID
в выводе команды ps
содержит ID родительского процесса (PID), либо процесса, запустившего данный процесс, либо, если этот процесс уже не выполняется, процесса init
(PID, равный 1).
Планировщик процессов ОС Linux решает, какому процессу разрешить выполнение, на основе приоритета. Конкретные реализации конечно отличаются, но высокоприоритетные процессы выполняются чаще. В некоторых случаях низкоприоритетные процессы не выполняются совсем, если высокоприоритетные процессы готовы к выполнению.
Запуск новых процессов
Применив библиотечную функцию system
, вы можете заставить программу выполняться из другой программы и тем самым создать новый процесс:
#include
int system(const char *string);
Функция system
выполняет команду, переданную ей как строку, и ждет ее завершения. Команда выполняется, как если бы командной оболочке была передана следующая команда:
$ sh -с string
Функция system
возвращает код 127, если командная оболочка не может быть запущена для выполнения команды, и -1 в случае другой ошибки. Иначе system
вернет код завершения команды.
Выполните упражнение 11.1.
Упражнение 11.1. Функция system
Вы можете использовать system
для написания программы, выполняющей команду ps
. Хотя нельзя сказать, что она необычайно полезна, вы увидите, как применять этот метод в последующих примерах. (Для простоты примера мы не проверяем, работает ли на самом деле системный вызов.)
#include
#include
int main() {
printf(«Running ps with systemn»);
system(«ps ax»);
printf(«Done n»);
exit(0);
}
Когда вы откомпилируете и выполните программу system1.с, то получите вывод, похожий на приведенный далее:
$ ./system1
Running ps with system
PID TTY STAT TIME COMMAND
1 ? Ss 0:03 init [5]
...
1262 pts/1 Ss 0:00 /bin/bash
1273 pts/2 S 0:00 su -
1274 pts/2 S+ 0:00 -bash
1463 pts/2 SN 0:00 oclock
1465 pts/1 S 0:01 emacs Makefile
1480 pts/1 S+ 0:00 ./system1
1481 pts/1 R+ 0:00 ps ax
Done.
Поскольку функция system
применяет командную оболочку для запуска нужной программы, вы можете перевести ее в фоновый режим, заменив вызов функции в файле system1.с на следующий:
system(«ps ах &»);
Когда вы откомпилируете и выполните эту версию программы, то получите следующий вывод:
$ ./system2
Running ps with system
PID TTY STAT TIME COMMAND
1 ? S 0:03 init [5]
...
Done.
$ 1274 pts/2 3+ 0:00 -bash
1463 pts/2 SN 0:00 oclock
1465 pts/1 S 0:01 emacs Makefile
1484 pts/1 R 0:00 ps ax
Как это работает
В первом примере программа вызывает функцию system
со строкой "ps ах
", выполняющую программу ps
. Когда команда ps
завершается, вызов system
возвращает управление программе. Функция system
может быть очень полезной, но она тоже ограничена. Поскольку программа вынуждена ждать, пока не завершится процесс, начатый вызовом system
, вы не можете продолжить выполнение других задач.
Во втором примере вызов функции system
вернет управление программе, как только завершится команда командной оболочки. Поскольку это запрос на выполнение программы в фоновом режиме, командная оболочка вернет управление в программу, как только будет запущена программа ps
, ровно то же, что произошло бы при вводе в строку приглашения командной оболочки команды
$ ps ах &
Далее программа system2 выводит Done.
и завершается до того, как у команды ps
появится возможность отобразить до конца весь свой вывод. Вывод ps
продолжает формироваться после завершения system2 и в этом случае не включает в список элемент, описывающий процесс system2
. Такое поведение процесса может сильно сбить с толку пользователей. Для того чтобы умело применять процессы, вы должны лучше управлять их действиями. Давайте рассмотрим низкоуровневый интерфейс для создания процесса, exec
.
Замена образа процессаПримечание
Вообще применение функции
system
– далеко не идеальный способ создания процессов, потому что запускаемая программа использует командную оболочку. Он неэффективен вдвойне: и потому что перед запуском программы запускается оболочка, и потому что сильно зависим от варианта установки командной оболочки и применяемого окружения. В следующем разделе вы увидите гораздо более удачный способ запуска программ, который почти всегда предпочтительней применения вызоваsystem
.
Существует целое семейство родственных функций, сгруппированных под заголовком exec
. Они отличаются способом запуска процессов и представлением аргументов программы. Функция exec
замещает текущий процесс новым, заданным в аргументе path
или file
. Функции exec
можно применять для передачи выполнения вашей программы другой программе. Например, перед запуском другого приложения с политикой ограниченного применения вы можете проверить имя пользователя и пароль. Функции exec
более эффективны по сравнению с system
, т.к. исходная программа больше не будет выполняться после запуска новой программы.
#include
char **environ;
int execl(const char *path, const char *arg0, ..., (char *)0);
int execlp(const char *file, const char *arg0, ..., (char *)0);
int execle(const char *path, const char *arg0, ..., (char *)0,
char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
Эти функции делятся на два вида. execl
, execlp
и execle
принимают переменное число аргументов, заканчивающихся указателем null
. У execv
и execvp
второй аргумент – массив строк. В обоих случаях новая программа стартует с заданными аргументами, представленными в массиве argv
, передаваемом функции main
.
Эти функции реализованы, как правило, с использованием execve
, хотя нет обязательных требований на этот счет.
Функции, имена которых содержат суффикс p
, отличаются тем, что ищут переменную окружения PATH
для определения исполняемого файла новой программы. Если эта переменная не позволяет найти нужный файл, необходимо передать функции как параметр абсолютное имя файла, включающее каталоги.
Передать значение окружению программы может глобальная переменная environ
. Другой вариант – дополнительный аргумент в функциях execle
и execve
, способный передавать строки, используемые как окружение новой программы.
Если вы хотите применить функцию exec
для запуска программы ps
, можно выбирать любую функцию из семейства exec
, как показано в вызовах приведенного далее фрагмента программного кода:
#include
/* Пример списка аргументов */
/* Учтите, что для argv[0] необходимо имя программы */
char *const ps_argv[] = {"ps", «ax», 0};
/* He слишком полезный пример окружения */
char *const ps_envp[] = {"PATH=/bin:/usr/bin", «TERM=console», 0};
/* Возможные вызовы функций exec */
execl(«/bin/ps», «ps», «ax», 0);
/* предполагается, что ps в /bin */
execlp(«ps», «ps», «ax», 0);
/* предполагается, что /bin в PATH */
execle(«/bin/ps», «ps», «ax», 0, ps_envp);
/* передается свое окружение */
execv(«/bin/ps», ps_argv);
execvp(«ps», ps_argv);
execve(«/bin/ps», ps_argv, ps_envp);
А теперь выполните упражнение 11.2.
Упражнение 11.2. Функция execlp
Давайте изменим пример и используем вызов execlp
:
#include
#include
#include
int main() {
printf(«Running ps with execlpn»);
execlp(«ps», «ps», «ax», 0);
printf(«Done.n»);
exit(0);
}
Когда вы выполните эту программу, рехес.с, то получите обычный вывод команды ps
, но без сообщения Done
. Кроме того, обратите внимание на то, что в выводе нет процесса с именем рехес
:
$ ./рехес
Running ps with execlp
PID TTY STAT TIME COMMAND
1 ? S 0:03 init [5]
...
1262 pts/1 Ss 0:00 /bin/bash
1273 pts/2 S 0:00 su -
1274 pts/2 S+ 0:00 -bash
1463 pts/1 SN 0:00 oclock
1465 pts/1 S 0:01 emacs Makefile
1514 pts/1 R+ 0:00 ps ax
Как это работает
Программа выводит первое сообщение и затем вызывает функцию execlp
, которая ищет каталоги, заданные в переменной окружения PATH
для обнаружения программы ps
. Далее она выполняет команду вместо программы рехес
, запустив ее так, как будто вы ввели команду командной оболочки
$ ps ax
Когда ps
завершается, вы получаете новую строку приглашения командной оболочки. Возврата в программу рехес
не происходит, поэтому второе сообщение не выводится. PID нового процесса тот же, что и у исходного, то же самое можно сказать о PID родительского процесса и значении nice
. В сущности, происходит следующее: выполняющаяся программа запустила на выполнение новый код и новый исполняемый файл, заданный в вызове функции exec
.
Существует ограничение для общего размера списка аргументов и окружения процесса, запускаемого функциями exec
. Оно задается в переменной ARG_MAX
и в системах Linux равно 128 Кбайт. В других системах может задаваться меньший предельный размер, что способно порождать проблемы. Стандарт POSIX гласит, что ARG_MAX
должна быть не менее 4096 байтов.
Функции exec
, как правило, не возвращаются в программу до тех пор, пока не возникла ошибка, в этом случае задается переменная errno
и функция exec
возвращает -1.
Новые процессы, запущенные exec, наследуют многие свойства исходного процесса. В частности, открытые файловые дескрипторы остаются открытыми в новом процессе, пока не установлен их флаг FD_CLOEXEC
(close on exec) (подробную информацию см. в описании системного вызова fcntl
в главе 3). Любые открытые в исходном процессе потоки каталогов закрываются.
Для применения процессов, выполняющих несколько функций одновременно, можно либо использовать потоки, обсуждаемые в главе 12, либо создавать в программе полностью отдельный процесс, как делает init
, вместо замещения текущего потока исполнения, как в случае применения функции exec
.
Создать новый процесс можно с помощью вызова fork
. Системный вызов дублирует текущий процесс, создавая новый элемент в таблице процессов с множеством атрибутов, таких же как у текущего процесса. Новый процесс почти идентичен исходному, выполняет тот же программный код, но в своем пространстве данных, окружении и со своими файловыми дескрипторами. В комбинации с функциями exec
вызов fork
– все, что вам нужно для создания новых процессов.
#include
#include
pid_t fork(void);
Как видно из рис. 11.2, вызов fork
возвращает в родительский процесс PID нового дочернего процесса. Новый процесс продолжает выполнение так же, как и исходный, за исключением того, что в дочерний процесс вызов fork
возвращает 0. Это позволяет родительскому и дочернему процессам определить, «кто есть кто».
Рис. 11.2
Если вызов fork
завершается аварийно, он возвращает -1. Обычно это происходит из-за ограничения числа дочерних процессов, которые может иметь родительский процесс (CHILD_MAX
), в этом случае переменной errno
будет присвоено значение EAGAIN
. Если для элемента таблицы процессов недостаточно места или не хватает виртуальной памяти, переменная errno
получит значение ENOMEM
.
Далее приведен фрагмент типичного программного кода, использующего вызов fork
:
pid_t new_pid;
new_pid = fork();
switch(new_pid) {
case -1:
/* Ошибка */
break;
case 0:
/* Мы – дочерний процесс */
break;
default:
/* Мы – родительский процесс */
break;
}
Выполните упражнение 11.3.
Упражнение 11.3. Системный вызов fork
Давайте рассмотрим простой пример fork1.с:
#include
#include
#include
#include
int main() {
pid_t pid;
char* message;
int n;
printf(«fork program startingn»);
pid = fork();
switch(pid) {
case -1:
perror(«fork failed»);
exit(1);
case 0:
message = «This is the child»;
n = 5;
break;
default:
message = «This is the parent»;
n = 3;
break;
}
for (; n > 0; n–) {
puts(message);
sleep(1);
}
exit(0);
}
Эта программа выполняет два процесса. Дочерний процесс создается и выводит пять раз сообщение. Исходный процесс (родитель) выводит сообщение только три раза. Родительский процесс завершается до того, как дочерний процесс выведет все свои сообщения, поэтому в вывод попадает очередное приглашение командной оболочки.
$ ./fork1
fork program starting
This is the child
This is the parent
This is the parent
This is the child
This is the parent
This is the child
$ This is the child
This is the child
Как это работает
Когда вызывается fork
, эта программа делится на два отдельных процесса. Родительский процесс идентифицируется ненулевым возвращаемым из fork
значением и используется для задания количества сообщений, выводимых с интервалом в одну секунду.