Смекни!
smekni.com

Администрирование локальных сетей (стр. 14 из 39)

Можно запустить несколько процессов, выполняющих программу из одного и того же файла; при этом все они будут (если только специально не было предусмотрено иначе) независимыми друг от друга. Так, у каждого пользователя, работающего в системе, имеется свой собственный процесс-интерпретатор команд (своя копия), выполняющий программу из файла /bin/csh (или /bin/sh).

Процесс представляет собой изолированный "мир", общающийся с другими "мирами" во Вселенной при помощи:

a) Аргументов функции main:

void main(int argc, char *argv[], char *envp[]);

Если мы наберем команду

$ a.outa1 a2 a3

то функция main программы из файла a.out вызовется с

argc = 4 /* количество аргументов */

argv[0] = "a.out" argv[1] = "a1"

argv[2] = "a2" argv[3] = "a3"

argv[4] = NULL

По соглашению argv[0] содержит имя выполняемого файла из которого загружена эта программа*.

b) Так называемого "окружения" (или "среды") char *envp[], продублированного также в предопределенной переменной

extern char **environ;

Окружение состоит из строк вида

"ИМЯПЕРЕМЕННОЙ=значение"

Массив этих строк завершается NULL (как и argv). Для получения значения переменной с именем ИМЯ существует стандартная функция

char *getenv( char *ИМЯ );

Она выдает либо значение, либо NULL если переменной с таким именем нет.

c) Открытых файлов. По умолчанию (неявно) всегда открыты 3 канала:

ВВОД В Ы В О Д

FILE * stdin stdout stderr

соответствует fd 0 1 2

связан с клавиатурой дисплеем

#include <stdio.h>

main(ac, av) char **av; {

execl("/bin/sleep", "Take it easy", "1000", NULL);

}

Эти каналы достаются процессу "в наследство" от запускающего процесса и связаны с дисплеем и клавиатурой, если только не были перенаправлены. Кроме того, программа может сама явно открывать файлы (при помощи open, creat, pipe, fopen). Всего программа может одновременно открыть до определенное количество файлов в зависииости от настройки ядра.

d) Процесс имеет уникальный номер, который он может узнать вызовом

int pid = getpid();

а также узнать номер "родителя" вызовом

int ppid = getppid();

Процессы могут по этому номеру посылать друг другу сигналы:

kill(pid /* кому */, sig /* номер сигнала */);

и реагировать на них

signal (sig /*по сигналу*/, f /*вызывать f(sig)*/);

e) Существуют и другие средства коммуникации процессов: семафоры, сообщения, общая память, сетевые коммуникации.

f) Существуют некоторые другие параметры (контекст) процесса: например, его текущий каталог, который достается в наследство от процесса-"родителя", и может быть затем изменен системным вызовом

chdir(char *имя_нового_каталога);

У каждого процесса есть свой собственный текущий рабочий каталог. К "прочим" характеристикам отнесем также: управляющий терминал; группу процессов (pgrp); идентификатор (номер) владельца процесса (uid), идентификатор группы владельца (gid), реакции и маски, заданные на различные сигналы; и.т.п.

g) Издания других запросов (системных вызовов) к операционной системе ("богу") для выполнения различных "внешних" операций.

h) Все остальные действия происходят внутри процесса и никак не влияют на другие процессы и устройства ("миры"). В частности, один процесс НИКАК не может получить доступ к памяти другого процесса, если тот не позволил ему это явно (механизм shared memory); адресные пространства процессов независимы и изолированы (равно и пространство ядра изолировано от памяти процессов).

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

Уже неоднократно упоминали "системные вызовы". Что же это такое? С точки зрения Си-программиста - это обычные функции. В них передают аргументы, они возвращают значения. Внешне они ничем не отличаются от написанных нами или библиотечных функций и вызываются из программ одинаковым с ними способом.

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

