Read читать блок из файла


Содержание

Read читать блок из файла

В UNIX и в MS DOS файлы не имеют предопределенной структуры и представляют собой просто линейные массивы байт. Если вы хотите задать некоторую структуру хранимой информации — вы должны позаботиться об этом в своей программе сами.

Файлы отличаются от обычных массивов тем, что

  • они могут изменять свой размер;
  • обращение к элементам этих массивов производится не при помощи операции индексации [], а при помощи специальных системных вызовов и функций;
  • доступ к элементам файла происходит в так называемой «позиции чтения/записи», которая автоматически продвигается при операциях чтения/записи, т.е. файл просматривается последовательно. Есть, правда, функции для произвольного изменения этой позиции.

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

Для работы с каким-либо файлом наша программа должна открыть этот файл — установить связь между именем файла и некоторой переменной в программе. При открытии файла в ядре операционной системы выделяется «связующая» структура file «открытый файл«, содержащая:
f_offset: указатель позиции чтения/записи, который в дальнейшем мы будем обозначать как
RWptr. Это long-число, равное расстоянию в байтах от начала файла до позиции чтения/записи;
f_flag: режимы открытия файла: чтение, запись, чтение и запись, некоторые дополнительные флаги;
f_inode: расположение файла на диске (в UNIX — в виде ссылки на I-узел файла * );
и кое-что еще.

У каждого процесса имеется таблица открытых им файлов — это массив ссылок на упомянутые «связующие» структуры ** . При открытии файла в этой таблице ищется

Содержимое некоторых полей этого паспорта можно узнать вызовом stat(). Все I-узлы собраны в единую область в начале файловой системы — так называемый I-файл. Все Iузлы пронумерованы, начиная с номера 1. Корневой каталог (файл с именем «/«) как правило имеет I-узел номер 2.

** — У каждого процесса в UNIX также есть свой «паспорт». Часть этого паспорта находится в таблице процессов в ядре ОС, а часть — «приклеена» к самому процессу, однако не доступна из программы непосредственно. Эта вторая часть паспорта носит название «u-area» или структура user. В нее, в частности, входят таблица открытых процессом файлов, свободная ячейка, в нее заносится ссылка на структуру «открытый файл» в ядре, и ИНДЕКС этой ячейки выдается в вашу программу в виде целого числа — так называемого «дескриптора файла«.

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

Дескрипторы являются локальными для каждой программы. Т.е. если две программы открыли один и тот же файл — дескрипторы этого файла в каждой из них не обязательно совпадут (хотя и могут). Обратно: одинаковые дескрипторы (номера) в разных программах не обязательно обозначают один и тот же файл. Следует учесть и еще одну вещь: несколько или один процессов могут открыть один и тот же файл одновременно несколько раз. При этом будет создано несколько «связующих» структур (по одной для каждого открытия); каждая из них будет иметь СВОЙ указатель чтения/записи. Возможна и ситуация, когда несколько дескрипторов ссылаются к одной структуре — смотри ниже описание вызова dup2. Параметр как_открыть: Если файл еще не существовал, то его нельзя открыть: open вернет значение (-1), ссылка на I-узел текущего каталога а также ссылка на часть паспорта в таблице процессов сигнализирующее об ошибке. В этом случае файл надо создать:

Дескриптор fd будет открыт для записи в этот новый пустой файл. Если же файл уже существовал, creat опустошает его, т.е. уничтожает его прежнее содержимое и делает его длину равной 0L байт. Коды_доступа задают права пользователей на доступ к файлу. Это число задает битовую шкалу из 9и бит, соответствующих строке

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

