ПРОГРАММНЫЙ ИНТЕРФЕЙС СИСТЕМЫ UNIX



Все версии системы UNIX предоставляют строго определенный ограниченный набор точек входа к услугам ядра ОС, получивших название системных вызовов (system calls). Они оформлены как функции языка С и являются стандартизованным (POSIX) интерфейсом взаимодействия прикладных процессов с ядром. Эти функции играют роль оболочки, инкапсулирующие механизм обращения к коду ядра системы. Современные версии UNIX предлагают более 120 системных вызовов. Полное описание каждого системного вызова можно получить командой man(2).

Базовый ввод/вывод

Программный интерфейс базового ввода/вывода представляет собой средство самого низкого уровня и обеспечивает обмен с файлами, интерпретируемыми как одномерный массив байтов с прямым доступом. Буферизация в адресном пространстве процесса на этом уровне отсутствует.

Основные системные вызовы сведены в табл. 1.

При создании файла указываются права доступа к нему либо в восьмеричном виде, либо, что удобнее, путем побитового сложения следующих констант, описанных в файле <sys/stat.h> и сведенных в табл. 2.

Режим доступа к файлу задается в параметре oflag побитовым сложением констант, описанных в файле <fcntl.h> приведенных в таблице 3. В этом параметре может быть задана только одна из опций следующих трех опций: O_RDONLY, O_WRONLY либо O_RDWR.

При позиционировании в файле новое положение указателя чтения/записи зависит от значения параметра «whence», что отображено в табл. 4.

Пример использования системного вызова fcntl:

fcntl (fd, F_SETFD, 1);

Устанавливается атрибут FD_CLOEXEC=1 для файла, задаваемого дескриптором fd. При выполнении функций семейства exec этот файл будет закрыт.

Пример программы, создающей файл с заданными правами доступа и записывающей в него данные из другого файла, приведен в листинге file.c.

 

Таблица 1

. Основные системные вызовы базового ввода/вывода

 

Системный вызов Описание
int open(const char *path, int oflag, mode_t mode) Открытие файла. Задается путь к файлу, режим открытия и права доступа. Возвращает файловый дескриптор, адресующий дальнейшие операции с файлом. Если файл не существует, он может быть создан, при этом устанавливаются права доступа к нему, задаваемые последним параметром..
int сreat(const char *path, mode_t mode) Создание файла, частный случай open. Эквивалентен вызову:  open(path, O_WRONLY | O_CREAT | O_TRUNC, mode)
int close(int fildes) Закрытие предварительно открытого файла, заданного файловым дескриптором.
int dup(int fildes) Дублирование файлового дескриптора. Возвращает новый файловый дескриптор с с минимальным номером (из числа свободных), связанный с предварительно открытым файлом. Позволяет, в частности, изменить дескриптор файла на один из стандартных. Частный случай fcntl.
off_t lseek(int fildes, off_t offset, int whence) Позиционирование в файле. Указатель чтения/записи устанавливается в позицию, заданную параметром offset. Функция возвращает новое смещение указателя.
size_t read(int fildes, void *buf, size_t nbyte) Чтение nbyte байтов из открытого файла в заданный буфер buf.
size_t write(int fildes, void *buf, size_t nbyte) Запись nbyte байтов из заданного буфера buf в открытый файл.
int fcntl(int fildes, int cmd, ...) Управление открытым файлом. Выполняется действие cmd с файлом. Возможный третий аргумент зависит от конкретного действия.

 

Надстройкой над базовым вводом/выводом является стандартный буферизованный ввод/вывод. В рамках этого уровня вводятся тип FILE и соответствующий набор функций. Буферизованный ввод/вывод реализован практически одинаково в различных операционных системах и является стандартным для языка С, поэтому здесь мы его рассматривать не будем.

Таблица 2.

 Задание прав доступа к файлу

Константа Значение
S_IRWXU     право владельца на чтение, запись и выполнение файла;
S_IRUSR      право владельца на чтение файла;
S_IWUSR     право владельца на запись файла;
S_IXUSR      право владельца на выполнение файла;
S_IRWXG     право группы на чтение, запись и выполнение файла;
S_IRGRP      право группы на чтение файла;
S_IWGRP     право группы на запись файла;
S_IXGRP      право группы на выполнение файла;
S_IRWXO     право остальных пользователей на чтение, запись и выполнение файла;
S_IROTH      право остальных пользователей на чтение файла;
S_IWOTH     право остальных пользователей на запись файла;
S_IXOTH      право остальных пользователей на выполнение файла;

 

Таблица 3.

 Задание режима открытия файла

Константа Значение
O_RDONLY Открыть файл только для чтения
O_WRONLY Открыть файл только для записи
O_RDWR Открыть файл для чтения и записи
O_APPEND Производить добавление в файл, т. е. устанавливать указатель чтения/записи на конец файла перед каждой операцией записи
O_CREAT Если файл не существует, то он создается. Атрибуты защиты устанавливаются с помощью параметра mode
O_EXCL При использовании совместно с O_CREAT приводит к ошибке, если файл уже существует
O_NOCTTY Если файл представляет собой терминал, не позволяет ему стать управляющим терминалом
O_SYNC Запись данных на диск будет производиться до возврата из системного вызова write
O_TRUNC Если файл существует и является обычным файлом, то он усекается до нулевой длины
O_NONBLOCK Устанавливается неблокируемый режим выполнения системных вызовов read и write. При невозможности произвести запись или чтение, соответствующие вызовы завершатся с ошибкой

Таблица 4.

 Позиционирование в файле

 

Значение whence Позиция указателя
SEEK_SET offset байт от начала файла
SEEK_CUR offset байт от текущей позиции
SEEK_END offset байт от конца файла

Листинг file.c

 

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#define MAX 1024

main ()

{

int fdr, fdw,n,n1;

mode_t mode;

char name[]="newfile";

char name1[]="file.c";

char buff[MAX];

mode=S_IRUSR | S_IWUSR;

 

if ((fdw=creat(name,mode))<0) {

printf("Error: don't open output file.");

exit(1);

}

if ((fdr=open(name1,O_RDONLY))<0)

printf("Error: don't open input file.");

while ((n=read(fdr,buff,MAX))>0){

printf("buff=\n%s\n",buff);

if ((n1=write (fdw,buff,n)) < 0 )

printf("Error write");

}

close(fdw,fdr);

}

Управление процессами

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

Процесс имеет несколько атрибутов, важнейшие из которых следующие.

Идентификатор процесса Process ID (PID). Уникальное в системе целое число, позволяющее ядру различать процессы. Номер текущего процесса можно получить посредством системного вызова getpid.

Идентификатор порождающего процесса Parent Process ID (PPID). Идентификатор процесса, создавшего данный процесс. Его получают посредством системного вызова getppid.

Идентификация группы процессов Process Group ID (PGID). Каждый процесс входит в состав группы, идентифицируемой своим номером PGID. Внутри каждой группы существует процесс, называемый лидером группы - это процесс, PID которого равен PGID. Понятие группы процессов вводится для приемки сигналов. Номер группы можно получить посредством вызова getprgp. Изменить его можно с помощью функции setprgp.

Терминальная линия. Операторский (управляющий) терминал или псевдотерминал, ассоциированный с процессом (если таковой существует). Каждый процесс является членом группы процессов, связанных с терминалом (Terminal Group), номером которой является PID лидера группы процессов, открывшего терминал. Данное понятие используется для сигналов, посылаемых с этого терминала. Любой процесс может использовать связанный с ним операторский терминал, открыв файл /dev/tty. Когда пользователь выполняет на своем терминале команду с помощью командного интерпретатора shell, запускается новый процесс. Этот процесс является лидером группы для всей создаваемой им цепочки процессов. Сигнал прерывания, посылаемый с клавиатуры этого терминала, посылается всей совокупности данных процессов. Любой процесс может стать лидером группы с помощью вызова setpg rp и, таким образом, избежать привязки к терминалу и, соответственно, возможности быть прерванным. Так могут программироваться процессы, не связанные с терминалом – демоны (daemons).

Идентификаторы пользователя UID и группы GID, соответствующие аналогичным идентификаторам владельца исполнимого файла образа процесса. Различают реальные UID и PID, которые процесс получает при создании, и эффективные идентификаторы Effective User ID (EUID) и Effective Group ID (EGID), которые процесс может получить при выполнении системного вызова exec. Права процесса по доступу к файлам определяются эффективными идентификаторами. UID, GID, EUID и EGID можно получить с помощью системных вызовов getuid, getgid, geteuid и getegid соответственно.

Единственный механизм создания новых процессов в системе UNIX – системные вызовы fork и exec. Создание процесса производится в два этапа:

