::Главная страница :: С++/Си :: Статьи Файл "errorx.c"

Файловый ввод и вывод

Мы начнем наше обсуждение системы UNIX описанием функций доступных для файлового ввода/вывода - открытие файла, чтение файла, запись в файл и так далее. Большинство операций файлового ввода вывода могут быть выполнены с использованием только пяти функций: open, read, write, lseek и close. Мы также рассмотрим к каким эффектам приводит буферизация в функциях read и write.

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

Файловые дескрипторы

Все открытые файлы ссылаются к ядру через так называемые файловые дескрипторы. Файловый дескриптор - это неотрицательное целое число. Когда мы открываем существующий файл и создаем новый файл, ядро возвращает процессу файловый дескриптор. Когда мы хотим прочитать или записать файл, мы идентифицируем последний с помощью файлового дескриптора, который был получен нами с помощью функций open() или creat(). Мы передаем файловый дескриптор в качестве аргумента функциям read и write.

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

Стандарт POSIX.1 заменил "магические" числа 0,1,2 символическими константами STDIN_FILENO, STDOUT_FILENO и STDERR_FILENO соответственно. Эти константы определены в заголовочном файле <unistd.h>

Файловые дескрипторы могут принимать значения от 0 до OPEN_MAX. Старые версии UNIX имели верхний предел до 19, позволяя одному процессу открывать до 20 файлов. Сейчас это значение увеличено. У меня, к примеру, возможно открыть до 1024 файлов на процесс.

Функция open

Файл может быть открыт или создан с помощью функции open.

 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open (const char *pathname, int oflag, ... );
    Return: файловый дескриптор если OK, -1 при ошибке

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

Аргумент pathname - это имя открываемого или создаваемого файла. Существует довольно большое количество опций для этой функции, которые указываются с помощью аргумента oflag. Этот аргумент формируется посредством операции побитового ИЛИ над одним или более констант из заголовка <fcntl.h>.

 
O_RDONLY открыть только для чтения
O_WRONLY открыть только для записи
O_RDWR открыть только для чтения и записи

Одна и только одна из этих трех констант может быть указана. Следующие константы являются необязательными:
O_APPEND Добавление данных в конец фала при каждой записи
O_CREAT Создать файл если он не существует. Эта опция требует наличия третьего аргумента в функции open, так называемый mode (режим создания), который указывает биты прав доступа нового файла. Когда мы будем рассматривать биты прав доступа, мы посмотрим каким образом назначать mode.
O_EXCL Генерирует ошибку если указан флаг O_CREAT и такой файл уже существует. Это проверка то существует ли файл с данным именем или нет. Если файла нет - он создается.
O_TRUNC Если файл существует и, если файл успешно открыт либо для записи, либо для чтения - записи, его длина усекается до 0.
O_NOCTTY Если pathname указывает на терминальное устройство, то этот файл нельзя использовать, как управляющий терминал вызывающего процесса
O_NONBLOCK Указывает, что все последующие операции чтения из файла и записи в файл должны выполняться без блокировки. Мы позже рассмотрим это.
O_SYNC Заставляет каждую операцию write ожидать физического завершения операции ввода/вывода

Файловый дескриптор, возвращаемый функцией open, имеет наименьшее не использованное значение. Это свойство используется некоторыми приложениями для открытия файлов на стандартном ввода, стандартном выводе и стандартной ошибке. Например, приложение может закрыть стандартный вывод (как правило дескриптор 1) и затем открыть какой-либо другой файл, зная, что он будет открыть на файловом дескрипторе 1.

Функция creat

Новый файл также может быть создан с помощью функции creat.

 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat (const char *pathname, mode_t mode);
    Return: файловый дескриптор если OK, -1 при ошибке

Замечу, что эта функция эквивалентна следующему:

open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

Мы рассмотрим как использовать параметр mode, когда будем описывать права доступа к файлам. У этой функции есть один недостаток. Догадайтесь какой? Она открывает файл только для записи. До того как была предоставлена новая версия функции open, если мы хотели создать временный файл, записать в него информацию, а потом прочитать обратно, то мы должны вызвать creat, close и затем open. Лучший способ - это использовать новую функцию open:

open (pathname, O_RDWR | O_CREAT | O_TRUNC, mode);

Функция close

Открытый файл закрывается посредством функции close.

 
#include <unistd.h>
int close (int filedes);
    Return: 0 если OK, -1 при ошибке

Закрытие файла также снимает любые блокировки на уровне записей, которые процесс мог иметь на файл.

Когда процесс завершается, все открытые файлы, автоматически закрываются ядром. Многие программы пользуются этим преимуществом и не вызывают close, чтобы закрыть файл.