Подробности — в руководствах по системе UNIX. Отметим в частности, что open() может вернуть код ошибки fd O_TRUNC означает, что если файл уже существует, то он должен быть опустошен при открытии. Коды доступа и владелец не изменяются. O_CREAT означает, что файл должен быть создан, если его не было (без этого флага файл не создастся, а open вернет fd коды_доступа *** . Если файл уже существует — этот флаг не имеет никакого эффекта, но зато вступает в действие O_TRUNC.

Существует также флаг O_EXCL который может использоваться совместно с O_CREAT. Он делает следующее: если файл уже существует, open вернет код ошибки (errno==EEXIST). Если файл не

*** — Заметим, что на самом деле коды доступа у нового файла будут равны di_mode = (коды_доступа &

u_cmask) | IFREG; (для каталога вместо IFREG будет IFDIR), где маска u_cmask задается системным вызовом umask(u_cmask); (вызов выдает прежнее значение маски) и в дальнейшем наследуется всеми потомками данного процесса (она хранится в u-area процесса). Эта маска позволяет запретить доступ к определенным операциям для всех создаваемых нами файлов, несмотря на явно заданные коды доступа, например umask(0077); /* . —— */ делает значащими только первые 3 бита кодов доступа (для владельца файла). Остальные биты будут равны нулю.

Все это относится и к созданию каталогов вызовом mkdir. существовал — срабатывает O_CREAT и файл создается. Это позволяет предохранить уже существующие файлы от уничтожения. Файл удаляется при помощи У каждой программы по умолчанию открыты три первых дескриптора, обычно связанные Если при вызове close(fd) дескриптор fd не соответствует открытому файлу (не был открыт) — ничего не происходит.

Часто используется такая метафора: если представлять себе файлы как книжки (только чтение) и блокноты (чтение и запись), стоящие на полке, то открытие файла это выбор блокнота по заглавию на его обложке и открытие обложки (на первой странице). Теперь можно читать записи, дописывать, вычеркивать и править записи в середине, листать книжку! Страницы можно сопоставить блокам файла (см. ниже), а «полку» с книжками — каталогу.

Читайте и записывайте файлы большими кусками, кратными 512 байтам. Это уменьшит число обращений к диску. Схема: Приведем несколько примеров использования write: Обратите внимание на такие моменты:

  • При использовании write() и read() надо передавать АДРЕС данного, которое мы хотим записать в файл (места, куда мы хотим прочитать данные из файла).
  • Операции read и write возвращают число действительно прочитанных/записанных байт (при записи оно может быть меньше указанного нами, если на диске не хватает места; при чтении — если от позиции чтения до конца файла содержится меньше информации, чем мы затребовали).
  • Операции read/write продвигают указатель чтения/записи При открытии файла указатель стоит на начале файла: RWptr=0. При записи файл если надо автоматически увеличивает свой размер. При чтении — если мы достигнем конца файла, то read будет возвращать «прочитано 0 байт» (т.е. при чтении указатель чтения не может стать больше размера файла).
  • Аргумент сколькоБайт имеет тип unsigned, а не просто int: Приведем упрощенные схемы логики этих сисвызовов, когда они работают с обычным дисковым файлом (в UNIXустройства тоже выглядят для программ как файлы, но иногда с особыми свойствами):

4.2.1. m = write(fd, addr, n);

4.2.2. m = read(fd, addr, n);

В системном вызове write второй аргумент должен быть адресом данного, которое мы хотим записать в файл. Поэтому мы должны были написать &c (во втором вызове write).

Ошибка в scanf — указание значения переменной вместо ее адреса — является довольно распространенной и не может быть обнаружена компилятором (даже при использовании прототипа функции scanf(char *fmt, . ), так как scanf — функция с переменным числом аргументов заранее не определенных типов). Приходится полагаться исключительно на собственную внимательность!

Как по дескриптору файла узнать, открыт он на чтение, запись, чтение и запись одновременно? Вот два варианта решения:

Константа NOFILE означает максимальное число одновременно открытых файлов для одного процесса (это размер таблицы открытых процессом файлов, таблицы дескрипторов). Изучите описание системного вызова fcntl (file control).

Напишите функцию rename() для переименования файла. Указание: используйте системные вызовы link() и unlink(). Ответ: Вызов link(существующее_имя, новое_имя); создает файлу альтернативное имя — в UNIX файл может иметь несколько имен: так каждый каталог имеет какое-то имя в родительском каталоге, а также имя «.» в себе самом.

Каталог же, содержащий подкаталоги, имеет некоторое имя в своем родительском каталоге, имя «.» в себе самом, и по одному имени «..» в каждом из своих подкаталогов.

Этот вызов будет неудачен, если файл новое_имя уже существует; а также если мы попытаемся создать альтернативное имя в другой файловой системе. Вызов unlink(имя_файла) удаляет имя файла. Если файл больше не имеет имен — он уничтожается. Здесь есть одна тонкость: рассмотрим фрагмент Первый оператор создает пустой файл. Затем мы открываем файл и уничтожаем его единственное имя. Но поскольку есть программа, открывшая этот файл, он не удаляется немедленно! Программа далее работает с безымянным файлом при помощи дескриптора fd. Как только файл закрывается — он будет уничтожен системой (как не имеющий имен). Такой трюк используется для создания временных рабочих файлов.

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

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

Существование альтернативных имен у файла позволяет нам решить некоторые проблемы, которые могут возникнуть при использовании чужой программы, от которой нет исходного текста (которую нельзя поправить). Пусть программа выдает некоторую информацию в файл zz.out (и это имя жестко зафиксировано в ней, и не задается через аргументы программы): Мы же хотим получить вывод на терминал, а не в файл. Очевидно, мы должны сделать файл zz.out синонимом устройства /dev/tty (см. конец этой главы). Это можно сделать командой ln: или программно: (про fork, exec, wait смотри в главе про UNIX).

Еще один пример: программа a.out желает запустить программу /usr/bin/vi (смотри про функцию system() сноску через несколько страниц): На вашей же машине редактор vi помещен в /usr/local/bin/vi. Тогда вы просто создаете альтернативное имя этому редактору:

Помните, что альтернативное имя файлу можно создать лишь в той же файловой системе, где содержится исходное имя. В семействе BSD **** это ограничение можно обойти, создав «символьную ссылку» вызовом

Символьная ссылка — это файл, содержащий имя другого файла (или каталога). Система не производит автоматический подсчет числа таких ссылок, поэтому возможны «висячие» ссылки — указывающие на уже удаленный файл. Прочесть содержимое файла-ссылки можно системным вызовом

Системный вызов stat автоматически разыменовывает символьные ссылки и выдает информацию про указуемый файл. Системный вызов lstat (аналог stat за исключением названия) выдает информацию про саму ссылку (тип файла S_IFLNK). Коды доступа к ссылке не имеют никакого значения для системы, существенны только коды доступа самого указуемого файла.

Еще раз: символьные ссылки удобны для указания файлов и каталогов на другом диске. Пусть у вас не помещается на диск каталог /opt/wawa. Вы можете разместить каталог wawa на диске USR: /usr/wawa. После чего создать символьную ссылку из /opt: чтобы программы видели этот каталог под его прежним именем /opt/wawa.

Еще раз: hard link — то, что создается системным вызовом link, имеет тот же I-node (индексный узел, паспорт), что и исходный файл. Это просто альтернативное имя файла, учитываемое в поле di_nlink в I-node. symbolic link — создается вызовом symlink. Это отдельный самостоятельный файл, с собственным I-node. Правда, коды доступа к этому файлу не играют никакой роли; значимы только коды доступа указуемого файла.

Напишите программу, которая находит в файле символ @ и выдает файл с этого места дважды. Указание: для запоминания позиции в файле используйте вызов lseek() позиционирование указателя чтения/записи: А для возврата в эту точку: По поводу lseek надо помнить такие вещи:

  • lseek(fd, offset, whence) устанавливает указатель чтения/записи на расстояние offset байт при whence: Эти значения whence можно обозначать именами:
  • Установка указателя чтения/записи — это виртуальная операция, т.е. реального подвода магнитных головок и вообще обращения к диску она не вызывает. Реальное движение головок к нужному месту диска произойдет только при операциях чтения/записи read()/write(). Поэтому lseek() — дешевая операция.
  • lseek() возвращает новую позицию указателя чтения/записи RWptr относительно начала файла (long смещение в байтах). Помните, что если вы используете это значение, то вы должны предварительно описать lseek как функцию, возвращающую длинное целое: long lseek();
  • Аргумент offset должен иметь тип long (не ошибитесь!).
  • Если поставить указатель за конец файла (это допустимо!), то операция записи write() сначала заполнит байтом ‘\0’ все пространство от конца файла до позиции указателя; операция read() при попытке чтения из-за конца файла вернет «прочитано 0 байт». Попытка поставить указатель перед началом файла вызовет ошибку.
  • Вызов lseek() неприменим к pipe и FIFO-файлам, поэтому попытка сдвинуться на 0 байт выдаст ошибку: выдает «истину», если fd — дескриптор «трубы»(pipe).

Напомним, что при записи в файл, его длина автоматически увеличивается, когда мы записываем информацию за прежним концом файла. Это вызывает отведение места на диске для хранения новых данных (порциями, называемыми блоками — размером от 1/2 до 8 Кб в разных версиях). Таким образом, размер файла ограничен только наличием свободных блоков на диске.

В нашем примере получится файл длиной 1024003 байта. Будет ли он занимать на диске 1001 блок (по 1 Кб)?

В системе UNIX — нет! Вот кое-что про механику выделения блоков:

  • Блоки располагаются на диске не обязательно подряд — у каждого файла есть специальным образом организованная таблица адресов его блоков.
  • Последний блок файла может быть занят не целиком (если длина файла не кратна размеру блока), тем не менее число блоков у файла всегда целое (кроме семейства BSD, где блок может делиться на фрагменты, принадлежащие разным файлам). Операционная система в каждый момент времени знает длину файла с точностью до одного байта и не позволяет нам «заглядывать» в остаток блока, пока при своем «росте» файл не займет эти байты.
  • Блок на диске физически выделяется лишь после операции записи в этот блок.

В нашем примере: при создании файла его размер 0, и ему выделено 0 блоков. При первой записи файлу будет выделен один блок (логический блок номер 0 для файла) и в его начало запишется «begin». Длина файла станет равна 5 (остаток блока — 1019 байт — не используется и файлу логически не принадлежит!). Затем lseek поставит указатель записи далеко за конец файла и write запишет в 1000-ый блок слово «end». 1000-ый блок будет выделен на диске. В этот момент у файла «возникнут» и все промежуточные блоки 1..999. Однако они будут только «числиться за файлом», но на диске отведены не будут (в таблице блоков файла это обозначается адресом 0)! При чтении из них будут читаться байты ‘\0’. Это так называемая «дырка» в файле. Файл имеет размер 1024003 байта, но на диске занимает всего 2 блока (на самом деле чуть больше, т.к. часть таблицы блоков файла тоже находится в специальных блоках файла). Блок из «дырки» станет реальным, если в него что-нибудь записать.

Будьте готовы к тому, что «размер файла» (который, кстати, можно узнать системным вызовом stat) — это в UNIX не то же самое, что «место, занимаемое файлом на диске».

Ответ: используется системный вызов open() вместо функции fopen(); а также close вместо fclose, а их форматы (и результат) различаются! Следует четко различать две существующие в Си модели обмена с файлами: через системные вызовы: open, creat, close, read, write, lseek; и через библиотеку буферизованного обмена stdio: fopen, fclose, fread, fwrite, fseek, getchar, putchar, printf, и.т.д. В первой из них обращение к файлу происходит по целому fd — дескриптору файла, а во втором — по указателю FILE *fp — указателю на файл. Это параллельные механизмы (по своим возможностям), хотя второй является просто надстройкой над первым. Тем не менее, лучше их не смешивать.

* I-узел (I-node, индексный узел) — своеобразный «паспорт», который есть у каждого файла (в том числе и каталога). В нем содержатся:

** BSD — семейство UNIX-ов из University of California, Berkley. Berkley Software Distribution.

© Copyright А. Богатырев, 1992-95
Си в UNIX


С++ чтение текстового файла блоками

Я действительно не нашел удовлетворительного ответа в google, а I/O в C++ немного сложнее. Я хотел бы, если это возможно, прочитать текстовый файл по блокам в вектор. Увы, я не мог понять, как это сделать. Я даже не уверен, если мой бесконечный цикл будет нарушать все возможности, потому что ввод-вывод сложнее. Итак, лучший способ, который я смог выяснить, это:

Илон Маск рекомендует:  Что такое код closemetafile

Пожалуйста, обратите внимание на вторую строку с ‘\ 0’. Разве это не странно? Могу ли я сделать что-то лучше? Могу ли я читать данные в векторе вместо массива символов? Уместно ли прямо читать вектор?

Спасибо за ваши ответы.

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

Вы должны быть в порядке:

Элементы в векторе гарантированно сохраняются смежно, поэтому это должно работать, но вы должны убедиться, что вектор никогда не пуст. Я задал аналогичный вопрос здесь недавно — проверьте ответы на это для конкретных деталей из стандарта. Могу ли я вызывать функции, которые принимают аргумент array/pointer с использованием std :: vector?

Именно то, что вам нужно.

В последнее время я столкнулся с той же проблемой. Я использую чтение и gcount founction для его решения. Это работает хорошо. Вот код.

Посмотрите другие вопросы по меткам c++ text-files ifstream или Задайте вопрос

Читать блок символов из файла за раз и следующий блок в следующем цикле в C

Предположим, у меня есть 100 символов в файл. Я хочу читать по 10 символов за раз, работать с этим, а затем я хочу прочитать 11-20 = 10 символов, работать с ними и так далее.

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

Все методы, найденные в сети, предлагают мне прочитать все символы сразу или прочитать символы один за другим. Я видел

Это выглядело многообещающе. Но я не могу использовать его для своих целей. После прочтения 1–10-го символа, как я могу дать указание от 11 до 20-го символа?

Решения C требуются, но также могут использоваться решения C ++.

Решение

fread() последовательная файловая операция Вы просто продолжаете называть это до EOF или itemsread == 0 ,

Другие решения

Я думаю, что вы пытаетесь обрабатывать 10 байтов за раз, пока не достигнете EOF?
Функция библиотеки C int fgetc(FILE *stream) получает следующий символ (беззнаковый символ) из указанного потока и продвигает индикатор положения для потока. Таким образом, мы можем попытаться прочитать 10 байтов в буфер, если встречается EOF, мы обрабатываем n байты в буфере.
Может быть, что-то вроде этого:

Read читать блок из файла

Синтаксис вызова системной функции read (читать):

где fd — дескриптор файла, возвращаемый функцией open, buffer — адрес структуры данных в пользовательском процессе, где будут размещаться считанные данные в случае успешного завершения выполнения функции read, count — количество байт, которые пользователю нужно прочитать, number — количество фактически прочитанных байт. На Рисунке 5.5 приведен алгоритм read, выполняющий чтение обычного файла. Ядро обращается в таблице файлов к записи, которая соответствует значению пользовательского дескриптора файла, следуя за указателем (см. Рисунок 5.3). Затем оно устанавливает значения нескольких параметров ввода-вывода в адресном пространстве процесса (Рисунок 5.6), тем самым устраняя необходимость в их передаче в качестве параметров функции. В частности, ядро указывает в качестве режима ввода-вывода «чтение», устанавливает флаг, свидетельствующий о том, что ввод-вывод направляется в адресное пространство пользователя, значение поля счетчика байтов приравнивает количеству байт, которые будут прочитаны, устанавливает адрес пользовательского буфера данных и, наконец, значение смещения (из таблицы файлов), равное смещению в байтах внутри файла до места, откуда начинается ввод-вывод. После того, как ядро установит значения параметров ввода-вывода в адресном пространстве процесса, оно обращается к индексу, используя указатель из таблицы файлов, и блокирует его прежде, чем начать чтение из файла.

Рисунок 5.5. Алгоритм чтения из файла

Рисунок 5.6. Параметры ввода-вывода, хранящиеся в пространстве процесса

Затем в алгоритме начинается цикл, выполняющийся до тех пор, пока операция чтения не будет произведена до конца. Ядро преобразует смещение в байтах внутри файла в номер блока, используя алгоритм bmap, и вычисляет смещение внутри блока до места, откуда следует начать ввод-вывод, а также количество байт, которые будут прочитаны из блока. После считывания блока в буфер, возможно, с продвижением (алгоритмы bread и breada) ядро копирует данные из блока по назначенному адресу в пользовательском процессе. Оно корректирует параметры ввода-вывода в адресном пространстве процесса в соответствии с количеством прочитанных байт, увеличивая значение смещения в байтах внутри файла и адрес места в пользовательском процессе, куда будет доставлена следующая порция данных, и уменьшая число байт, которые необходимо прочитать, чтобы выполнить запрос пользователя. Если запрос пользователя не удовлетворен, ядро повторяет весь цикл, преобразуя смещение в байтах внутри файла в номер блока, считывая блок с диска в системный буфер, копируя данные из буфера в пользовательский процесс, освобождая буфер и корректируя значения параметров ввода-вывода в адресном пространстве процесса. Цикл завершается, либо когда ядро выполнит запрос пользователя полностью, либо когда в файле больше не будет данных, либо если ядро обнаружит ошибку при чтении данных с диска или при копировании данных в пространство пользователя. Ядро корректирует значение смещения в таблице файлов в соответствии с количеством фактически прочитанных байт; поэтому успешное выполнение операций чтения выглядит как последовательное считывание данных из файла. Системная операция lseek (раздел 5.6) устанавливает значение смещения в таблице файлов и изменяет порядок, в котором процесс читает или записывает данные в файле.

Рисунок 5.7. Пример программы чтения из файла

Рассмотрим программу, приведенную на Рисунке 5.7. Функция open возвращает дескриптор файла, который пользователь засылает в переменную fd и использует в последующих вызовах функции read. Выполняя функцию read, ядро проверяет, правильно ли задан параметр «дескриптор файла», а также был ли файл предварительно открыт процессом для чтения. Оно сохраняет значение адреса пользовательского буфера, количество считываемых байт и начальное смещение в байтах внутри файла (соответственно: lilbuf, 20 и 0), в пространстве процесса. В результате вычислений оказывается, что нулевое значение смещения соответствует нулевому блоку файла, и ядро возвращает точку входа в индекс, соответствующую нулевому блоку. Предполагая, что такой блок существует, ядро считывает полный блок размером 1024 байта в буфер, но по адресу lilbuf копирует только 20 байт. Оно увеличивает смещение внутри пространства процесса на 20 байт и сбрасывает счетчик данных в 0. Поскольку операция read выполнилась, ядро переустанавливает значение смещения в таблице файлов на 20, так что последующие операции чтения из файла с данным дескриптором начнутся с места, расположенного со смещением 20 байт от начала файла, а системная функция возвращает число байт, фактически прочитанных, т.е. 20.

При повторном вызове функции read ядро вновь проверяет корректность указания дескриптора и наличие соответствующего файла, открытого процессом для чтения, поскольку оно никак не может узнать, что запрос пользователя на чтение касается того же самого файла, существование которого было установлено во время последнего вызова функции. Ядро сохраняет в пространстве процесса пользовательский адрес bigbuf, количество байт, которые нужно прочитать процессу (1024), и начальное смещение в файле (20), взятое из таблицы файлов. Ядро преобразует смещение внутри файла в номер дискового блока, как раньше, и считывает блок. Если между вызовами функции read прошло непродолжительное время, есть шансы, что блок находится в буферном кеше. Однако, ядро не может полностью удовлетворить запрос пользователя на чтение за счет содержимого буфера, поскольку только 1004 байта из 1024 для данного запроса находятся в буфере. Поэтому оно копирует оставшиеся 1004 байта из буфера в пользовательскую структуру данных bigbuf и корректирует параметры в пространстве процесса таким образом, чтобы следующий шаг цикла чтения начинался в файле с байта 1024, при этом данные следует копировать по адресу байта 1004 в bigbuf в объеме 20 байт, чтобы удовлетворить запрос на чтение.

Теперь ядро переходит к началу цикла, содержащегося в алгоритме read. Оно преобразует смещение в байтах (1024) в номер логического блока (1), обращается ко второму блоку прямой адресации, номер которого хранится в индексе, и отыскивает точный дисковый блок, из которого будет производиться чтение. Ядро считывает блок из буферного кеша или с диска, если в кеше данный блок отсутствует. Наконец, оно копирует 20 байт из буфера по уточненному адресу в пользовательский процесс. Прежде чем выйти из системной функции, ядро устанавливает значение поля смещения в таблице файлов равным 1044, то есть равным значению смещения в байтах до места, куда будет производиться следующее обращение. В последнем вызове функции read из примера ядро ведет себя, как и в первом обращении к функции, за исключением того, что чтение из файла в данном случае начинается с байта 1044, так как именно это значение будет обнаружено в поле смещения той записи таблицы файлов, которая соответствует указанному дескриптору.

Пример показывает, насколько выгодно для запросов ввода-вывода работать с данными, начинающимися на границах блоков файловой системы и имеющими размер, кратный размеру блока. Это позволяет ядру избегать дополнительных итераций при выполнении цикла в алгоритме read и всех вытекающих последствий, связанных с дополнительными обращениями к индексу в поисках номера блока, который содержит данные, и с конкуренцией за использование буферного пула. Библиотека стандартных модулей ввода-вывода создана таким образом, чтобы скрыть от пользователей размеры буферов ядра; ее использование позволяет избежать потерь производительности, присущих процессам, работающим с небольшими порциями данных, из-за чего их функционирование на уровне файловой системы неэффективно (см. упражнение 5.4).

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

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

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

Ядро может выгружать процесс, ведущий чтение, в режим задачи на время между двумя вызовами функций и планировать запуск других процессов. Так как по окончании выполнения системной функции с индекса снимается блокировка, ничто не мешает другим процессам обращаться к файлу и изменять его содержимое. Со стороны системы было бы несправедливо держать индекс заблокированным все время от момента, когда процесс открыл файл, и до того момента, когда файл будет закрыт этим процессом, поскольку тогда один процесс будет держать все время файл открытым, тем самым не давая другим процессам возможности обратиться к файлу. Если файл имеет имя «/etc/ passwd», то есть является файлом, используемым в процессе регистрации для проверки пользовательского пароля, один пользователь может умышленно (или, возможно, неумышленно) воспрепятствовать регистрации в системе всех остальных пользователей. Чтобы предотвратить возникновение подобных проблем, ядро снимает с индекса блокировку по окончании выполнения каждого вызова системной функции, использующей индекс. Если второй процесс внесет изменения в файл между двумя вызовами функции read, производимыми первым процессом, первый процесс может прочитать непредвиденные данные, однако структуры данных ядра сохранят свою согласованность.

Предположим, к примеру, что ядро выполняет два процесса, конкурирующие между собой (Рисунок 5.8). Если допустить, что оба процесса выполняют операцию open до того, как любой из них вызывает системную функцию read или write, ядро может выполнять функции чтения и записи в любой из шести последовательностей: чтение1, чтение2, запись1, запись2, или чтение1, запись1, чтение2, запись2, или чтение1, запись1, запись2, чтение2 и т.д. Состав информации, считываемой процессом A, зависит от последовательности, в которой система выполняет функции, вызываемые двумя процессами; система не гарантирует, что данные в файле останутся такими же, какими они были после открытия файла. Использование возможности захвата файла и записей (раздел 5.4) позволяет процессу гарантировать сохранение целостности файла после его открытия.

Рисунок 5.8. Процессы, ведущие чтение и запись файла

Наконец, программа на Рисунке 5.9 показывает, как процесс может открывать файл более одного раза и читать из него, используя разные файловые дескрипторы. Ядро работает со значениями смещений в таблице файлов, ассоциированными с двумя файловыми дескрипторами, независимо, и поэтому массивы buf1 и buf2 будут по завершении выполнения процесса идентичны друг другу при условии, что ни один процесс в это время не производил запись в файл «/etc/passwd».

функция read читает не весь файл

В итоге возращается 2 разных значения. В чем дело — не знаю.

Re: функция read читает не весь файл

Re: функция read читает не весь файл

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

Re: функция read читает не весь файл

девелопер, найди три отличия

p_var = new type(initializer);

p_var = new type [size];

Re: функция read читает не весь файл

>char * buf = new char(bufLength);

char * buf = new char[bufLength];


Re: функция read читает не весь файл

Re: функция read читает не весь файл

и тут valgrind бы не помешал. Может, сделать минифак по сям и не пропускать посты тех кто его не читал? :)

