Системные вызовы для работы с файлами в Linux

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

Начнем с open. Этот системный вызов нужен для открытия файла. Для его использования необходимо подключить заголовочный файл <fcntl.h>. Прототипы:

Возвращает файловый дескриптор в случае успешного выполнения, и значение меньше 0, если выполнить не удалось. Аргумент filename — это путь к файлу. Может быть как полным, так и относительным. Аргумент flags устанавливает режим открытия файла. Для удобства можно использовать следующие макроопределения:

O_RDONLY — только для чтения;
O_WRONLY — только для записи;
O_RDWR — для чтения и записи;
O_NONBLOCK — не блокирующее открытие, только для чтения;
O_APPEND — открыть файл и переместить указатель курсора в конец;
O_CREAT — создать файл, если такой не существует;
O_TRUNC — удалить содержимое файла, при открытии;
O_EXCL — открытие потерпит ошибку, если файл уже существует;
O_DIRECT — пробовать не использовать кэширующие операции;
O_NOFOLLOW — если файл символьная ссылка, открывает именно файл-ссылку;

Все макроопределения можно совмещать, путем использования побитового или «|». Например, следующий код откроет файл для чтения, записи и удалит все содержимое:

Аргумент mode определяет права файла, если используется флаг O_CREAT, иначе просто игнорируется. Если подключить <sys/stat.h>, то можно использовать макроопределения вместо битовой маски, для задания прав:

S_IRUSR — чтения для владельца;
S_IWUSR — запись для владельца;
S_IXUSR — исполнение для владельца;
S_IRGRP — чтение для группы;
S_IWGRP — запись для группы;
S_IXGRP — исполнение для группы;
S_IROTH — чтения для остальных;
S_IWOTH — запись для остальных;
S_IXOTH — исполнение для остальных;

Так же, как и для аргумента flags, можно использовать битовое «или» для «сложения» прав. Например следующий код создаст файл, который доступен для изменения владельцем, группа может только читать, а остальные — только исполнение, и откроет его в режиме чтения/записи.

Теперь рассмотрим системный вызов close. Он закрывает открытый дескриптор. Прототип очень прост:

Возвращает 0 — при успешном закрытии, и значение меньше 0 — при ошибке. Принимает конечно же файловый дескриптор.

Думаю с open и close вопросов нет. Идем дальше. Системный вызов read выполняет чтение из файла в буфер указанное количество байт. Прототип следующий.

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

Программа просто открывает файл и пытаеться прочесть первые 64 байта. На экран выводиться содержимое файла, которое удалось прочитать и количество успешно прочитанных байт. Я создал файл input.txt, и написал в нем «This is test file!». Запустил программу, он вывела на экран «This is test file!» и количество прочитанных байт — 19. Поскольку 1 символ занимает 1 байт, а в предложении «This is test file!» 19 символов, значит все правильно!

Читать из файла научились. Теперь научимся записывать. Системный вызов записывающий в файл информацию очень похож на read:

Принцип работы аналогичный. Функция пытается записать в файл с файловым дискриптором fd, данный из буффера ptr, в количестве байт numbytes.

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

Ничего сложного. Поскольку в функцию write необходимо передать количество записываемых байт из буфера, я использую функцию strlen. Она определяет длину строки, то есть от первого символа и до символа конца строки(‘‘).

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

Аргумент fd — дескриптор файла, offset — смещение, whence — тип смещения. Возвращает количество байтов, на которое сместился указатель от начала файла, если не было ошибки, иначе -1. В качестве whence можно ипспользовать следующие макросы:

  • SEEK_SET — смещение в файле устанавливается в значение offset;
  • SEEK_CUR — смещение в файле устанавливается в текущее значение + offset;
  • SEEK_END — смещение в файле устанавливается в конец файла + offset.

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

Я создал файл 1.txt и написал в него «This is file!». После этого, запустил программу, она вывела «This is file!» и спросила, что дописать. Я написал «Okay!». После чего в файле оказалось:

This is file!
Okay!

Теперь немного исправим нашу программу:

Проделываю тоже самую операцию с файлом, и получаю:

Okay!is file!

Как видно, указатель на текущую позицию в файле сместился, в результате чего «Okay!» записалось в начале.

Кроме смещения текущей позиции, системный вызов lseek может послужить в других целях. Например для определения текущей позиции:

Или например для определения размера файла:

Для записи или чтения байт в определенное место файла, кроме комбинации read/write + lseek есть еще две функции. Это pread и pwrite, прототипы:

Функция pread считывает из файла, с дескриптором fd, смещением offset, количество байт count в буфер buff. Функция pwrite работает аналогично, но для записи.

Приведу примеры. Первая мини-программа открывает файл для чтения/записи и считывает с начала файла 10 байт. Выводит их на экран.

Содержимое файла 1.txt:

This is line! This is Sparta!

Вывод на экран:

This is li

Примечание: пробелы тоже учитываются, так как они тоже символы.

Теперь изменим программу. Добавим запись в файл.

Данная программа затрет начало файла введенной строкой с экрана.

Далее рассмотрим 2 системных вызова для изменения прав файла. Прототипы:

Как видно из определения, функция chmod изменяет права файла по его пути, а функция fchmod — по его файловому дескриптору. В случае успешного изменения прав, возвращают 0, в случае ошибки -1. Для параметра mode определены макросы, описанные мною выше вместе с функцией open. Создадим 2 файла с правами 111:

Проверим их права командой ls *.txt -l:

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

После выполнения, права файла 1.txt изменились:

А почему не изменились у 2.txt? Все просто — его права запрещают чтение содержимого, поэтому наша программа не смогла его открыть. Если изменить ему права командой chmod 711 2.txt, и запустить нашу программу еще раз, вывод ls *.txt -l даст следующий результат:

Все заработало! Рассмотрим еще один системный вызов, для удаления файлов:

Из его прототипа видно, что он удаляет файл по указанному пути. Возвращает 0 — в случае успеха, и -1 — в случае ошибки. Ничего сложного в использовании нет. Пример программы:

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

Задавайте свои ответы, удачи в программировании!

Комментарии 3

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *