Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лек_1_10_Интерфейс с файловой системой.doc
Скачиваний:
1
Добавлен:
21.09.2019
Размер:
158.72 Кб
Скачать

Файлы, отображаемые в виртуальную память

В современных версиях UNIX у процессов появилась возможность работать с содержимым обычных файлов не с помощью системных вызовов read, write и lseek, а с помощью обычных операций чтения из памяти и записи в память. Другими словами после надлежащего системного вызова процесс имеет возможность отобразить участки файла в собственное адресное пространство Этот прием был базовым в историческом предшественнике ОС UNIX - операционной системе Multics.

После открытия файла следует выполнить системный вызов mmap(2), действие которого состоит в том, что создается сегмент разделяемой памяти, ассоциированный с открытым файлом, и автоматически подключается к виртуальной памяти процесса (подробнее о разделяемой памяти будем говорить в последующих лекциях). После этого процесс может читать из нового сегмента (реально будут читаться байты, содержащиеся в файле) и писать в него (реально все записи отображаются в файл). При закрытии файла, либо при завершении процесса, либо при вызове в программе munmap(2) соответствующий сегмент автоматически отключается от виртуальной памяти процесса и уничтожается, если только файл не подключен к виртуальной памяти некоторого другого процесса. Реальное обновление файла (если в него осуществлялась запись) будет произведено ядром согласно алгоритмам управления виртуальной памятью. Обновление файла можно производить принудительно с помощью функции msync(3)1

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

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

Владение файлами

Владелец-пользователь и владелец–группа могут быть изменены программно с помощью системных вызовов chown(2), fchown(2), lchown(2). Все три вызова работают одинаково, за исключением ситуации, когда файлом является символическая связь (lchown(2) не следует символической связи, здесь и далее на это косвенно указывает символ ‘l”). В двух функциях к файлу происходит обращение по имени, а в одной – по файловому дескриптору. Стандарт POSIX.1 определяет, что сокрытие файлов разрешено только суперпользователю, а владельца-группу можно изменить только в том случае, если пользователь, запустивший программу, является владельцем файла и входит в новую группу.

В случае успешного изменения владельцев файла биты SUID и SGID сбрасываются, если процесс, вызвавший chown(2) не обладает правами суперпользователя.

Права доступа

Когда стандартные утилиты создают файлы, они по умолчанию используют права доступа –rw-rw-rw- (или 0666). Биты дополнительных разрешений не должны использоваться при первоначальном создании файла – лучше всего не пробовать, а изменить права доступа сразу после его создания. Права доступа к файлу могут быть изменены с помощью системных вызовов chmod(2) и fchmod(2), которые различаются только способом указания файла – с помощью имени или дескриптора. Устанавливаются и права доступа и дополнительные биты SUID и SGID.

После каждой установки прав доступа (или других изменениях в файловой системе) в программе можно вызвать библиотечную функцию system(3), которая запустит утилиту ls(1) и отобразит параметры файла (каталога). Например:

#include <stdio.h>

. . .

любой системный вызов или вызов библиотечной функции для работы с файлом my_file;

system (“ls –l my_file”);

Получение информации о файле

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

Структура, возвращаемая системным вызовом stat:

struct stat

{

dev_t st_dev; //устройство для монтируемой Ф.С.

ino_t st_ino; //номер индекса (inode)

mode_t st_mode; //тип файла и права доступа (закодированы в одном поле)

n_link_t st_nlink; //число жестких (прямых) ссылок

uid_t st_uid; //ID владельца

gid_t st_gid; //ID владельца-группы

dev_t st_rdev; //тип устройства (блочное или символьное) (закодирована)

off_t st_size; //общий размер в байтах (из-за «дыр» может не отражать истинный размер)

blksize_t st_blksize; //предпочтительный размер для блока для ввода/вывода

//(почти всегда превышает размер физического сектора, для etx2, ext3 – 4096)

blkcnt_t st_blocks; //число выделенных блоков (в Linux – в единицах 512-байтных блоков)

time_t st_atime; //время последнего доступа (когда последний раз читались данные файла)

time_t st_mtime; //время посл. изменения (когда посл. раз данные записыв. или урезались)

time_t st_ctime; //время посл. изменения индекса (когда посл. раз изменялись служебные

//данные – права доступа или владелец)

};