Re: функция read читает не весь файл

> Может, сделать минифак по сям

по сям плям плям

Re: функция read читает не весь файл

> и не пропускать посты тех кто его не читал? :)

Лучше переносить с позором в толксы.

Re: функция read читает не весь файл

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

Re: функция read читает не весь файл

да. невнимательность — страшный враг программиста

Re: функция read читает не весь файл

>и тут valgrind бы не помешал. Может, сделать минифак по сям и не пропускать посты тех кто его не читал? :)

Дело не в том, что не читал. Я уже и читал много и писал. Просто работая под *nix и имея в наличии только vi и g++ не всегда легко разобратся в своем коде и найти описку. Извиняюсь, если потратил чье-то бесценное время. Ну а тем, кто по теме отписался — еще раз огромное спасибо.

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

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

Как читать файлы

Python содержит в себе функцию, под названием «open», которую можно использовать для открытия файлов для чтения. Создайте текстовый файл под названием test.txt и впишите:

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

В первом примере мы открываем файл под названием test.txt в режиме «только чтение». Это стандартный режим функции открытия файлов. Обратите внимание на то, что мы не пропускаем весь путь к файлу, который мы собираемся открыть в первом примере. Python автоматически просмотрит папку, в которой запущен скрипт для text.txt. Если его не удается найти, вы получите уведомление об ошибке IOError. Во втором примере показан полный путь к файлу, но обратите внимание на то, что он начинается с «r». Это значит, что мы указываем Python, чтобы строка обрабатывалась как исходная. Давайте посмотрим на разницу между исходной строкой и обычной:

Как видно из примера, когда мы не определяем строку как исходную, мы получаем неправильный путь. Почему это происходит? Существуют определенные специальные символы, которые должны быть отображены, такие как “n” или “t”. В нашем случае присутствует “t” (иными словами, вкладка), так что строка послушно добавляет вкладку в наш путь и портит её для нас. Второй аргумент во втором примере это буква “r”. Данное значение указывает на то, что мы хотим открыть файл в режиме «только чтение». Иными словами, происходит то же самое, что и в первом примере, но более явно. Теперь давайте, наконец, прочтем файл!

Введите нижеизложенные строки в скрипт, и сохраните его там же, где и файл test.txt.

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

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

Если вы используете данный пример, будет прочтена и распечатана только первая строка текстового файла. Это не очень полезно, так что воспользуемся методом readlines() в дескрипторе:

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

Как читать файл по частям

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

Таким образом мы открываем файл в дескрипторе в режиме «только чтение», после чего используем цикл для его повторения. Стоит обратить внимание на то, что цикл можно применять к любым объектам Python (строки, списки, запятые, ключи в словаре, и другие). Весьма просто, не так ли? Попробуем прочесть файл по частям:

В данном примере мы использовали Python в цикле, пока читали файл по килобайту за раз. Как известно, килобайт содержит в себе 1024 байта или символов. Теперь давайте представим, что мы хотим прочесть двоичный файл, такой как PDF.