Порождается новый процесс с помощью системного вызова fork. Порожденный (дочерний, сыновний) процесс является точной копией порождающего (родительского) процесса. Эти процессы отличаются:

идентификаторами PID и PPID;

значением, возвращаемым fork();

тем, что дочерний процесс имеет пустое множество сигналов, ожидающих доставки.

Функция fork() возвращает “отцу” PID “сына”, а “сыну” – нулевое значение. Это позволяет организовать ветвление и выполнять в порождающем и порожденном процессах различные действия. Других возможностей (кроме проверки идентификаторов) их различить нет, поскольку в обоих процессах выполняется один и тот же код. В простейшем случае такого ветвления вполне достаточно для организации параллельной работы, и второй этап создания процесса не требуется.

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

Новый образ процесса наследует часть атрибутов старого, в частности:

идентификаторы PID, PPID, UID, GID;

эффективные идентификаторы EUID, EGID, если для исполняемого файла не установлено обратное;

ограничения, накладываемые на процесс;

текущий и корневой каталоги;

маску создания файлов и сигнальную маску;

множество посланных, но не переданных процессу сигналов;

терминальную линию;

дескрипторы открытых файлов, для которых не установлен атрибут FD_CLOEXEC=1;

таймер процесса и часть временных параметров;

 

Пример программы, порождающей новый процесс:

Листинг process.c

 

#include <stdio.h>

#include <unistd.h>

main()

{

int childPID;

if ((childPID = fork()) == -1 )

{

perror ("Can 't fork");

exit(1);

}

else if (childPID == 0 )

{

/* child */

      printf ("Сhild:PID=%d,PPID=%d\n",

      getpid(),getppid());

      exit(0);

}

else

{

/*parent*/

      printf ("Parent:childPID=%d, PID=%d\n, PPID=%d\n",

      childPID,getpid(),getppid());

      sleep (10);

      exit(0);

}

}

Каждый из процессов сообщает свои идентификаторы, и на этом выполнение программы прекращается.

При загрузке нового образа процесса функциями exec изменяются эффективные идентификаторы EUID и EGID, если для исполняемого файла установлены атрибуты Set User ID (SUID) и(или) Set Group ID (SGID). Процесс принимает ЕUID и(или) ЕGID владельца файла. Это необходимо, в частности, чтобы пользователь мог выполнить программу, принадлежащую привилегированному пользователю и требующую соответствующих прав доступа к файлам: для этого достаточно установить атрибут SUID в исполняемом файле.

Системный вызов exec на программном уровне представлен функциями, отличающимися списками параметров и механизмами их передачи и сведенными в табл. 5.

Списки и массивы аргументов и массивы переменных окружения (составляющих среду процесса) заканчиваются значениями NULL. В списке (массиве) аргументов должны быть заданы по крайней мере два значения: argv[0] – имя выполнимого файла и NULL, завершающее список. Переменные окружения задаются указателями на строки в формате:

<имя переменной>=<значение>

Последний элемент массива также должен иметь значение NULL.

Приведенные функции фактически являются надстройками над системным вызовом execve. Аргументы и среда программы используются стандартным для языка С образом.

 

Таблица 5.

Функции семейства exec

 

Функция Комментарий
int execl(const char *path, const char *arg, ...) Задается полный путь к файлу и список аргументов
int execlp(const char *file, const char *arg, ...) Задается имя файла и список аргументов
int execle(const char *path, const char *arg, ..., const char **env) Задается полный путь к файлу, список аргументов и массив строк переменных окружения
int execv(const char *path, const char **argv) Задается полный путь к файлу и массив аргументов
int execvp(const char *file, const char **argv) Задается имя файла и массив аргументов
int execve(const char *path, const char **argv, const char **env) Задается полный путь к файлу, массив аргументов и массив строк переменных окружения

 

Как правило, вызовы fork и exec используются совместно. Такой вызов часто называют fork-and-exec. Для синхронизации выполнения процессов могут использоваться функции wait и waitpid, позволяющие контролировать выполнение процессов-потомков. Выполнение процесса блокируется, пока (в зависимости от вызова и его параметров) не завершится кто-либо из процессов-потомков, либо кто-либо из группы процессов-потомков, либо конкретный процесс. Для синхронизации возможно использовать также аппарат сигналов.

 


Дата добавления: 2019-02-12; просмотров: 288; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!