Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Файлы заголовков




Использование текстовых файлов заголовков (header-файлов), которые вставляются в текст программы на языке Си с помощью директивы #include препроцессора Си, является традиционной техникой программирования на языке Си в среде ОС UNIX, обеспечивающей синтаксическую правильность использования библиотечных функций (в том числе и системных вызовов) в прикладной программе. Ранее файлы заголовков, главным образом, содержали определения типов и символических констант (символические константы - это константы, которым сопоставлены имена посредством директивы #define препроцессора Си), используемых в интерфейсах соответствующих библиотечных функций. Корректное применение файлов заголовков позволяло программистам не заботиться о правильности типов данных, используемых при обращении к библиотечным функциям и обработке их результатов.

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

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

В последнее время файлы заголовков содержат большое количество операторов условной компиляции, относящихся большей частью к определению символических констант. Дело в том, что в зависимости от версии операционной системы (имеется в виду версии одной линии ОС UNIX, например, UNIX System V) значения констант, используемых с одним и тем же смыслом, часто меняются. Конечно, прикладная программа не должна зависеть от таких изменений. Наличие операторов условной компиляции внутри файла заголовков разрешает эту проблему.

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

 

Большинство системных файлов заголовков находится в каталогах /usr/include или /usr/include/sys. Используя малознакомую функцию, обращайтесь к справочной системе man. Помимо описания формата функции, возвращаемого значения, особых ситуаций содержится указание, какие файлы заголовков следует включить в программу.

Если в директиве #include имя файла включено в угловые скобки (<>), то поиск файла будет производиться в общепринятых каталогах хранения файлов заголовков. Если же имя файла заключено в кавычки, используется явно указанное (абсолютное либо относительное) имя файла.

В ранних версиях UNIX большинство системных вызовов использовали стандартные типы для своих параметров (int, double, char и т.п.). В современных версиях часто используются т.н. производные типы ( или примитивы системных данных). Производные типы переменных имеют окончание _t, большинство их определено в файле <sys/types.h>, а их назначение заключается в улучшении переносимости программ. Например:

int creat(const char*path, int mode); #устаревший вариант

int creat(const char*path, mode_t mode); #современный вариант

Этот набор системных типов гарантированно неизменен в контексте системных вызовов (и сегодня, и 10 лет спустя, и т.д.)

 

Как мы уже говорили (во Введении), среда программирования UNIX определяется несколькими стандартами. Стандарты ANCI C, POSIX.1 и XPG4 определяют названия и назначения файлов заголовков (40 шт). Приведем некоторые из них (те, которые наверняка потребуются в лаб. и курс. работах):

 

<dirent.h>   определения структур данных каталога, прототипы функций для работы с каталогами opendir(3), readdir(3) и т.д.
<errno.h> определения кодов ошибок
<fcntl.h>   определения констант и структур данных для работы с файлами, прототипы системных вызовов для работы с файлами
<limits.h>   определения констант, определяющие различные виды ограничений: min и max значения основных типов данных, max число файловых связей, vax длина имени файла и т.п.
<stdarg.h> определения для поддержки списков аргументов разной длины
<stddef.h> стандартные определения (например size_t)
<stdio.h> определения стандартной библиотеки ввода/вывода
<stdlib.h> определения стандартной библиотеки
<unistd.h> определения системных символьных констант, прототипы большинства системных вызовов
<utime.h>   определения структур данных и прототип системного вызова utime(2) для работы с временными характеристиками файла (временем доступа и модификации)
<sys/stat.h>   определения структур данных и прототипы системных вызовов, необходимых для получения сведений о файле
<sys/types.h> определения примитивов системных данных (производных типов)

 

 

Еще ряд функций файловой системы (синтаксис по-прежнему в тексте лекции не приводится)

Работа со ссылками

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

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

Для удаления жесткой связи служит системный вызов ulink(2). Эту функцию, например, вызывает команда rm(1) Системный вызов, явно удаляющий файл, отсутствует. (Почему? Вопрос для отличнико в).

 

ISO C предоставляет для удаления файла функцию remove(3) (прототип в <stdio.h>)., которая пригодна для любой ОС, поддерживающей ISO c, а не только для UNIX. Для удаления файлов она использует ulink(2), а для удаления каталогов – системный вызов rmdir(2), обсуждаемый ниже. Хотя технически это не системный вызов, возвращаемое значение в том же стиле: 0 в случае успеха, -1 в случае ошибки и errno при этом содержит код ошибки.

 

Символическая связь позволяет косвенно адресовать другой файл файловой системы. Системный вызов symlink(2) служит для этой цели (команда ls –s его использует). Функция open(2), принимая в качестве аргумента имя символической связи, на самом деле откроет целевой файл. Такая особенность называется следованием символической связи. Некоторые системные вызовы обладают этим свойством, а некоторые – нет)

Следуют:

access(2), chdir(2),chmod(2), chown(2), creat(2), exec(2), link(2), mkdir(2), open(2), stat(2)

Не следуют:

lchown(2), mknode(2), readlink(2), rename(2), lstat(2), unlink(2)

 

Чтобы прочитать содержимое файла-ссылки используется системный вызов readlink(2)

 

 

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

В современных версиях 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).




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


Дата добавления: 2014-01-11; Просмотров: 762; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.011 сек.