Как читать бинарные (двоичные) файлы

Это очень просто. Все что вам нужно, это изменить способ доступа к файлу:

Мы изменили способ доступа к файлу на rb, что значит read-binaryy. Стоит отметить то, что вам может понадобиться читать бинарные файлы, когда вы качаете PDF файлы из интернете, или обмениваетесь ими между компьютерами.

Пишем в файлах в Python

Как вы могли догадаться, следуя логике написанного выше, режимы написания файлов в Python это “w” и “wb” для write-mode и write-binary-mode соответственно. Теперь давайте взглянем на простой пример того, как они применяются.
ВНИМАНИЕ: использование режимов “w” или “wb” в уже существующем файле изменит его без предупреждения. Вы можете посмотреть, существует ли файл, открыв его при помощи модуля ОС Python.

Вот так вот просто. Все, что мы здесь сделали – это изменили режим файла на “w” и указали метод написания в файловом дескрипторе, чтобы написать какой-либо текст в теле файла. Файловый дескриптор также имеет метод writelines (написание строк), который будет принимать список строк, который дескриптор, в свою очередь, будет записывать по порядку на диск.

Выбирайте дешевые лайки на видео в YouTube на сервисе https://doctorsmm.com/. Здесь, при заказе, Вам будет предоставлена возможность подобрать не только недорогую цену, но и выгодные персональные условия приобретения. Торопитесь, пока на сайте действуют оптовые скидки!

Использование оператора «with»

В Python имеется аккуратно встроенный инструмент, применяя который вы можете заметно упростить чтение и редактирование файлов. Оператор with создает диспетчер контекста в Пайтоне, который автоматически закрывает файл для вас, по окончанию работы в нем. Посмотрим, как это работает:

Учим файловую систему читать

Что будет в этой статье

Продолжаем цикл статей о создании файловой системы в ядре Linux, основанный на материалах курса ОС в Академическом университете .

В прошлый раз мы настроили окружение, которое понадобится нам, чтобы знакомится с ядром. Затем мы взглянули на загружаемые модули ядра и написали простой «Hello, World!». Ну и наконец, мы написали простую и бесполезную файловую систему. Пришло время продолжить.

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

Почему так мало? Дело в том, что в этом посте нам потребуется определить структуру нашей файловой системы — то как она будет хранится на диске. Кроме того мы столкнемся с парой интересных моментов, таких как SLAB и RCU. Все это потребует некоторых объяснений — много слов и мало кода, так что пост и так будет довольно объемным.

Скучное начало

Наша файловая система должна быть простой, поэтому и хранится на диске она будет просто:

  • data blocks — блоки полезных данных, т. е. содержимое файлов и папок;
  • table of inodes — таблица индексных узлов; количество индексных узлов определяется при форматировании и хранятся они в непрерывной последовательности блоков;
  • free inodes — битовая карта свободных/занятых индексных узлов, которая занимает ровно 1 блок;
  • free blocks — битовая карта свободных/занятых блоков, так же занимает 1 блок;
  • super block — очевидно, суперблок нашей файловой системы; он хранит размер блока, количество блоков в таблице индексных узлов, номер индексного узла корневой папки;


Собственно похожая разметка диска используется и в других файловых системах. Например, группа блоков в ext2/3 имеет похожую структуру, только ext2/3 работает с несколькими такими группами, а мы ограничимся одной. Такой формат считается устаревшим, и новые файловые системы отходят от него. Например, btrfs использует более интересную схему , которая дает ряд преимуществ над семейством ext. Но да вернемся к нашим баранам.

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

В утилите mke2fs, которая используется для форматирования диска под файловые системы ext2/3, есть ключик -i , который указывает на какой объем диска нужно создавать индексный узел, т. е. если указать -i 16384, то на каждые 16 килобайт дискового пространства будет создано по индексному узлу. Я воспользуюсь самым простым вариантом — буду создавать индексный узел на каждые 16 Кб дискового пространства, без возможности изменения этого значения (пока по крайней мере).

Последний общий момент, который стоит затронуть — размер блока. Файловая система может работать с блоками разного размера, я буду поддерживать блоки в 512, 1024, 2048 и 4096 байт — ничего необычного. Это связано с тем, что с блоками, влезающими в страницу работать проще (мы вернемся к этому чуть позже), но делать так совсем не обязательно, более того, большие размеры блоков могут способствовать большей производительности.

Вообще подбор правильного размера блока для классических файловых систем — довольно занятная тема. Например, в известной книге по ОС приводятся информация о том, что при размере блока в 4 Кб 60 — 70% файлов будут помещаться в один блок. Чем больше файлов помещается в один блок, тем меньше фрагментация, выше скорость чтения, но и больше места используется впустую. В нашем случае, ко всему прочему, размер блока является главным ограничителем файловой системы — при размере блока в 4 Кб битовая карта свободных блоков может покрыть всего 128 Мб дискового пространства.

Назад к практике

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

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

Учитывая, что мы будем поддерживать разные размеры блоков (что, впрочем, не потребует от нас никаких усилий), мы должны знать какой размер используется в данной конкретной файловой системе, и суперблок хранит для нас эту информацию в поле dsb_block_size.

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

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

Обратите внимание, что для полей используются типы фиксированного размера. Это потому что мы будем записывать эту структуру на диск «как есть». Однако зафиксировать размер недостаточно, нужно зафиксировать еще порядок байт. Я буду использовать big endian , он же сетевой порядок байт, о чем говорит название типа (__be32).

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

Тип __be32, по факту, является синонимом uint32_t, но его название подчеркивает, что переменная хранит данные в big endian (этакий способ документации). В ядре есть аналогичный тип и для little endian.

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

Индексный узел, в первую очередь, определяет где на диске хранится файл/каталог. Способы хранения файлов могут быть самыми разнообразными, я буду использовать довольно простой — один экстент на один файл. Экстент — непрерывная последовательность блоков диска, т. е. каждый файл/каталог будет храниться в непрерывной последовательности блоков. Поля di_first и di_blocks хранят первый блок и количество блоков в экстенте соответственно.

Тут вы скажете, а как же в такой файловой системе дописывать данные в конец файла и добавлять записи в каталог? Действительно, полноценная реализации операций, приводящих к изменению размера файла/каталога, при таком способе хранения — это головная боль (и это не говоря об эффективности такой реализации), поэтому мы не будем делать полноценную реализации записи, но об этом в другой статье.

Однако у такой организации есть и положительные моменты — файлы не фрагментированы, а это хорошо влияет на скорость последовательного чтения. Поэтому такая структура может замечательно использоваться в файловых системах рассчитанных только на чтение, например, в iso 9660 (хотя и она уже поддерживает фрагментацию файлов).

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

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

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

Поле di_mode — хранит права доступа для группы владельца файла, владельца файла и всех остальных пользователей. Раз уж я сохранил группу и владельца то и права доступа стоит сохранить. Еще di_mode хранит тип объекта, который описывает индексный узел, например, является ли объект каталогом или файлом.

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

Форматируем диск

Итак, когда мы определили формат хранения файловой системы на диске, пора написать утилиту, которая приведет диск в правильный формат. Диск в Linux — это просто файл (тут уместно вспомнить известное дизайнерское решение Unix ). Так что форматирование диска — просто запись в файл нужных данных. А нужные данные в нашем случае — суперблок, битовые карты, и корневой каталог (пока пустой).