Функция lseek

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

 
#include <sys/types.h>
#include <unistd.h>
off_t lseek (int filedes, off_t offset, int whence);
    Return: новое файловое смещение если OK, -1 при ошибке

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

 
Значение whence Базовый адрес
SEEK_CUR Текущее файловое смещение
SEEK_SET Начало файла
SEEK_END Конец файла

Интерпретация offset зависит от значения аргумента whence.

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

off_t currpos;
currpos=lseek(fd,0,SEEK_CUR);

Эта технология также используется для определения того, способен ли файл к изменению файлового смещения: если файловый дескриптор ссылается на канал или буфер FIFO, то lseek возвращает -1 и устанавливает переменную errno в значение EPIPE.

Пример 2.1


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

int main()
{
  if (lseek(STDIN_FILENO,0,SEEK_CUR)==-1)
 printf("cannot seek\n");
   else
 printf("seek OK\n");

 exit(0);
}
Программа 2.1 проверяет, способен ли стандартный ввод к изменению своего файлового смещения.

Если мы запустим эту программу в интерактивном режиме, мы получим:


$ test_seek < /etc/motd
seek OK
$ cat </etc/motd | test_seek
cannot seek
$ test_seek < /var/spool/cron/FIFO
cannot seek

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

lseek только записывает текущее значение файлового смещения в ядро - при этом не происходят никакие операции ввода/вывода. Это смещение используется последующими функциями read и write.

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

Пример 2.2


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "errorx.h"

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
char  buf1[] = "abcdefghij";
char  buf2[] = "ABCDEFGHIJ";

int main ()
{
  int fd;

  if((fd=creat("file.hole",FILE_MODE)) <0)
    err_sys("creat error");

  if(write(fd,buf1,10)!=10)
    err_sys("buf1 write error");

  if(lseek(fd,40,SEEK_SET)==-1)
    err_sys("lseek error");

  if(write(fd,buf2,10)!=10)
    err_sys("buf2 write error");

  exit(0);

}
Программа 2.2 создает файл с дырой в нем
Не забудте скопмилировать эту программу совместно с файлом ошибок errorx.c, котоый мы написали в первых выпусках.

Запустим эту программу:


$ file_hole
$ ls -l file.hole   проверяем его размер
-rw-r-r--    1   baba_yaga     50   Nov  04  05:50   file.hole

$ od -c file.hole

0000000  a  b  c   d  e  f   g   h  I  j  \0  \0  \0  \0  \0  \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0  \0  \0  \0  \0  \0
0000040 \0 \0 \0 \0 \0 \0 \0 \0  A B  C  D  E  F    G  H
0000060  I   J
0000062

Мы используем команду od, чтобы посмотреть действительное содержимое файла. Флаг -c говорит ей печатать содержимое как символы. Мы можем видеть, что 30 не записанных байт в середине, обратно читаются как нули.

Семи цифровые числа в начале каждой строки это байтовые смещения в восьмеричной системе счисления.

Функция read

Данные читаются из открытого файла с помощью функции read.
#include <unistd.h>
ssize_t read (int filedes, void *buff, size_t nbytes);
   Return: количество прочитанных байт, 0 - если конец файла, -1 при ошибке

Если read успешна, то она возвращает количество прочитанных байт. Если встретился конец файла - возвращается нуль.

Существует несколько случаев в которых количество действительно прочитанных байт меньше чем количество запрошенных:

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

Стандарт POSIX.1 определил новый примитивный тип данных ssize_t, который означает знаковое целое. Третий аргумент - это беззнаковое 16 битное целое, и позволяет читать вплоть до 65534 байт за раз.

Функция write

Данные записываются в открытый файл с помощью функции write.
#include <unistd.h>
ssize_t write (int filedes, const void *buff, size_t nbytes);
   Return: количество записанных байт если OK, -1 при ошибке

Возвращаемое значение обычно равно аргументу nbytes, в противном случае - произошла ошибка. Как правило причиной ошибки служит либо заполнение диска, либо превышение лимита на размер для данного процесса.

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

Пример 2.3


#include <unistd.h>
#include "errorx.h"

#define BUFFSIZE   8192

int main ()
{
    int n;
    char buf[BUFFSIZE];

    while ( (n=read(STDIN_FILENO,buf,BUFFSIZE)) >0)
         if(write(STDOUT_FILENO, buf, n)!=n)
   err_sys("write error");

    if(n<0)
 err_sys("read error");
    exit(0);
}
Программа 2.3 Копируем стандартный ввод на стандартный вывод

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

Тематические ссылки
Ваша ссылка Ваша ссылка

Обмен кнопками, ведение статистики, реклама.