• Структура файла в Linux
  • Каталоги
  • Файлы и устройства
  • Системные вызовы и драйверы устройств
  • Библиотечные функции
  • Низкоуровневый доступ к файлам
  • write
  • read
  • open
  • Исходные права доступа
  • Другие системные вызовы для управления файлами
  • Стандартная библиотека ввода/вывода
  • fopen
  • fread
  • fwrite
  • fclose
  • fflush
  • fseek
  • fgetc, getc и getchar
  • fputc, putc и putchar
  • fgets и gets
  • Форматированные ввод и вывод
  • printf, fprintf и sprintf
  • scanf, fscanf и sscanf
  • Другие потоковые функции
  • Ошибки потока
  • Потоки и дескрипторы файлов
  • Ведение файлов и каталогов
  • chmod
  • chown
  • unlink, link и symlink
  • mkdir и rmdir
  • chdir и getcwd
  • Просмотр каталогов
  • opendir
  • readdir
  • telldir
  • seekdir
  • closedir
  • Ошибки 
  • strerror
  • perror
  • Файловая система procfs
  • Более сложные приемы: fcntl и mmap
  • fcntl
  • mmap
  • Резюме
  • Глава 3

    Работа с файлами

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

    Прежде чем перейти к способам обработки файлового ввода/вывода в системе Linux, мы дадим краткий обзор понятий, связанных с файлами, каталогами и устройствами. Для управления файлами и каталогами вам придется выполнять системные вызовы (аналог Windows API в системах UNIX и Linux), но, кроме того, для обеспечения более эффективного управления файлами существует большой набор библиотечных функций стандартной библиотеки ввода/вывода (stdio).

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

    □ файлы и устройства;

    □ системные вызовы;

    □ библиотечные функции;

    □ низкоуровневый доступ к файлу;

    □ управление файлами;

    □ стандартная библиотека ввода/вывода;

    □ форматированный ввод и вывод;

    □ сопровождение файлов и каталогов;

    □ просмотр каталогов;

    □ ошибки;

    □ файловая система /proc;

    □ более сложные приемы —

    fcntl
    и
    mmap
    .

    Структура файла в Linux

    Вы можете спросить: "Зачем вы останавливаетесь на структуре файла? Я уже знаком с ней." Дело в том, что в среде Linux, как и UNIX, файлы особенно важны, поскольку они обеспечивают простую и согласованную взаимосвязь со службами операционной системы и устройствами. В ОС Linux файл — это все что угодно. Ну, или почти все!

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

    open
    ,
    close
    ,
    read
    ,
    write
    и
    ioctl
    .

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

    opendir/readdir
    , и им нет нужды знать подробности реализации каталогов в системе. Позже в этой главе мы вернемся к специальным функциям работы с каталогами.

    Действительно, в ОС Linux почти все представлено в виде файлов или может быть доступно с помощью специальных файлов. И основная идея сохраняется даже, несмотря на то, что существуют в силу необходимости небольшие отличия от известных и любимых вами традиционных файлов. Давайте рассмотрим особые случаи, о которых мы уже упоминали.

    Каталоги

    Помимо содержимого у файла есть имя и набор свойств, или "административная информация", т.е. дата создания/модификации файла и права доступа к нему. Свойства хранятся в файловом индексе (inode), специальном блоке данных файловой системы, который также содержит сведения о длине файла и месте хранения файла на диске. Система использует номер файлового индекса; для нашего удобства структуру каталога также называют файлом.

    Каталог — это файл, содержащий номера индексов и имена других файлов. Каждый элемент каталога — ссылка на файловый индекс; удаляя имя файла, вы удаляете ссылку. (Номер индекса файла можно увидеть с помощью команды

    ln -i
    .) Применяя команду
    ln
    , вы можете создать ссылки на один и тот же файл в разных каталогах.

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

    ls -l
    ) становится равно нулю, индекс файла и блоки данных, на которые он ссылается, больше не используются и помечаются как свободные.

    Файлы помещаются в каталоги, которые могут содержать подкаталоги. Так формируется хорошо знакомая иерархия файловой системы. Пользователь, скажем neil, обычно хранит файлы в исходном (home) каталоге, возможно /home/neil, с подкаталогами для хранения электронной почты, деловых писем, служебных программ и т.д. Имейте в виду, что у многих командных оболочек систем UNIX и Linux есть отличное обозначение для указания начала пути в вашем исходном каталоге: символ "тильда" (~). Для другого пользователя наберите

    ~user
    . Как вы знаете, исходные каталоги пользователей — это, как правило, подкаталоги каталога более высокого уровня, создаваемого специально для этой цели, в нашем случае это каталог /home.

    Примечание

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

    Каталог /home в свою очередь является подкаталогом корневого каталога /, расположенного на верхнем уровне иерархии и содержащего все системные файлы и подкаталоги. В корневой каталог обычно включен каталог /bin для хранения системных программ (бинарных файлов), каталог /etc, предназначенный для хранения системных файлов конфигурации, и каталог /lib для хранения системных библиотек. Файлы, представляющие физические устройства и предоставляющие интерфейс для этих устройств, принято помещать в каталог /dev. На рис. 3.1 показана в качестве примера часть типичной файловой системы Linux. Мы рассмотрим структуру файловой системы Linux более подробно в главе 18, когда будем обсуждать стандарт файловой системы Linux (Linux File System Standard).

    Рис. 3.1 

    Файлы и устройства

    Даже физические устройства очень часто представляют (отображают) с помощью файлов. Например, будучи суперпользователем, вы можете смонтировать дисковод IDE CD-ROM как файл:

    # mount -t iso9660 /dev/hdc /mnt/cdrom

    # cd /mnt/cdrom

    который выбирает устройство CD-ROM (в данном случае вторичное ведущее (secondary master) устройство IDE, которое загружается как /dev/hdc во время начального запуска системы; у устройств других типов будут другие элементы каталога /dev) и монтирует его текущее содержимое как файловую структуру в каталоге /mnt/cdrom. Затем вы перемещаетесь по каталогам компакт-диска как обычно, конечно за исключением того, что их содержимое доступно только для чтения.

    В системах UNIX и Linux есть три важных файла устройств: /dev/console, /dev/tty и /dev/null.

    dev/console

    Это устройство представляет системную консоль. На него часто отправляются сообщения об ошибках и диагностическая информация. У всех систем UNIX есть выделенный терминал или экран для получения сообщений консоли. Иногда он может быть выделенным печатающим терминалом. На современных рабочих станциях и в ОС Linux обычно это активная виртуальная консоль, а под управлением графической среды X Window это устройство станет специальным окном консоли на экране.

    /dev/tty

    Специальный файл /dev/tty — это псевдоним (логическое устройство) управляющего терминала (клавиатуры и экрана или окна) процесса, если таковой есть. (Например, у процессов и сценариев, автоматически запускаемых системой, не будет управляющего терминала, следовательно, они не смогут открыть файл /dev/tty.)

    Там где этот файл, /dev/tty может применяться, он позволяет программе писать непосредственно пользователю независимо от того, какой псевдотерминал или аппаратный терминал он использует. Это полезно при перенаправлении стандартного вывода. Примером может служить отображение содержимого длинного каталога в виде группы страниц с помощью команды

    ls -R | more
    , в которой у программы more есть пользовательская подсказка для каждой новой страницы вывода. Вы узнаете больше о файле /dev/tty в главе 5.

    Учтите, что существует только одно устройство /dev/console, и в то же время может существовать много разных физических устройств, к которым можно обратиться с помощью файла dev/tty.

    /dev/null

    Файл /dev/null — это фиктивное устройство. Весь вывод, записанный на это устройство, отбрасывается. Когда устройство читается, немедленно возвращается конец файла, поэтому данное устройство можно применять с помощью команды cp как источник пустых файлов. Нежелательный вывод очень часто перенаправляется на dev/null.

    $ echo do not want to see this >/dev/null

    $ cp /dev/null empty_file

    Примечание

    Другой способ создания пустых файлов — применение команды

    touch <имя файла>
    , изменяющей время модификации файла или создающей новый файл при отсутствии файла с заданным именем. Хотя она и не очищает содержимое обрабатываемого файла.

    В каталоге /dev можно найти и другие устройства, такие как дисководы жестких дисков и флоппи-дисководы, коммуникационные порты, ленточные накопители, дисководы CD-ROM, звуковые карты и некоторые устройства, представляющие внутреннюю структуру системы. Есть даже устройство /dev/zero, действующее как источник нулевых байтов для создания файлов, заполненных нулями. Для доступа к некоторым из этих устройств вам понадобятся права супер пользователя; обычные пользователи не могут писать программы, непосредственно обращающиеся к низкоуровневым устройствам, таким как накопители жестких дисков. Имена файлов устройств могут быть в разных системах различными. В дистрибутивах ОС Linux обычно есть приложения, выполняемые от имени суперпользователя и управляющие устройствами, которые иначе будут недоступны, например, mount для монтируемых пользователями файловых систем.

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

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

    Системные вызовы и драйверы устройств

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

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

    Для формирования одинакового интерфейса драйверы устройств включают в себя все аппаратно-зависимые свойства. Уникальные аппаратные средства обычно доступны через системный вызов

    ioctl
    (I/O control, управление вводом/выводом).

    Файлы устройств из каталога /dev используются одинаково: они могут открываться, читаться, на них можно записывать и их можно закрывать. Например, один и тот вызов

    open
    , используемый для доступа к обычному файлу, применяется для обращения к пользовательскому терминалу, принтеру или ленточному накопителю.

    К низкоуровневым функциям или системным вызовам, используемым для обращения к драйверам устройств, относятся следующие:

    open
    — открывает файл или устройство;

    read
    — читает из открытого файла или устройства;

    □ 

    write
    — пишет в файл или устройство;

    close
    — закрывает файл или устройство;

    ioctl
    — передает управляющую информацию драйверу устройства.

    Системный вызов

    ioctl
    применяется для аппаратно-зависимого управления (как альтернатива стандартного ввода/вывода), поэтому он у каждого устройства свой. Например, вызов
    ioctl
    может применяться для перемотки ленты в ленточном накопителе или установки характеристик управления потоками последовательного порта. Этим объясняется необязательная переносимость
    ioctl
    с машины на машину. Кроме того, у каждого драйвера определен собственный набор команд
    ioctl
    .

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

    #define
    с определением констант представлены в файлах
    include
    . Нужные для каждого системного вызова дополнения будут подключаться с описаниями отдельных вызовов.

    Библиотечные функции

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

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

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

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

    Библиотечные функции, как правило, описываются в разделе 3 интерактивного справочного руководства и часто снабжаются стандартным файлом директивы

    include
    , связанным с ними, например, файл stdio.h для стандартной библиотеки ввода/вывода.

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

    Рис. 3.2

    Низкоуровневый доступ к файлам

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

    □ 0 — стандартный ввод;

    □ 1 — стандартный вывод;

    □ 2 — стандартный поток ошибок.

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

    write
    .

    write

    Системный вызов

    write
    предназначен для записи из
    buf
    первых
    nbytes
    байтов в файл, ассоциированный с дескриптором
    fildes
    . Он возвращает количество реально записанных байтов, которое может быть меньше
    nbytes
    , если в дескрипторе файла обнаружена ошибка или дескриптор файла, расположенный на более низком уровне драйвера устройства, чувствителен к размеру блока. Если функция возвращает 0, это означает, что ничего не записано; если она возвращает -1, в системном вызове
    write
    возникла ошибка, которая описывается в глобальной переменной
    errno
    ,

    Далее приведена синтаксическая запись.

    #include <unistd.h>

    size_t write(int fildes, const void *buf, size_t nbytes);

    Благодаря полученным знаниям вы можете написать свою первую программу, simple_write.c:

    #include <unistd.h>

    #include <stdlib.h>


    int main() {

     if ((write(1, "Here is some data\n", 18)) != 18)

      write(2, "A write error has occurred on file descriptor 1\n", 46);

     exit(0);

    }

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

    $ ./simple_write

    Here is some data

    $

    И еще одно маленькое замечание: вызов

    write
    может сообщить о том, что записал меньше байтов, чем вы просили. Это не обязательно ошибка. В ваших программах вам придется для выявления ошибок проверить переменную
    errno
    и еще раз вызвать
    write
    для записи оставшихся данных.

    read

    Системный вызов

    read
    считывает до
    nbytes
    байтов данных из файла, ассоциированного с дескриптором файла
    fildes
    , и помещает их в область данных
    buf
    . Он возвращает количество действительно прочитанных байтов, которое может быть меньше требуемого количества. Если вызов
    read
    возвращает 0, ему нечего считывать; он достиг конца файла. Ошибка при вызове заставляет его вернуть -1.

    #include <unistd.h>

    size_t read(int fildes, void *buf, size_t nbytes);

    Программа simple_read.c копирует первые 128 байтов стандартного ввода в стандартный вывод. Она копирует все вводимые данные, если их меньше 128 байтов.

    #include <unistd.h>

    #include <stdlib.h>


    int main() {

     char buffer[128];

     int nread;

     nread = read(0, buffer, 128);

     if (nread == -1)

      write(2, "A read error has occurred\n", 26);

     if ((write(1, buffer, nread)) != nread)

      write(2, "A write error has occurred\n", 27);

     exit(0);

    }

    Если вы выполните программу, то получите следующий результат:

    $ echo hello there | ./simple_read

    hello there

    $ ./simple_read < draft1.txt

    Files

    In this chapter we will be looking at files and directories and how to

    manipulate them. We will learn how to create files, $

    Первое выполнение программы с помощью команды

    echo
    формирует некоторый ввод программы, который по каналу передается в вашу программу. Во втором выполнении вы перенаправляете ввод из файла draft1.txt. В этом случае вы видите первую часть указанного файла, появляющуюся в стандартном выводе.

    Примечание

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

    open

    Для создания дескриптора нового файла вы должны применить системный вызов

    open
    .

    #include <fcntl.h>

    #include <sys/types.h>

    #include <sys/stat.h>


    int open(const char *path, int oflags);

    int open(const char *path, int oflags, mode_t mode);

    Примечание

    Строго говоря, для использования вызова

    open
    вы не должны включать файлы sys/types.h и sys/stat.h в системах, удовлетворяющих стандартам POSIX, но они могут понадобиться в некоторых системах UNIX.

    Не вдаваясь в подробности, скажем, что вызов

    open
    устанавливает путь к файлу или устройству. Если установка прошла успешно, он возвращает дескриптор файла, который может применяться в системных вызовах
    read
    ,
    write
    и др. Дескриптор файла уникален и не используется совместно другими процессами, которые могут в данный момент выполняться. Если файл открыт одновременно в двух программах, они поддерживают отдельные дескрипторы файла. Если они обе пишут в файл, то продолжат запись с того места, где остановились. Их данные не чередуются, но данные одной программы могут быть записаны поверх данных другой. У каждой программы свое представление о том, какая порция файла (каково смещение текущей позиции в файле) прочитана или записана. Вы можете помешать нежелательным накладкам такого сорта с помощью блокировки файла, которая будет обсуждаться в главе 7.

    Имя открываемого файла или устройства передается как параметр

    path
    ; параметр
    oflags
    применяется для указания действий, предпринимаемых при открытии файла.

    Параметр

    oflags
    задается как комбинация обязательного режима доступа к файлу и других необязательных режимов. Системный вызов
    open
    должен задавать один из режимов доступа к файлу, указанных в табл. 3.1.


    Таблица 3.1

    Режим Описание
    О_RDONLY
    Открытие только для чтения
    О_WRONLY
    Открытие только для записи
    O_RDWR
    Открытие для чтения и записи

    Вызов может также включать в параметр

    oflags
    комбинацию (с помощью побитовой операции
    OR
    ) следующих необязательных режимов:

    O_APPEND
    — помещает записываемые данные в конец файла;

    O_TRUNC
    — задает нулевую длину файла, отбрасывая существующее содержимое;

    O_CREAT
    — при необходимости создает файл с правами доступа, заданными в параметре
    mode
    ;

    O_EXCL
    — применяется с режимом
    O_CREAT
    , который гарантирует, что вызывающая программа создаст файл. Вызов
    open
    атомарный, т.е. он выполняется только одним вызовом функции. Это предотвращает одновременное создание файла двумя программами. Если файл уже существует,
    open
    завершится неудачно.

    Другие возможные значения параметра

    oflags
    описаны на странице интерактивного справочного руководства, посвященной open; ее можно найти в разделе 2 руководства (примените команду
    man 2 open
    ).

    Вызов

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

    Существует также системный вызов

    creat
    , стандартизованный POSIX, но он применяется не часто. Он не только создает файл, как можно ожидать; но также и открывает его. Такой вызов эквивалентен вызову
    open
    с параметром
    oflags
    , равным
    O_CREAT|О_WRONLY|O_TRUNC
    .

    Количество файлов, одновременно открытых в любой выполняющейся программе, ограничено. Предельное значение обычно определяется константой

    OPEN_MAX
    в файле limits.h и меняется от системы к системе, но стандарт POSIX требует, чтобы оно было не меньше 16. Это значение само по себе может быть ограничено в соответствии с предельными значениями локальной системы, поскольку программа не сможет всегда иметь возможность держать открытыми такое количество файлов. В ОС Linux это предельное значение можно изменять во время выполнения и поэтому
    OPEN_MAX
    уже не константа. Как правило, ее начальное значение равно 256.

    Исходные права доступа

    Когда вы создаете файл, применяя флаг

    O_CREAT
    в системном вызове open, вы должны использовать форму с тремя параметрами. Третий параметр
    mode
    формируется из флагов, определенных в заголовочном файле sys/stat.h и соединенных поразрядной операцией
    OR
    . К ним относятся:

    S_IRUSR
    — право на чтение, владелец;

    S_IWUSR
    — право на запись, владелец;

    S_IXUSR
    — право на выполнение, владелец;

    S_IRGRP
    — право на чтение, группа;

    S_IWGRP
    — право на запись, группа;

    S_IXGRP
    — право на выполнение, группа;

    S_IROTH
    — право на чтение, остальные;

    S_IWOTH
    — право на запись, остальные;

    S_IXOTH
    — право на выполнение, остальные.

    Например, вызов

    open("myfile", O_CREAT, S_IRUSR|S_IXOTH);

    в результате приведет к созданию файла с именем myfile с правом на чтение для владельца и правом на выполнение для остальных и только с этими правами доступа.

    $ ls -ls myfile

    0 -r-------х 1 neil software 0 Sep 22 08:11 myfile*

    Есть пара факторов, способных повлиять на права доступа к файлу. Во-первых, заданные права применяются, только если файл создается. Во-вторых, на права доступа к созданному файлу оказывает воздействие маска пользователя (заданная командой командной оболочки,

    umask
    ). Значение параметра
    mode
    , заданное в вызове
    open
    , на этапе выполнения объединяется с помощью операции
    AND
    с инвертированной маской пользователя. Например, если заданы маска пользователя 001 и в параметре
    mode
    флаг
    S_IXOTH
    , у созданного файла не будет права на выполнение для "остальных", т.к. маска пользователя указывает на то, что это право не должно предоставляться. Флаги в вызовах
    open
    и
    creat
    являются на самом деле запросами на установку прав доступа. Будут ли предоставлены запрошенные права, зависит от значения
    umask
    во время выполнения.

    umask

    umask
    — это системная переменная, содержащая маску для прав доступа к файлу, которые будут применяться при создании файла. Вы можете изменить значение переменной, выполнив команду
    umask
    , предоставляющую новое значение. Значение этой переменной представляет собой трёхзнаковое восьмеричное число. Каждая цифра — результат объединения с помощью операций
    OR
    значений 1, 2 или 4 (табл. 3.2). Отдельные цифры указывают на права доступа "пользователя", "группы" и "остальных" соответственно.


    Таблица 3.2

    Цифра Значение Смысл
    1 0 Никакие права пользователя не отвергнуты
    4 Право пользователя на чтение отвергается
    2 Право пользователя на запись отвергается
    1 Право пользователя на выполнение отвергается
    2 0 Никакие права группы не отвергнуты
    4 Право группы на чтение отвергается
    2 Право группы на запись отвергается
    1 Право группы на выполнение отвергается
    3 0 Никакие права остальных не отвергнуты
    4 Право остальных на чтение отвергается
    2 Право остальных на запись отвергается
    1 Право остальных на выполнение отвергается

    Например, для блокирования права "группы" на запись и выполнение и права "остальных" на запись переменная umask должна была бы быть следующей (табл. 3.3).


    Таблица 3.3

    Цифра Значение
    1 0
    2 2
    1
    3 2

    Значения каждой цифры объединяются операциями

    OR
    , поэтому для получения значения второй цифры нужна операция
    2 | 1
    , дающая в результате
    3
    . Результирующее значение
    umask
    — 032.

    Когда вы создаете файл с помощью системного вызова open или creat, параметр mode сравнивается с текущим значением переменной

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

    close

    Системный вызов

    close
    применяется для разрыва связи файлового дескриптора
    fildes
    с его файлом. Дескриптор файла после этого может использоваться повторно. Вызов возвращает 0 в случае успешного завершения и -1 при возникновении ошибки.

    #include <unistd.h>

    int close (int fildes);

    Примечание

    В некоторых случаях проверка возвращаемого значения вызова

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

    ioctl

    Системный вызов

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

    #include <unistd.h>

    int ioctl(int fildes, int cmd, ...)

    Вызов

    ioctl
    выполняет операцию, указанную в аргументе
    cmd
    , над объектом, заданным в дескрипторе
    fildes
    . У вызова может быть необязательный третий аргумент, зависящий от функций, поддерживаемых конкретным устройством.

    Например, следующий вызов

    ioctl
    в ОС Linux включает световые индикаторы клавиатуры (LEDs).

    ioctl(tty_fd, KDSETLED, LED_NUM|LED_CAP|LED_SCR);

    Выполните упражнения 3.1 и 3.2.

    Упражнение 3.1. Программа копирования файла

    Теперь вы знаете достаточно о системных вызовах

    open
    ,
    read
    и
    write
    , чтобы написать простенькую программу copy_system.c для посимвольного копирования одного файла в другой.

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

    1. Сначала вам нужно создать тестовый входной файл размером, скажем, 1 Мбайт и именем file.in.

    2. Далее откомпилируйте программу copy_system.c.

    #include <unistd.h>

    #include <sys/stat.h>

    #include <fcntl.h>

    #include <stdlib.h>


    int main() {

     char c;

     int in, out;

     in = open("file.in", O_RDONLY);

     put = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);

     while(read(in, &c, 1) == 1) write(out, &c, 1);

     exit(0);

    }

    Примечание

    Имейте в виду, что строка

    #include <unistd.h>
    должна быть первой, поскольку она определяет флаги, касающиеся соответствия стандарту POSIX и способные повлиять на другие включенные в
    #include
    файлы.

    3. Выполнение программы даст результат, похожий на следующий:

    $ TIMEPORMAT="" time ./copy_system

    4.67user 146.90system 2:32.57elapsed 99%CPU

    ...

    $ ls -ls file.in file.out

    1029 -rw-r--r-- 1 neil users 1048576 Sep 17 10:46 file.in

    1029 -rw------- 1 neil users 1048576 Sep 17 10:51 file.out

    Как это работает

    Вы используете команду

    time
    для определения времени выполнения программы. В ОС Linux переменная
    TIMEFORMAT
    применяется для переопределения принятого по умолчанию в стандарте POSIX формата вывода времени, в который не включено время использования ЦПУ. Как видите, что в этой очень старой системе входной файл file.in размером 1 Мбайт был успешно скопирован в файл file.out, созданный с правами на чтение/запись только для владельца. Копирование заняло две с половиной минуты и затратило фактически все доступное время ЦПУ. Программа так медлительна потому, что вынуждена была выполнить более двух миллионов системных вызовов.

    В последние годы ОС Linux продемонстрировала огромные успехи в повышении производительности системных вызовов и файловой системы. Для сравнения аналогичный тест с применением ядра 2.6 занял чуть менее 14 секунд:

    $ TIMEFORMAT="" time ./copy_system

    2.08user 10.59system 0:13.74elapsed 92%CPU

    ...

    Упражнение 3.2. Вторая версия программы кодирования файла

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

    #include <unistd.h>

    #include <sys/stat.h>

    #include <fcntl.h>

    #include <stdlib.h>


    int main() {

     char block[1024];

     int in, out;

     int nread;

     in = open("file.in", O_RDONLY);

     out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);

     while((nread = read(in, block, sizeof(block))) > 0)

      write(out, block, nread);

     exit(0);

    }

    Теперь испытайте программу, но сначала удалите старый выходной файл.

    $ rm file.out

    $ TIMEFORMAT="" time ./copy_block

    0.00user 0.02system 0:00.04elapsed 78%CPU

    ...

    Как это работает

    Теперь программа выполняется только сотые доли секунды, поскольку ей требуется около 2000 системных вызовов. Конечно, это время очень зависит от системы, но оно показывает, что системные вызовы сопряжены с поддающимися измерению издержками, поэтому их применение стоит оптимизировать.

    Другие системные вызовы для управления файлами

    Существует ряд других системных вызовов, оперирующих низкоуровневыми дескрипторами файлов. Они позволяют программе контролировать использование файла, возвращая информацию о его состоянии,

    lseek

    Системный вызов

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

    #include <unistd.h>

    #include <sys/types.h>

    off_t lseek(int fildes, off_t offset, int whence);

    Параметр

    offset
    применяется для указания позиции, а параметр
    whence
    определяет способ применения
    offset
    и может принимать следующие значения:

    SEEK_SET
    offset
    задает абсолютную позицию;

    SEEK_CUR
    offset
    задается относительно текущей позиции;

    SEEK_END
    offset
    задается относительно конца файла.

    Вызов

    lseek
    возвращает величину параметра
    offset
    в байтах, измеряемую от начала файла, для которого установлен указатель, или -1 в случае неудачного завершения. Тип данных
    off_t
    , применяемый для параметра
    offset
    в операциях поиска, — зависящий от реализации тип
    integer
    (целое), определенный в файле sys/types.h.

    fstat, stat и lstat

    Системный вызов

    fstat
    возвращает информацию о состоянии файла, ассоциированного с открытым дескриптором файла. Эта информация записывается в структуру
    buf
    , адрес которой передается как параметр.

    Далее приведена синтаксическая запись вызовов.

    #include <unistd.h>

    #include <sys/stat.h>

    #include <sys/types.h>

    int fstat(int fildes, struct stat *buf);

    int stat(const char *path, struct stat *buf);

    int lstat(const char *path, struct stat *buf);

    Примечание

    Учтите, что включение файла sys/types.h не обязательное, но мы рекомендуем включать его при использовании системных вызовов, поскольку некоторые из их определений применяют для стандартных типов псевдонимы, которые могут измениться когда-нибудь.

    Родственные функции

    stat
    и
    lstat
    возвращают информацию о состоянии названного файла. Они возвращают те же результаты за исключением того, что файл является символической ссылкой. Вызов
    lstat
    возвращает данные о самой ссылке, а вызов
    stat
    — о файле, на который ссылка указывает.

    Элементы вызываемой структуры stat могут меняться в разных UNIX-подобных системах, но обязательно включают перечисленные в табл. 3.4 элементы.


    Таблица 3.4

    Элемент структуры
    stat
     Описание
    st_mode
    Права доступа к файлу и сведения о типе файла
    st_ino
    Индекс, ассоциированный с файлом
    st_dev
    Устройство, на котором размещен файл
    st_uid
    Идентификатор (user identity) владельца файла
    st_gid
    Идентификатор группы (group identity) владельца файла
    st_atime
    Время последнего обращения
    st_ctime
    Время последнего изменения прав доступа, владельца, группы или объема
    st_mtime
    Время последней модификации содержимого
    st_nlink
    Количество жестких ссылок на файл

    У флагов

    st_mode
    , возвращаемых в структуре
    stat
    , также есть ряд ассоциированных макросов в заголовочном файле sys/stat.h. В эти макросы включены имена флагов для прав доступа и типов файлов и некоторые маски, помогающие проверять специфические типы и права.

    Флаги прав доступа такие же, как в системном вызове

    open
    , описанном ранее. Для флагов типов файла включены следующие имена:

    S_IFBLK
    — блочное устройство;

    S_IFDIR
    — каталог;

    S_IFCHR
    — символьное устройство;

    S_IFIFO
    — FIFO (именованный канал);

    S_IFREG
    — обычный файл;

    S_IFLNK
    — символическая ссылка.

    Для других флагов режима файла включены следующие имена:

    S_ISUID
    — элемент получает setUID при выполнении;

    S_ISGUID
    — элемент получает setGID при выполнении.

    Для масок, интерпретирующих флаги

    st_mode
    , включены следующие имена:

    S_IFMT
    — тип файла;

    S_IRWXU
    — права пользователя на чтение/запись/выполнение;

    S_IRWXG
    — права группы на чтение/запись/выполнение;

    S_IRWXO
    — права остальных на чтение/запись/выполнение.

    Существует ряд макросов, помогающих определить типы файлов. Они просто сравнивают надлежащим образом установленные флаги режима файла с подходящим флагом, типа устройства. К ним относятся следующие:

    S_ISBLK
    — проверка для блочного файла;

    S_ISCHR
    — проверка для символьного файла;

    S_ISDIR
    — проверка для каталога;

    S_ISFIFO
    — проверка для FIFO;

    S_ISREG
    — проверка для обычного файла;

    S_ISLNK
    — проверка для символической ссылки.

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

    struct stat statbuf;

    mode_t modes;

    stat("filename", &statbuf);

    modes = statbuf.st_mode;

    if (!S_ISDIR(modes) && (modes & S_IRWXU) = S_IXUSR)

    ...

    dup и dup2

    Системные вызовы

    dup
    позволяют дублировать дескриптор файла, предоставляя два или несколько разных дескрипторов, обращающихся к одному и тому же файлу. Эта возможность может применяться для чтения и записи в разные части файла. Системный вызов
    dup
    дублирует файловый дескриптор
    fildes
    и возвращает новый дескриптор. Системный вызов
    dup2
    умело копирует один дескриптор файла в другой, задавая дескриптор, применяемый для копии.

    Далее приведена синтаксическая запись для вызовов.

    #include <unistd.h>

    int dup(int fildes);

    int dup2(int fildes, int fildes2);

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

    dup
    в главе 13.

    Стандартная библиотека ввода/вывода

    Стандартная библиотека ввода/вывода (stdio) и ее заголовочный файл stdio.h предоставляют универсальный интерфейс для системных вызовов ввода/вывода нижнего уровня. Библиотека, теперь часть языка С стандарта ANSI, в отличие от системных вызовов, с которыми вы встречались ранее, включает много сложных функций для форматирования вывода и просмотра ввода. Она также обеспечивает необходимые условия буферизации для устройств.

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

    FILE*
    .

    Примечание

    Не путайте эти потоки файлов с потоками ввода/вывода в языке С++ и механизмом STREAMS, описывающим взаимодействие процессов и введенным в системе AT&T UNIX System V Release 3, который не рассматривается в данной книге. Для получения дополнительной информации о средствах STREAMS обратитесь к спецификации X/Open (по адресу http://www.opengroup.org) и руководству по программированию AT&T STREAMS Programming Guide, поставляемому с системой System V.

    Три файловых потока открываются автоматически при старте программы. К ним относятся stdin, stdout и stderr. Эти потоки объявлены в файле stdio.h и представляют вывод, ввод и стандартный поток ошибок, которым соответствуют низкоуровневые файловые дескрипторы 0, 1 и 2.

    В данном разделе мы рассмотрим следующие функции:

    fopen
    ,
    fclose
    ;

    fread
    ,
    fwrite
    ;

    fflush
    ;

    fseek
    ;

    fgetc
    ,
    getc
    , getchar;

    fputc
    ,
    putc
    ,
    putchar
    ;

    fgets
    ,
    gets
    ;

    printf
    ,
    fprintf
    и
    sprintf
    ;

    scanf
    ,
    fscanf
    и
    sscanf
    ;

    fopen
    .

    fopen

    Библиотечная функция f

    o
    pen — это аналог низкоуровневого системного вызова
    open
    . Она используется в основном для файлов и терминального, ввода и вывода. Там, где нужно явное управление устройствами, больше подойдут системные вызовы, поскольку они устраняют потенциальные нежелательные побочные эффекты применения библиотек, например, в случае буферизации ввода/вывода.

    Далее приведена синтаксическая запись функции:

    #include <stdio.h>

    FILE *fopen(const char *filename, const char *mode);

    Функция

    fopen
    открывает файл, заданный в параметре
    filename
    , и ассоциирует с ним поток. Параметр
    mode
    описывает, как файл должен быть открыт. Он задается одной из следующих строк:

    □ "

    r
    " или "
    rb
    " — открыть только для чтения;

    □ "

    w
    " или "
    wb
    " — открыть для записи, укоротить до нулевой длины;

    □ "

    а
    " или "
    ab
    " — открыть для записи, дописывать в конец файла;

    □ "

    r+
    " или "
    rb+
    " или "
    r+b
    " — открыть для изменения (чтение и запись);

    □ "

    w+
    " или "
    wb+
    " или "
    w+b
    " — открыть для изменения, укоротить до нулевой длины;

    □ "

    a+
    " или "
    ab+
    " или "
    а+b
    " — открыть для изменения, дописывать в конец файла. Символ
    b
    означает, что файл бинарный, а не текстовый.

    Примечание

    В отличие от MS-DOS, системы UNIX и Linux не делают различий между текстовыми и бинарными файлами. UNIX и Linux обрабатывают их одинаково с эффективностью обработки бинарных файлов. Важно также учесть, что параметр

    mode
    должен быть строкой, а не символом. Всегда применяйте двойные кавычки, а не апострофы.

    В случае успешного завершения функция

    fopen
    возвращает ненулевой указатель на структуру
    FILE*
    . В случае сбоя она вернет значение
    NULL
    , определенное в файле stdio.h.

    Количество доступных потоков ограничено, как и число дескрипторов файлов. Реальное предельное значение содержится в определенной в файле stdio.h константе

    FOPEN_MAX
    и всегда не менее 8, а в ОС Linux обычно 16.

    fread

    Библиотечная функция

    fread
    применяется для чтения данных из файлового потока. Данные считываются из потока
    stream
    в буфер данных, заданный в параметре
    ptr
    . Функции
    fread
    и
    fwrite
    имеют дело с записями данных. Записи описываются размером
    size
    и количеством передаваемых записей
    nitems
    . Функция возвращает количество записей (а не байтов), успешно считанных в буфер данных. При достижении конца файла может быть возвращено меньше записей, чем
    nitems
    , вплоть до нуля.

    Далее приведена синтаксическая запись функции:

    #include <stdio.h>

    size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);

    Как и в других функциях стандартного ввода/вывода, записывающих данные в буфер, выделять место для данных и проверять ошибки должен программист. См. также функции

    ferror
    и
    feof
    далее в этой главе.

    fwrite

    Интерфейс библиотечной функции

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

    Далее приведена синтаксическая запись функции:

    #include <stdio.h>

    size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream);

    Примечание

    Имейте в виду, что функции

    fread
    и
    fwrite
    не рекомендуется применять со структурированными данными. Частично проблема заключается в том, что файлы, записанные функцией fwrite, могут быть непереносимыми между машинами с разной архитектурой.

    fclose

    Библиотечная функция

    fclose
    закрывает заданный поток stream, заставляя записать все незаписанные данные. Важно применять функцию
    fclose
    , поскольку библиотека stdio будет использовать буфер для данных. Если программе нужна уверенность в том, что все данные записаны, следует вызвать
    fclose
    . Имейте в виду, что функция
    fclose
    вызывается автоматически для всех файловых потоков, которые все еще открыты к моменту нормального завершения программы, но при этом у вас, конечно же, не будет возможности проверить ошибки, о которых сообщает
    fclose
    .

    Далее приведена синтаксическая запись функции:

    #include <stdio.h>

    int fclose(FILE* stream);

    fflush

    Библиотечная функция

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

    Далее приведена синтаксическая запись функции:

    #include <stdio.h>

    int fflush(FILE *stream);

    fseek

    Функция

    fseek
    — это эквивалент для файлового потока системного вызова
    lseek
    . Она задает в
    stream
    позицию для следующей операции чтения этого потока или записи в него. Значения и смысл параметров
    offset
    и
    whence
    такие же, как у ранее описанных одноименных параметров вызова
    lseek
    . Но там, где
    lseek
    возвращает
    off_t
    , функция
    fseek
    возвращает целое число: 0, если выполнилась успешно, и -1 при аварийном завершении с ошибкой, указанной в переменной
    errno
    . Какое поле деятельности для стандартизации!

    Далее приведена синтаксическая запись функции:

    #include <stdio.h>

    int fseek(FILE *stream, long int offset, int whence);

    fgetc, getc и getchar

    Функция

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

    Далее приведена синтаксическая запись функций:

    #include <stdio.h>

    int fgetc(FILE *stream);

    int getc(FILE *stream);

    int getchar();

    Функция

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

    Функция

    getchar
    эквивалентна вызову функции
    getc(stdin)
    и читает следующий символ из стандартного ввода.

    fputc, putc и putchar

    Функция

    fputc
    записывает символ в файловый поток вывода. Она возвращает записанное значение или
    EOF
    в случае аварийного завершения.

    #include <stdio.h>

    int fputc(int с, FILE *stream); int putc(int c, FILE *stream); int putchar(int c);

    Как и в случае функций

    fgetc/getc
    , функция
    putc
    — эквивалент
    fputc
    , но может быть реализована как макрос.

    Функция

    putchar
    — то же самое, что вызов
    putc(с, stdout)
    , записывающий один символ в стандартный вывод. Имейте в виду, что функция
    putchar
    принимает, а функция
    getchar
    возвращает символы как данные типа
    int
    , а не
    char
    . Это позволяет индикатору конца файла (
    EOF
    ) принимать значение -1, лежащее вне диапазона кодов символов.

    fgets и gets

    Функция

    fgets
    читает строку из файла ввода
    stream
    .

    #include <stdio.h>

    char *fgets(char *s, int n, FILE *stream);

    char *gets(char *s);

    Функция

    fgets
    пишет символы в строку, заданную указателем
    s
    , до тех пор, пока не встретится новая строка, либо не будет передано
    n-1
    символов, либо не будет достигнут конец файла. Любая встретившаяся новая строка передается в строку, принимающую символы, и добавляется завершающий нулевой байт
    \0
    . Любой вызов передает максимум
    n-1
    символов, т.к. должен быть вставлен нулевой байт, обозначающий конец строки и увеличивающий общее количество до n байтов.

    При успешном завершении функция

    fgets
    возвращает указатель на строку
    s
    . Если поток указывает на конец файла, она устанавливает индикатор
    EOF
    для потока и возвращает пустой указатель. Если возникает ошибка чтения,
    fgets
    возвращает пустой указатель и устанавливает значение переменной
    errno
    , соответствующее типу ошибки.

    Функция

    gets
    аналогична
    fgets
    за исключением того, что она читает из стандартного ввода и отбрасывает любые обнаруженные обозначения новой строки. Функция добавляет завершающий нулевой байт в принимающую строку.

    Примечание

    Учтите, что функция

    gets
    не ограничивает количество символов, которые могут передаваться, поэтому она может переполнить свой пересылочный буфер. По этой причине вам следует избегать применения этой функции и заменять ее функцией
    fgets
    . Многие проблемы безопасности порождены функциями в программах, сделанных для переполнения буфера тем или иным способом. Это одна из таких функций, поэтому будьте осторожны!

    Форматированные ввод и вывод

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

    printf
    и родственные функции для вывода значений в файловый поток, а также
    scanf
    и другие функции для чтения значений из файлового потока.

    printf, fprintf и sprintf

    Семейство функций

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

    #include <stdio.h>

    int printf(const char *format, ...);

    int sprintf(char *s, const char *format, ...);

    int fprintf(FILE * stream, const char *format, ...);

    Функция

    printf
    выводит результат в стандартный вывод. Функция
    fprintf
    выводит результат в заданный файловый поток
    stream
    . Функция
    sprintf
    записывает результат и завершающий нулевой символ в строку
    s
    , передаваемую как параметр. Эта строка должна быть достаточно длинной, чтобы вместить весь вывод функции.

    У семейства

    printf
    есть и другие члены, обрабатывающие свои аргументы различными способами. См. подробную информацию на страницах интерактивного руководства.

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

    printf
    выбирать и форматировать дополнительные аргументы, передаваемые как параметры. Спецификаторы всегда начинаются с символа
    %
    . Далее приведен простой пример:

    printf("Some numbers: %d, %d, and &d\n", 1, 2, 3);

    Он порождает в стандартном выводе следующую строку.

    Some numbers: 1, 2, and 3

    Для вывода символа

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

    Далее перечислены наиболее часто применяемые спецификаторы преобразований:

    %d
    ,
    %i
    — выводить целое как десятичное число;

    ,
    %x
    — выводить целое как восьмеричное, шестнадцатеричное число;

    — выводить символ;

    %s
    — выводить строку;

    %f
    — выводить число с плавающей точкой (одинарной точности);

    %e
    — выводить число с двойной точностью в формате фиксированной длины;

    %g
    — выводить число двойной точности в общем формате.

    Очень важно, чтобы число и тип аргументов, передаваемых функции

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

    Он может быть равен

    h
    , например,
    %hd
    для обозначения типа
    short int
    (короткие целые), или
    l
    , например,
    %ld
    для обозначения типа
    long int
    (длинные целые). Некоторые компиляторы могут проверять эти установки
    printf
    , но они ненадежны. Если вы применяете компилятор GNU gcc, можно вставить для этого в команду компиляции опцию
    -Wformat
    .

    Далее приведен еще один пример:

    char initial = 'А';

    char *surname = "Matthew";

    double age = 13.5;


    printf("Hello Mr %c %s, aged %g\n", initial, surname, age);

    Будет выводиться следующая информация:

    Hello Mr A Matthew, aged 13.5

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

    Спецификаторы полей задаются в виде чисел, следующих в спецификаторах преобразований непосредственно за знаком

    %
    . В табл. 3.5 приведены дополнительные примеры использования спецификаторов преобразований и результирующий вывод. Для большей ясности мы применяем знак вертикальной черты, чтобы показать границы вывода.


    Таблица 3.5

    Формат Аргумент Вывод
    %10s
    "Hello"
    |     Hello|
    %-10s
    "Hello"
    |Hello     |
    %10d
    1234
    |      1234|
    %-10d
    1234
    |1234      |
    %010d
    1234
    |0000001234|
    %10.4f
    12.34
    |   12.3400|
    %*s
    10, "Hello"
    |     Hello|

    Все приведенные примеры выводятся в поле шириной 10 символов. Обратите внимание на то, что отрицательная ширина поля означает выравнивание элемента по левому краю в пределах поля. Переменная ширина поля обозначается символом "звездочка" (

    *
    ). В этом случае следующий аргумент применяется для задания ширины. Ведущий ноль указывает на вывод элемента с ведущими нулями. В соответствии со стандартом POSIX функция
    printf
    не обрезает поля; наоборот она расширяет поле, чтобы вместить в него аргумент. Например, если вы попытаетесь вывести строку большей длины, чем заданное поле, ширина поля будет увеличена (табл. 3.6).


    Таблица 3.6

    Формат Аргумент Вывод
    %10s
    "HelloTherePeeps"
    |HelloTherePeeps|

    Функции семейства

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

    scanf, fscanf и sscanf

    Семейство функций

    scanf
    действует аналогично функциям группы
    printf
    за исключением того, что эти функции читают элементы из потока и помещают их в переменные, адреса которых им передаются как параметры-указатели. Для управления преобразованиями ввода функции применяют строку format аналогичным образом и используют многие спецификаторы преобразований функций группы
    printf
    .

    #include <stdio.h>

    int scanf(const char *format, ...);

    int fscanf(FILE *stream, const char *format, ...);

    int sscanf(const char *s, const char *format, ...);

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

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

    Строка

    format
    функции
    scanf
    и других функций этого семейства, как и в случае функции
    printf
    , содержит как обычные символы, так и спецификаторы преобразований. Но обычные символы применяются для задания символов, которые должны присутствовать во вводе.

    Рассмотрим простой пример:

    int num;

    scanf("Hello %d", &num);

    Вызов функции

    scanf
    будет успешным, только если следующие пять символов в стандартном вводе —
    Hello
    . Затем, если следующие символы формируют распознаваемое десятичное число, оно будет считано и присвоено переменной
    num
    . Пробел в строке формата при вводе применяется для игнорирования во вводном файле всех пробельных символов (пробелы, табуляции, переводы страницы и переходы на новую строку) между спецификаторами преобразований. Это означает, что вызов,
    scanf
    будет успешным и поместит 1234 в переменную
    num
    в случае следующих двух вариантов ввода.

    Hello    1234

    Hellol234

    Пробельные символы обычно игнорируются во вводе, когда начинается преобразование. Это означает, что строка формата

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

    Примечание

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

    К другим спецификаторам преобразований относятся следующие:

    %d
    — считывание десятичного целого;

    %o
    ,
    %x
    — считывание восьмеричного, шестнадцатеричного целого;

    %f
    ,
    %e
    ,
    %g
    — считывание числа с плавающей запятой;

    %c
    — считывание символа (пробельный символ не пропускается);

    %s
    — считывание строки;

    %[]
    — считывание множества символов (см. последующее обсуждение);

    %%
    — считывание знака
    %
    .

    Как и в случае

    printf
    , у спецификаторов преобразований функции
    scanf
    есть ширина поля, ограничивающая объем ввода. Спецификатор размера (
    h
    для коротких или
    l
    для длинных целых) показывает, короче или длиннее стандартного получаемый аргумент. Таким образом,
    %hd
    обозначает число типа
    short int
    ,
    %ld
    — число типа
    long int
    и
    %lg
    — число с плавающей точкой двойной точности.

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

    Применяйте спецификатор

    %c
    для чтения одиночного символа во вводе. Он не пропускает начальные пробельные символы.

    Используйте спецификатор

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

    Лучше применять спецификатор ширины поля или комбинацию функций

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

    Применяйте спецификатор

    %[]
    для чтения строки, составленной из символов, включенных в множество. Формат
    %[A-Z]
    будет читать строку из прописных букв латинского алфавита. Если в множестве первый символ — знак вставки (
    ^
    ), то спецификатор считывает строку, состоящую из символов, не входящих в множество. Итак, для того чтобы прочитать строку с пробелами, но остановиться на первой запятой, примените спецификатор
    %[^, ]
    .

    Если задана следующая строка ввода:

    Hello, 1234, 5.678, X, string to the end of the line

    приведенный далее вызов

    scanf
    корректно считает четыре элемента:

    char s[256];

    int n;

    float f;

    char c;


    scanf("Hello, %d, %g, %c, %[^\n]", &n, &f, &c, s);

    Функции семейства

    scanf
    возвращают количество успешно считанных элементов. Оно может быть нулевым, если сбой возник при чтении первого элемента. Если достигнут конец ввода прежде, чем найдено соответствие первому элементу, возвращается
    EOF
    . Если в файловом потоке возникает ошибка чтения, устанавливается флаг ошибки потока и тип ошибки задается в переменной
    errno
    . Более подробную информацию см. в разд. "Ошибки потока" далее в этой главе.

    Функция

    scanf
    и другие члены семейства, как правило, не высоко ценятся в основном по трем причинам:

    □ традиционно их реализации полны ошибок;

    □ в использовании эти функции не гибки;

    они могут привести к созданию программного кода, в котором трудно решить, что подвергать синтаксическому анализу.

    В качестве альтернативы попытайтесь применять другие функции, такие как

    fread
    или
    fgets
    , для чтения строк ввода, а затем воспользуйтесь строковыми функциями для разделения введенной строки на нужные элементы.

    Другие потоковые функции

    В библиотеке stdio существует ряд других функций, использующих потоки как параметры или стандартные потоки

    stdin
    ,
    stdout
    ,
    stderr
    :

    fgetpos
    — возвращает текущую позицию в файловом протоке;

    fsetpos
    — устанавливает текущую позицию в файловом потоке;

    ftell
    — возвращает величину текущего смещения файла в потоке;

    rewind
    — сбрасывает текущую позицию файла в потоке и переводит ее в начало файла;

    freopen
    — повторно использует файловый поток;

    setvbuf
    — задает схему буферизации для потока;

    remove
    — эквивалент функции
    unlink
    , до тех пор пока параметр
    path
    не является каталогом, в этом случае она эквивалентна функции
    rmdir
    .

    Эти библиотечные функции описаны на страницах интерактивного справочного руководства в разделе 3.

    Вы можете использовать функции обработки файловых потоков для повторной реализации с их помощью программы копирования файлов. Взгляните на программу copy_stdio.c в упражнении 3.3.

    Упражнение 3.3. Третья версия программы копирования файлов

    Эта программа очень похожа на предыдущие версии, но посимвольное копирование выполняется с помощью вызовов функций, заданных в файле stdio.h:

    #include <stdio.h>

    #include <stdlib.h>


    int main() {

     int c; 

     FILE *in, *out;

     in = fopen("file.in", "r");

     out = fopen("file.out", "w");

     while((c = fgetc(in)) != EOF) fputc(c, out);

     exit(0);

    }

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

    $ TIMEFORMAT="" time ./copy_stdio

    0.06user 0.02system 0:00.11elapsed 81%CPU

    Как это работает

    На этот раз программа выполняется 0,11 с, не так быстро, как низкоуровневая блочная версия, но значительно быстрее другой посимвольной версии. Это произошло потому, что библиотека stdio поддерживает внутренний буфер в структуре

    FILE
    , и низкоуровневые системные вызовы выполняются, только когда буфер заполняется. Не ленитесь экспериментировать, тестируя программный код построчного и блочного копирования с помощью stdio, чтобы увидеть, как они действуют в случае проверенных нами трех примеров.

    Ошибки потока

    Для обозначения ошибок многие функции библиотеки stdio применяют значения за пределами допустимых, например, пустые указатели или константу

    EOF
    . В этих случаях ошибка указывается во внешней переменной
    errno
    .

    #include <errno.h>

    extern int errno;

    Примечание

    Имейте в виду, что многие функции могут изменять значение

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

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

    #include <stdio.h>

    int ferror(FILE *stream);

    int feof(FILE *stream);

    void clearerr(FILE *stream);

    Функция

    ferror
    проверяет индикатор ошибок потока и возвращает ненулевое значение, если индикатор установлен, и ноль в противном случае.

    Функция

    feof
    проверяет индикатор конца файла в потоке и возвращает ненулевое значение, если индикатор установлен, или ноль в противном случае. Применяйте ее следующим образом:

    if (feof(some_stream))

     /* Мы в конце */

    Функция

    clearerr
    очищает индикаторы конца файла и ошибки для потока, на который указывает параметр
    stream
    . Она не возвращает никакого значения, и для нее не определены никакие ошибки. Вы можете применять эту функцию для сброса состояния ошибки в потоках. Примером может быть возобновление записи в поток после разрешения проблемы, связанной с ошибкой "disk full" (диск заполнен).

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

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

    #include <stdio.h>

    int fileno(FILE *stream);

    FILE *fdopen(int fildes, const char *mode);

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

    fileno
    . Она возвращает дескриптор файла для заданного потока или -1 в случае сбоя. Эта функция полезна при необходимости низкоуровневого доступа к открытому потоку, например для вызова функции
    fstat
    применительно к этому потоку.

    Можно создать новый поток файла на основе дескриптора файла, открытого только для чтения, применив функцию

    fdopen
    . По существу, эта функция предоставляет буферы stdio для уже открытого файлового дескриптора, это может быть самый легкий вариант объяснения ее назначения.

    Функция

    fdopen
    действует так же, как функция
    fopen
    , но в отличие от имени файла она принимает в качестве параметра низкоуровневый дескриптор файла. Это может пригодиться, если вы используете вызов open для создания файла, может быть для более тонкого управления правами доступа, но хотите применить поток для записи в файл. Параметр mode такой же, как у функции
    fopen
    и должен быть совместим с режимами доступа к файлу, установленными при первоначальном открытии файла. Функция
    fdopen
    возвращает новый файловый поток или
    NULL
    в случае неудачного завершения.

    Ведение файлов и каталогов

    Стандартные библиотеки и системные вызовы обеспечивают полный контроль над созданием и ведением файлов и каталогов.

    chmod

    С помощью системного вызова

    chmod
    вы можете изменять права доступа к файлу или каталогу. Он лежит в основе программы командной оболочки
    chmod
    .

    Далее приведена синтаксическая запись вызова:

    #include <sys/stat.h>

    int chmod(const char *path, mode_t mode);

    Права доступа к файлу, заданному параметром

    path
    , изменяются в соответствии со значением параметра
    mode
    . Режим файла
    mode
    задается как в системном вызове open с помощью поразрядной операции
    OR
    , формирующей требуемые права доступа. Если программе не даны соответствующие полномочия, только владелец файла и суперпользователь могут изменять права доступа к файлу.

    chown

    Суперпользователь может изменить владельца файла с помощью системного вызова chown.

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

    int chown(const char *path, uid_t owner, gid_t group);
     

    В вызове применяются числовые значения идентификаторов (ID) нового пользователя и группы (взятые из системных вызовов

    getuid
    и
    getgid
    ) и системная величина, используемая для ограничения пользователей, имеющих разрешение изменять владельца файла. Владелец и группа файла изменяются, если заданы соответствующие полномочия.

    Примечание

    Стандарт POSIX в действительности допускает существование систем, в которых несуперпользователи могут изменять права владения файлом. Все "правильные" с точки зрения POSIX системы не допускают этого, но строго говоря, это расширение стандарта (в FIPS 151-2). Все виды систем, с которыми мы имеем дело в этой книге, подчиняются спецификации XSI (X/Open System Interface) и соблюдают на деле правила владения.

    unlink, link и symlink

    С помощью вызова

    unlink
    вы можете удалить файл.

    Системный вызов

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

    #include <unistd.h>

    int unlink(const char *path);

    int link(const char *path1, const char *path2);

    int symlink(const char *path1, const char *path2);

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

    rm
    . Дополнительные ссылки, предоставляющие альтернативные имена файла, обычно создаются программой
    ln
    . Вы можете программно создать новые ссылки на файл с помощью системного вызова
    link
    .

    Примечание

    Создание файла с помощью вызова

    open
    и последующее обращение к
    unlink
    для этого файла — трюк, применяемый некоторыми программистами для создания временных или транзитных файлов. Эти файлы доступны программе, только пока они открыты; и будут удалены автоматически, когда программа завершится, и файлы будут закрыты.

    Системный вызов

    link
    создает новую ссылку на существующий файл
    path1
    . Новый элемент каталога задается в
    path2
    . Символические ссылки можно создавать аналогичным образом с помощью системного вызова
    symlink
    . Имейте в виду, что символические ссылки на файл не увеличивают значение счетчика ссылок и таким образом, в отличие от обычных (жестких) ссылок, не мешают удалению файла.

    mkdir и rmdir

    Вы можете создавать и удалять каталоги, применяя системные вызовы

    mkdir
    и
    rmdir
    .

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

    int mkdir(const char *path, mode_t mode);

    Системный вызов

    mkdir
    используется для создания каталогов и эквивалентен программе
    mkdir
    . Вызов
    mkdir
    формирует новый каталог с именем, указанным в параметре
    path
    . Права доступа к каталогу передаются в параметре mode и задаются как опция о
    O_CREAT
    в системном вызове open и также зависят от переменной
    umask
    .

    #include <unistd.h>

    int rmdir(const char *path);

    Системный вызов

    rmdir
    удаляет каталоги, но только если они пустые. Программа
    rmdir
    использует этот системный вызов для выполнения аналогичной работы.

    chdir и getcwd

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

    cd
    для смены каталога, так и программа может использовать системный вызов
    chdir
    .

    #include <unistd.h>

    int chdir(const char *path);

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

    getcwd
    .

    #include <unistd.h>

    char *getcwd(char *buf, size_t size);

    Функция

    getcwd
    записывает имя текущего каталога в заданный буфер
    buf
    . Она возвращает
    NULL
    , если имя каталога превысит размер буфера (ошибка
    ERANGE
    ), заданный в параметре
    size
    . В случае успешного завершения она возвращает
    buf
    .

    Функция

    getcwd
    может также вернуть значение
    NULL
    , если во время выполнения программы каталог удален (
    EINVAL
    ) или изменились его права доступа (
    EACCESS
    ).

    Просмотр каталогов

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

    Функции работы с каталогами объявлены в заголовочном файле dirent.h. В них используется структура

    DIR
    как основа обработки каталогов. Указатель на эту структуру, называемый потоком каталога (
    DIR*
    ), действует во многом так же, как действует поток файла (
    FILE*
    ) при работе с обычным файлом. Элементы каталога возвращаются в структурах dirent, также объявленных в файле dirent.h, поскольку никому не следует изменять поля непосредственно в структуре
    DIR
    .

    Мы рассмотрим следующие функции:

    opendir
    ,
    closedir
    ;

    readdir
    ;

    telldir
    ;

    seekdir
    ;

    closedir
    .

    opendir

    Функция

    opendir
    открывает каталог и формирует поток каталога. Если она завершается успешно, то возвращает указатель на структуру
    DIR
    , которая будет использоваться для чтения элементов каталога.

    #include <sys/types.h>

    #include <dirent.h>

    DIR *opendir(const char *name);

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

    readdir

    Функция

    readdir
    возвращает указатель на структуру, содержащую следующий элемент каталога в потоке каталога
    dirp
    . Успешные вызовы
    readdir
    возвращают следующие элементы каталогов. При возникновении ошибки и в конце каталога
    readdir
    возвращает
    NULL
    . Системы, удовлетворяющие стандарту POSIX, возвращая
    NULL
    , не меняют переменную
    errno
    в случае достижения конца каталога и устанавливают ее значение, если обнаружена ошибка.

    #include <sys/types.h>

    #include <dirent.h>

    struct dirent *readdir(DIR *dirp);

    Просмотр каталога с помощью функции

    readdir
    не гарантирует формирование списка всех файлов (и подкаталогов) в каталоге, если в это время выполняются другие процессы, создающие и удаляющие файлы в каталоге.

    В состав структуры

    dirent
    , содержащей реквизиты элемента каталога, входят следующие компоненты.

    ino_t d_ino
    — индекс файла;

    char d_name[]
    — имя файла.

    Для выяснения других реквизитов файла в каталоге вам необходимо вызвать

    stat
    , который мы обсуждали ранее.

    telldir

    Функция

    telldir
    возвращает значение, регистрирующее текущую позицию в потоке каталога. Вы можете использовать ее в последующих вызовах функции
    seekdir
    для переустановки просмотра каталога, начиная с текущей позиции.

    #include <sys/types.h>

    #include <dirent.h>

    long int telldir(DIR *dirp);

    seekdir

    Функция

    seekdir
    устанавливает указатель на элемент каталога в потоке каталога, заданном в параметре
    dirp
    . Значение параметра
    loc
    , применяемого для установки позиции, следует получить из предшествующего вызова функции
    telldir
    .

    #include <sys/types.h>

    #include <dirent.h>

    void seekdir (DIR *dirp, long int loc);

    closedir

    Функция

    closedir
    закрывает поток каталога и освобождает ресурсы, выделенные ему. Она возвращает 0 в случае успеха и -1 при наличии ошибки.

    #include <sys/types.h>

    #include <dirent.h>

    int closedir(DIR *dirp);

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

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

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

    Для того чтобы познакомиться с методами повышения универсальности программ, посмотрите исходный код таких утилит Linux, как

    ls
    и
    find
    .

    Упражнение 3.4. Программа просмотра каталога

    1. Начните с соответствующих заголовочных файлов и функции

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

    #include <unistd.h>

    #include <stdio.h>

    #include <dirent.h>

    #include <string.h>

    #include <sys/stat.h>

    #include <stdlib.h>


    void printdir(char *dir, int depth) {

     DIR *dp;

     struct dirent *entry;

     struct stat statbuf;

     if ((dp = opendir(dir)) == NULL) {

      fprintf(stderr, "cannot open directory: %s\n", dir);

      return;

     }

     chdir(dir);

     while((entry = readdir(dp)) != NULL) {

      lstat(entry->d_name, &statbuf);

      if (S_ISDIR(statbuf.st_mode)) {

       /* Находит каталог, но игнорирует . и .. */

       if (strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0)

        continue;

       printf("%*s%s/\n", depth, "", entry->d_name);

       /* Рекурсивный вызов с новый отступом */

       printdir(entry->d_name, depth+4);

      } else printf("%*s%s\n", depth, " ", entry->d_name);

     }

     chdir("..");

     closedir(dp);

    }

    2. Теперь переходите к функции

    main
    .

    int main() {

     /* Обзор каталога /home */

     printf("Directory scan of /home:\n");

     printdir("/home", 0);

     printf("done.\n");

     exit(0);

    }

    Программа просматривает исходные каталоги и формирует вывод, похожий на приведенный далее (отредактированный для краткости). Для того чтобы заглянуть в каталоги других пользователей, вам могут понадобиться права доступа суперпользователя.

    $ ./printdir

    Directory scan of /home:

    neil/

        .Xdefaults

        .Xmodmap

        .Xresources

        .bash_history

        .bashrc

        .kde/

            share/

                apps/

                    konqueror/

                        dirtree/

                            public_html.desktop

                        toolbar/

                            bookmarks.xml

                            konq_history

                        kdisplay/

                            color-schemes/

        BLP4e/

            Gnu_Public_License

            chapter04/

                argopt.с

                args.с

            chapter03/

                file.out

                mmap.с

                printdir

    done.

    Как это работает

    Большинство операций сосредоточено в функции

    printdir
    . После некоторой начальной проверки ошибок с помощью функции
    opendir
    , проверяющей наличие каталога,
    printdir
    выполняет вызов функции
    chdir
    для заданного каталога. До тех пор пока элементы, возвращаемые функцией
    readdir
    , не нулевые, программа проверяет, не является ли очередной элемент каталогом. Если нет, она печатает элемент-файл с отступом, равным
    depth
    .

    Если элемент — каталог, вы встречаетесь с рекурсией. После игнорирования элементов

    .
    и
    ..
    (текущего и родительского каталогов) функция
    printdir
    вызывает саму себя и повторяет весь процесс снова. Как она выбирается из этих повторений? Как только цикл
    while
    заканчивается, вызов
    chdir("..")
    возвращает программу вверх по дереву каталогов, и предыдущий перечень можно продолжать. Вызов
    closedir(dp)
    гарантирует, что количество открытых потоков каталогов не больше того, которое должно быть.

    Для того чтобы составить представление об окружении в системе Linux, обсуждаемом в главе 4, познакомьтесь с одним из способов, повышающих универсальность программы. Рассматриваемая программа ограничена, потому что привязана каталогу /home. Следующие изменения в функции

    main
    могли бы превратить эту программу в полезный обозреватель каталогов:

    int main(int argc, char* argv[]) {

     char *topdir = ".";

     if (argc >= 2) topdir = argv[1];

     printf("Directory scan of %s\n", topdir);

     printdir(topdir, 0);

     printf("done.\n");

     exit(0);

    }

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

    $ ./printdir2 /usr/local | more

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

    Ошибки 

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

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

    Имена констант и варианты ошибок перечислены в заголовочном файле errno.h. К ним относятся следующие:

    EPERM
    — Operation not permitted (операция не разрешена);

    ENOENT
    — No such file or directory (нет такого файла или каталога);

    EINTR
    — Interrupted system call (прерванный системный вызов);

    EIO
    — I/O Error (ошибка ввода/вывода);

    EBUSY
    — Device or resource busy (устройство или ресурс заняты);

    EEXIST
    — File exists (файл существует);

    EINVAL
    — Invalid argument (неверный аргумент);

    EMFILE
    — Too many open files (слишком много открытых файлов);

    ENODEV
    — No such device (нет такого устройства);

    EISDIR
    — Is a directory (это каталог);

    ENOTDIR
    — Isn't a directory (это не каталог).

    Есть пара полезных функций, сообщающих об ошибках при их возникновении:

    strerror
    и
    perror
    .

    strerror

    Функция

    strerror
    преобразует номер ошибки в строку, описывающую тип возникшей ошибки. Она может быть полезна для регистрации условий, вызывающих ошибку.

    Далее приведена ее синтаксическая запись:

    #include <string.h>

    char *strerror(int errnum);

    perror

    Функция

    perror
    также превращает текущую ошибку в виде, представленном в переменной
    errno
    , в строку и выводит ее в стандартный поток ошибок. Ей предшествует сообщение, заданное в строке
    s
    (если указатель не равен
    NULL
    ), за которым следуют двоеточие и пробел.

    Далее приведена синтаксическая запись функции:

    #include <stdio.h>

    void perror(const char *s);

    Например, вызов

    perror("program");

    может дать следующий результат в стандартном потоке ошибок:

    program: Too many open files

    Файловая система procfs

    Ранее в этой главе мы уже писали о том, что ОС Linux обрабатывает многие вещи как файлы, и в файловой системе есть ряд элементов для аппаратных устройств. Эти файлы /dev применяются для доступа к оборудованию особыми методами с помощью низкоуровневых системных вызовов.

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

    В прошлом для связи с драйверами устройств применялись утилиты общего назначения. Например, hdparm использовалась для настройки некоторых параметров диска, a ifconfig могла сообщить сетевую статистику. В недавнем прошлом появилась тенденция, направленная на обеспечение более подходящего способа доступа к информации драйвера и, как расширение, включающая взаимодействие с различными элементами ядра Linux.

    ОС Linux предоставляет специальную файловую систему procfs, которая обычно доступна в виде каталога /proc. Она содержит много специальных файлов, обеспечивающих высокоуровневый доступ к информации драйвера и ядра. Приложения, выполняющиеся с корректными правами доступа, могут читать эти файлы для получения информации и записывать в них устанавливаемые параметры.

    Набор файлов в каталоге /proc меняется от системы к системе, и с каждым новым выпуском Linux появляются новые файлы, дополнительные драйверы и средства поддержки файловой системы procfs. В этом разделе мы рассмотрим некоторые из самых широко распространенных файлов и кратко обсудим их применение.

    В перечень каталога /proc на компьютере, использовавшемся для написания этой главы, включены следующие элементы:

    1/     10514/ 20254/ 6/    9057/ 9623/     ide/       mtrr

    10359/ 10524/ 29/    698/  9089/ 9638/     interrupts net/

    10360/ 10530/ 983/   699/  9118/ acpi/     iomem      partitions

    10381/ 10539/ 3/     710/  9119/ asound/   ioports    scsi/

    10438/ 10541/ 30/    711/  9120/ buddyinfo irq/       self@

    10441/ 10555/ 3069/  742/  9138/ bus/      kallsyms   slabinfo

    10442/ 10688/ 3098/  7808/ 9151/ cmdline   kcore      splash

    10478/ 10689/ 3099/  7813/ 92/   config.gz keys       stat

    10479/ 10784/ 31/    8357/ 9288/ cpuinfo   key-users  swaps

    10482/ 113/   3170/  8371/ 93/   crypto    kmsg       sys/

    10484/ 115/   3171/  840/  9355/ devices   loadavg    sysrq-trigger

    10486/ 116/   3177/  8505/ 9407/ diskstats locks      sysvipc/

    10495/ 1167/  32288/ 8543/ 9457/ dma       mdstat     tty/

    10497/ 1168/  3241/  8547/ 9479/ driver/   meminfo    uptime

    Во многих случаях файлы могут только читаться и дают информацию о состоянии. Например, /proc/cpuinfo предоставляет сведения о доступных процессорах:

    $ cat /proc/cpuinfo

    processor    : 0

    vendor_id     : GenuineIntel

    cpu family    : 15

    model         : 2

    model name    : Intel(R) Pentium(R) 4 CPU 2.66GHz

    stepping      : 8

    cpu MHz       : 2665.923

    cache size    : 512 KB

    fdiv_bug      : no

    hlt_bug       : no

    f00f_bug      : no

    coma_bug      : no

    fpu           : yes

    fpu_exception : yes

    cpuid level   : 2

    wp            : yes

    flags         : fpu vme de pse tsc msr рае mce cx8 apic sep mtrr pge mca cmov

    pat pse36 clflush dts acpi mmx fxsr sse sse2 ss up

    bogomips      : 5413.47

    clflush size  : 64

    Файлы /proc/meminfo и /рroc/version предоставляют данные об использовании оперативной памяти и версии ядра соответственно:

    $ cat /proc/meminfo

    MemTotal:     776156 kB

    MemFree:       28528 kB

    Buffers:      191764 kB

    Cached:       369520 kB

    SwapCached:       20 kB

    Active:       406912 kB

    Inactive:     274320 kB

    HighTotal:         0 kB

    HighFree:          0 kB

    LowTotal:     776156 kB

    LowFree:       28528 kB

    SwapTotal:   1164672 kB

    SwapFree:    1164652 kB

    Dirty:            68 kB

    Writeback:         0 kB

    AnonPages:     95348 kB

    Mapped:        49044 kB

    Slab:          57848 kB

    SReclaimable:  48008 kB

    SUnreclaim:     9840 kB

    PageTables:     1500 kB

    NFS_Unstable:      0 kB

    Bounce:            0 kB

    CommitLimit: 1552748 kB

    Committed_AS: 189680 kB

    VmallocTotal: 245752 kB

    VmallocUsed:   10572 kB

    VmallocChunk: 234556 kB

    HugePages_Total:   0

    HugePages_Free:    0

    HugePages_Rsvd:    0

    Hugepagesize:   4096 kB

    $ cat /proc/version

    Linux version 2.6.20.2-2-default (geeko@buildhost) (gcc version 4.1.3 20070218 (prerelease) (SUSE Linux)) #1 SMP Fri Mar 9 21:54:10 UTC 2007

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

    Получить дополнительную информацию от специальных функций ядра можно в подкаталогах каталога /proc. Например, статистику использования сетевых сокетов вы можете узнать из /proc/net/sockstat:

    $ cat /proc/net/sockstat

    sockets: used 285

    TCP: inuse 4 orphan 0 tw 0 alloc 7 mem 1

    UDP: inuse 3

    UDPLITE: inuse 0

    RAW: inuse 0

    FRAG: inuse 0 memory 0

    В некоторые элементы каталога /proc можно производить запись, а не только читать их. Например, общее количество файлов, которые могут быть открыты одновременно всеми выполняющимися программами, — это параметр ядра Linux. Текущее значение можно прочитать из /proc/sys/fs/file-max:

    $ cat /proc/sys/fs/file-max

    76593

    В данном случае задана величина

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

    Примечание

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

    Для увеличения предельного значения одновременно обрабатываемых в системе файлов до 80000 вы можете просто записать новое предельное значение в файл file-max.

    # echo 80000 >/proc/sys/fs/file-max

    Теперь, повторно прочитав файл, вы увидите новое значение:

    $ cat /proc/sys/fs/file-max

    80000

    Подкаталоги каталога /proc с числовыми именами применяются для обеспечения доступа к информации о выполняющихся программах. В главе 11 вы узнаете больше о том, что программы выполняются как процессы.

    Сейчас только отметьте, что у каждого процесса есть уникальный идентификатор: число в диапазоне от 1 до почти 32 000. Команда ps предоставляет список выполняющихся в данный момент процессов. Например, когда писалась эта глава:

    neil@susel03:~/BLP4e/chapter03> ps -а

      PID TTY       TIME CMD

     9118 pts/1 00:00:00 ftp

     9230 pts/1 00:00:00 ps

    10689 pts/1 00:00:01

    bash neil@susel03:~/BLP4e/chapter03>

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

    ftp
    . Просмотрев каталог /proc, вы получите более подробную информацию о сеансе
    ftp
    .

    В данном случае для ftp задан идентификатор процесса

    9118
    , поэтому вы должны заглянуть в каталог /proc/9118 для получения подробной информации о нем:

    $ ls -l /proc/9118

    total 0

    0 dr-xr-xr-x 2 neil users 0 2007-05-20 07:43 attr

    0 -r-------- 1 neil users 0 2007-05-20 07:43 auxv

    0 -r--r--r-- 1 neil users 0 2007-05-20 07:35 cmdline

    0 -r--r--r-- 1 neil users 0 2007-05-20 07:43 cpuset

    0 lrvxrwxrwx 1 neil users 0 2007-05-20 07:43 cwd -> /home/neil/BLP4e/chapter03

    0 -r-------- 1 neil users 0 2007-05-20 07:43 environ

    0 lrwxrwxrwx 1 neil users 0 2007-05-20 07:43 exe -> /usr/bin/pftp

    0 dr-x------ 2 neil users 0 2007-05-20 07:19 fd

    0 -rw-r--r-- 1 neil users 0 2007-05-20 07:43 loginuid

    0 -r--r--r-- 1 neil users 0 2007-05-20 07:43 maps

    0 -rw------- 1 neil users 0 2007-05-20 07:43 mem

    0 -r--r--r-- 1 neil users 0 2007-05-20 07:43 mounts

    0 -r-------- 1 neil users 0 2007-05-20 07:43 mountstats

    0 -rw-r--r-- 1 neil users 0 2007-05-20 07:43 oom_adj

    0 -r--r--r-- 1 neil users 0 2007-05-20 07:43 oom_score

    0 lrwxrwxrwx 1 neil users 0 2007-05-20 07:43 root -> /

    0 -rw------- 1 neil users 0 2007-05-20 07:43 seccomp

    0 -r--r--r-- 1 neil users 0 2007-05-20 07:43 smaps

    0 -r--r--r-- 1 neil users 0 2007-05-20 07:33 stat

    0 -r--r--r-- 1 neil users 0 2007-05-20 07:43 statm

    0 -r--r--r-- 1 neil users 0 2007-05-20 07:33 status

    0 dr-xr-xr-x 3 neil users 0 2007-05-20 07:43 task

    0 -r--r--r-- 1 neil users 0 2007-05-20 07:43 wchan

    В данном перечне вы видите разные специальные файлы, способные сообщить вам, что происходит с процессом.

    Можно сказать, что выполняется программа /usr/bin/pftp, и ее текущий рабочий каталог — home/neil/BLP4e/chapter03. Есть возможность прочитать другие файлы из этого каталога, чтобы увидеть командную строку, применяемую для запуска программы, а также ее окружение. Файлы cmdline и environ предоставляют эту информацию в виде последовательности нуль-терминированных строк, поэтому вам следует соблюдать осторожность при их просмотре. Более подробно окружение ОС Linux мы обсудим в главе 4.

    $ od -с /proc/9118/cmdline

    0000000 f  t  p \0  1  9  2  .  1  6  8  .  0  .  1  2

    0000020 \0

    0000021

    Из полученного вывода видно, что

    ftp
    была запущена из командной строки
    ftp 192.163.0.12
    .

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

    ftp
    есть открытые дескрипторы 0, 1, 2 и 3. Они включают стандартные дескрипторы ввода, вывода и потока ошибок плюс подключение к удаленному серверу.

    $ ls /proc/9118/fd

    0 1 2 3

    Более сложные приемы: fcntl и mmap

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

    fcntl

    Системный вызов

    fcntl
    предоставляет дополнительные методы обработки низкоуровневых дескрипторов файлов:

    #include <fcntl.h>

    int fcntl(int fildes, int cmd);

    int fcntl(int fildes, int cmd, long arg);

    С помощью системного вызова

    fcntl
    вы можете выполнить несколько разнородных операций над открытыми дескрипторами файлов, включая их дублирование, получение и установку флагов дескрипторов файлов, получение и установку флагов состояния файла и управление блокировкой файла (advisory file locking).

    Различные операции выбираются разными значениями параметра команды

    cmd
    , как определено в файле fcntl.h. В зависимости от выбранной команды системному вызову может потребоваться третий параметр
    arg
    .

    fcntl(fildes, F_DUPFD, newfd)
    — этот вызов возвращает новый дескриптор файла с числовым значением, равным или большим целочисленного параметра
    newfd
    . Новый дескриптор — копия дескриптора
    fildes
    . В зависимости от числа открытых файлов и значения
    newfd
    этот вызов может быть практически таким же, как вызов
    dup(fildes)
    .

    fcntl(fildes, F_GETFD)
    — этот вызов возвращает флаги дескриптора файла, как определено в файле fcntl.h. К ним относится
    FD_CLOEXEC
    , определяющий, закрыт ли дескриптор файла после успешного вызова одного из системных вызовов семейства exec.

    fcntl(fildes, F_SETFD, flags)
    — этот вызов применяется для установки флагов дескриптора файла, как правило, только
    FD_CLOEXEC
    .

    fcntl(fildes, F_GETFL)
    и
    fcntl(fildes, F_SETFL, flags)
    — эти вызовы применяются, соответственно, для получения и установки флагов состояния файла и режимов доступа. Вы можете извлечь режимы доступа к файлу с помощью маски
    O_ACCMODE
    , определенной в файле fcntl.h. Остальные флаги включают передаваемые значения в третьем аргументе вызову open с использованием
    O_CREAT
    . Учтите, что вы не можете задать все флаги. В частности, нельзя задать права доступа к файлу с помощью вызова fcntl.

    Вызов

    fcntl
    также позволяет реализовать блокировку файла. См. более подробную информацию в разделе 2 интерактивного справочного руководства или главу 7, в которой мы обсуждаем блокировку файлов.

    mmap

    Система UNIX предоставляет полезное средство, позволяющее программам совместно использовать память, и, к счастью, оно включено в версию 2.0 и более поздние версии ядра Linux. Функция

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

    Вы можете применить то же самое средство для работы с файлами, заставив все содержимое файла на диске выглядеть как массив в памяти. Если файл состоит из записей, которые могут быть описаны структурами на языке С, вы сможете обновлять файл с помощью методов доступа к массиву структур.

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

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

    #include <sys/mman.h>

    void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);

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

    off
    . Открытый дескриптор файла задается в параметре
    fildes
    . Объем данных, к которым возможен доступ (т. е. размер сегмента памяти), указывается в параметре
    len
    .

    Параметр

    addr
    можно использовать для запроса конкретного адреса памяти. Если он равен нулю, результирующий указатель формируется автоматически. Последний вариант рекомендуется, потому что в противном случае трудно добиться переносимости; диапазоны доступных адресов в разных системах отличаются.

    Параметр

    prot
    используется для установки прав доступа к сегменту памяти. Он представляет собой результат поразрядной операции or, примененной к следующим константам:

    PROT_READ
    — сегмент может читаться;

    PROT_WRITE
    — в сегмент можно писать;

    PROT_EXEC
    — сегмент может выполняться;

    PROT_NONE
    — к сегменту нет доступа.

    Параметр

    flags
    контролирует, как изменения, сделанные программой в сегменте, отражаются в других местах; его возможные значения приведены в табл. 3.7.


    Таблица 3.7

    Константа Описание
    MAP_PRIVATE
    Сегмент частный, изменения локальные
    MAP_SHARED
    Изменения сегмента переносятся в файл
    MAP_FIXED
    Сегмент должен располагаться по заданному адресу
    addr

    Функция

    msync
    вызывает запись изменений в части или во всем сегменте памяти обратно а отображенный файл (или считывание из файла).

    #include <sys/mman.h>

    int msync(void *addr, size_t len, int flags);

    Корректируемая часть сегмента задается передачей начального адреса

    addr
    и размера
    len
    . Параметр
    flags
    управляет способом выполнения корректировки с помощью вариантов, приведенных в табл. 3.8.


    Таблица 3.8

    Константа Описание
    MS_ASYNC
    Выполнять запись асинхронно
    MS_SYNC
    Выполнять запись синхронно
    MS_INVALIDATE
    Обновить другие отражения этого файла так, чтобы они содержали изменения, внесенные этим вызовом

    Функция

    munmap
    освобождает сегмент памяти.

    #include <sys/mman.h>

    int munmap(void *addr, size_t len);

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

    mmap
    и обращений в стиле массива. Ядро Linux версий, меньших 2.0, не полностью поддерживает применение функции
    mmap
    . Программа работает корректно в системе Sun Solaris и других системах.

    Упражнение 3.5. Применение функции
    mmap

    1. Начните с определения структуры

    RECORD
    и создайте
    NRECORDS
    вариантов, в каждый из которых записывается собственный номер. Они будут добавлены в конец файла records.dat.

    #include <unistd.h>

    #include <stdio.h>

    #include <sys/mman.h>

    #include <fcntl.h>

    #include <stdlib.h>


    typedef struct {

     int integer;

     char string[24];

    } RECORD;


    #define NRECORDS (100)


    int main() {

     RECORD record, *mapped;

     int i, f;

     FILE *fp;

     fp = fopen("records.dat", "w+");

     for (i=0; i<NRECORDS; i++) {

      record.integer = i;

      sprintf(record.string, "RECORD-%d", i);

      fwrite(&record, sizeof(record), 1, fp);

     }

     fclose(fp);

    2. Далее измените целое значение записи с 43 на 143 и запишите его в строку 43-й записи.

     fp = fopen("records.dat", "r+");

     fseek(fp, 43*sizeof(record), SEEK_SET);

     fread(&record, sizeof(record), 1, fp);

     record.integer =143;

     sprintf(record.string, "RECORD-%d", record.integer);

     fseek(fp, 43*sizeof(record), SEEK_SET);

     fwrite(&record, sizeof(record), 1, fp);

     fclose(fp);

    3. Теперь отобразите записи в память и обратитесь к 43-й записи для того, чтобы изменить целое на 243 (и обновить строку записи), снова используя отображение в память.

     f = open("records.dat", O_RDWR);

     mapped = (RECORD *)mmap(0, NRECORDS*sizeof(record),

      PROT_READ|PROT_WRITE, MAP_SHARED, f, 0);

     mapped[43].integer = 243;

     sprintf(mapped[43].string, "RECORD-%d", mapped[43].integer);

     msync((void *)mapped, NRECORDS*sizeof(record), MS_ASYNC);

     munmap((void *)mapped, NRECORDS*sizeof(record));

     close(f);

     exit(0);

    }

    В главе 13 вы встретитесь с еще одним средством совместного использования памяти — разделяемой памятью System V.

    Резюме

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

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








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх