VisualDSP: ввод/вывод с использованием файлов (stdio) Печать
Добавил(а) microsin   

Рабочая среда VisualDSP++ предоставляет доступ к файлам на хосте отладки (Windows PC) через функции stdio. Это так называемая поддержка файлового ввода/вывода (File I/O support), которая представляет библиотечные примитивы с реализацией стандартных операций open, close, read, write и seek. Эти функции определены в заголовочном файле stdio.h, и подключение этого заголовка предоставляет возможности удобного ввода и вывода в программе на языке C или C++. Исходные файлы этих примитивов ввода/вывода доступны в каталоге инсталляции VisualDSP++, см. подпапку Blackfin/lib/src/libio.

В этом переводе документации [1] рассмотрены следующие темы:

• Расширение поддержки I/O для новых устройств.
• Интерфейс драйвера устройства по умолчанию.

[STDIO и printf]

В среду программирования встроена система отладочного вывода, основанная на стандартных вызовах printf. По умолчанию для вывода используется консоль. Это окно Output Windows, которое обычно прикреплено к нижней части главного окна VisualDSP (если оно не отображается, то его можно включить, поставив галку в пункте меню View -> Output Window), закладка Console. Чтобы этот отладочный вывод заработал, достаточно подключить заголовочный файл stdio.h.

VisualDSP stdio printf

Однако система STDIO предоставляет дополнительные, расширенные возможности, которые рассматриваются в этой статье.

Заголовочный файл stdio.h предоставляет набор функций, макросов и типов данных (см. таблицу ниже), которые выполняют ввод и вывод. Библиотечные функции, определенные в этом хедере, являются потокобезопасными (thread-safe), однако они не являются в целом безопасными для использовании в обработчике прерывания (они не interrupt-safe); таким образом, они не должны вызываться прямо или косвенно из кода ISR (interrupt service routine, обработчик прерывания).

Таблица 3-27. Поддерживаемые библиотечные функции в хедере stdio.h.

clearerr fclose feof
ferror fflush fgetc
fgetpos fgets fprintf
fputc fputs fopen
freopen fscanf fread
fseek fsetpos ftell
fwrite getc getchar
gets perror printf
putc putchar puts
remove rename rewind
scanf setbuf setvbuf
snprintf sprintf sscanf
tmpfile tmpnam ungetc
vfprintf vprintf vsnprintf
vsprintf

