КаталогИндекс раздела
НазадОглавлениеВперед


2. Специальные средства языка Турбо-Си

Традиционно считалось, что единственным языковым средством, дающим полный доступ к аппаратным средствам ЭВМ, является язык Ассемблера. Применительно к ПЭВМ это не совсем так. Многие языки, системы программирования которых реализованы на ПЭВМ, имеют в своем арсенале такие средства, в частности язык Си, который был разработан в 1972 году Д.Ритчи именно как язык системного программиста для написания операционных систем (системы UNIX). Не следует, однако, считать, что средства низкоуровневого доступа являются исключительной прерогативой языка Си - они есть и в других языках, например, программные иллюстрации в [4] базируются на Паскале, а в [6] - на Бэйсике. Популярность Си для целей системного программирования обусловлена наряду с наличием упомянутых средств (а может быть и в первую очередь) высокой эффективностью объектных кодов, вырабатываемых Си-трансляторами.

В данной работе использовалась версия 2.0 системы программирования Турбо-Си. Выбор языка Си в качестве базового для нашего пособия объясняется также и тем, что запись интересующих нас в первую очередь обращений к аппаратным и системным средствам в языке Си весьма "прозрачна" и соответствующие фрагменты текста Си-программ легко могут быть спроецированы на язык Ассемблера или на какой-либо другой язык высокого уровня. Не следует рассматривать наше пособие как пособие по программированию на языке Си. В целом ряде программ мы сознательно отказывались от присущих языку Си компактности и изящества, чтобы сделать тексты программ более удобочитаемыми. Кроме того, в системах программирования Си очень многие обращения к DOS и к BIOS реализованы в виде стандартных функций (в Турбо-Си описания этих функций находятся в файлах DOS.H и BIOS.H), мы же в наших программных примерах игнорировали наличие таких функций, чтобы достичь большего соответствия с Ассемблерным программированием.


2.1. Доступ к регистрам

1). В любой программе, разрабатываемой в среде Турбо-Си по умолчанию предопределены имена: _AX, _AL, _AH, _BX, _BL, _BH, _CX, _CL, _CH, _DX, _DL, _DH, _ES, _SS, _CS, _DS, _SI, _DI, _BP, _SP. Эти переменные (т.наз. псевдорегистры) типа int и char могут быть использованы для доступа к регистрам микропроцессора.

2). При обращениях к прерываниям передача входных и выходных параметров происходит через регистры. Для этих целей используются следующие описания данных, находящиеся в файле DOS.H (там же находятся и описания всех рассматриваемых далее функций и макросов).
Объединение REGS, используемое для задания содержимого регистров общего назначения:


     union REGS {
       struct WORDREGS x;
       struct BYTEREGS h;
       };
     struct WORDREGS {
      unsigned int ax, bx, cx, dx;
      unsigned int si, di, cflag, flags;
      };
     struct BYTEREGS {
       unsigned char al, ah, bl, bh;
       unsigned char cl, ch, cl, dh;
       };

Поле flags структуры WORDREGS отражает состояние флагов микропроцессора, а поле cflag - состояние системного флага переноса CY, в котором обычно при обращениях к DOS и к BIOS индицируется ошибка. Использование объединения REGS позволяет программисту обращаться к регистру общего назначения как к целому двухбайтному слову или к каждому байту этого слова, выбирая описатель второго уровня x или h соответственно. Так, если в программе имеется определение:

    union REGS rr;
то результаты выполнения операторов:

     rr.x.ax=0x1122;
и:

     rr.h.ah=0x11; rr.h.al=0x22;
будут одинаковы.
Структура SREGS, служащая для задания содержимого сегментных регистров:

    struct SREGS {
       unsigned int es, cs, ss, ds;
       };
Наконец, структура REGPACK, обеспечивающая наиболее полный набор регистров микропроцессора:

     struct REGPACK {
       unsigned int r_ax, r_bx, r_cx, r_dx;
       unsigned int r_bp, r_si, r_di;
       unsigned int r_ds, r_es, r_flags;
       };
Данные этих типов служат агрументами функций обращения к прерываниям.

3). Интересный способ доступа к регистрам возможен в программах обработки прерываний, он будет рассмотрен ниже вместе с такими программами.

2.2. Доступ к оперативной памяти

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

     int peek(unsigned int segm, unsigned int offs);
     char peekb(unsigned int segm, unsigned int offs);
Для записи данных в память испольэуются функции:

     void poke(unsigned int segm, unsigned int offs, int val);
     void pokeb(unsigned int segm, unsigned int offs, char val);
Аргументы этих функций: segm - сегментный адрес памяти, offs - смещение в сегменте. Функции peek и peekb возвращают прочитанное значение, а в функциях poke и pokeb записываемое в память значение задается аргументом val. Функции peekb и pokeb работают с одним байтом, peek и poke - с двухбайтным словом.
Следует помнить, что слово в памяти ПЭВМ хранится в двух последовательных байтах памяти, причем в байте с меньшим адресом - младшая часть слова. Поэтому при побайтном (peekb) чтении из двух последовательных адресов мы получим сначала младшую, а затем старшую часть слова, а при чтении слова (peek) - целое слово, в котором обе части находятся "на своих местах".

Определение физических адресов переменных возможно благодаря наличию в языке Си данных типа "указатели", представляющих собой адреса переменных. Указатель может быть преобразован в физический адрес при помощи макросов:


     unsigned int FP_SEG(void far *ptr);
     unsigned int FP_OFF(void far *ptr);
которые возвращают сегментную часть адреса и смещение соответственно.
Обратное преобразование производится макросом:

     void far *MK_FP(unsigned int segm, unsigned int offs);
Следует учитывать, что внутреннее представление указателей зависит от модели памяти, в которой транслируется программа или от явного описания. Указатель может быть либо ближним (явное описание: near) и представляться двухбайтным смещением в текущем сегменте, либо дальним (явное описание: far) и иметь четырехбайтное представление - сегментная часть адреса и смещение. Указанные макросы работают только с дальними указателями.

Программные примеры данного пособия компилируются в модели памяти large (если в тексте программы не указана другая модель). По умолчанию в этой модели все указатели являются far. При компиляции в той модели памяти, где указатель по умолчанию near необходимо включить в описания всех указателей явные описатели far.

2.3. Порты ввода-вывода

Для чтения данных из порта испольэуются функции:


     int inport(int port);
     unsigned char inportb(int port);
Для записи данных в порт испольэуются функции:

     void outport(int port, int val);
     void outportb(int port, unsigned char val);
Аргумент этих функций port - номер порта ввода-вывода. Функции inport и inportb возвращают прочитанное из порта значение, а в функциях outport и outportb записываемое в порт значение задается аргументом val. Функции inportb и outportb работают с однобайтными, а inport и outport - с двухбайтными портами.
Программист должен точно знать, работает он с одно- или с двухбайтным портом. Применение функций inport, outport к однобайтным портам может привести к весьма неожиданным результатам. Так, например, оператор программы:

     outport(0x20,0x11);
(а 0x20 - однобайтный порт) эквивалентен двум операторам: outportb(0x20,0x11); outportb(0x21,0);


КаталогИндекс раздела
НазадОглавлениеВперед