Основы низкоуровневого ввода-вывода



Обзор механизмов ввода-вывода в Linux

 

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

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

Функции ввода/вывода делятся на три класса:

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

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

Для пользователя файл, открытый на верхнем уровне, представляется как последовательность считываемых или записываемых байтов. Чтобы отразить эту особенность организации ввода/вывода, предложено понятие "поток" (англ. stream). Когда файл открывается, с ним связывается поток, выводимая информация записывается "в поток", а считываемая берется "из потока".

Когда поток открывается для ввода/вывода, он связывается со структурой типа FILE, определенной с помощью typedef в файле stdio . h. Эта структура содержит свою необходимую информацию о файле. При открытии файла с помощью стандартной функции fopen возвращается указатель на структуру типа FILE. Этот указатель, называемый указателем потока, используете для последующих операций с файлом. Его значение передается всем библиотечным функциям, используемым для ввода/вывода через этот поток.

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

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

Они позволяют читать или записывать на консоль (терминал) или в порт ввода/вывода (например, порт принтера). Эти функции читают или записывают данные побайтно. Для ввода или вывода с консоли устанавливаются некоторые дополнительные режимы, например, ввод с эхо-печатью символов или без нее, установка окна вывода, цветов текста и фона. Функции для консоли и порта являются уникальными для компьютеров, совместимых с IBM D PC.

3. Ввод/вывод низкого уровня (с использованием понятия "дескриптор"). Функции низкого уровня не выполняют буферизацию и форматирование данных, они позволяют непосредственно пользоваться средствами ввода/вывода операционной системы.

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

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

С точки зрения модели КИС (Клиент-Интерфейс-Сервер), сервером стандартных механизмов ввода вывода языка С (printf , scant , FILE *, fprintf , fputc и т. д.) является библиотека языка. А сервером низкоуровневого ввода-вывода в Linux является само ядро операционной системы.

Пользовательские программы взаимодействуют с ядром операционной системы посредством специальных механизмов, называемых системными вызовами (system calls, syscalls). Внешне системные вызовы реализованы в виде обычных функций языка С, однако каждый раз вызывая такую функцию, мы обращаемся непосредственно к ядру операционной системы. Список всех системных вызовов Linux можно найти в файле / usr / include / asm / unistd . h. Здесь мы рассмотрим основные системные вызовы, осуществляющие ввод-вывод: open (), close (), read (), write (), lseek () и некоторые другие.

 

 

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

 

В языке С при осуществлении ввода-вывода мы используем указатель FILE *. Даже функция printf () в итоге сводится к вызову vfprintf ( stdout ,...), разновидности функции fprintf (); константа stdout имеет тип
struct _ IO _ FILE *, синонимом которого является тип FILE *. Это к тому, что консольный ввод-вывод - это файловый ввод-вывод. Стандартный поток ввода, стандартный поток вывода и поток ошибок (как в С, так и в C++) - это файлы. В Linux все, куда можно что-то записать или откуда можно что-то прочитать представлено (или может быть представлено) в виде файла. Экран, клавиатура, аппаратные и виртуальные устройства, каналы, сокеты - все это файлы. Это очень удобно, поскольку ко всему можно применять одни и те же механизмы ввода-вывода, с которыми мы и познакомимся в этой главе. Владение механизмами низкоуровневого ввода-вывода дает свободу перемещения данных в Linux. Работа с локальными файловыми системами, межсетевое взаимодействие, работа с аппаратными устройствами, - все это осуществляется в Linux посредством низкоуровневого ввода-вывода.

Вы знаете, что при запуске программы в системе создается новый процесс (здесь есть свои особенности, о которых пока говорить не будем). У каждого процесса (кроме init) есть свой родительский процесс (parent process или просто parent), для которого новоиспеченный процесс является дочерним (child process, child). Каждый процесс получает копию окружения (environment) родительского процесса. Оказывается, кроме окружения дочерний процесс получает в качестве багажа еще и копию таблицы файловых дескрипторов.

Файловый дескриптор (file descriptor) - это целое число (int), соответствующее открытому файлу. Дескриптор, соответствующий реально открытому файлу всегда больше или равен нулю. Копия таблицы дескрипторов (читай: таблицы открытых файлов внутри процесса) скрыта в ядре. Мы не можем получить прямой доступ к этой таблице, как при работе с окружением через environ. Можно, конечно, кое-что "вытянуть" через дерево /р r ос, но нам это не надо. Программист должен лишь понимать, что каждый процесс имеет свою копию таблицы дескрипторов. В пределах одного процесса все дескрипторы уникальны (даже если они соответствуют одному и тому же файлу или устройству). В разных процессах дескрипторы могут совпадать или не совпадать - это не имеет никакого значения, поскольку у каждого процесса свой собственный набор открытых файлов.

Возникает вопрос: сколько файлов может открыть процесс? В каждой системе есть свой лимит, зависящий от конфигурации. Если вы используете bash или ksh (Kom Shell), TO можете воспользоваться внутренней командой оболочки ulimit, чтобы узнать это значение.

$ ulimit - n

1024

$

Если вы работаете с оболочкой C-shell (csh, tcsh), то в вашем распоряжении команда limit:

$ limit descriptors

descriptors 1024

$

В командной оболочке, в которой вы работаете (bash, например), открыты три файла: стандартный ввод (дескриптор 0), стандартный вывод (дескриптор 1) и стандартный поток ошибок (дескриптор 2). Когда под оболочкой запускается программа, в системе создается новый процесс, который является для этой оболочки дочерним процессом, следовательно, получает копию таблицы дескрипторов своего родителя (то есть все открытые файлы родительского процесса). Таким образом, программа может осуществлять консольный ввод-вывод через эти дескрипторы..

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

 


Дата добавления: 2019-03-09; просмотров: 505; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!