Компилятор использует определения из заголовочного файла stdio.h, чтобы выбрать подходящий набор функций, который соответствует текущему выбранному размеру типа double (либо на базе 32 бит, либо 64 бит). Любой исходный файл, который использует функционал из stdio.h, должен подключать к себе (директивой #include) хедер stdio.h, особенно если происходит компиляция с использованием ключа -double-size-64 (см. опции компилятора [2]). Ошибка с подключением этого заголовка может привести к ошибке линкера, так как компилятор должен видеть корректный прототип функции, чтобы генерировать правильную последовательность её вызова.

Текущий релиз VisualDSP++ 5.0 предоставляет 3 альтернативы библиотек выполняемого кода (run-time libraries), которые реализуют функционал хедера stdio.h. Если приложение собрано с ключом -full-io (см. [2]), то код будет слинкован с библиотекой ввода/вывода сторонних разработчиков (third-party I/O library), которая предоставляет всестороннюю реализацию функциональности стандартного ввода/вывода ANSI C, однако ценой снижения производительности. Эта библиотека полностью совместима с предыдущими релизами VisualDSP++ (версии 4.5 и более ранней). Она также поддерживает вывод на печать стандартных типов с плавающей точкой (native fixed-point types) fract и accum в десятичном формате. Для этой проприетарной библиотеки исходный код не предоставляется.

Однако нормальное поведение компилятора состоит в том, чтобы линковать библиотеку I/O, предоставленную компанией Analog Devices. Эта версия библиотеки не поддерживает все возможности вывода, которые есть в сторонней библиотеке, но она работает быстрее и меньше по размеру. Чтобы уменьшить размер библиотеки, native fixed-point типы fract и accum печатаются только в hex-формате. Исходный код для этой библиотеки доступен вместе с установкой системы VisualDSP++ (см. подкаталог Blackfin/lib/src/libio в корневом каталоге инсталляции VisualDSP++ IDE).

Третья возможная опция - линковать приложение с этой же библиотекой ввода/вывода по умолчанию, но с включенной дополнительной поддержкой печати native fixed-point типов в десятичном формате. Вы можете достичь этого указанием опции -fixed-point-io компилятора (см. [2]). В этом варианте библиотеки также нет поддержки всех возможностей сторонней библиотеки, но код будет выполняться быстрее и будет меньше по размеру. Исходный код для этой библиотеки также доступен вместе с установкой системы VisualDSP++ (см. подкаталог Blackfin/lib/src/libio в корневом каталоге инсталляции VisualDSP++ IDE).

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

Ниже описаны различия в функционале сторонней библиотеки I/O (которая используется через ключ компилятора -full-io) и библиотекой I/O run-time от Analog Devices, которая используется по умолчанию:

• Сторонняя библиотека I/O поддерживает ввод и вывод "широких" символов (соответствующих типу данных wchar_t) и многобайтовых символов. Подобной поддержки нет в библиотеке I/O Analog Devices.

• Функции fread() и fwrite() обычно используются для передачи данных между приложением и двоичным потоком. Для повышения эффективности Analog Devices I/O library может не использовать буфер для чтения или записи данных через эти функции; таким образом, данные могут напрямую передаваться между программой и внешним устройством. Если приложение полагается на эти функции для чтения и записи данных через буфер I/O , то нужно линковать стороннюю библиотеку (с использованием ключа компилятора -full-io).

• Функции tmpfile и tmpnam поддерживаются только сторонней библиотекой I/O, функция albeit имеет ограниченный функционал; подробнее см. документацию по каждой из этих функций.

• Когда вводятся данные по формату (через fscanf, sscanf и т. д.), оба варианта библиотеки I/O - и сторонняя, и по умолчанию - поддерживают следующие дополнительные квалификаторы размера, как это определено в стандарте C99 (ISO/IEC 9899:1999).

hh signed char или unsigned char
j intmax_t или uintmax_t
t ptrdiff_t
z size_t

Эти дополнительные квалификаторы могут использоваться со спецификаторами преобразования d, i, o, u, x или X, чтобы описать тип соответствующего аргумента. Однако только библиотека I/O сторонних производителей также поддерживает эти дополнительные квалификаторы размера, когда данные выводятся на печать по формату с использованием printf и связанных с ней функций.

• Библиотека I/O сторонних производителей имеет доступ к текущей локали хоста, чтобы определить символ, используемый в качестве десятичной точки.

• Альтернативные библиотеки имеют разные соглашения по печати значений с плавающей точкой IEEE, которые могут выводить либо NaN (Not-A-Number, не число), либо Infinity (Inf, бесконечность). Сторонняя библиотека I/O также принимает nan и inf (в любом случае) как входное значение для спецификаторов предобразования e, f и g.

• Форма вывода, генерируемая спецификатором преобразования у альтернативных библиотек отличается (однако обе формы делают вывод с сохранением выполнения требований ISO/IEC 9899:1999).

• Сторонней библиотекой I/O принимается спецификатор преобразования F; его действие такое же, как и у спецификатора f.

• Сторонняя библиотека I/O также поддерживает полный функционал спецификатора преобразования [, в то время как библиотека I/O от Analog Devices предоставляет только минимальный уровень функционала, требуемый стандартом ANSI.

Реализация обоих библиотек I/O основана на простом интерфейсе с драйвером устройства, который предоставляет набор низкоуровневых примитивов для операций open, close, read, write и seek. По умолчанию эти операции предоставлены симулятором VisualDSP++ и системами отладки EZ-KIT Lite; работа этого механизма затронут в разделе "Интерфейс драйвера устройства по умолчанию". Однако могут быть зарегистрированы альтернативные драйверы устройств (см. раздел "Расширение поддержки I/O для новых устройств") что может прозрачно использоваться функциями stdio.h (можно осуществлять ввод/вывод через любое периферийное устройство, например через UART или SPI, см. [3]).

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

• Когда открывается или закрывается файл.
• Когда входной буфер опустошается, или когда переполняется выходной буфер, или когда он сбрасывается (flush).
• Когда опрашивается указатель или меняется его значение.
• Когда удаляется файл через библиотечную функцию remove.
• Когда переименовывается файл через библиотечную функцию rename.

Во всех вышеперечисленных условиях драйвер по умолчанию запретит прерывания, и остановит работу DSP в процессе обмена данными с хостом, когда осуществляются операции ввода/вывода. Как только операция I/O завершится, драйвер устройства по умолчанию снова запустит выполнение кода DSP, и снова разрешит прерывания.

Когда DSP останавливается, регистры счетчика тактов (cycle count) не обновляются, и DSP сам по себе не может инициировать прерывания; однако все еще могут произойти сигналы, соответствующие внешним событиям, и их обработка может быть произведена после того, как драйвер устройства по умолчанию снова разрешит прерывания.

В этом релизе программного обеспечения (VisualDSP++ 5.0) к любой из библиотек прикладываются следующие ограничения:

• Функции rename() и remove() поддерживаются только драйвером устройства по умолчанию, который предоставлен симулятором VisualDSP++ и системой EZ-KIT Lite, и это будет работать только на файловой системе хоста отладки (Windows PC).
• Позиционирование внутри текстового файла, который был открыт как текстовый поток, поддерживается только в том случае, когда строки в файле завершаются последовательностью символов \r\n.
• Имеется поддержка чтения и записи по формату для типа данных long double только в том случае, когда приложение собрано с опцией командной строки компилятора -double-size-64.

[Расширение поддержки I/O для новых устройств]

В библиотеке реализованы примитивы ввода/вывода (I/O primitives) с использованием механизма расширения подключенных драйверов устройств (extensible device driver mechanism). Стартовый код по умолчанию (default start-up code) подключает драйвер устройства, который может выполнять ввод/вывод через симулятор VisualDSP++ или оценочные платы отладки EZ-KIT. Другие драйверы устройств могут быть зарегистрированы и затем использованы через стандартные функции ввода/вывода (stdio). В этом разделе рассмотрены следующие вопросы:

• Структура DevEntry.
• Регистрация новых устройств.
• Предварительная регистрация устройств.
• Устройство по умолчанию.
• Функции Remove и Rename.

Структура DevEntry. У драйвера устройства есть набор привязанных к нему функций-примитивов, информация о которых сгруппирована в структуре DevEntry. Эта структура определена в заголовке device.h.

struct DevEntry
{
   int DeviceID;
   void *data;
   int (*init)(struct DevEntry *entry);
   int (*open)(const char *name, int mode);
   int (*close)(int fd);
   int (*write)(int fd, unsigned char *buf, int size);
   int (*read)(int fd, unsigned char *buf, int size);
   long (*seek)(long fd, long offset, int whence);
   int stdinfd;
   int stdoutfd;
   int stderrfd;
}
 
typedef struct DevEntry DevEntry;
typedef struct DevEntry *DevEntry_t;

Ниже описаны поля структуры DevEntry и их назначение.

DeviceID: поле DeviceID это уникальный идентификатор устройства, известный пользователю. DeviceID используется глобально в пределах всего приложения.

data: поле data это указатель на приватные данные, которые могут понадобиться устройству; оно не используется библиотеками выполняемого кода (run-time libraries).

init: поле init это указатель на функцию инициализации. Библиотека выполняемого кода (run-time library) вызывает эту функцию, когда устройство регистрируется первый раз, с передачей адреса структуры DevEntry (что дает функции инициализации доступ к DeviceID и другим полям этой структуры). Если в функции init произошла ошибка, то она должна вернуть -1; иначе должно быть возвращено положительное значение, чтобы показать успешное завершение функции.

open: поле open это указатель на функцию, которая выполняет операцию открытия файла (open file) на устройстве. Библиотека run-time вызовет эту функцию в ответ на такие запросы как fopen(), когда устройство в настоящий момент выбрано как устройство по умолчанию. Параметр name это путь до открываемого файла, и параметр mode это битовая маска, которая показывает, каким образом файл должен быть открыт. Биты 0 и 1 показывают чтение и/или запись.

0x0000 открыть файл для чтения
0x0001 открыть файл для записи
0x0002 открыть файл для чтения и записи
0x0003 недопустимое значение

Операцией OR могут быть добавлены следующие дополнительные биты, которые изменяют поведение файла:

0x0008 открыть файл для добавления в него данных
0x0100 создать файл, если он пока не существует
0x0200 обрезать содержимое файла до нуля, если файл уже существует
0x4000 открыть файл как поток текста (с преобразованием последовательности символов \r\n в \n при чтении, и \n в \r\n при записи)
0x8000 открыть файл как двоичный поток (raw mode)

Функция open должна вернуть положительное значение - дескриптор файла (file descriptor), если произошло успешное открытие файла; этот дескриптор используется для идентификации файла на устройстве для последующих операций (чтение, запись, позиционирование). Дескриптор файла должен быть уникальным для всех файлов, открытых в настоящий момент на устройстве, однако не нужно, чтобы они отличались от дескрипторов файлов, которые были возвращены с других устройств — библиотека run-time идентифицирует файл по комбинации дескриптора устройства и дескриптора файла.

Если функция open потерпела неудачу, то она должна вернуть -1, чтобы показать ошибку.

close: поле close это указатель на функцию, которая выполняет закрытие файла (close file) на устройстве. Библиотека run-time вызывает функцию close в ответ на запрос fclose() к потоку, который был открыт на устройстве. Параметр fd это дескриптор файла, который был ранее возвращен вызовом функции open. Функция close должна вернуть значение 0 для успеха и не нулевое значение в случае ошибки.

write: поле write это указатель на функцию, которая выполняет операцию записи в файл на устройстве. Библиотека run-time вызывает функцию write в ответ на такие запросы, как fwrite(), fprintf() и так далее, которые работают как потоки, открытые на устройстве.

Функция write принимает 3 параметра:

• fd – дескриптор файла, идентифицирующий файл, куда происходит запись. Это значение, которое было ранее возвращено функцией open.
• buf – указатель на данные, которые записываются в файл.
• size – количество байт для записи в файл.

Функция write должна вернуть одно из следующих значений:

• Положительное значение от 1 до size включительно, показывая тем самым, сколько байт было успешно записано в файл.
• 0, показывая тем самым, что файл был закрыт по любой причине (например, из-за потери сетевого соединения).
• Отрицательное значение, что показывает ошибку.

read: поле read это указатель на функцию, которая выполняет операцию чтения из файла на устройстве. Библиотека run-time вызывает функцию read в ответ на такие вызовы, как fread(), fscanf() и т. д., что работает как потоки, открытые на устройстве.

Функция read принимает 3 параметра:

• fd – дескриптор файла, идентифицирующий файл, откуда происходит чтение. Это значение, которое было ранее возвращено функцией open.
• buf – указатель на буфер, куда будут записаны прочитанные данные.
• size – количество байт, прочитанных из файла. Это значение не должно превышать размера буфера, на который указывает buf.

Функция read должна вернуть одно из следующих значений:

• Положительное значение от 1 до size включительно, показывая тем самым, сколько байт было успешно прочитано в buf из файла.
• 0, показывая конец файла (EOF, end-of-file).
• Отрицательное значение, что показывает ошибку.

Примечание: библиотека run-time ожидает, что функция read вернет 0xa (10) в качестве символа новой строки.

seek: поле seek содержит указатель на функию, которая осуществляет динамический доступ к содержимому файла. Библиотека run-time вызывает функцию seek в ответ на такие запросы как rewind(), fseek() и т. д., что работает как потоки, открытые на устройстве.

Функция seek принимает следующие параметры:

• fd – дескриптор файла, у которого будет изменена текущая позиция чтения/записи.
• offset - значение, которое используется для определения новой позиции чтения/записи; значение указывается в байтах.
• whence – значение, которое показывает точку отсчета для параметра offset:

   0: offset это абсолютная величина, дающая новую позицию чтения/записи в файле.
   1: offset это значение относительно текущей позиции в файле.
   2: offset это значение относительно конца файла.   

Функция seek вернет положительное значение которое показывает новую (абсолютную) позицию указателя чтения/записи в файле. Если произошла ошибка, то функция seek должна вернуть отрицательное значение.

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

stdinfd: поле stdinfd устанавливается в значение файлового дескриптора устройства для stdin, если устройство ожидает требования потока stdin; иначе в значение перечисления dev_not_claimed.

stdoutfd: поле stdoutfd устанавливается в значение файлового дескриптора устройства для stdout, если устройство ожидает требования потока stdout; иначе в значение перечисления dev_not_claimed.

stderrfd: поле stderrfd устанавливается в значение файлового дескриптора устройства для stderr, если устройство ожидает требования потока stderr; иначе в значение перечисления dev_not_claimed.

Регистрация новых устройств. Новое устройство может быть зарегистрировано следующей функцией:

int add_devtab_entry(DevEntry_t entry);

Если устройство было успешно зарегистрировано, то подпрограмма init() устройства вызывается с entry в качестве своего параметра. Функция add_devtab_entry() вернет DeviceID зарегистрированного устройства.

Если устройство не было успешно зарегистрировано, то будет возвращено отрицательное значение. Случаи ошибки включают (но не ограничены этим) следующее:

• DeviceID такое же, как и у другого, уже зарегистрированного устройства.
• Больше нет свободных слотов в таблице регистрации устройств.
• DeviceID меньше нуля.
• Какой-то из указателей на функцию равен NULL.
• Функция устройства init() вернула ошибку.
• Устройство попыталось заполучить стандартный поток, который уже занят другим устройством.

Предварительная регистрация устройств. Библиотечный исходный файл devtab.c (который находится в подкаталоге Blackfin/lib/src/libio каталога инсталляции VisualDSP++), декларирует массив:

DevEntry_t DevDrvTable[];

Это массив указателей на структуры DevEntry для каждого предварительно зарегистрированного устройства (так что эти устройства доступны, как только произошел вход в тело функции main()), и не требуется их отдельно регистрировать runtime вызовом add_devtab_entry().

По умолчанию зарегистрировано устройство PrimIO. Оно поддерживает обмен между целью отладки (процессор DSP) и хостом отладки (среда VisualDSP++, работающая на Windows PC), когда используются симуляторы агенты отладки эмуляторов Analog Devices. Это устройство является предварительно зарегистрированным, так что функции printf() и подобные работают, как и ожидалось от них (вывод сообщений в окно Console среды VisualDSP) без дополнительной настройки.

Чтобы выполнить предварительную регистрацию дополнительных устройств, сделайте следующее:

1. Добавьте копию файла исходного кода devtab.c в Ваш проект.

2. Декларируйте новую структуру устройства DevEntry внутри файла devtab.c. Пример:

extern DevEntry myDevice;

3. Добавьте адрес новой структуры DevEntry в массив DevDrvTable[]. Убедитесь, что эта таблица завершена указателем null-terminated, например:

DevEntry_t DevDrvTable[MAXDEV] =
{
#ifdef PRIMIO
   &primio_deventry,
#endif
   &myDevice, /* новое предварительно зарегистрированное устройство */
   0,
};

Предварительно зарегистрированные устройства инициализируются автоматически, когда на нем первый раз был произведен ввод или вывод. Библиотека run-time вызовет функцию init() для каждого из предварительно зарегистрированных устройств.

Нормальное поведение устройства PrimIO, что когда оно регистрируется, то занимает первые 3 файла как stdin, stdout и stderr. Это стандартные потоки, которые могут быть переоткрыты на других устройствах во время выполнения с использованием функции freopen() для закрытия потоков на основе PrimIO, и повторного открытия потоков на текущем устройстве по умолчанию.

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

1. Добавьте копию исходного файла primiolib.c в свой проект.

2. Отредактируйте нужные поля stdinfd, stdoutfd и stderrfd файловых дескрипторов в структуре primio_deventry, чтобы задать там значение dev_not_claimed.

3. Убедитесь, что структура DevEntry альтернативного устройства настроена на установку соответствующего файлового дескриптора.

Подпрограммы инициализации устройства, вызванные как из кода запуска (startup), так и в add_devtab_entry(), вернут ошибку, если была попытка занять стандартный поток, который уже занят другим устройством.

Устройство по умолчанию (Default Device). Как только устройство было зарегистрировано, оно может быть сделано устройством по умолчанию с помощью функции:

void set_default_io_device(int);

Этой функции нужно передать DeviceID устройства. Есть соответствующая функция, которая вернет текущее устройство по умолчанию:

int get_default_io_device(void);

Устройство по умолчанию используется функцией fopen(), когда файл открывается в первый раз. Функция fopen() передает запрос открытия в функцию open() устройства, на которое покажет get_default_io_device(). Идентификатор файла на устройстве (fd), который возвращен функцией open(), является приватным для устройства; другие устройства могут одновременно иметь другие открытые файлы с таким же идентификатором. Открытый файл уникально идентифицируется комбинацией DeviceID и fd.

Функция fopen() записывает DeviceID и fd в глобальную таблицу открытых файлов, и выделяет свой собственный внутренний fid для этой комбинации. Все будущие операции над файлом будут использовать этот fid, чтобы получить DeviceID, и таким образом направить запрос на соответствующую функцию примитива устройства, передавая fd вместе с другими параметрами. Как только файл был открыт вызовом fopen(), текущее значение get_default_io_device() будет иррелевантно к этому файлу.

Функции удаления и переименования (remove, rename). Устройство PrimIO поддерживает функции remove() и rename(). Эти функции не являются частью расширяемого интерфейса ввода/вывода, поскольку они имеют дело чисто с именами файлов и их путями, а не с дескрипторами файлов. Все вызовы remove() и rename() в библиотеке run-time передаются напрямую в устройство PrimIO.

[Интерфейс драйвера устройства по умолчанию]

Функции stdio предоставляют доступ к файлам на операционной системе хоста (Windows) через драйвер устройства, который поддерживает набор примитивов ввода/вывода низкого уровня, что было описано в разделе "Расширение поддержки I/O для новых устройств". Драйвер устройства по умолчанию реализует эти примитивы на основе простого интерфейса, предоставляемого симулятором VisualDSP++ и оценочными системами EZ-KIT Lite.

Все запросы I/O подаются через драйвер устройства по умолчанию, который управляется по каналу через C-функцию _primIO. Соответствующая ассемблерная метка имеет 2 подчеркивания: __primIO. Исходный код для этой функции (все другие библиотечные функции) находятся в каталоге инсталляции VisualDSP++, в поддиректории Blackfin/lib/src/libio.

Функция __primIO не принимает аргументов. Вместо этого она проверяет блок управления ввода/вывода (I/O control block) по метке _PrimIOCB. Без внешнего вмешательства со стороны рабочего окружения хоста, подпрограмма __primIO просто делает возврат, показывая тем самым ошибку запроса. Предоставляются 2 схемы перехвата запросов ввода/вывода.

Первая схема модифицирует поток управления в подпрограмму __primIO и из неё. Обычно это достигается механизмом точки останова, который имеется в отладчике/симуляторе. На входе в __primIO данные запроса находятся в блоке управления по метке _PrimIOCB. Если используется эта схема, хост должен согласовать перехват управления, когда происходит вход в подпрограмму __primIO, и после обработки запроса возвратить управление в вызвавший код.

Вторая схема вовлекает обмен с процессором DSP через пару простых семафоров. Эта схема больше подходит для плат разработчика со встроенным отладчиком. По этой схеме система хоста очищает слово данных по метке __lone_SHARC; это приводит к тому, что __primIO предполагает, что присутствует окружение хоста, и можно обмениваться данными с процессом.

Если __primIO на входе видит, что __lone_SHARC очищено (например, когда делается запрос ввода/вывода), она устанавливает не нулевое значение в слово, помеченное меткой __Godot. Тогда подпрограмма __primIO ждет, пока это слово не будет очищено хостом. Ненулевое значение __Godot, устанавливаемое __primIO, это адрес блока управления ввода/вывода (I/O control block).

Упаковка данных для примитива ввода/вывода. Реализация интерфейса __primIO базируется на машине, адресующей данные словами, где каждое слово включает фиксированное количество байтов. Все запросы READ и WRITE указывают переместить некоторое количество байт (так что соответствующие поля считают байты, не слова). Упаковка данных всегда осуществляется в порядке байт little endian, где первый байт читаемого или записываемого файла будет младшим в первом отправляемом слове.

Для процессоров Blackfin упаковка данных установлена на один байт на слово. Упаковка данных может быть изменена, чтобы походить к другим архитектурам, путем модификации константы BITS_PER_WORD, определенной в _wordsize.h. Например, для процессоров со словами, адресуемыми по 16-бит, поменяйте это значение на 16.

Имейте в виду, что имя, предоставленное в запросе OPEN, использует традиционный (native) формат строки процессора, что соответствует одному байту на слово. Упаковка данные применяется только для запросов READ и WRITE.

Структура данных для примитива ввода/вывода. Блок управления ввода/вывода (I/O control block) декларирован в заголовке _primio.h следующим образом:

typedef struct
{
   enum
   {
      PRIM_OPEN = 100,
      PRIM_READ,
      PRIM_WRITE,
      PRIM_CLOSE,
      PRIM_SEEK,
      PRIM_REMOVE,
      PRIM_RENAME
   } op;
   int fileID;
   int flags;
   unsigned char *buf;  /* буфер данных или имя файла          */
   int nDesired;        /* количество символов для чтения      */
                        /* или записи                          */
   int nCompleted;      /* количество действительно записанных */
                        /* или прочитанных символов            */
   void *more;          /* для будущего использования          */
}PrimIOCB_T;

Первое поле op идентифицирует 7 поддерживаемых операций, которые запрашиваются.

Идентификатор файла fileID для открываемого файла это не отрицательное целое число, назначенное отладчиком или другим механизмом "хоста". Значения fileID 0, 1 и 2 зарезервированы для stdin, stdout и stderr соответственно. Для этих идентификаторов не требуется запрос на открытие.

Перед "активацией" отладчика или другого окружения отладки хоста, запрос OPEN или REMOVE может установить поле fileID в значение длины имени файла для операции открытия файла или удаления файла; запрос RENAME может также установить это поле в длину старого имени файла. Если поле fileID содержит длину строки, то это будет показано полем flags (см. ниже), и отладчик или другое окружение хоста сможет использовать эту информацию для выполнения чтения памяти, чтобы вытащить оттуда имя файла. Если эта информация не предоставлена, то имя файла должно быть распаковано по одному символу.

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

M_OPENR = 0x0001    /* открыть для чтения */
M_OPENW = 0x0002    /* открыть для записи */
M_OPENA = 0x0004    /* открыть для добавления */
M_TRUNCATE = 0x0008 /* обрезать файл до нулевой длины, */
                    /* если он существует */
M_CREATE = 0x0010   /* создать файл, если это необходимо */
M_BINARY = 0x0020   /* двоичный файл (против текстового файла) */
M_STRLEN_PROVIDED = 0x8000 /* доступна длина имени (имен) файла */

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

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

M_ALIGN_BUFFER = 0x10

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

Для операций OPEN, REMOVE и RENAME отладчик (или другой механизм хоста) должен распаковать имя (имена) файла по одному символу из памяти отлаживаемой цели. Однако, если бит, соответствующий M_STRLEN_PROVIDED, установлен, то блок управления ввода/вывода (I/O control block) содержит длину имени (имен) файла и отладчик может сразу прочитать память отлаживаемой цели (см. описание полей fileID и nCompleted).

Для запроса SEEK поле flags показывает режим позиционирования (откуда) следующим образом:

enum
{
   M_SEEK_SET = 0x0001, /* позиционировать относительно начала файла */
   M_SEEK_CUR = 0x0002, /* позиционировать относительно текущей позиции файла */
   M_SEEK_END = 0x0004, /* позиционировать относительно конца файла */
};

Для запроса CLOSE поле flags не используется.

Поле buf содержит указатель на имя файла для запросов OPEN или REMOVE, или указатель на буфер данных для запроса READ или WRITE. Для операции RENAME это поле содержит указатель на старое имя файла.

Поле nDesired устанавливается в количество байт, которое должно быть передано для запроса READ или WRITE. Это поле также используется для запроса RENAME, и устанавливается в указатель на новое имя файла.

Для запроса SEEK поле nDesired содержит смещение, на которое должно быть осуществлено позиционирование относительно точки отсчета, указанного полем flags. На архитектурах, которые поддерживают только 16-битные целые числа, 32-битное смещение, на которое должно быть осуществлено позиционирование в файле, сохраняется в скомбинированных полях [buf, nDesired].

Поле nCompleted устанавливается в __primIO на количество байт, реально переданных операцией READ или WRITE. Для операции SEEK __primIO устанавливает это поле в новое значение позиции в файле. На архитектурах, которые поддерживают только 16-битные целые числа, __primIO устанавливает новое значение позиции в файле в скомбинированных полях [nCompleted, more].

Операция RENAME может также использовать поле nCompleted. Если операция может определить длины старого и нового имени файла, то эти длины должны сохраняться в полях fileID и nCompleted соответственно, и также устанавливаются флаги битового поля в M_STRLEN_PROVIDED. Отладчик (или другой механизм хоста) может использовать эту информацию для выполнения чтения памяти цели, чтобы вытащить имена файла. Если эта информация не предоставлена, то каждый символ имен файлов будет прочитан по отдельности.

Поле more зарезервировано для будущего использования, и в настоящий момент всегда установлено в NULL перед вызовом _primIO.

[Ссылки]

1. File I/O Support (C and C++ Run-Time Library Guide) site:analog.com.
2. Опции командной строки компилятора Blackfin.
3. Blackfin: форматированный вывод в окно терминала через UART.