Поведение всех программ в системе вытекает из поведения системных вызовов, которыми они пользуются. Даже то, что UNIX является многозадачной системой, непосредственно вытекает из наличия системных вызовов fork, exec, wait и спецификации их функционирования! То же можно сказать про язык Си - мобильность программы зависит в основном от набора используемых в ней библиотечных функций (и, в меньшей степени, от диалекта самого языка, который должен удовлетворять стандарту на язык Си). Если две разные системы предоставляют все эти функции (которые могут быть по-разному реализованы, но должны делать одно и то же), то программа будет компилироваться и работать в обоих системах, более того, работать в них одинаково.

Сам термин "системный вызов" как раз означает "вызов системы для выполнения действия", т.е. вызов функции в ядре системы. Ядро работает в привелегированном режиме, в котором имеет доступ к некоторым системным таблицам*, регистрам и портам внешних устройств и диспетчера памяти, к которым обычным программам доступ аппаратно запрещен (в отличие от MS DOS, где все таблицы ядра доступны пользовательским программам, что создает раздолье для вирусов). Системный вызов происходит в 2 этапа: сначала в пользовательской программе вызывается библиотечная функция-"корешок", тело которой написано на ассемблере и содержит команду генерации программного прерывания. Это - главное отличие от нормальных Си-функций - вызов по прерыванию. Вторым этапом является реакция ядра на прерывание:

1. переход в привелегированный режим;

2. разбирательство, КТО обратился к ядру, и подключение u-area этого процесса к адресному пространству ядра (context switching);

3. извлечение аргументов из памяти запросившего процесса;

4. выяснение, ЧТО же хотят от ядра (один из аргументов, невидимый нам - это номер системного вызова);

5. проверка корректности остальных аргументов;

6. проверка прав процесса на допустимость выполнения такого запроса;

7. вызов тела требуемого системного вызова - это обычная Си-функция в ядре;

8. возврат ответа в память процесса;

9. выключение привелегированного режима;

10. возврат из прерывания.

Во время системного вызова (шаг 7) процесс может "заснуть", дожидаясь некоторого события (например, нажатия кнопки на клавиатуре). В это время ядро передаст управление другому процессу. Когда наш процесс будет "разбужен" (событие произошло) - он продолжит выполнение шагов системного вызова.

Большинство системных вызовов возвращают в программу в качестве своего значения признак успеха: 0 - все сделано, (-1) - сисвызов завершился неудачей; либо некоторое содержательное значение при успехе (вроде дескриптора файла в open(), и (-1) при неудаче. В случае неудачного завершения в предопределенную переменную errno заносится номер ошибки, описывающий причину неудачи (коды ошибок предопределены, описаны в include-файле <errno.h> и имеют вид Eчтото). Заметим, что при УДАЧЕ эта переменная просто не изменяется и может содержать любой мусор, поэтому проверять ее имеет смысл лишь в случае, если ошибка действительно произошла:

#include <errno.h> /* коды ошибок */

extern int errno;

extern char *sys_errlist[];

int value;

if((value = sys_call(...)) < 0 ){

printf("Error:%s(%d)&bsol;n", sys_errlist[errno],

errno );

exit(errno); /* принудительное завершение программы */

}

Предопределенный массив sys_errlist, хранящийся в стандартной библиотеке, содержит строки-расшифровку смысла ошибок (по-английски). Посмотрите описание функции per-ror().

Время в UNIX.

Ниже приведены примеры как узнавать время:

. В системе UNIX время обрабатывается и хранится именно в виде числа секунд; в частности текущее астрономическое время можно узнать системным вызовом

#include <sys/types.h>

#include <time.h>

time_tt = time(NULL); /* time(&t); */

Функция

struct tm *tm = localtime( &t );

разлагает число секунд на отдельные составляющие, содержащиеся в int-полях структуры:

tm_year год (надо прибавлять 1900)

tm_yday день в году 0..365

tm_mon номер месяца 0..11 (0 - Январь)

tm_mday дата месяца 1..31

tm_wday день недели 0..6 (0 - Воскресенье)

tm_hour часы 0..23

tm_min минуты 0..59

tm_sec секунды 0..59

Номера месяца и дня недели начинаются с нуля, чтобы вы могли использовать их в качестве индексов:

char *months[] = { "Январь", "Февраль", ..., "Декабрь" };