Все значения времени, связанные с файлом хранятся в секундах, так что при выводе не забудьте применить к ним функцию ctime(3):

#include <sys/types.h>

#include <sys/stat.h>

#include <time.h>

. . .

struct stat s;

lstat(“my_file”, &s);

printf (“atime=%s”, ctime(&s.st_atime);

printf (“mtime=%s”, ctime(&s.st_mtime);

printf (“ctime=%s”, ctime(&s.st_ctime);

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

  • S_ISLNK(s.st_mode) будет true

  • s.st_size содержит размер файла-ссылки(т.е. число байтов в имени указываемого файла)

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

readlink(имя_ссылки, буфер, мах_число_копируемых символов)ж

count=readlink(filename, buf, PATH_MAX)

if (count != s.st_size) { . . . /*обработать ошибку*/}

buf[count]=’0’; /* закрыть buf нулевым байтом, что бы работать со строкой */

Определение типа файла

В поле st_mode закодирован тип файла и права доступа к нему. В <sys/stat.h> описан ряд макросов, с помощью которых можно определить тип файла. Эти макросы возвращают true или false при использовании с полем st_mode (В GNU/Linux эти макросы возвращают 1 для true и 0 для false).

У каждого из 6-ти типов файлов имеется свой макрос:

#define S_IFMT 0170000 /*Маска для выделения типа файла*/

#define S_IFDIR 0040000 /*Каталог*/

#define S_IFCHR 0020000 /*Специальный символьный*/

#define S_IFBLK 0060000 /*Специальный блочный*/

#define S_IFREG 0100000 /*Обычный файл*/

#define S_IFIFO 0010000 /*Именованный канал*/

#define S_IFLNK 0120000 /*Символическая ссылка*/

#define S_IFSOCK 0140000 /*Сокет*/

Пример.

struct stat s;

char filename[PATH_MAX]; /* PATH_MAX] – <из limits.t> */

. . . /*поместить имя файла в filename*/

if (stat(filename, &s) < 0)

. . . /*обработать ошибку, хотя вряд ли она здесь может быть */

if (S_IFREG(s.st_mode)) . . . /* Если это обычный файл, то … корректно */

if (S_IFREG(s.st_mode) ==1) . . /* не корректно, т.к. POSIX определяет лишь ненулевое */.

/* значение для true и нулевое для false*/

Сведения об устройствах

Когда истинно S_ISBLK(s.st_mode) или S_ISCHR(s.st_mode), сведения об устройстве находятся в поле s.st_dev.

POSIX не определяет значение этого поля, .т.к. предполагалось его использование и на UNIX-системах, и на не-UNIX системах. Традиционно файлы устройств кодируют в переменной типа dev_t старший и младший номера устройства. Старший номер – тип устройства (дисковый привод, ленточный привод, диск SCSI, диск IDE,…), младший номер – различают устройства данного типа.

Команда ls –l для устройств вместо размера файла отображает старший и младший номера (5 и 6 столбцы). Посмотрите каталог для жесткого диска /dev/hda: ls –l /dev/had

/dev/hda – имя диска в целом; /dev/hda1, /dev/hda2, . . . – имена разделов внутри диска. У них у всех общий номер устройства (3), но разные младшие номера устройств (0,1,2, . . .).

Команда ls –l /dev/null выдаст информацию о фиктивном устройстве. Оно является символьным (его ст. и мл. номера - 1 и 3).

Блочные и символьные устройства могут иметь один и тот же старший номер устройства, но они не связаны между собой. Оба номера устройства можно извлечь из переменной типа dev_t с помощью функций или макросов major() и minor(), определенных в <sys/sysmacros.h>.

Функция makedev() выполняет обратную работу. Она принимает значения двух номеров и кодирует их в значение типа dev_t.

Любое приложение, работающее с иерархиями файлов должно уметь различать различные типы файлов. Например утилита ls, утилита find, различные архиваторы, и т.д.

s.st_mode&=S_IFMT; /* отбросить всю информацию кроме типа файла */

if (S_IFCHR(s.st_mode)) /* Если это файл символьного устройства */

devtype=”char”;

else if ((S_IFBLK(s.st_mode)) /* Если это файл блочного устройства */

devtype=”block”;

else { printf( “%s is not a block or charactr device”, filename); exit(1);}

printf(“%s: major - %d, minor - %d \n”, devtype, major(s.st_rdev),minor(s.st_rdev);

Максимальное число открытых файлов

Дескриптор файла – целое значение, начинающееся с 0 и растущее до некоторого, установленного ОС предела. Эти числа фактически являются индексами таблицы открытых файлов для каждого процесса. Таблица поддерживается самой ОС (ядром) и недоступна запущенным программам. В современных версиях ОС UNIX размеры таблиц очень большие (например, 1024). Команда ulimit –n печатает это значение. В программе размер таблицы можно получить с помощью системного вызова getdtablesize(2).

Для дескрипторов файлов нет предопределенного типа, и используется стандартный тип int.

Установка длины файла

Системные вызовы truncate(2) и ftruncate(2) устанавливают длину файла. На старых системах они только сокращали длину файла, а на современных версиях могут и увеличивать файл. Если файл сокращается, все данные после новой границы теряются (нельзя снова удлинить файл и найти там прежние данные). Если файл расширяется, то данные между старым и новым концом файла читаются как нули («дыра» аналогична lseek(2)). Используются очень редко.

Переименование файла

Переименование файла концептуально очень просто:

  1. Если новое имя файла обозначает существующий файл, то сначала удалить этот файл.

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

  3. Удалить старое имя (ссылку) для файла.

Ранние версии команд работали именно таким образом. Однако при таком способе операция переименования не является атомарной (т.е. не осуществляется за одну операцию). На сильно загруженной системе злонамеренный пользователь мог бы воспользоваться состоянием гонки (race condition, подменяя оригинальный файл другим.

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

В BSD 4.2 был введен системный вызов rename(2). На системах Linux эта операция является атомарной (помним, что Linux – наследник BSD)

К А Т А Л О Г И

Создание и удаление каталогов

mkdir(2) и rmdir(2)

Обе функции работают на одном уровне каталога за раз. Пример: если /dir1 существует, а /dir1/dir2 нет, то mkdir(“dir1/dir2/dir3”) завершится неудачей. Каждый компонент в пути должен создаваться отдельно.

Смена каталогов

Процесс может изменить свой корневой каталог (!) с помощью системных вызовов chroot(2) или fchroot(2). Различия – в способе указания каталога (с помощью имени или дескриптора). После этого поиск всех адресуемых файлов с абсолютными именами будет производиться, начиная с нового корневого каталога. Где и зачем это может быть нужно? Например, 1) при распаковке архива, созданного с абсолютными именами, в другом месте файловой системы; 2) при работе над проектами, захватывающими существенную часть корневой файловой системы рекомендуется для отладки использовать пробную иерархию; 3) при создании программ-демонов.

Смена текущего каталога в программе производится с помощью системных вызовов chdir(2) и fchdir(2).

Открытие, закрытие и чтение каталогов

(библиотечные функции из #include <dirent.h> , а не системные вызовы!!!)

В s5fs читать содержимое каталогов было просто. С помощью open(2) можно непосредственно читать двоичные структуры по 16 байтов за раз. С появлением новых версий файловых систем (с длинными именами файлов), были созданы и несколько новых функций для абстрактного чтения каталогов. Т.е. эти функции можно использовать для любой Ф.С. с ее особенностями организации каталогов.

Элементы каталога представлены в структуре struct dirent:

struct dirent

{

. . .

ino_t d_ino /* номер файлового дескриптора */

char d_name[. . .] /* имя каталога - последовательность байтов, завершаемая ‘\0’ */

. . .

}

Аналогом типа FILE в <stdio.h> является тип DIR . Это непрозрачный тип, т.е. код приложения не знает, что там внутри, а содержимое DIR используется другими функциями, работающими с каталогами. Функция opendir(3) открывает каталог:

DIR *opendir(const char *name);

если возвращается NULL, каталог не ожет быть открыт для чтения, в errno ‑ код ошибки.

Пример. Фрагмент программы для поиска в каталоге элемента name:

#include <sys/types.h>

#include <dirent.h>

DIR dptr;

struct dirent * d

dptr = opendir (".");

while ((d = readdir (dptr)) != NULL)

if (strcmp (d->d_name, name) == 0)

{ closedir (dptr);

return FOUND;

}

closedir (dptr);

return NOT_FOUND;

Функция closedir(3) является аналогом fclose(3) из <stdio.h>. Она закрывает переменную DIR.

Функция readdir(3) возвращает указатель на структуру struct dirent, представляющую элемент каталога:

struct dirent *readdir(DIR *dir);

Если достигнут конец или произошла ошибка, возвращается NULL.

Пример:

DIR *dptr;

struct dirent *d;

. . . /*здесь открыть каталог и проверить на ошибку */

errno=0;

while ((d=readdir(dptr) !=NULL)

printf(“%8ld %s\n”, ent->d_ino,ent->d_name);

if (errno !=0) {. . . /* при чтении каталога возникла jb,f */ }

Чтобы начать чтение с начала каталога, используется функция rewiddir (3)

Получение текущего каталога

Функция getcwd(3) возвращает путь к текущему каталогу.

Если любой из компонентов каталога, ведущих к текущему каталогу, не допускает чтения или выполнения, getcwd(3) может закончиться неудачей (NULL)

Функции для обхода дерева каталогов

Иногда полезно отметить текущее положение в каталоге, чтобы позже к нему вернуться.

Функция telldir(3) выдает текущую позицию в указанном потоке каталога.

Функция seekdir устанавливает позицию для последующей операции readdir над содержимым каталога. Данная позиция совпадает с той, которая была получена в результате выполнения операции telldir(3).

Значения, которые возвращает telldir, корректны только в том случае, если каталог не сжимался и не расширялся. Такая проблема не возникает в случае версии 5, но может возникнуть для некоторых других типов файловых систем.

Функция nftw(3) из <ftw.h> осуществляет всю работу по прохождению дерева (иерархии) файлов. Ей предоставляется функция, и она вызывает эту функцию для каждого элемента, с которым сталкивается.

Работа с этими функциями не входит ни в лабораторные, ни в курсовую работы, студенты могут ознакомиться с ними самостоятельно.

Многие системные вызовы предназначены непосредственно для использования программистами, которые являются разработчиками GNU/Linux.

Но существуют системные вызовы, которые предназначены лишь для реализации библиотечных функций более высокого уровня. Они никогда не должны вызываться неопсредственно. Пример: getdents(2) – читает несколько элементов каталога в буфер, используется для работы readdir(3)/

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

Форматы исполняемых файлов (кратко)

Виртуальная память процесса состоит из нескольких областей или сегментов. Размер, содержимое и расположение сегментов в памяти определяется как самой программой, так и форматом исполняемого файла. В большинстве современных ОС UNIX используется два стандартных формата исполняемых файлов: COFF (Common Object File Format) и ELF (Executable and Linking Format). Описание форматов этих файлов выходит за рамки курса.

1 На самом деле работа функции msync(3) не так проста и с разными типами отображений она работает по-разному. См. полный синтаксис mmap(2) и полный синтаксис msync(3).