ОС Linux. Мет. указ. к лаб. работам
..pdfДля удобства рассмотрения ссылочных файлов создайте в домашнем каталоге новый подкаталог, например, с именем 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 |