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

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

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

  int open(char * filename, int flags)
  int open(char * filename, int flags, int mode)

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

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

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

myFile = open("myFile.txt", O_RDWR | O_TRUNC);

Аргумент 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, можно использовать битовое «или» для «сложения» прав. Например следующий код создаст файл, который доступен для изменения владельцем, группа может только читать, а остальные — только исполнение, и откроет его в режиме чтения/записи.

myFile = open("myFile.txt", O_RDWR | O_CREAT, S_IWUSR | S_IRGRP | S_IXOTH);

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

int close(int fd)

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

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

int read(int fd, void * ptr, int numbytes)

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

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
	int input_file = 0; //Файловая переменная
	input_file = open("input.txt", O_RDONLY); //Открываю файл для чтения
	char buff[64]; //Буфер для чтения
	memset(buff, 0, 64); //Заполняю буфер нулями
	int count = 0; //Переменная для хранения количества прочитанных байт
	count = read(input_file, buff, 64); //Читаю из файла
	close(input_file); //Закрываю файловый дескриптор
	/* Вывожу содержимое и количество прочитанных байт */
	printf("Содержимое файла:rn%srnВсего прочитано: %irn", buff, count);
	return 0;
}

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

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

int write(int fd, void * ptr, int numbytes)

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

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

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
	//Открытие файла
	int file = open("output.txt", O_RDWR | O_CREAT, S_IRWXU | S_IRWXO | S_IRWXG );
	printf("Введите строку...rn");
	char buff[2048];
	//Считывание строки
	scanf("%s", buff);
	//Запись в файл
	write(file, buff, strlen(buff));
	close(file);
}

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

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

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

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

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

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

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
	char buff[2048];
	//Открытие файла
	int file = open("1.txt", O_RDWR);
	//Вывод содержимого
	read(file, buff, 2048);
	printf("Содержимое файла:rn%srn", buff);
	//Обнуляем буфер
	memset(buff, 0, 2048);
	printf("Введите строку, которую следует дописать в файлrn");
	//Считывание строки
	scanf("%s", buff);
	//Запись в файл
	write(file, buff, strlen(buff));
	close(file);
}

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

This is file!
Okay!

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

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
	char buff[2048];
	//Открытие файла
	int file = open("1.txt", O_RDWR);
	//Вывод содержимого
	read(file, buff, 2048);
	printf("Содержимое файла:rn%srn", buff);
	//Обнуляем буфер
	memset(buff, 0, 2048);
	printf("Введите строку, которую следует дописать в файлrn");
	//Считывание строки
	scanf("%s", buff);
	//Смещение
	lseek(file, 0, SEEK_SET);
	//Запись в файл
	write(file, buff, strlen(buff));
	close(file);
}

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

Okay!is file!

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

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

int pos = lseek(file, 0, SEEK_CUR);

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

int size = lseek(file, 0, SEEK_END);

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

ssize_t pread(int fd, void *buff, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buff, size_t count, off_t offset);

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

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

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
	char buff[2048];
	//Открытие файла
	int file = open("1.txt", O_RDWR);
	//Вывод содержимого через pread
	pread(file, buff, 10, 0);
	printf("Первые десять байт файла:rn%srn", buff);
	close(file);
}

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

This is line! This is Sparta!

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

This is li

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

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

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
	char buff[2048];
	//Открытие файла
	int file = open("1.txt", O_RDWR);
	//Вывод содержимого через pread
	pread(file, buff, 10, 0);
	printf("Первые десять байт файла:rn%srn", buff);
	//Считаем новую строку с экрана
	memset(buff, 0, 2048);
	printf("Введите новую строку:rn");
	scanf("%s", buff);
	//Запишем ее в файл
	pwrite(file, buff, strlen(buff), 0);
	close(file);
}

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

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

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

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

touch 1.txt 2.txt
chmod 111 1.txt 2.txt

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

---x--x--x 1 jakeroid users 0 Dec 28 12:12 1.txt
---x--x--x 1 jakeroid users 0 Dec 28 12:12 2.txt

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

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
	//Изменяю права для файла 1.txt по его пути
	chmod("1.txt", S_IRUSR | S_IWUSR | S_IXUSR);
	int file = open("2.txt", O_RDWR);
	//Изменяю права для файла 2.txt по его дескриптору
	fchmod(file, S_IRGRP | S_IWGRP | S_IXGRP);
	close(file);
}

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

-rwx------ 1 jakeroid users 0 Dec 28 12:12 1.txt
---x--x--x 1 jakeroid users 0 Dec 28 12:12 2.txt

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

-rwx------ 1 jakeroid users 0 Dec 28 12:12 1.txt
----rwx--- 1 jakeroid users 0 Dec 28 12:12 2.txt

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

int remove ( const char * filename );

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

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
	char path[255];
	printf("Введите название файла: ");
	scanf("%s", path);
	if (!remove(path))
		printf("Файл удален!rn");
	else
		printf("Ошибка при удалении!rn");
	return 0;
}

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

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

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

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

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