Хотя обычные файлы автоматически растут при записи данных в их конец, у системы нет способа автоматически усекать файлы, когда данные в конце не нужны. К тому же, как система может узнать, что данные стали излишними? Это находится в компетенции процесса — извещать систему о том, когда файл можно сократить до определенной точки.
#include <unistd.h>
int truncate(const char *pathname, size_t length);
int ftruncate(int fd, size_t length);
Размер
файла устанавливается равным
length
, и все данные, находящиеся за новым концом файла, теряются.
Если
length
больше текущего размера файла, то файл увеличивается до заданного размера (по возможности используя "дырки"), хотя такое поведение и не гарантируется POSIX, поэтому на него нельзя полагаться при написании переносимых программ.
11.2.7. Синхронизация файлов
Когда программа пишет данные в файл, обычно они сохраняются в кэше ядра до тех пор, пока оно не выполнит запись на физический носитель (такой как жесткий диск), но ядро возвращает управление программе сразу после того, как данные скопируются в кэш. Это обеспечивает значительный рост производительности, так как позволяет ядру определять порядок записи на диск и объединять несколько записей в одну блочную операцию. Однако в случае системного сбоя у такой технологии есть два существенных недостатка, которые могут оказаться важными. Например, приложение, которое предполагает, что данные сохранены в базе данных прежде, чем был сохранен индекс для этих данных, может не справиться со сбоем, явившимся результатом того, что индекс был просто обновлен.
Есть несколько механизмов, которые может использовать приложение, чтобы дождаться записи данных на физический носитель. Флаг
O_SYNC
, описанный ранее в этой главе, при каждой операции записи в файл вызывает блокирование вызывающего процесса до тех пор, пока носитель не будет действительно обновлен. Хотя это, конечно, работает, все же такой подход не является достаточно аккуратным. Обычно приложения не нуждаются в том, чтобы все операции были синхронизированы, гораздо чаще они нуждаются в том, чтобы гарантировать, что некий набор операций завершился перед тем, как может быть начат другой набор операций. Системные вызовы
fsync
и
fdatasync
обеспечивают такую семантику.
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
Оба системных вызова приостанавливают приложение до тех пор, пока в файл
fd
не будут записаны все данные,
fsync
также ожидает обновления информации в inode файла, подобной времени доступа (информация inode для файлов перечислена в табл. 11.3). Однако ни один из этих вызовов не гарантирует записи на неразрушимое устройство хранения. Современные дисковые приводы имеют большие собственные кэши, поэтому сбой питания может привести к тому, что некоторые данные, сохраненные в кэше, будут потеряны.
11.2.8. Прочие операции
Файловая модель Linux достаточно хорошо поддерживает стандартизацию большинства файловых операций через обобщенные функции наподобие
read
и
write
(например, запись в программный канал выполняется так же, как запись в файл на диске). Однако некоторые устройства поддерживают операции, которые плохо моделируются такой абстракцией. Например, терминальные устройства, представленные как устройства символьные, нуждаются в представлении метода изменения скорости терминала, и приводы CD-ROM, представленные как блочные устройства, нуждаются в том, чтобы знать, кода они должны воспроизводить аудиодорожки, чтобы помочь увеличить производительность работы программистов.
Все эти разнообразные операции доступны через единственный системный вызов —
ioctl
(сокращение для "I/O control" — управление вводом-выводом), прототип которого показан ниже.
#include <sys/ioctl.h>
int ioctl(int fd, int request, ...);
Хотя часто он применяется следующим образом:
int ioctl (int fd, int request, void *arg);
Всякий раз когда используется
ioctl
, его первый аргумент — это файл, с которым выполняются манипуляции, а второй аргумент указывает операцию, которая должна быть выполнена. Последний аргумент обычно представляет собой указатель на нечто, но на что именно, а так же точная семантика возвращаемого кода зависит от типа файла
fd
и типа запрошенной операции. Для некоторых операций
arg
— длинное целое вместо указателя; в этих случаях обычно применяется приведение типов. В нашей книге есть множество примеров применения
ioctl
, и вам нет нужды заботиться об
ioctl
до тех пор, пока вы не доберетесь до них.
11.3. Запрос и изменение информации inode
11.3.1. Поиск информации inode
В начале этой главы информационный узел файла (inode) был представлен как структура данных, которая отслеживает информацию о файле, независимо от представления ее для процесса. Например,
размер файла является константой в любой момент времени — он не изменяется для разных процессов, которые имеют доступ к этому файлу (сравните это с текущей позицией в файле, которая уникальна для каждого вызова
open
, а не свойство самого файла). Linux предлагает три способа чтения информации inode.
#include <sys/stat.h>
int stat(const char *pathname, struct stat *statbuf);
int lstat (const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
Первая версия,
stat
возвращает информацию
inode
для файла, на который осуществляется ссылка через
pathname
, следуя всем символическим ссылкам, которые она представляет. Если вы не хотите следовать символическим ссылкам (например, чтобы проверить, не является ли само имя такой ссылкой), то используйте вместо этого
lstat
. Последняя версия,
fstat
, возвращает
inode
, на который ссылается текущий открытый файловый дескриптор. Все три системных вызова заполняют структуру
struct stat
, на которую указывает параметр
statbuf
, информацией о файловом inode. В табл. 11.3 описана информация, доступная в
struct stat
.
Таблица 11.3. Члены структуры
struct stat
Тип
Поле
Описание
dev_t
st_dev
Номер устройства, на котором находится файл.
ino_t
st_ino
Номер файлового on-disk inode. Каждый файл имеет номер on-disk inode, уникальный в пределах устройства, на котором он расположен. То есть пара (
st_dev
,
st_ino
) представляет собой уникальный идентификатор файла.
mode_t
st mode
Режим файла. Сюда включена информация о правах доступа и типе файла.
nlink_t
st_nlink
Количество путевых имен, ссылающихся на данный inode. Сюда не включаются символические ссылки, потому что они ссылаются на другие имена, а не на inode.
uid_t
st_uid
Идентификатор пользователя, владеющего файлом.
gid_t
st_gid
Идентификатор группы, владеющей файлом.
dev_t
st_rdev
Если файл — символьное или блочное устройство, это задает старший (major) и младший (minor) номера файла. Чтобы получить информацию о членах и макросах, которые манипулируют этим значением, обратитесь к обсуждению mknod далее в этой главе.
off_t
st size
Размер файла в байтах. Это определено только для обычных файлов.
unsigned long
st_blksize
Размер блока в файловой системе, хранящей файл.
unsigned long
st_blocks
Количество блоков, выделенных файлу. Обычно
st_blksize * st_blocks
— это немного больше, чем
st_size
, потому что некоторое пространство в конечном блоке не используется. Однако для файлов с "дырками"
st_blksize * st_blocks
может быть заметно меньше, чем
st_size
.
time_t
st_atime
Время последнего доступа к файлу. Обновляется при каждом открытии файла или модификации его inode.
time_t
st_mtime
Время последней модификации файла. Обновляется при изменении данных файла.
time_t
st_ctime
Последнее время изменения файла или его inode, включая владельца, группу, счетчик связей и так далее.