|
::Главная страница :: С++/Си :: Статьи Файл "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.
#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.
#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);
#include <unistd.h> int close (int filedes); Return: 0 если OK, -1 при ошибке |
Закрытие файла также снимает любые блокировки на уровне записей, которые процесс мог иметь на файл.
Когда процесс завершается, все открытые файлы, автоматически закрываются ядром. Многие программы пользуются этим преимуществом и не вызывают close, чтобы закрыть файл.
Каждый открытый файл имеет связанное с ним текущее файловое смещение. Это не отрицательное целое, которое измеряет количество байт от начала файла. Операции чтения и записи обычно начинаются с текущего файлового смещения и это смещение увеличивается на количество байт записанных или прочитанных. По умолчанию, это смещение инициализируется нулем, когда открывается файл, если, конечно, не указан флаг 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); } |
Если мы запустим эту программу в интерактивном режиме, мы получим:
$ 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); } |
Запустим эту программу:
$ 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.
#include <unistd.h> ssize_t read (int filedes, void *buff, size_t nbytes); Return: количество прочитанных байт, 0 - если конец файла, -1 при ошибке |
Если read успешна, то она возвращает количество прочитанных байт. Если встретился конец файла - возвращается нуль.
Существует несколько случаев в которых количество действительно прочитанных байт меньше чем количество запрошенных:
Операция чтения начинается с текущего файлового смещения. До успешного возврата, смещение инкрементируется на количество прочитанных байт.
Стандарт POSIX.1 определил новый примитивный тип данных ssize_t, который означает знаковое целое. Третий аргумент - это беззнаковое 16 битное целое, и позволяет читать вплоть до 65534 байт за раз.
Данные записываются в открытый файл с помощью функции 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); } |
Программа читает со стандартного ввода и пишет на стандартный вывод. Программа полагает, что эти дескрипторы уже открыты до ее выполнения. Программа не вызывает close для входного и выходного файлов. Взамен она пользуется тем фактом, что всякий раз когда процесс завершается он закроет все открытые дескрипторы.
Тематические
ссылки
|
Ваша ссылка | Ваша ссылка |
Обмен кнопками, ведение статистики, реклама. |
|||