Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ОС Linux. Мет. указ. к лаб. работам

..pdf
Скачиваний:
26
Добавлен:
21.05.2015
Размер:
271.51 Кб
Скачать

Для удобства рассмотрения ссылочных файлов создайте в домашнем каталоге новый подкаталог, например, с именем new и перейдите в него.

mkdir new cd new

Создайте два текстовых файла (например, с именами txtl, txt2), ис­ пользуя команду cat.

cat >txtl первый файл Ctrl+z

Здесь устройством ввода служит клавиатура, вывод перенаправлен в файл с именем txtl. Как и для большинства команд подобного рода, стандартным устройством ввода будет служить клавиатура, а стандарт­ ным устройством вывода - монитор. Знак «»> указывает, что вывод пере­ направлен в файл с именем txtl. При вводе с клавиатуры можно построчно вводить текст. Каждая строка завершается при нажатии клавиши «Enter». Завершение ввода текста - «Ctrl+z». Таким образом, в файле txtl будет содержаться текст «первый файл». Аналогично можно создать файл txt2.

Просмотрите содержимое каталога командой ls, используя два ключа: i - для вывода inode и l - для использования широкого формата.

ls-i-l

2655 -rw-r--r--1 root root 22 2009-10-12 08:20 txtl 2656 -rw-r--r--1 root root 22 2009-10-12 08:21 txt2

Сделайте жесткую ссылку newl на файл txtl, используя команду ln, и опять просмотрите содержимое каталога.

ln txt1 new1

 

 

 

Is -i -l

 

 

 

2655

-rw-r--r-- 2

root

root 22 2009-10-12 08:20

txt1

2656

-rw-r--r-- 1

root

root 22 2009-10-12 08:21

txt2

2655

-rw-r--r-- 2

root root 22 2009-10-12 08:20

newl

/* Создание канала (добавьте проверку на успешность операции)*/ pipe(fd);

/* Запись всей строки вместе с признаком конца строки в канал */ size = write(fd[l], string, 14);

/* Чтение строки из канала */ size = read(fd[0], resstring, 14);

/* Печать прочитанной строки */ printf("%s\n ",resstring);

/* Закрыть входной и выходной потоки */ close(fd[0]);

close(fdfl]); return 0;

}

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

#include <sys/types.h> #include <unistd.h> #include <stdio.h>

