Сегодня я коротко расскажу вам про системные вызовы для работы с файлами в 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; }
Программа спрашивает у пользователя имя файла, после чего удаляет его. Если произошла ошибка при удалении — сообщает об этом.
Задавайте свои ответы, удачи в программировании!
ага есть такое — расписано что надо ) спасибо просветился 🙂
Рад был помочь.
Кстати, зацени, у меня теперь есть блог на английском, в шапке сайта ссылка.
Спасибо, так чуть лучше понятно :-*
Отлично.
Кстати, зацени, теперь у меня есть блог на английском. Ссылка в шапке сайта.
Афтору репы, и продолжать просвещать людей 😉 thanks!
Спасибо. Рад был помочь. Зацени, у меня теперь есть блог на английском. Ссылка в шапке сайта.