Чтобы не превращать статью о ядре Linux в статью о C++ (особенно в свете отношения Линуса к последнему ) я предлагаю вам самостоятельно разобраться с исходниками на github, но кратко пройдусь по основным классам:

  • Configuration — класс который хранит конфигурацию будущей файловой системы (размер блока, размер таблицы индексных узлов, количество блоков, имя файла устройства).
  • Block — представляет один блок диска. Данные пишутся и читаются с диска блоками.
  • BlocksCache — предоставляет доступ к блокам.
  • Inode — обертка над индексным узлом. Она скрывает преобразования порядка байт, запись и чтение данных индексного узла из блока.
  • SuperBlock — обертка на суперблоком. Как и в случае с Inode скрывает запись и чтение из блока, заполняет битовые карты, выделяет индексные узлы и блоки, т. е. по факту выполняет форматирование.

Утилита позволяет изменять размер блока через ключи -s или —block_size, и количество блоков, которые будут использованы под файловую систему через ключи -b или —blocks.

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

Назад к файловой системе

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

Эта структура будет представлять суперблок в памяти. Идея простая — читаем с диска aufs_disk_super_block и преобразуем его в aufs_super_block выполняя преобразования порядка байт и попутно вычисляя всякие полезные данные (в данном случае asb_inodes_in_block). Вообще эта структура — отличное место для всяких глобальных переменных файловой системы.

Вспоминая прошлый пост, мы имеем уже три структуры для представления суперблока:

  • super_block — структура, которую предоставляет ядро;
  • aufs_disk_super_block — структура, которая хранится на диске;
  • aufs_super_block — еще одна структура, которая будет хранится в памяти;

Две структуры — это понятно, но зачем нам третья? Дело в том, что Linux не знает ничего о нашей файловой системе, поэтому вполне вероятно, что super_block (как и inode, как и любая другая структура ядра Linux) не содержит всех нужных нам полей. Поэтому мы вынуждены заводить свои дополнительные структуры и связывать их со структурами ядра. Как же их связать?

В ядре есть два распространенных способа организации такой связи (назовем их композицией и наследованием). Для супер блока мы воспользуемся композицией. Этот способ требует поддержки со стороны ядра — внутри структуры super_block есть интересное поле:

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

Первое, что делает эта функция — это выделяет память под структуру суперблока. Для выделения памяти в ядре есть довольно много способов, kzalloc (и kmalloc вместе с ним) — самый простой. Работает как и обычный malloc, только требует передачи дополнительного набора флагов. Отличие kzalloc от kmalloc в том, что первый заполняет выделенную память нулями (что просто сводится к передаче дополнительно флага внутрь kmalloc).

Я упомянул о флагах, зачем они? Дело в том, что разные части ядра должны удовлетворять различным гарантиям. Например, в контексте обработки сетевого пакета нельзя блокироваться, а чтобы задействовать DMA требуется выделять память в специальном регионе памяти. Так как выделение памяти используется везде, требуется механизм «настройки». В нашем случае используется флаг GFP_NOFS, который говорит, что аллокатор памяти не будет обращаться к средствам файловой системы, что логично при реализации файловой системы, хотя в данном конкретном случае и не обязательно.

Естественно в ядре не забываем проверить, что память была выделена без проблем.

Следующий принципиальный момент — вызов функции sb_bread . Вот оно чтение с диска! Функция принимает указатель на суперблок и номер блока, который нужно прочитать — совсем просто. Возвращает функция указатель на структуру buffer_head , а сами данные блока доступны через поле b_data этой структуры.

Естественно и в этом случае не забываем проверить, что чтение прошло удачно.

Далее мы просто преобразуем указатель на char к указателю на структуру aufs_disk_super_block. Функция aufs_super_block_fill заполняет структуру aufs_super_block используя aufs_disk_super_block не делая ничего необычного:

Как не трудно догадаться функция be32_to_cpu преобразует число из big endian в порядок байт используемый платформой.

После того, как мы закончили работу с блоком его нужно освободить, для этого существует функция brelse . Она на самом деле просто уменьшает счетчик ссылок на этот блок. Блок не будет освобожден сразу, как только счетчик ссылок дойдет до 0 — для блоков в ядре работает сборщик мусора, который без серьезной необходимости не будет освобождать блок. Причина в том, что чтение блоков с диска — довольно дорогая операция, поэтому разумно поддерживать кеш прочитанных блоков, и при повторном чтении того же блока возвращать уже прочитанный (если, конечно, он еще присутствует в кеше).

Последнее что мы делаем — проверяем магическое число, нужно же убедиться, что на диске хранится действительно aufs.

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


Внимательный читатель, наверно, обратил внимание на одну нестыковку. Как я уже говорил, файловые системы могут работать с разными размерами блока, и хранится эта информация скорее всего в суперблоке. Так какого же размера блок прочитает функция sb_bread при чтении суперблока? В нашем случае все просто, по-умолчанию, размер блока устанавливается в размер блока блочного устройства (как много блоков. ). И мы надеемся, что его размера достаточно для структуры суперблока — в нашем случае так и есть.

Мы написали функцию для чтения суперблока, вызывать ее мы будем из aufs_fill_super (см. предыдущий пост), теперь она выглядит так:

Как я уже упоминал, мы сохраняем указатель на aufs_super_block в поле s_fs_info. Кроме того мы устанавливаем правильный размер блока вызовом sb_set_blocksize . Как говорит комментарий внутри функции размер блока должен быть от 512 байт до размера страницы — этим и обусловлен наш выбор размеров блока. Если файловая система должна работать с большим размером блока — потребуются дополнительные усилия (впрочем не такие большие).

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

Не трудно догадаться что парной к функции kmalloc является функция kfree , точнее даже функции, так как есть несколько реализаций kfree в ядре (еще тут и тут ), но не будем углубляться в детали.

Еще одно важное изменение внутри функции aufs_fill_sb — вызов aufs_inode_get. В прошлой статье мы создавали фиктивный inode, теперь мы научимся читать их с диска.

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

Работу с индексными узлами мы начнем с расширения структуры super_operations , с которой мы познакомились в прошлый раз. Теперь мы заполним ее таким образом:

Мы добавили в нее еще пару указателей на функции aufs_inode_alloc и aufs_inode_free. Это специфичные функции для аллокации и освобождения inode, тут то мы и столкнемся с SLAB (с этим зверем мы, на самом, деле уже столкнулись в виде kmalloc) и RCU (совсем чуть-чуть).

Итак выделение памяти для индексного узла начнем с определения еще одной структуры — представления индексного узла в памяти (как это было с суперблоком):

В этот раз мы будем использовать «наследование» вместо композиции. Наследование в C выглядит совсем не хитро (что не удивительно, учитывая, что в C нет поддержки наследования). Для этого мы просто делаем первым полем структуры aufs_inode базовую структуру (базовый класс) — структуру inode . Таким образом указатель на aufs_inode можно использовать в качестве указателя на inode, как впрочем и наоборот (если конечно мы точно знаем, что данный указатель ссылается именно на aufs_inode).

По сравнению с композицией, «наследование» само по себе не требует поддержки со стороны ядра, кроме того оно выгоднее с точки зрения числа выделений памяти — на каждый индексный узел требуется одно выделение, вместо двух (как это было с суперблоком). Так же в отличие от суперблока, почти все нужные поля уже присутствуют внутри inode. Однако это скорее исключение, чем правило, ведь наша файловая система хранит данные на диске очень просто.

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

Но изначально выигрыш в скорости выделения памяти при использовании SLAB-ов был не только (и не столько) за счет более простого управления памяти, а за счет сокращения расходов на инициализацию этой памяти. Действительно, SLAB аллокатор зачастую используется не просто для выделения объектов одного размера, а для выделения объектов одного типа, что позволяет пропустить инициализацию некоторых полей при повторном выделении одного участка памяти. Например, мьютексы, спинлоки и другие подобные объекты при освобождении объекта скорее всего имеют «правильное» значение, и при повторном выделении не нуждаются в повторной инициализации. За деталями и результатами измерений прошу обратиться к оригинальной статье .