int main(){ intfd[2], result; size_t size;

char resstring[14]; pipe(fd); /* Создаем pipe */

result =fork(); /* Порождаем новый процесс */

10

31

Параметр fd является указателем на массив из двух целых перемен­ ных. При нормальном завершении вызова, в первый элемент массива (fd[0]) будет занесен файловый дескриптор, соответствующий входному потоку данных канала и позволяющий выполнять только операцию чте­ ния, а во второй элемент массива (fd[1]) будет занесен файловый дескрип­ тор, соответствующий выходному потоку данных и позволяющий выпол­ нять только операцию записи. Системный вызов возвращает значение равное 0 при нормальном завершении и значение -1 при возникновении ошибок.

Системный вызов организует выделение области памяти под буфер и указатели и заносит информацию, соответствующую входному и выход­ ному потокам данных, в два элемента таблицы открытых файлов, связы­ вая тем самым с каждым каналом два файловых дескриптора. Для выпол­ нения операций передачи данных можно использовать системные вызовы read() и write(). По окончании использования входного или/и выходного потока данных, нужно закрыть соответствующий поток с помощью сис­ темного вызова close(). Необходимо отметить, что, когда все процессы, использующие pipe, закрывают все ассоциированные с ним файловые де­ скрипторы, операционная система ликвидирует pipe.

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

#include <sys/types.h> #include <unistd.h> #include <stdio.h>

int main(){ intfd[2J; size_t size;

char string[] = "Hello, world!"; char resstring[14];

Обратите внимание на то, что у файла newl тот же inode (2655) и совпадает время создания. В третьей колонке число 2 показывает коли­ чество ссылок на файл.

Измените содержимое файла newl и убедитесь, что файлы txtl и newl - это один и тот же файл на диске, только у него два имени. Также проверьте, что жесткая ссылка не нарушается при перемещении файлов по каталогам, а также то, что нельзя делать жесткие ссылки на каталоги.

Символьную ссылку можно делать как на файлы, так и на каталоги. Например, можно сделать ссылку на файл txt2.

ln -s 2 new2

Результатом будет новый файл со своим inode и временем создания. Количество ссылок на файл txt2 не изменится. Размер файла соответствует длине имени файла, на который делается ссылка. Обращение к файлу new2 будет переадресовано на файл txt2.

2887 Irwxrwxrwx 1 root root 1 2009-10-12 08:23 new2 -> 2

Можно даже сделать символьную связь из каталога на сам этот каталог. Рассмотрите, что произойдет при многократном переходе в этот каталог.

ln -s ../new newdir

Проверьте глубину символьных связей в данной системе. Найдите места расположения ссылок на компилятор с языка Си и расположение остальных каталогов и файлов, относящихся к компилятору.

3. Монтирование файловых систем.

Видимое пользователю дерево каталогов образуется по следующей схеме. Одну из доступных файловых систем ядро считает корневой. Эта файловая система монтируется на корневой каталог «/», в результате чего ее содержимое становится доступно в виде дерева каталогов, растущего непосредственно из корневого. Любой из каталогов текущего дерева мо­ жет служить точкой монтирования другой файловой системы. После вы­ полнения команды вида mount файл-дырка каталог, например,

30

11

mount /dev/hda5 /usr

содержимое файловой системы, лежащей на разделе, которому соответст­ вует файл-дырка, становится доступно в виде дерева, растущего из ката­ лога /usr. Если до монтирования, в /usr были какие-то файлы и подкатало­ ги, принадлежащие корневой файловой системе, они становятся недос­ тупны до выполнения соответствующей команды umount. Все устройства, файловые системы которых могут быть смонтированы, в виде файладырки можно увидеть в каталоге /dev, например, для устройства CD-ROM это будет /dev/cdrom. Точки монтирования, то есть каталоги, в которые будут монтироваться файловые системы, расположены в каталоге /mnt, например, /mnt/cdrom. Список всех файловых систем, которые монтиру­ ются по ходу начальной загрузки, обычно лежит в файле /etc/fstab. Обра­ тите внимание в этом файле на тип каждой из файловых систем. Напри­ мер, содержимое файла fstab может выглядеть так.

Помимо дисковых файловых систем (hda - могут быть разделами од­ ного и того же диска) здесь можно встретить файловые системы в памяти (временные - sys или proc - представление структуры процессов в виде дерева каталогов). Некоторые устройства (например, CD-ROM) помечены noauto в знак того, что при старте их монтировать не надо. Запись в fstab служит только напоминанием, какое именно устройство какой точке монтирования соответствует.

В командной строке проведите монтирование устройств. Найдите, куда монтируется devx_301.sfs.

программы мы указываем ее полное имя с путем от корневого каталога /bin/cat (аргумент с индексом 0). Первое слово в командной строке должно совпадать с именем запускаемой программы. Второе слово в командной строке - это имя файла, содержимое которого распечатывается. */

(void) execle("/bin/cat", "/bin/cat", "l.c", 0, envp);

/* Это сообщение печатается только при возникновении ошибки */ printf("Error on program start\n ");

exit(-l); return 0;

}

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

4. Межпроцессное взаимодействие через канал pipe.

Наиболее простым способом для передачи информации с помощью потоковой модели между различными процессами или даже внутри одно­ го процесса в операционной системе UNIX является pipe (канал, труба, конвейер). Pipe имеет и другое название - именованный канал. Прочитан­ ная информация немедленно из него удаляется и не может быть прочитана повторно. Pipe представляет собой область памяти, недоступную пользо­ вательским процессам напрямую. Она зачастую организована в виде кольцевого буфера (хотя существуют и другие виды организации). По бу­ феру при операциях чтения и записи перемещаются два указателя, соот­ ветствующие входному и выходному потокам. При этом выходной указа­ тель никогда не может перегнать входной и наоборот. Для создания ново­ го экземпляра такого кольцевого буфера внутри операционной системы используется системный вызов pipe(). Прототип системного вызова

#include <unistd.h> int pipe(int *fd);

12

29

Аргумент file является указателем на имя файла, который должен быть загружен. Аргумент path - это указатель на полный путь к файлу, который должен быть загружен. Аргументы arg0, argN представляют собой указатели на аргументы командной строки. Заметим, что аргумент arg0 должен указывать на имя загружаемого файла. Аргумент argv пред­ ставляет собой массив из указателей на аргументы командной строки. На­ чальный элемент массива должен указывать на имя загружаемой про­ граммы, а заканчиваться массив должен элементом, содержащим указа­ тель NULL. Аргумент envp содержит переменные окружения, установленные в операционной системе для данного пользователя. Переменные ок­ ружения можно взять те же, что передаются в функцию main()

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

Задание: напишите программу, которая распечатывает на экране значения переменных окружения.

Поскольку системный контекст процесса при вызове ехес() остает­ ся практически неизменным, большинство атрибутов процесса, доступ­ ных пользователю через системные вызовы (PID, UID, GID, PPID и дру­ гие), после запуска новой программы также не изменяется.

Важно понимать разницу между системными вызовами fork() и ехес(). Системный вызов fork() создает новый процесс, у которого поль­ зовательский контекст совпадает с пользовательским контекстом процес­ са-родителя. Системный вызов ехес() изменяет пользовательский контекст текущего процесса, не создавая новый процесс.

Задание: Рассмотрите пример использования системного вызова exec(). Проанализируйте результат.

#include <sys/types.h> #include <unistd.h> #include <stdio.h>

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

{/* Программа запускает команду "cat 1.c", которая должна вывести со­ держимое данного файла на экран. Для функции execle в качестве имени

28

4. Системные вызовы для работы с файлами.

Из курса программирования на языке Си известны функции работы с файлами из стандартной библиотеки ввода-вывода, такие как fopen(), fread(), fwrite() и т.д. Эти функции входят как неотъемлемая часть в стан­ дарт ANSI на язык Си и позволяют программисту получать информацию из файла или записывать ее в файл. Но операции, определяемые функция­ ми стандартной библиотеки ввода-вывода, не являются потоковыми опе­ рациями, так как каждая из них требует наличия некоторой структуры пе­ редаваемых данных. В операционной системе UNIX эти функции пред­ ставляют собой надстройку (сервисный интерфейс) над системными вы­ зовами, осуществляющими прямые потоковые операции обмена ин­ формацией между процессом и файлом и не требующими никаких зна­

ний о том, что она содержит. В системных вызовах open(), read(), write()и close(), которы понятие файлового дескриптора. Информация о файлах, используемых процессом, входит в состав его системного контекста и хранится в его

блоке управления - РСВ. В операционной системе UNIX можно упрощен­ но полагать, что информация о файлах, с которыми процесс осуществляет операции потокового обмена, наряду с информацией о потоковых линиях связи, соединяющих процесс с другими процессами и устройствами вво­ да-вывода, хранится в некотором массиве, получившем название табли­ цы открытых файлов или таблицы файловых дескрипторов. Индекс элемента этого массива, соответствующий определенному потоку вводавывода, получил название файлового дескриптора для этого потока. Та­ ким образом, файловый дескриптор представляет собой небольшое целое неотрицательное число, которое для текущего процесса в данный момент времени однозначно определяет некоторый действующий канал вводавывода. Некоторые файловые дескрипторы на этапе старта любой про­ граммы ассоциируются со стандартными потоками ввода-вывода. Так, например, файловый дескриптор О соответствует стандартному потоку ввода, файловый дескриптор 1 - стандартному потоку вывода, файловый

13

дескриптор 2 — стандартному потоку для вывода ошибок. В нормальном интерактивном режиме работы стандартный поток ввода связывает процесс с клавиатурой, а стандартные потоки вывода и вывода оши­ бок — с текущим терминалом.

Системный вызов open предназначен для выполнения операции от­ крытия файла и, в случае ее удачного осуществления, возвращает файло­ вый дескриптор открытого файла. Прототип системного вызова open

#include <fcntl.h>

int open(char *path, int flags);

int open (char *path, int flags, int mode);

Параметр path является указателем на строку, содержащую полное или относительное имя файла. Параметр flags может принимать одно из следующих трех значений:

OR_DONLY - если над файлом в дальнейшем будут совершаться только операции чтения;

O_WRONLY- если над файлом в дальнейшем будут осуществляться только операции записи;

O_RDWR - если над файлом будут осуществляться и операции чте­ ния, и операции записи.

Каждое из этих значений может быть скомбинировано посредством операции «побитовое или (|)» с одним или несколькими флагами:

O_CREAT- если файла с указанным именем не существует, он дол­ жен быть создан;

O_EXCL - применяется совместно с флагом O_CREAT. При совме­ стном их использовании и существовании файла с указанным име­ нем, открытие файла не производится и констатируется ошибочная ситуация;

O_NDELAY - запрещает перевод процесса в состояние «ожидание» при выполнении операции открытия и любых последующих опера­ циях над этим файлом;

полнение либо до завершения процесса-родителя, либо до того момента, когда родитель получит эту информацию. Процессы, находящиеся в со­ стоянии закончил исполнение, в операционной системе UNIX принято называть процессами-зомби (zombie, defunct).

Задание: напишите программу, создающую процесс-зомби. Выведи­ те на экран его pid и посмотрите в другом окне эмулятора терминала, как обозначается данный процесс при вызове команды просмотра состояния процессов (команда ps).

3. Системный вызов ехес()

Для изменения пользовательского контекста процесса (загрузки новой программы в системный контекст текущего процесса) применяется системный вызов ехес(). Вызов ехес() заменяет пользовательский кон­ текст текущего процесса содержимым некоторого исполняемого файла и устанавливает начальные значения регистров процессора (в том числе устанавливает программный счетчик на начало загружаемой программы). Этот вызов требует для своей работы задания имени исполняемого файла, аргументов командной строки и параметров окружающей среды. Для осуществления вызова программист может воспользоваться одной из шес­ ти функций: execlp(), execvp(), execl() и, execv(), execle(), execve(), отли­ чающихся друг от друга представлением параметров, необходимых для работы системного вызова ехес(). Прототипы вызовов имеют вид

#include <unistd.h>

int execlp(const char *flle, const char *arg0,... const char *argN, (char *)NULL);

int execvp(const char *file, char *argv[]);

int execl(const char *path, const char *arg0,... const char *argN, (char *)NULL);

int execv(const char *path, char *argv[]);

int execle(const char *path, const char *arg0, ... const char *argN, (char *)NULL, char * envp[]);

int execve(const char *path, char *argv[], char *envp[]).

14

27

else

if (pid == 0) { ... /*ребенок */ ... } else {... /*родитель */ ... }

Задание: измените предыдущую программу с fork() так, чтобы роди­ тель и ребенок совершали разные действия (какие - не важно).

Существует два способа корректного завершения процесса в про­ граммах, написанных на языке Си. Первый способ мы использовали до сих пор: процесс корректно завершался по достижении конца функции main() или при выполнении оператора return в функции main(), второй способ применяется при необходимости завершить процесс в каком-либо другом месте программы. Для этого используется функция exit() из стан­ дартной библиотеки функций для языка Си. При выполнении этой функ­ ции происходит сброс всех частично заполненных буферов ввода-вывода с закрытием соответствующих потоков, после чего инициируется систем­ ный вызов прекращения работы процесса и перевода его в состояние за­ кончил исполнение. Возврата из функции в текущий процесс не проис­ ходит и функция exit() процессу-родителю ничего не возвращает. Значе­ ние параметра функции exit(), то есть кода завершения процесса, переда­ ется ядру операционной системы и затем может быть получено процес­ сом, породившим завершившийся процесс. При этом используются только младшие 8 бит параметра, так что для кода завершения допустимы значе­ ния от 0 до 255 (или от -128 до +127). По соглашению, код завершения О означает безошибочное завершение процесса. На самом деле при дости­ жении конца функции main() также неявно вызывается эта функция со значением параметра 0. Прототип функции exit() имеет вид

#include <stdlib.h> void exit (int status);

Если процесс завершает свою работу раньше, чем его родитель, и родитель явно не указал, что он не хочет получать информацию о ста­ тусе завершения порожденного процесса, то завершившийся процесс не исчезает из системы окончательно, а остается в состоянии закончил ис-

O_APPEND - при открытии файла и перед выполнением каждой операции записи (если операция разрешена) указатель текущей пози­ ции в файле устанавливается на конец файла;

O_TRUNC - если файл существует, уменьшить его размер до 0, с со­ хранением существующих атрибутов файла, кроме времени послед­ него доступа к файлу и времени его последней модификации.

Параметр mode устанавливает атрибуты прав доступа различных ка­ тегорий пользователей к новому файлу при его создании. Он обязателен, если среди заданных флагов присутствует флаг O_CREAT, и может быть опущен в противном случае. Этот параметр задается как сумма следую­ щих восьмеричных чисел X00+0X0+00X. Первое число определяет права доступа владельца, второе - группы, третье - других пользователей. Каж­ дая восьмеричная цифра, обозначенная как «X», представляет собой триа­ ду двоичных цифр, соответствующих правам доступа rwx.

Системные вызовы read и write предназначены для осуществления потоковых операций ввода (чтения) и вывода (записи) информации над каналами связи, описываемыми файловыми дескрипторами, т.е. для фай­ лов pipe, FIFO и socket. Прототипы системных вызовов read и write.

#include <sys/types.h> #include <unistd.h>

size_t read(int fd, void *addr, size_t nbytes); size_t write (int fd, void *addr, size_t nbytes);

Параметр fd является файловым дескриптором созданного ранее по­ токового канала связи. Параметр addr представляет собой адрес области памяти, начиная с которого будет браться информация для передачи или размещаться принятая информация. Параметр nbytes, определяет количе­ ство передаваемых (для вызова write) или принимаемых байтов (для вызо­ ва read). Возвращаемое значение - количество реально принятых (read) или переданных (write) байтов.

26

15

Системный вызов close(fd) производит действие, обратное по отно­ шению к вызову open. Возвращаемое значение - 0, если операция прошла успешно, и -1 в случае возникновения ошибки.

Системные вызовы stat, fstat и lstat служат для получения информа­ ции об атрибутах файла. Прототипы системных вызовов:

#include <sys/stat.h> #include <unistd.h>

int stat(char *filename, struct stat *buf); int fstat(intfd, struct stat *buf);

int lstat(char *filename, struct stat *buf);

Системный вызов stat читает информацию об атрибутах файла, на имя которого указывает параметр filename, и заполняет ими струк­ туру, расположенную по адресу buf. Имя файла должно быть полным, либо должно строиться относительно того каталога, который является текущим для процесса, совершившего вызов. Если имя файла относит­ ся к файлу типа «связь», то читается информация об атрибутах файла,

на который указывает символическая связь.

Системный вызов lstat идентичен системному вызову stat за одним исключением: если имя файла относится к файлу типа «связь», то читает­ ся информация о самом файле типа «связь».

Системный вызов fstat идентичен системному вызову stat, только файл задается не именем, а своим файловым дескриптором (естественно, что файл к этому моменту должен быть открыт).

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

Структура stat в различных версиях UNIX может быть описана поразному. В Linux она содержит следующие поля:

struct stat {

dev_t st_dev; /* устройство, на котором расположен файл */ ino_t st_ino; /* номер индексного узла для файла */

/* При успешном создании нового процесса с этого места псевдопараллельно начинают работать два процесса: старый и новый */

(void)fork();

/* Узнаем идентификаторы текущего и родительского процесса (дальней­ шие действия будут выполняться в каждом из процессов) */

pid = getpid(); ppid = getppid();

/* Перед выполнением следующего выражения значение переменной а

вобоих процессах равно 0 */

а++;

/* Печатаем значения PID, PPID и вычисленное значение переменной а (в каждом из процессов) */

printf("My pid = %d, my ppid = %d, result = %d\n", (int)pid, (int)ppid, a); return 0;

}

Задание: измените программу так, чтобы увеличение значения пе­ ременной а и вывод на экран монитора выполнялось в цикле. Количество повторений выберите такое, чтобы за один квант времени, выделенный процессу, программа выполнила только часть цикла.

Для того чтобы после возвращения из системного вызова fork() про­ цессы могли определить, кто из них является ребенком, а кто родителем, и, соответственно, по-разному организовать свое поведение, системный вызов возвращает в них разные значения. При успешном создании нового процесса процессу-родителю возвращается положительное значение, рав­ ное идентификатору процесса-ребенка. А процессу-ребенку возвращается значение 0. Если по какой-либо причине создать новый процесс не уда­ лось, то системный вызов вернет в инициировавший его процесс значение -1. Таким образом, общая схема организации различной работы процессаребенка и процесса-родителя выглядит так:

pid =fork();

 

if (pid ==-1) { ...

/* ошибка */ ...}

16

25

зывать процессом-ребенком (child process). Процесс-ребенок является почти полной копией родительского процесса. Но у порожденного про­ цесса изменяются значения следующих параметров:

идентификатор процесса;

идентификатор родительского процесса;

время,

оставшееся

до получения сигнала SIGALRM;

 

 

• сигналы,

ожидавшие доставки родительскому процессу, не будут

 

 

доставляться порожденному процессу.

При однократном системном вызове fork возврат из него может про­ изойти дважды: один раз в родительском процессе, а второй раз в порож­ денном процессе. Если создание нового процесса произошло успешно, то в порожденном процессе системный вызов вернет значение 0, а в роди­ тельском процессе - положительное значение, равное идентификатору процесса-ребенка. Если создать новый процесс не удалось, то системный вызов вернет в инициировавший его процесс отрицательное значение. По­ сле выхода из системного вызова оба процесса продолжают выполнение регулярного пользовательского кода, следующего за системным вызовом.

Задание: Наберите следующую программу, откомпилируйте ее и за­ пустите на исполнение (лучше всего это делать не из оболочки тс, так как она не очень корректно сбрасывает буферы ввода-вывода). Проанализи­ руйте полученный результат.

/* Пример создания

нового процесса с одинаковой работой процессов

ребенка и родителя */

#include <sys/types.h>

#include

<unistd.h>

#include <stdio.h> int main()

{

pid_t pid, ppid; int a = 0;

mode_t st_mode; /* тип файла и права доступа к нему */ nlink_t st_nlink; /* счетчик числа жестких связей */

uid_t st_uid; /* идентификатор пользователя владельца */ gid_t st_gid; /* идентификатор группы владельца */

dev_t st_rdev; /* тип устройства для специальных файлов устройств*/ off J st_size; /* размер файла в байтах */

unsigbed long st_blksize; /* размер блока для файловой системы */ unsigned long st_blocks; /* число выделенных блоков */

time_t st_atime;

/*

время последнего доступа к файлу */

time_t st_mtime;

/* время последней модификации файла */

time_t st_ctime;

/*

время создания файла */

}

 

 

Для определения типа файла можно использовать следующие логи­ ческие макросы, применяя их к значению поля st_mode:

S_ISLNK(m) - файл типа «связь»?

S_ISREG(m) - регулярный файл?

S_ISDIR(m) - каталог?

S_ISCHR(m) - специальный файл символьного устройства?

S_ISBLK(m) — специальный файл блочного устройства?

S_ISFIFO(m) - файл типа FIFO?

S_ISSOCK(m) - файл типа "socket"?

Младшие 9 бит поля st_mode определяют права доступа к файлу по­ добно тому, как это делается в маске создания файлов текущего процесса.

Системные вызовы для создания связи: link, symlink, unlink.

#include <unistd.h>

int link(char *pathname, char *linkpathname); int symlink(char *pathname, char *linkpathname); int unlink(char *pathname);

Задание: рассмотренные ранее по пунктам 1-3 действия над файла­ ми реализуйте в программе на языке Си с помощью системных вызовов.

24

17

5. Системные вызовы для работы с каталогами (директориями).

#include <dirent.h>

DIR *opendir (char *dirname); struct dirent *readdir(DIR *dirp); struct dirent *rewindir(DIR *dirp); int closedir(DIR *dirp);

Функция opendir служит для открытия потока информации для ка­ талога. Тип данных DIR представляет собой некоторую структуру дан­ ных, описывающую такой поток. Функция opendir позиционирует поток на первой записи каталога. С точки зрения программиста в этом интер­ фейсе каталог представляется как файл последовательного доступа, над которым можно совершать операции чтения очередной записи и пози­ ционирования на начале файла. Чтение очередной записи из каталога осуществляет функция readdir(), одновременно позиционируя указатель на начале следующей записи (если она, конечно, существует). Для опера­ ции нового позиционирования на начале каталога (при необходимости) применяется функция rewinddir(). После окончания работы с каталогом его необходимо закрыть с помощью функции closedir(). Тип данных struct dirent представляет собой некоторую структуру данных, описывающую одну запись в каталоге. Поля этой записи сильно варьируются от одной файловой системы к другой, но одно из полей всегда присутствует в ней. Это поле char d_name[] неопределенной длины, не превышающей значе­ ния NAME_MAX+1, которое содержит символьное имя файла, завершаю­ щееся символом конца строки. Данные, возвращаемые функцией readdir, переписываются при очередном вызове этой функции для того же самого потока каталога.

Задание: используя системные вызовы для работы с файлами и кaталогами, реализуйте программу на языке Си, позволяющую сделать рас­ печатку содержимого каталога, указав тип файла и права доступа (аналог команды ls с ключом -l). Задача повышенной сложности - рекурсивная распечатка содержимого вложенных каталогов.

ЛАБОРАТОРНАЯ РАБОТА №3 Управление процессами

1. Идентификатор процесса

Данные ядра, находящиеся в контексте ядра процесса, не могут быть прочитаны процессом непосредственно. Для получения информации о них процесс должен совершить соответствующий системный вызов. Значение идентификатора текущего процесса может быть получено с по­ мощью системного вызова getpid(), а значение идентификатора родитель­ ского процесса для текущего процесса - с помощью системного вызова getppid(). Прототипы этих системных вызовов и соответствующие типы данных описаны в системных файлах <sys/types.h> и <unistd.h>. Систем­ ные вызовы не имеют параметров и возвращают идентификатор текуще­ го процесса и идентификатор родительского процесса, соответственно. Прототипы системных вызовов

#include <sys/types.h> #include <unistd.h> pid_t getpid(void) ; pid_t getppid(void);

Тип данных pid_t является синонимом для одного из целочисленных типов языка Си.

Задание: В качестве примера использования системных вызо­ вов getpid() и getppid() самостоятельно напишите программу, печатаю­ щую значения PID и PPID для текущего процесса. Запустите ее несколько раз подряд. Посмотрите, как меняется идентификатор текущего процесса. Объясните наблюдаемые изменения.

2. Создание нового процесса

В операционной системе UNIX новый процесс может быть порожден единственным способом - с помощью системного вызова fork(). Процесс, который инициировал системный вызов fork, принято называть родитель­ ским процессом (parent process). Вновь порожденный процесс принято на-

18

23

/* Открываем файл */

fd = open("mapped.dat", 0_RDWR | O_CREAT, 0666);

/* Вычисляем будущую длину файла для записи в него 1000 структур */ length = 1000*sizeof(struct А);

/*Увеличиваем длину файла с помощью вызова ftruncate(). */ ftruncate(fd, length);

/* Файл отображаем с его начала (offset = 0) и до конца (length = длине файла). */

ptr = (struct А *) mmap (NULL, length,

PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0);

/* Файловый дескриптор нам более не нужен, и мы его закрываем */ close(fd);

/* Если отобразить файл не удалось - сообщаем об ошибке */ if (ptr == MAP_FAILED )

{printf("Mappingfailed! \n"); exit(2); }

/* В цикле заполняем образ файла числами */ tmpptr = ptr;

for(i = 1; i <=100000; i++) {tmpptr->f= i;

tmpptr->f2 = i*i; tmpptr++;}

/* Прекращаем отображать файл в память, записываем содержимое ото­ бражения на диск и освобождаем память. */

munmap((void *)ptr, length); return 0;

}

Задания: Модифицируйте приведенную программу так, чтобы она отображала файл mapped.dat, записанный программой из примера. Опре­ делите размер файла, который можно отобразить в память. Проверьте, можно ли отобразить в одну и ту же область памяти несколько файлов. Реализуйте указанные действия другими средствами языка Си.

6.Системные вызовы для работы с файлами, отображаемыми в память.

Сточки зрения программиста работа с такими файлами выглядит следующим образом:

Отображение файла из пространства имен в адресное пространство процесса происходит в два этапа: сначала выполняется отображение

вдисковое пространство, а уже затем из дискового пространства

вадресное. Поэтому вначале файл необходимо открыть, используя обычный системный вызов ореn().

. Вторым этапом является отображение файла целиком или частично из дискового пространства в адресное пространство процесса. Для этого используется системный вызов mmap(). Файл после этого можно закрыть, выполнив системный вызов close(), так как необхо­ димая информация о расположении файла на диске сохраняется в других структурах данных при вызове mmap().

#include <sys/types.h> #include <unistd.h> #include <sys/mman.h>

void *mmap (void *start, size_t length, int prot, int flags, int fd, off_t offset);

Параметр fd является файловым дескриптором файла, отображаемо­ го в адресное пространство (возвращает системный вызов open()). Значе­ ние параметра start чаще всего выбирается равным NULL, позволяя опера­ ционной системе самой выбрать начало области адресного пространства,

вкоторую будет отображен файл.

Впамять будет отображаться часть файла, начиная с позиции внутри его, заданной значением параметра offset - смещение от начала файла

вбайтах, и длиной, равной значению параметра length (тоже в байтах). Значение параметра length может и превышать реальную длину от пози­ ции offset до конца существующего файла. На поведении системного вызова это никак не отразится, но в дальнейшем при попытке досту­ па к ячейкам памяти, лежащим вне границ реального файла, возникнет

22

19