На данный момент в Linux имеется три различных вида SLAB аллокаторов — SLAB, SLUB и SLOB. Не будем вдаваться в различия между ними, интерфейс они предоставляют один и тот же. Итак, для создания SLAB аллокатора мы будем использовать следующую функцию:

При создании SLAB-а функции kmem_cache_create передаются имя, размер объекта, функция инициализации (функция, которая будет вызвана только один раз, при первом выделении объекта), и еще пара параметров, в суть которых я вдаваться не буду. Но чтобы не оставлять интересующихся совсем без информации я скажу, что создание SLAB-а для индексных узлов во всех файловых системах выглядит одинаково — различия не существенны.

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

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

Теперь обещанное касание RCU. В двух словах, RCU — распространенный в ядре механизм синхронизации (а также безопасного освобождения памяти для lock-free алгоритмов). RCU сам по себе заслуживает отдельной статьи и на хабре есть такая . Более того, создатель этой техники, а по совместительству, мейнтейнер RCU в ядре Linux написал целую книгу , в которой коснулся и своего детища тоже.

Но нам из всего зоопарка RCU функций необходимо разобраться только с rcu_barrier (как и с kfree, есть и другая реализация этой функции тут ). Если по-простому, то эта функция дождется, пока все отложенные работы над защищенными RCU данными завершатся, после чего вернет управление тому, кто ее вызвал, соответственно функция блокирующая. Зачем нам это нужно мы увидим чуть ниже.

Вернемся к выделению памяти, рассмотрим уже упомянутую выше функцию:

Она использует созданный ранее SLAB аллокатор (через одну из реализаций kmem_cache_alloc ) и возвращает указатель на inode — ничего необычного, а вот функция освобождения немного более интересная:

Тут мы опять сталкиваемся с RCU. Тут стоит сказать пару слов о lock-free алгоритмах, проблема таких алгоритмов в том, что без блокировок нет гарантий, что объект не используется параллельно каким-либо другим потоком исполнения, а значит освобождать память занятую этим объектом нельзя — другой поток может хранить указатель на него. Поэтому в lock-free алгоритмах приходится задумываться о стратегиях безопасного освобождения памяти, а RCU предоставляет средства для решения этой проблемы. Все реализации функции call_rcu откладывают выполнение некоторой функции (в нашем случае функции освобождения aufs_free_callback) до тех пор, пока это не станет безопасным. А уже упомянутая выше rcu_barrier ждет завершения всех отложенных функций.

Вы устали? Ничего страшного, мы уже приближаемся к финалу. Теперь мы будем читать индексный узел с диска. Для этого я написал уже упомянутую функцию aufs_inode_get:

Объяснение я начну с простых моментов — функции AUFS_SB и AUFS_INODE позволяют получить указатель на структуры aufs_super_block и aufs_inode, через указатели на super_block и inode соответственно. Я не буду приводить их код (он довольно простой), потому что я уже описал выше как эти структуры связаны.

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

А вот теперь интересный момент — пара функций iget_locked и unlock_new_inode . Как и в случае с блоками ядро поддерживает кеш inode-ов, это нужно не только для того, чтобы лишний раз не читать индексный узел с диска. Дело в том, что один и тот же файл/каталог может быть открыт сразу несколькими процессами, в этом случае все они должны оперировать одним экземпляром inode, чтобы их можно было синхронизировать друг с другом. Аналогичное рассуждение справедливо для блоков пожалуй даже в большей степени, так что к блока это тоже применимо.

Итак функция idet_locked в первую очередь ищет inode в кеше и выделяет память под новый, если inode не найден. Если индексный узел был выделен заново, а не найден в кеше, в поле i_state будет установлен флаг I_NEW, а также будет захвачен спинлок этого узла (поле i_lock). Поэтому наша функция сначала проверяет поле i_state, и если флаг I_NEW сброшен просто возвращаем кешированный inode. В противном случае мы должны заполнить inode, для этого мы читаем нужный блок с диска (с помощью уже известной вам sb_bread).

Функция aufs_inode_fill как раз и занимается заполнением:

Опять никакой магии, за исключением пары функций i_uid_write и i_gid_write . Но и они не делают ничего особенного — просто присваивают значения соответствующим полям.

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

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

Вместо заключения

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

Все исходники доступны по ссылке. В репозитории теперь две папки — kern и user. Как не трудно догадаться, одна хранит код нашего модуля, вторая — код утилиты для форматирования. Кода стало больше, а значит вероятность появления в нем ошибок стала больше — замечания, исправления, любая конструктивная критика и pull request-ы приветствуются.

Чтобы получить образ диска для монтирования можно сделать так:

Теперь можно использовать файл image так, как это показано в предыдущем посте.

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

лабы по информатике, егэ

лабораторные работы и задачи по программированию и информатике, егэ по информатике

Python Урок 9. Работа с файлами

Файлы в Python

В целом различают два типа файлов (и работы с ними):

  • текстовые файлы со строками неопределенной длины;
  • двоичные (бинарные) файлы (хранящие коды таких данных, как, например, рисунки, звуки, видеофильмы);
  1. открытие файла;
    • режим чтения,
    • режим записи,
    • режим добавления данных.
  2. работа с файлом;
  3. закрытие файла.

В python открыть файл можно с помощью функции open с двумя параметрами:

  • имя файла (путь к файлу);
  • режим открытия файла:
  • «r» – открыть на чтение,
  • «w» – открыть на запись (если файл существует, его содержимое удаляется),
  • «a» – открыть на добавление.

В коде это выглядит следующим образом:

Fin = open ( «input.txt» ) Fout = open ( «output.txt», «w» ) # работа с файлами Fout.close() Fin.close()

Работа с текстовыми файлами в Питон

  • Чтение из файла происходит двумя способами:
    1. построчно с помощью метода readline:

str1 = Fin.readline() # str1 = 1 str2 = Fin.readline() # str2 = 2

str = Fin.read() »’ str = 1 2 3 »’

str = Fin.readline().split() print(str[0]) print(str[1])

файл input.txt:
12 17

Fin = open ( «D:/input.txt» ) str = Fin.readline().split() x, y = int(str[0]), int(str[1]) print(x+y)

. x, y = [int(i) for i in s] print(x+y)

* Функция int преобразует строковое значение в числовое.

Fout = open ( «D:/out.txt»,»w» ) Fout.write («hello»)

Fout.write ( » <:d>+ <:d>= <:d>\n».format(x, y, x+y) )

В таком случае вместо шаблонов последовательно подставляются значения параметров метода format (сначала x, затем y, затем x+y).

Аналогом «паскалевского» eof (если конец файла) является обычный способ использования цикла while или с помощью добавления строк в список:

while True: str = Fin.readline() if not str: break

Fin = open ( «input.txt» ) lst = Fin.readlines() for str in lst: print ( str, end = «» ) Fin.close()

for str in open ( «input.txt» ): print ( str, end = «» )

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

lst = [] while True: st = Fin.readline() if not st: break lst.append (int(st))

Fout = open ( «output.txt», «w» ) Fout.write (str(lst)) # функция str преобразует числовое значение в символьное Fout.close()

for x in lst: Fout.write (str(x)+»\n») # запись с каждой строки нового числа

Рассмотрим на примере обработку строковых значений.

Необходимо записать в текстовый файл сведения о сотрудниках, возраст которых меньше 40.

функция read читает не весь файл

В итоге возращается 2 разных значения. В чем дело — не знаю.

Re: функция read читает не весь файл

Re: функция read читает не весь файл

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

Re: функция read читает не весь файл

девелопер, найди три отличия

p_var = new type(initializer);

p_var = new type [size];

Re: функция read читает не весь файл

>char * buf = new char(bufLength);

char * buf = new char[bufLength];

Re: функция read читает не весь файл

Re: функция read читает не весь файл

и тут valgrind бы не помешал. Может, сделать минифак по сям и не пропускать посты тех кто его не читал? :)


Re: функция read читает не весь файл

> Может, сделать минифак по сям

по сям плям плям

Re: функция read читает не весь файл

> и не пропускать посты тех кто его не читал? :)

Лучше переносить с позором в толксы.

Re: функция read читает не весь файл

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

Re: функция read читает не весь файл

да. невнимательность — страшный враг программиста

Re: функция read читает не весь файл

>и тут valgrind бы не помешал. Может, сделать минифак по сям и не пропускать посты тех кто его не читал? :)

Дело не в том, что не читал. Я уже и читал много и писал. Просто работая под *nix и имея в наличии только vi и g++ не всегда легко разобратся в своем коде и найти описку. Извиняюсь, если потратил чье-то бесценное время. Ну а тем, кто по теме отписался — еще раз огромное спасибо.

Разработка имитационных моделей в виде приложений с интерфейсом

8.1. Применение текстовых объектов и потоков данных

Модель представляет собой набор операторов, содержащихся в одном объекте «Модель» и в любом количестве необязательных текстовых объектов.

Текстовые объекты с наборами операторов модели подключаются к объекту «Модель» командой INCLUDE. Формат команды :

Операнд А — спецификация файла (полный путь доступа к файлу). Допустимые значения — String . Например:

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

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

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

Команда INCLUDE допускает пять уровней вложенности файлов модели. Нельзя помещать команду INCLUDE в Plus-процедуру.

Операторы INCLUDE можно также закреплять за функциональными клавишами. Это позволяет одним нажатием клавиши объекту «Процесс моделирования» передать набор команд и (или) Plus-операторов, содержащихся в текстовом файле.

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

Поток данных — это последовательность текстовых строк, используемых процессом моделирования. Существуют два типа потоков данных:

  • потоки ввода-вывода (I/O или «файловые» потоки) для доступа к файлам;
  • потоки в памяти для тестирования или прямого доступа к внутренним данным.

Основной элемент потока данных — текстовая строка, которая является строкой печатных символов, включая пробелы.

Для обработки потоков данных существуют пять блоков GPSS World: OPEN , CLOSE , READ , WRITE , SEEK . Три из них — READ , WRITE , SEEK — выполняют операции только с одной отдельной строкой текста.

Перейдем к рассмотрению блоков обработки потоков данных.

8.1.1. Блок OPEN

Блок OPEN предназначен для инициализации потока данных. Формат блока:

Операнд А — дескриптор потока данных. Операнд определяет тип потока данных. Он обрабатывается как строка. Если это нулевая строка, создается поток в памяти. Если это канальное имя, такое как «\pipe\mypipe» , создается канальный поток. В противном случае создается поток ввода-вывода и предполагается, что операнд А является спецификацией файла.

Операнд В — номер потока данных, произвольное положительное число, задаваемое пользователем. Нумерация потоков введена с целью использования одновременно в одном процессе моделирования нескольких потоков данных. По умолчанию номер потока данных равен 1.

Операнд С — метка блока, в который направляется транзакт в случае ошибки инициализации потока данных. Коды ошибок:

  • 0 — нет ошибки;
  • 10 — длинное имя файла;
  • 11 — ошибка чтения внешнего файла;
  • 12 — во время попытки открыть файл был запрещен доступ к памяти.

В первом примере открывается поток ввода-вывода, потоку присваивается номер 3 и в случае ошибки открытия активный транзакт направляется к блоку с меткой Kon1 . Операндом А указан неполный путь доступа к файлу, поэтому подразумевается, что используется папка объекта «Процесс моделирования». Если файл с указанным именем не найден, то предполагается, что файл создается, и ошибка не возникает.

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

В третьем примере открывается поток в памяти.

8.1.2. Блок CLOSE

Блок CLOSE предназначен для закрытия потока данных. Формат блока:

Операнд А — номер или имя параметра транзакта, в который записывается код ошибки закрытия потока данных. Если такой параметр не существует, он создается.

Операнд В — номер закрываемого потока данных, по умолчанию равен 1, т. е. если операнд В не используется, закрывается поток номер один.

Операнд С — метка блока, в который направляется транзакт в случае ошибки закрытия потока данных. Коды ошибок:

  • 0 — нет ошибки;
  • 41 — запись файла на диск не произведена из-за ошибки ввода-вывода;
  • 42 — файл не был открыт.

В примере блок CLOSE закрывает поток данных, номер которого задан выражением в скобках. Это выражение вычисляется, округляется и используется в качестве номера потока данных (должен быть положительным целым числом).

Блок CLOSE для потоков ввода-вывода записывает данные из виртуальной памяти в дисковый файл.

8.1.3. Блок READ

Блок READ предназначен для считывания из потока данных текстовой строки. Формат блока:

Операнд А — номер или имя параметра транзакта, в который записывается считанная из потока ввода или потока в памяти строка, находящаяся на позиции текущей строки. После считывания позиция текущей строки увеличивается на единицу. Если такой параметр активного транзакта не существует, он создается.

Операнд В — номер потока данных, из которого производится считывание. По умолчанию равен 1, т. е. если операнд В не используется, считывание производится из потока номер один.

Операнд С — метка блока, в который направляется транзакт в случае ошибки считывания. Если операнд С не используется, код ошибки все равно сохраняется. Для его получения нужно использовать блок CLOSE . Коды ошибок:

  • 21 — во время попытки выполнить чтение был запрещен доступ к памяти;
  • 22 — файл не был открыт.

В примере блок READ считывает текстовую строку из потока данных номер 4 и записывает в параметр транзакта с именем Stro-ka_Text . Если текстовая строка считывается без ошибки, а параметр активного транзакта не существует, он создается. В случае ошибки строка не считывается и активный транзакт направляется к блоку с меткой Kon5 .

8.1.4. Блок WRITE

Блок WRITE предназначен для передачи текстовой строки потоку данных. Формат блока:

Операнд А — текстовая строка, которая должна быть передана потоку данных.

Операнд В — номер потока данных, по умолчанию равен 1.

Операнд С — метка блока, в который направляется транзакт в случае ошибки записи. Коды ошибок:

  • 0 — нет ошибки;
  • 31 — во время попытки выполнить запись был запрещен доступ к памяти;
  • 32 — файл не был открыт.

Операнд D — задает режим работы блока WRITE . Если операнд D не используется (по умолчанию) или равен ON , блок WRITE работает в режиме вставки. Если операнд D равен OFF — в режиме замены.

  • все текстовые строки, находящиеся на или за позицией текущей строки, сдвигаются на одну позицию;
  • если позиция текущей строки находится далеко за последней текстовой строкой, она устанавливается сразу после последней строки потока данных;
  • копия новой текстовой строки помещается на позицию текущей строки;
  • позиция текущей строки увеличивается на единицу. Режим замены:
  • если позиция текущей строки находится далеко за последней текстовой строкой, все промежуточные позиции заполняются нулевыми текстовыми строками;
  • текстовая строка, находящаяся на позиции текущей строки, удаляется;
  • копия новой текстовой строки помещается на позицию текущей строки;
  • позиция текущей строки увеличивается на единицу. Пример:

В этом примере блок WRITE передает текстовую строку потоку данных с номером 5. Если происходит ошибка, активный транзакт переходит к блоку с меткой Met3 . Иначе он переходит к следующему по порядку блоку. Если в данном случае поток данных является потоком вывода или потоком в памяти, запись производится в режиме вставки, так как операнд D не используется.

Илон Маск рекомендует:  Что такое код sybase_data_seek
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL