На чтение 5 мин. Просмотров 3.8k. Опубликовано 14.09.2019
Содержание
- Устройства отправляют IRQ процессору для запроса доступа
- Ошибки IRQ
- Просмотр и редактирование настроек IRQ
- Общие IRQ каналы
Устройства отправляют IRQ процессору для запроса доступа
IRQ, сокращение от Interrupt Request, используется на компьютере для отправки именно этого – запрос на interrupt ЦП другим аппаратным обеспечением.
Запрос на прерывание необходим для таких вещей, как нажатия клавиш, движения мыши, действия принтера и многое другое. Когда устройство делает запрос на мгновенную остановку процессора, компьютер может дать устройству некоторое время для запуска своей собственной операции.
Например, каждый раз, когда вы нажимаете клавишу на клавиатуре, обработчик прерываний сообщает процессору, что ему нужно остановить то, что он в данный момент делает, чтобы он мог обрабатывать нажатия клавиш.
Каждое устройство передает запрос по уникальной линии данных, называемой каналом. Большую часть времени вы видите IRQ со ссылкой, он находится рядом с этим номером канала, который также называется IRQ number . Например, IRQ 4 может использоваться для одного устройства, а IRQ 7 – для другого.
Примечание. IRQ произносится как буквы I-R-Q, а не как erk .
Ошибки IRQ
Ошибки, связанные с запросом прерывания, обычно видны только при установке нового оборудования или изменении настроек в существующем оборудовании. Вот некоторые ошибки IRQ, которые вы можете увидеть:
IRQL_NOT_DISPATCH_LEVEL
IRQL_NOT_GREATER_OR_EQUAL
STOP: 0x00000008
STOP: 0x00000009
Примечание. См. Как исправить ошибки STOP 0x00000008 или Как исправить ошибки STOP 0x00000009, если у вас возникла одна из этих ошибок остановки.
Хотя один и тот же канал IRQ может использоваться более чем для одного устройства (при условии, что оба фактически не используются одновременно), обычно это не так. Конфликт IRQ, скорее всего, возникает, когда два устройства пытаются использовать один и тот же канал для запроса прерывания.
Так как программируемый контроллер прерываний (PIC) не поддерживает это, компьютер может зависнуть или устройства перестанут работать должным образом (или перестанут работать полностью).
Еще в первые дни Windows ошибки IRQ были обычным явлением, и для их устранения потребовалось немало проблем. Это было связано с тем, что чаще было устанавливать каналы IRQ вручную, как с DIP-переключателями, что повышало вероятность того, что более одного устройства использовали одну линию IRQ.
Однако IRQ обрабатываются намного лучше в новых версиях Windows, в которых используется технология «включай и работай», поэтому вы редко увидите конфликт IRQ или другую проблему IRQ.
Просмотр и редактирование настроек IRQ
Самый простой способ просмотра информации IRQ в Windows – с помощью диспетчера устройств. Измените параметр меню Просмотр на Ресурсы по типу , чтобы увидеть раздел Запрос прерывания (IRQ) .
Вы также можете использовать Системную информацию. Выполните команду msinfo32.exe в диалоговом окне «Выполнить» ( Ключ Windows + R ), а затем перейдите к Ресурсы аппаратного обеспечения> IRQ .
Пользователи Linux могут запустить команду cat/proc/interrupts , чтобы просмотреть сопоставления IRQ.
Возможно, вам придется изменить линию IRQ для конкретного устройства, если оно использует тот же IRQ, что и другое, хотя обычно это не требуется, поскольку системные ресурсы автоматически выделяются для новых устройств. Это только старые устройства промышленной стандартной архитектуры (ISA), которые могут нуждаться в ручной настройке IRQ.
Вы можете изменить настройки IRQ в BIOS или в Windows через диспетчер устройств. Вот как можно изменить настройки IRQ с помощью диспетчера устройств:
Важно: . Помните, что внесение неверных изменений в эти настройки может вызвать проблемы, которых у вас не было раньше. Убедитесь, что вы знаете, что делаете, и записали все существующие настройки и значения, чтобы вы знали, к чему следует вернуться, если что-то пойдет не так.
- Откройте диспетчер устройств.
- Дважды щелкните или дважды нажмите устройство, чтобы открыть его окно Свойства .
- На вкладке Ресурсы отмените выбор параметра Использовать автоматические настройки .
- Используйте раскрывающееся меню «Настройки на основе:» для выбора конфигурации оборудования, которое необходимо изменить.
- В Настройки ресурса> Тип ресурса выберите Запрос прерывания (IRQ) .
- Используйте кнопку Изменить настройки … , чтобы изменить значение IRQ.
Примечание. Если вкладка «Ресурсы» отсутствует или «Использовать автоматические настройки» неактивны или неактивны, это означает, что либо вы не можете указать ресурс для этого устройства, так как он подключен и работает или что у устройства нет других настроек, которые могут быть применены к нему.
Общие IRQ каналы
Вот для чего используются некоторые из наиболее распространенных каналов IRQ:
IRQ Line | Описание |
IRQ 0 | Системный таймер |
IRQ 1 | Контроллер клавиатуры |
IRQ 2 | Получает сигналы от IRQ 8-15 |
IRQ 3 | Контроллер последовательного порта для порта 2 |
IRQ 4 | Контроллер последовательного порта для порта 1 |
IRQ 5 | Параллельный порт 2 и 3 (или звуковая карта) |
IRQ 6 | Контроллер дискеты |
IRQ 7 | Параллельный порт 1 (часто принтеры) |
IRQ 8 | CMOS/часы реального времени |
IRQ 9 | Прерывание ACPI |
IRQ 10 | Периферийные устройства |
IRQ 11 | Периферийные устройства |
IRQ 12 | Подключение мыши PS/2 |
IRQ 13 | Числовой процессор данных |
IRQ 14 | Канал ATA (основной) |
IRQ 15 | Канал ATA (вторичный) |
Примечание. Поскольку IRQ 2 имеет предназначенную цель, любое устройство, настроенное для его использования, будет использовать IRQ 9.
Рассказывает Arjun Sreedharan
В прошлой статье я писал о том, как создать простейшее x86-ядро, использующее GRUB, работающее в защищённом режиме и выводящее на экран строку. В этот раз мы подключим к ядру драйвер клавиатуры, который может считывать символы a–z и 0–9 с клавиатуры и выводить их на экран. Весь используемый код можно найти на GitHub.
Мы общаемся с устройствами ввода / вывода, используя I/O-порты. Эти порты — просто определённые адреса на шине ввода / вывода x86-системы. Операции чтения / записи на этих портах обрабатываются специальными инструкциями, встроенными в процессор.
Чтение из портов и запись в них
read_port:
mov edx, [esp + 4]
in al, dx
ret
write_port:
mov edx, [esp + 4]
mov al, [esp + 4 + 4]
out dx, al
ret
Доступ к портам I/O можно получить, используя инструкции in
и out
, являющиеся частью набора инструкций x86.
В read_port
номер порта принимается как аргумент. Когда компилятор вызывает вашу функцию, он пушит все её аргументы в стек. Аргумент копируется в регистр edx
по указателю на стек. Регистр dx
— это младшие 16 бит edx
. Инструкция in
читает из порта, номер которого хранится в dx
, и помещает результат в al
. Регистр al
— это младшие 8 бит eax
. Если вы помните, чему вас учили, то знаете, что возвращаемые функциями значения передаются через регистр eax
register. Таким образом, read_port
обеспечивает чтение из I/O-портов.
write_port
очень похожа. Мы принимаем два аргумента: номер порта и данные для записи. Инструкция out
записывает данные в указанный порт.
Прерывания
Теперь, прежде чем мы продолжим писать какой-либо драйвер устройства, давайте разберемся, как процессор узнаёт о том, что устройство совершило действие.
Самое простое решение — это поллинг, постоянная проверка состояния устройства. По очевидным причинам это не самое практичное решение. Тут в дело вступают прерывания. Прерывание — это сигнал, посылаемый процессору аппаратным или системным обеспечением, который оповещает о событии. Используя прерывания, мы можем действовать только тогда, когда возникает интересующее нас прерывание.
Устройство, называемое контроллером прерываний (Programmable Interrupt Controller, PIC), отвечает за обработку аппаратных прерываний и отправку их соответствующим системным прерываниям.
Когда на аппаратном обеспечении происходит какое-то событие, оно отправляет сигнал, называемый запросом прерывания, по свому специальному каналу на контроллер прерываний. Контроллер преобразует запрос в системное прерывание и отправляет его в процессор, где им занимается ядро.
Если бы у нас не было контроллера прерываний, то нам пришлось бы постоянно опрашивать все устройства на наличие произошедших событий.
Рассмотрим случай с клавиатурой. Она работает через I/O-порты 0x60
и 0x64
. Порт 0x60
передаёт данные (о нажатой клавише), а порт 0x64
— состояние. Однако нужно знать наверняка, когда и из какого порта читать данные.
Для этого отлично подходят прерывания. Когда происходит нажатие клавиши, клавиатура посылает сигнал на контроллер прерываний по линии IRQ1. Контроллер обладает значением offset
, полученным во время его инициализации. Он добавляет номер входной линии к этому offset
и получает число прерывания. Затем процессор обращается к специальной структуре данных, называющейся дескрипторной таблицей прерываний (Interrupt Descriptor Table, IDT), для передачи обработчику прерываний адреса, соответствующего числу прерывания.
После этого запускается код, расположенный по этому адресу, и обрабатывает исключение.
Настраиваем IDT
struct IDT_entry{
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char type_attr;
unsigned short int offset_higherbits;
};
struct IDT_entry IDT[IDT_SIZE];
void idt_init(void)
{
unsigned long keyboard_address;
unsigned long idt_address;
unsigned long idt_ptr[2];
/* populate IDT entry of keyboard's interrupt */
keyboard_address = (unsigned long)keyboard_handler;
IDT[0x21].offset_lowerbits = keyboard_address & 0xffff;
IDT[0x21].selector = 0x08; /* KERNEL_CODE_SEGMENT_OFFSET */
IDT[0x21].zero = 0;
IDT[0x21].type_attr = 0x8e; /* INTERRUPT_GATE */
IDT[0x21].offset_higherbits = (keyboard_address & 0xffff0000) >> 16;
/* Ports
* PIC1 PIC2
*Command 0x20 0xA0
*Data 0x21 0xA1
*/
/* ICW1 - begin initialization */
write_port(0x20 , 0x11);
write_port(0xA0 , 0x11);
/* ICW2 - remap offset address of IDT */
/*
* In x86 protected mode, we have to remap the PICs beyond 0x20 because
* Intel have designated the first 32 interrupts as "reserved" for cpu exceptions
*/
write_port(0x21 , 0x20);
write_port(0xA1 , 0x28);
/* ICW3 - setup cascading */
write_port(0x21 , 0x00);
write_port(0xA1 , 0x00);
/* ICW4 - environment info */
write_port(0x21 , 0x01);
write_port(0xA1 , 0x01);
/* Initialization finished */
/* mask interrupts */
write_port(0x21 , 0xff);
write_port(0xA1 , 0xff);
/* fill the IDT descriptor */
idt_address = (unsigned long)IDT ;
idt_ptr[0] = (sizeof (struct IDT_entry) * IDT_SIZE) + ((idt_address & 0xffff) << 16);
idt_ptr[1] = idt_address >> 16 ;
load_idt(idt_ptr);
}
Мы реализуем IDT как массив структур IDT_entry
. Мы обсудим то, как прерывание клавиатуры привязано к своему обработчику, позже. Сперва посмотрим, как работают контроллеры прерываний.
Современные x86-системы имеют два контроллера прерываний по 8 входных линий. Назовём их PIC1 и PIC2. PIC1 получает сигналы с IRQ0 до IRQ7, а PIC2 — с IRQ8 до IRQ15. PIC1 использует порт 0x20
для команд и 0x21
— для данных. PIC2 использует порт 0xA0
для команд и 0xA1
— для данных.
Контроллеры инициализируются 8-битными инициализирующими командными словами (Initialization command words, ICW). Подробный синтаксис этих команд можно изучить здесь.
В защищённом режиме первой командой, которую вы должны передать двум контроллерам прерываний, является инициализирующая команда ICW1 (0x11
). Эта команда заставляет контроллер ждать поступления ещё трёх инициализирующих слов на порт данных.
Эти команды сообщают контроллерам следующее:
- сдвиг (ICW2);
- состояние подключения (master/slave) (ICW3);
- дополнительную информацию об окружении (ICW4).
Вторая инициализирующая команда — это ICW2, которая записывает в порты данных каждого контроллера его сдвиг.
Контроллеры допускают каскадирование выводов и вводов, что настраивается командой ICW3, но мы не будем его использовать, поэтому установим все значения в ноль.
ICW4 устанавливает дополнительные параметры окружения. Мы заполним младший бит, чтобы сказать контроллерам, что мы работаем в режиме 80×86.
Та-дам! Контроллеры прерываний инициализированы!
У каждого контроллера есть внутренний 8-битный регистр, называющийся регистр масок прерывания (Interrupt Mask Register, IMR). Этот регистр хранит битмэп линий IRQ, идущих в контроллер. Если бит установлен, контроллер игнорирует запрос. Это означает, что мы можем включать и выключать любую линию IRQ, меняя соответствующее значение в IMR. Чтение из порта данных возвращает значение из IMR, а запись — задаёт это значение. В нашем коде мы отключаем все линии IRQ, чтобы позже включить ту, что соответствует клавиатуре.
Если линии IRQ включены, наши контроллеры могут получать идущие по ним сигналы и преобразовывать их в числа прерываний, добавляя сдвиг. Теперь нам нужно найти IDT такую, чтобы число прерывания для клавиатуры было привязано к адресу обработчика событий клавиатуры, который мы напишем.
Какое число прерывания должно соответствовать обработчику событий клавиатуры?
Клавиатура использует IRQ1. Это линия ввода 1 контроллера PIC1. Его сдвиг равен 0x20
. Для получения числа прерывания сложим 1
+ 0x20
и получим 0x21
. Таким образом, адрес обработчика событий должен быть связан с прерыванием 0x21
в IDT.
Теперь нам нужно определить IDT для прерывания 0x21
. Мы привяжем это прерывание к функции keyboard_handler
, которую запишем в ассемблерном файле.
Каждое значение в IDT состоит из 64 битов. В значении IDT для прерывания мы не храним адрес функции-обработчика целиком, а делим его на 2 части по 16 бит. Младшие биты хранятся в первых 16 битах значения IDT, а старшие 16 битов — в последних. Это сделано в целях совместимости с 286. Да, такие костыли от Intel можно встретить часто!
В значении IDT мы также должны задать тип — это нужно для того, чтобы поймать исключение. Нам также нужно передать коду ядра смещение. GRUB создаст для нас глобальную таблицу дескрипторов, каждое значение которое занимает 8 байт. Дескриптор кода ядра — это второй сегмент, поэтому его сдвиг равен 0x08
. Окно прерывания равно 0x8e
. Оставшиеся посередине 8 бит должны быть заполнены нулями. Таким образом, мы заполнили значение IDT в соответствии с прерыванием клавиатуры.
После того, как мы закончили все махинации с IDT, мы сообщаем процессору её адрес, используя инструкцию lidt
, принимающую один операнд, который должен быть указателем на структуру дескрипторов, описывающую IDT.
Дескриптор достаточно прост. Он содержит размер IDT в байтах и её адрес. Для хранения значений я использовал массив, но вы можете воспользоваться структурой.
У нас есть указатель в переменной idt_ptr
, который мы передаём в lidt
, используя функцию load_idt()
.
load_idt:
mov edx, [esp + 4]
lidt [edx]
sti
ret
Кроме того, функция load_idt()
включает прерывания, используя инструкцию sti
.
Когда IDT настроена, мы можем включить линию IRQ клавиатуры, используя маску прерываний, о которой мы говорили ранее.
void kb_init(void)
{
/* 0xFD is 11111101 - enables only IRQ1 (keyboard)*/
write_port(0x21 , 0xFD);
}
Функция обработки прерываний клавиатуры
Итак, наши прерывания клавиатуры привязаны к функции keyboard_handler
через значение IDT для прерывания 0x21
. Каждый раз, когда мы нажимаем клавишу, мы можем быть уверены, что функция будет вызвана.
keyboard_handler:
call keyboard_handler_main
iretd
Эта функция просто вызывает другую функцию, написанную на Си, и возвращает значение, используя класс инструкций iret
. Мы могли бы написать здесь весь необходимый код, но на Си писать безусловно приятнее. iret
/iretd
используются вместо ret
при работе с прерываниями.
void keyboard_handler_main(void) {
unsigned char status;
char keycode;
/* write EOI */
write_port(0x20, 0x20);
status = read_port(KEYBOARD_STATUS_PORT);
/* Lowest bit of status will be set if buffer is not empty */
if (status & 0x01) {
keycode = read_port(KEYBOARD_DATA_PORT);
if(keycode < 0)
return;
vidptr[current_loc++] = keyboard_map[keycode];
vidptr[current_loc++] = 0x07;
}
}
Сперва мы отправляем сигнал EOI (конец прерывания), записывая его в порт команд контроллера. Только после этого контроллер принимает следующие прерывания. Нам нужно читать два порта — порт данных 0x60
и порт команд / данных 0x64
.
Сперва мы читаем порт 0x64
для получения состояния. Если младший бит состояния равен 0
, то буфер пуст, и данных для чтения нет. В обратном случае мы читаем порт данных 0x60
. Этот порт даёт нам код каждой нажатой клавиши. Мы используем простой массив символов в файле keyboard_map.h
для связывания кодов и символов.
В этой статье мы обрабатываем только строчные буквы и цифры. Конечно, можно настроить и обработку других клавиш, а также их сочетаний.
Вы можете собрать ядро и запустить его на реальной машине или в эмуляторе (QEMU) так же, как и в прошлой статье.
Печатайте!
Перевод статьи «Kernel 201 — Let’s write a Kernel with keyboard and screen support»
Думаю, многие любознательные пользователи, наверняка не раз встречали такое сокращение, как IRQ. Его можно встретить, например, если вы любите заглядывать в программу «Менеджер устройств» в Windows. Если вы выберете любое устройство, к примеру, клавиатуру, выберете при помощи правой кнопки мыши пункт меню «Свойства», и в появившемся окне сделаете активной закладку «Ресурсы», то в списке ресурсов вы увидите надпись IRQ 01.
Для чего нужны IRQ
Что же такое IRQ и для чего оно нужно?
Аббревиатура IRQ расшифровывается как Interrupt ReQuest (запрос на прерывание). Для того, чтобы понять, для чего оно нужно, следует вспомнить подробности организации работы персонального компьютера.
Кровеносной системой компьютера, по которой обмениваются информацией процессор и прочие устройства, является системная шина. Но как вообще процессор способен отличить запросы на обработку информации, поступающие по шине от различных устройств?
Для этого и существует система аппаратных прерываний (IRQ). Каждое прерывание имеет определенный номер (нумерация начинается с 0) и закреплено за определенным устройством. Так, за клавиатурой закреплено прерывание под номером 1, отсюда и обозначение IRQ 01.
При поступлении запроса от устройства компьютер прерывает (отсюда и появился сам термин «прерывание») обработку текущей информации и начинает обработку вновь поступившего. Если прерываний несколько, то они обрабатывается в порядке приоритетов, закрепленных за каждым из них. Как правило, чем меньше номер прерывания, тем больший приоритет для процессора имеет устройство, закрепленное за этим прерыванием, но это правило соблюдается далеко не всегда.
Обслуживает обработку IRQ специальный чип, который носит название контроллера прерываний. Как правило, эта микросхема является частью центрального процессора, а иногда выделяется в отдельный чип на материнской плате. Для обработки каждого прерывания в BIOS существует специальная микропрограмма, называемая обработчиком прерывания. Адреса всех обработчиков хранятся в так называемой таблице векторов прерываний.
Раньше, в первых компьютерах семейства XT была распространена 8-разрядная шина ISA, поэтому всего устройствам было доступно 8 прерываний. С появлением 16-разрядной шины ISA их количество увеличилось до 16.
Настройка Interrupt ReQuest
Надо сказать, что прерывания, закрепленные за некоторыми устройствами, не является фиксированными и их можно изменить программно. Например, IRQ стандартно использующееся последовательным портом Com 2, может использовать и устанавливаемый в слот расширения модем. В современных компьютерах и операционных системах, поддерживающих стандарт PnP и работающих под управлением ОС Windows, значения IRQ для устройств, подключаемых в слоты шины, подбираются автоматически.
Но не все было так просто в прежние времена, когда пользователь должен был вручную устанавливать значение IRQ во многих программах, работавших под операционной системой DOS. Например, при установке в систему звуковой карты, пользователю требовалось выбрать свободное прерывание из очень небольшого числа доступных (как правило, это было IRQ 5) и указать это значение в запускаемой программе, например, в какой-нибудь игре.
Во многих BIOS имеется возможность поменять стандартные значения IRQ в программе Setup. Обычно эта опция располагается в разделах IRQ Resources или PCI/PNP Configuration.
Установка для устройства значения IRQ, равного значению IRQ, уже занятого каким-либо устройством в большинстве случаев приводит к неработоспособности одного из этих устройств или сразу обоих, а иногда чревато и зависанием компьютера.
В более современной шине PCI система управления прерываниями была кардинально изменена, а возможности управления прерываниями были расширены. Благодаря технологии IRQ Sharing, а также технологии ACPI стало возможным размещение нескольких устройств на одном канале прерывания, а у внешних устройств, подключаемых в слоты PCI, появилась возможность автоматического распределения ресурсов между собой.
Кроме того, в современных компьютерах обычно используется расширенный программируемый контроллер прерываний (APIC, Advanced Programmable Interrupt Controller), поддерживающий 24 канала Interrupt ReQuest. Расширенный контроллер прерываний выполнен в виде двух микросхем, одна из которых расположена в самом процессоре, а другая на материнской плате. Этот контроллер прерываний впервые появился в системах на основе процессоров Pentium. Однако при этом была оставлена поддержка старой системы прерываний в целях совместимости. Очередным шагом в развитии принципов обработки прерываний является технология Message Signaled Interrupts, поддержка которой появилась в линейке ОС Windows, начиная с Windows Vista.
Не следует путать аппаратные прерывания IRQ c программными прерываниями BIOS, о которых речь пойдет в отдельной статье. Программные прерывания BIOS, как правило, используются для организации работы программного обеспечения с устройствами ввода-вывода и обозначаются при помощи сокращения INT. Многие из них аналогичны по своим функциям аппаратным IRQ, но имеют при этом другие номера.
Список номеров Interrupt ReQuest в стандартной схеме для 16-битной шины ISA:
- Системный таймер
- Клавиатура
- Дополнительный контроллер прерываний (для совместимости с 8-битной шиной)
- Порты Com 1 и 3
- Порты Com 2 и 4
- Свободно (в 8-битной шине — контроллер жесткого диска)
- Контроллер гибких дисков (FDD)
- Параллельный порт LPT
- Часы реального времени CMOS
- Совмещено с IRQ 2
- Свободно
- Свободно
- Порт мыши PS/2
- Сопроцессор (в настоящее время практически не используется)
- Первый контроллер IDE
- Второй контроллер IDE
Список дополнительных номеров IRQ, которые использует расширенный контроллер прерываний APIC:
- Контроллер USB
- Интегрированная звуковая подсистема (AC’97 или HDA)
- Контроллер USB
- Контроллер USB
- Встроенная сетевая карта
- Свободно
- Свободно
- Контроллер USB 2.0
Соответствие номеров IRQ и прерываний BIOS:
Таблица соотношения аппаратных IRQ и программных INT BIOS
Заключение
Итак, в этой статье вы смогли узнать, что означает сокращение IRQ, и что представляют собой аппаратные прерывания. Они являются встроенным механизмом распределения ресурсов компьютера и предназначены для организации доступа устройств к центральному процессору. Правильное распределение и настройка IRQ позволяет избежать конфликтов между устройствами и обеспечить стабильную работу системы.
В этой части мы рассмотрим какой путь проходит информация о нажатой клавише от клавиатуры до CPU, будет очень много картинок и это не последняя часть. Я буду рассказывать об этом с точки зрения программиста который пишет в режиме пользователя — пользовательские програмы, web, мобильные приложения — поэтому здесь могут быть неточности. Люди занимающиеся электроникой навряд ли найдут для себя что-то полезное. Первая часть находится здесь.
Проблема высшего программистского образования в том что студенты весьма подробно изучают отдельные аспекты вырванные из контекста не понимая как это всё увязывается вместе. Несколько семестров высшей математики, чтобы понимать физику, чтобы понимать электротехнику, электроприборы, ассемблер, ОС, алгоритмы, системное программирование и куча других предметов утрамбованных в стандартную пятилетнюю программу. Обилие деталей и никто не объясняет как это вписывается в общую картину, предполагается что через 5 лет студент сам увяжет это в голове, а потом пойдёт работать с .Net и никогда не притронется к электронике и режиму ядра. Я считаю, что не нужно так подробно знать о работе компьютера, достаточно общего понимания что происходит ниже по технологическому стеку. Если бы люди составляющие программы обучения для ВУЗов открывали автошколы, вы бы учили русский язык, каллиграфию и гидродинамику, потому как надо общаться с инспекторами, менять жидкости и писать объяснительные. В статье будут некоторые неточности, так что для сдачи экзамена по профильным предметам она не подойдёт, но после неё будет легче понять устройство ПК.
Под катом трафик.
Клавиша клавиатуры представляет из себя кнопку, которая замыкает контакты и через них проходит электрический ток. Механизм замыкания/размыкания сделан таким образом чтобы кнопку не приходилось вдавливать до конца, потому как иначе пальцы будут быстро уставать и клавиатура будет неэргономичной. В разрезе клавиша выглядит так. В правой части находится контакт на который подаётся напряжение.
“Мозгами” клавиатуры которые могут определять нажатие клавиши является микроконтроллер, который по сути является мини-компьютером с программой проверяющей каждую клавишу подавая на неё напряжение и если оно проходит то клавиша нажата. Ножки микроконтроллера имеют свои названия и к ним можно обращаться в коде, они используются для общения с внешним миром. Ножки могут управлять работой микроконтроллера, сообщать состояние чипа или служить для чтения/передачи данных.
Программу (прошивку) можно писать на языке С или ассемблере, она заливается на микроконтроллер через программатор. Он представляет из себя устройство с разъёмом для установки микроконтроллера и может подключаться к компьютеру через USB, на котором запущен специальный софт. Вот как выглядит программатор в реальной жизни:
Через программу на каждую ножку можно установить либо замерить напряжение. Алгоритм нахождения нажатой клавиши заключается в том чтобы подавать напряжение на одну ножку и замерять его на другой и если клавиша была нажата, то на второй ножке будет примерно столько же вольт сколько на входной. Так в бесконечном цикле проверяются все клавиши. Как правило клавиатура имеет 80-110 кнопок, а у контроллера контактов (пинов) гораздо меньше. Поэтому используют такой подход как “клавиатурная матрица” — все клавиши распределяют по столбцам и строкам и алгоритм сводится к нахождению столбца который пересекается со строкой на которую программа подала напряжение. Здесь отмечено цветом соответствие столбцов/строк контактам.
В реальности матрица может выглядеть так. Слева промышленная и справа самодельная.
На псевдокоде часть программы прошивки определяющая нажатые клавиши может выглядеть следующим образом.
for (int i = 0; i < COLSC; i++) {
SetPower(columns[i], 3.0 f);
for (int j = 0; j < ROWSC; j++) {
float power = GetPower(rows[j]);
if (power >= 3.0f - THRESHOLD) {
BYTE key = keys[i, j];
SendKey(key);
}
}
}
Каждой клавише соответствует скан код, он стандартизирован и представляет из себя 8ми битное число, т.е. один байт. Поэтому когда нажата клавиша Y контроллер клавиатуры должен будет отправить число 21 (0x15), а когда отпущена — 149 (0x95). Каким образом происходит отправка? Наверняка вы работали с JSON, веб-сервисами или отправляли данные между процессами и знаете что для отправки данные надо сериализовать, т.е. превратить в массив байт или отформатированный текст, которые получатель может десериализовать, т.е. воссоздать объект в своём адресном пространстве. А во что можно сериализовать данные на таком низком уровне? Нам нужно передать всего лишь 1 байт (8 бит). Забегая вперёд скажу, что данные мы будем передавать побитно.
В математике есть формула которая может преобразовать любое привычное нам десятичное число в последовательность нулей и единиц и обратно. Этой формуле нашлось применение в вычислительной технике. В первой части я вскользь упомянул, что аналоговая техника эксплуатирует законы физики, в то время как цифровая работает на уровне нулей и единиц. Это означает, что аналоговый телефон кодирует весь спектр человеческого голоса в последовательность электромагнитных волн, а цифровой телефон использует микросхему которая преобразовывает человеческий голос в цифровые данные, к примеру в файлы в формате WAV, а потом передаёт их последовательностью нулей и единиц в виде электромагнитных волн. Только в данном случае вместо всего спектра голоса надо представлять только два значения — 0 и 1. Представлять их можно волнами разной длины, разным напряжением, световыми импульсами через оптоволокно, черными и белыми полосками на бумаге, дырками на перфокарте.
Скан код нажатой клавиши Y в двоичном виде выглядит как 0001 0101. Передавать мы их будем по ножке микроконтроллера которая отвечает за данные (DATA). Логическая единица — это напряжение 3.3В и выше, логический ноль — напряжение около 0В. Здесь возникает загвоздка — как передать три нуля подряд? Для этого нам нужна вторая ножка которую назовём CLOCK, когда на ней единичка это значит что сеанс передачи одного бита начался, а ноль — закончился. Такая перемена значений (напряжений) будет происходить с определённым интервалом времени, скажем 50 наносекунд, потому что на другом конце находится второй микроконтроллер который работает со своей скоростью и в бесконечном цикле слушает ножки к которым подключены CLOCK и DATA. В данном примере я буду исходить из того что клавиатура подключается через разъём PS2, который показан ниже. Через USB порт алгоритм передачи будет другим. Как видите у порта PS2 есть пины которые называются Data, Clock. Помимо них есть ещё контакт по которому контроллер порта PS2 раздаёт клавиатуре напряжение 5В необходимое для работы и контакт заземления, который просто выводится на корпус клавиатуры. Остальные контакты не используются.
Порт PS/2 называется последовательным портом (serial bus), потому как передаёт биты один за другим (последовательность битов). Паралельный порт передаёт данные сразу по нескольким контактам и может за один сеанс передачи данных передать к примеру сразу один байт (8 бит).
В чём разница между портом, шиной (bus) и протоколом? Шина как и порт это набор контактов (проводков) и соглашение как их использовать, только порт имеет соединение для подключения внешних устройств, а шина используется для общения компонентов на материнской плате. Порт это по сути шина с разъёмом по середине. Протокол — это порядок взаимодействия через контакты. В примере с PS/2 это был порядок передачи данных через контакты Clock и Data.
Раньше микроконтроллер Intel 8042 был очень распространённым и использовался как в клавиатуре, так и в качестве контроллера порта PS2, т.е. данными обменивались два одинаковых чипа. Драйвер порта PS2 в Windows называется 8042prt.sys.
На самом деле мы передаём не 8 бит, а 11, потому что данные передаются в виде пакета данных или же сообщения. Дополнительные 3 бита обозначают начало и конец данных — один нолик в начале и 0 1 в конце, такой протокол передачи данных от устройства хосту в PS2. Так может выглядеть функция SendKey в псевдокоде, если вам удобнее понимать код. Она отправляет данные о нажатой клавише через шину PS2.
void SendBit(BYTE bit) {
float power = (bit != 0) ? 3.3f : 0.0f;
SetPower(DATA, power);
SetPower(CLOCK, 3.3f);
Sleep(50);
SetPower(CLOCK, 0.0f);
Sleep(50);
}
void SendData(BYTE data) {
SendBit(0);
for (BYTE i = 0; i < sizeof(BYTE); i++) {
BYTE bit = (data >> i) & 1;
SendBit(bit);
}
SendBit(0);
SendBit(1);
}
Не всегда для передачи данных нужно вручную устанавливать напряжение на каждой ножке индивидуально. В некоторых случаях значение сохранённое в регистре автоматически отображается на контакты.
На графике такая передача данных будет визуализирована следующим образом. По оси X время, по Y — напряжение.
В аналоговой технике сигнал может искажаться, т.е. лежит провод ни к чему не подключённый, но вольтметр показывает на нём 0.5В из-за того что рядом есть электромагнитное поле. Поэтому используется понятие порогового напряжения. Если напряжение меньше порогового, то считаем что получили логический ноль, иначе единичка. С учётом возможных искажений скан-код нажатой клавиши Y может прийти вот таким:
Прежде чем мы подробно рассмотрим как данные от клавиатуры добираются до CPU давайте поговорим о микросхемах, шинах и материнских платах.
Микроконтроллеры и микросхемы
Микроконтроллер может выполнять вшитую в него программу, имеет некоторый объём RAM памяти и место для хранения данных и кода программы. В микросхему программу можно задать ещё на этапе проектирования. Вручную создавать электрическую схему реализующую алгоритм очень трудоёмко и поэтому для проектирования микросхем может использоваться специальный язык программирования который называется VHDL (Hardware Description Language). Это высокоуровневый язык программирования который транслируется в план электрической схемы, она прогоняется через программу находящую оптимальное расположение радиоэлементов на плате и в конечном счёте производится в физическом виде. Изображения носят иллюстративный характер.
Каким же образом данные и команды представлены в микросхемах и микроконтроллерах? Основой вычислительной техники является транзистор, который человечество научилось делать в микроскопических размерах. Транзистор это такой радиоэлемент у которого есть три ножки: вход, выход и между ними управляющая, которая открывает или закрывает ток между двумя ножками. Рисунок ниже иллюстрирует работу транзистора, вода иллюстрирует ток.
На входную ножку подаётся напряжение, если управляющая имеет напряжение, то ток проходит на выходную, иначе там будет 0В. Имея 8 транзисторов у каждого из которых на выходной ножке подключен светодиод который либо горит либо нет, мы можем представить 256 уникальных комбинаций (2 в степени 8). Лапочки интерпретируются справа налево, так же как и десятичные числа. Младшие разряды находятся справа.
Т.е. одна лампочка представляет один бит информации (0 или 1), а восемь таких лампочек соответствуют одному байту. На транзисторах можно строить и логические операторы И, ИЛИ, НЕ, XOR.
К примеру в схеме оператора AND (слева на картинке выше) на выходе будет напряжение только если оба входных напряжения ненулевые. Есть уже кем-то придуманные алгоритмы сложения, умножения, деления, вычитания основанные на побитовых логических операциях и битовых сдвигах. Производителям микросхем надо их просто реализовать. Ниже проиллюстрирована работа алгоритма побитового сложения, разбирать мы его не будем:
Нанотранзисторы микроскопические и их можно размещать на плате миллионами. Ниже изображён процессор Intel и как примерно может выглядеть одно из его ядер. Картинка носит иллюстративный характер.
Микросхемы могут содержать на той же плате и в том же корпусе и микроконтроллер.
Шины
Обычно в учебниках шины показывают в виде жирных стрелок, как на картинке ниже. Это делается чтобы не рисовать все соединения контактов, которых может быть много. Работа шины PS2 очень простая, там всего нужно три контакта. Но есть шины у которых к примеру 124 контакта для передачи данных.
Различают шины следующих типов:
- Данных — биты на этих контактах интерпретируются как данные: число, символ, часть картинки или других бинарных данных. Ширина шины влияет на пропускную способность, количество переданных бит за секунду
- Адреса — биты на этих пинах интерпретируются как физический адрес в памяти. Ширина этой шины определяет максимальный поддерживаемый объём RAM.
- Управления — контакты которые используются для передачи управляющих сигналов на подключенные устройства. CPU использует их для отправки команд чтения записи памяти RAM и устройств I/O.
- Питания. Хоть она обычно и не упоминается в списке шин, но привести в пример её стоит. Вот как выглядит ATX 24 Pin 12V PSU Connector, она подаёт разные напряжения для разных подсистем.
Шина может состоять из подшин, т.е. одни контакты используются для данных, другие для адресов, третьи для управления и контакты по которым передаётся питание. Подход когда одни и те же контакты используются попеременно для передачи и данных и адресов называется мультиплексированием. К примеру процессор Intel 8086 имеет шину данных и адресов 20 бит, на диаграмме пинов её контакты обозначены AD0-AD19 (ножки 16-2 и 39-35).
В более сложном случае у нас могут быть несколько микросхем подключенных к тем же контактам. Для нормального общения им нужен дополнительный чип, который будет определять кто в какой момент времени может их использовать, он называется контроллер шины. На рисунке ниже сферическая шина в вакууме: четыре одинаковых микроконтроллера передают данные микроконтроллеру-потребителю через контроллер шины. Красный провод — напряжение, которое контроллер шины раздаёт всем подключенным к нему чипам. По зелёным проводам передаются данные и производится “договаривание” с контроллером шины и синий провод это Clock, по которому контроллер шины синхронизирует общение контроллеров, потому как они могут работать с разной скоростью. Если на синем проводе логическая единица, то чип имеющий право на пользование шиной может выполнить один акт взаимодействия со внешним миром — прочитать бит например.
Контроллер шины можно рассматривать как цельный компонент, потому общение со внешним миром будет проходить через него и сколько реально на шине чипов или реальных внешних устройств не важно. Для передачи данных в материнской плате имеется разветвленная сеть шин. Несмотря на то что чип-сетов великое множество, в большинстве своём они следуют типовой компоновке, потому что все устройства подключённые к материнской плате делятся на:
- Медленные — клавиатура, жёсткий диск, сетевая карта, аудио и пр.
- Быстрые — CPU, RAM, GPU.
Исходя из этого деления в материнской плате есть два главных чипа — Южный мост, координирующий работу всех медленных устройств, и Северный мост для координации быстрых устройств. В современных компьютерах Северный мост находится в самом CPU, а Южный может называться Platform Controller Hub, но сути это не меняет. Оба эти моста соединены между собой шиной по которой и сообщают друг другу о значимых событиях. Особый интерес представляет собой генератор тактовой частоты по которому процессор, оперативная память и графическая карта синхронизируют свою работу, тот самый синий провод. Оверклокинг это по сути изменения настроек BIOS, чтобы синхронизация происходила чаще, при этом железо, в первую очередь CPU будет сильнее греться, потреблять больше энергии и быстрее вырабатывать свой ресурс. Противоположностью является андерклокинг, когда скорость работы понижается ради экономии заряда батареи или уменьшения шумности.
Чипсет — это набор микросхем, которые все были созданы для работы друг с другом. Они обеспечивают коммуникацию компонентов на материнской плате и предоставляют функциональность, например таймеры. Чип-сет работает только с одной маркой процессоров, AMD нельзя вставить в материнку с чипсетом Intel, у них даже контакты разные. Схема материнской платы представлена ниже:
Хотите пример хардверной инкапсуляции? В чипсетах фирмы Intel имеется чип под названием Super IO, он представлен на картинке ниже и через шину LPC подключен к Южному мосту. LPC — умное название проводков CLOCK, DATA, VCC (POWER). Этот чип содержит в себе эмуляцию всех старых чипов которые когда-либо использовались для периферийных устройств, в том числе и чип 8042 который использовался для PS2 порта. Там же находится и эмулятор контроллера порта для Floppy и прочие реликты которые мешают прогрессу. На общей схеме материнской платы выше указаны и Super IO и шина LPC.
Современный порт PS2 напрямую подключается к чипу Super I/O. Зелёное — клавиатура, фиолетовый — мышка. Раньше он подключался к микроконтроллеру Intel 8042.
Материнская плата выполнена из диэлектрика, т.е. материала который не проводит ток. Ток может проходить только про пропечатанным на плате магистралям. Материнская плата имеет множество слоёв, на каждом из которых пропечатаны свои контакты и поэтому если просверлить материнку там где магистралей не видно её можно испортить повредив невидимые контакты внутри платы. Теперь можно детально рассмотреть процесс распространения данных от PS2 к CPU.
Дорога от PS2 к процессору
Как правило архитектуру компьютера рассматривают на процессоре 8086. С одной стороны это правильно, потому как он достаточно простой по сравнению с современными CPU, с другой стороны неправильно, потому что он старый и не отражает архитектуру современной машины. Intel 8086 не нужны были никакие мосты, потому что он был настолько медленный что мог работать с периферией на одной шине, т.е. на одной частоте. Я плохо знаю современные CPU и чип сеты, поэтому буду объяснять на выдуманных, которые напоминают реальные. В моём примере будет вымышленный CPU сильно похожий на Intel 8086. У Super IO чипа больше ста контактов и по ним есть документация в Интернете, но я не вижу смысла разбирать какие пины используются клавиатурой и LPC-шиной для общения с South Bridge на самом деле. Главное это принцип, который может быть реализован по-разному.
Давайте быстренько посмотрим на картинку чтобы вспомнить что мы уже прошли. Зелёные стрелки показывают путь который мы рассмотрим.
Итак данные от клавиатуры уже пришли в контроллер порта PS2, который когда-то был чипом Intel 8042, а теперь эмулируется чипом Super IO. А теперь давайте разбирать дальнейший ход действий на моей выдуманной материнке с выдуманным CPU. Контроллер PS2 получил скан код нажатой клавишы Y и теперь подаёт напряжение на контакт сигнал (фиолетовая линия, см картинку ниже) на котором должен уведомить программируемый контроллер прерываний о данных с клавиатуры. Этот сигнал передаётся от одного чипа к другому пока Северный мост не передаст его чипу управляющему прерываниями.
Programmable Interrupt Controller представляет из себя чип Intel 8259 у которого 8 ножек (их имена IRQ0-IRQ7) зарезервированы для получения уведомлений от определённых портов (Interrupt ReQuest). На пин IRQ1 подвязана клавиатура, IRQ7 — принтер, на какой-то пин Floppy диск, звуковая карта, параллельные порты и другие. Конечно устройств может гораздо больше восьми, поэтому применялся такой приём как каскадирование, когда к ножке с именем IRQ2 подключался другой такой же PIC, у которого отсчёт начинался не с 0, а 7. Мышка привязана к IRQ12, т.е. ножка IRQ5 на втором PIC.
Сейчас контроллер прерываний должен уведомить CPU о событии на клавиатуре. Это происходит следующим образом:
- Контроллер прерываний подаёт на свою ножку INT (Interrupt) напряжение, которое идёт на ножку INTR (Interrupt Request) процессора. CPU может быть в данный момент занят и потому будет игнорировать этот сигнал, потому что уведомление от клавиатуры считается маскируемым прерыванием, т.е. его можно игнорировать до поры до времени. Немаскируемое прерывание требует немедленного внимания, к примеру ошибки внутри логики работы чипсета. Ошибку следует понимать так же как и exception внутри приложения.
- Процессор наконец обращает внимание на сигнал и несколько раз снижает и повышает сигнал на ножке INTR, давая понять что к приёму номера прерывания готов.
- Контроллер прерывания выводит на свою шину данных (ножки D0-D7) номер прерывания, который называется вектором прерывания. Во время загрузки ОС настраивает PIC возвращать определённый номер когда происходит уведомление от определённого устройства (сигнал на ножку IRQ).
Почему вектор, а не номер? У меня есть два объяснения. Вектором называется одномерный массив и данные здесь передаются по сути как массив нулей и единиц. Второе объяснение — вектор в математике обозначает направление (x, y, z, w). В процессоре вектор прерывания меняет направление исполнения программы.
- CPU считывает со своих ножек AD0-AD7 этот номер, вектор прерывания. Операционная система сохранила в регистре IDT (Interrupt Descriptior Table) указатель на массив функций, которые называются таблица векторов прерываний, где вектор прерывания используется как индекс в этом массиве. По нему CPU вызывает обработчик прерывания.
На самом деле таблица векторов прерываний содержит больше данных чем просто указатель на функцию. В этом массиве хранятся данные такого типа. Для простоты будем думать, что в IDT хранятся указатели на функцию или в терминах C# делегаты.
struct IDT_entry{ unsigned short int offset_lowerbits; unsigned short int selector; unsigned char zero; unsigned char type_attr; unsigned short int offset_higherbits; }; struct IDT_entry IDT[256];
- Обработчик клавиатуры вызывает команду процессора которая завершает обработку прерывания, о чем сообщает контроллеру прерываний подав напряжение на ножку INTA (Interrupt Acknowledged).
Обработчик прерывания от клавиатуры в самом простом коде будет выглядеть так. Он вызывает команду которая сигнализирует о завершении обработки прерывания, т.е. подаёт сигнал на ножку INTA.
void irq1_handler(void) {
outb(0x20, 0x20); //EOI
}
Более подробно ознакомится с тем как настраивается таблица векторов прерываний можно на osdev.
Теперь мы знаем о том как произошло прерывание, но не знаем как обработчик прерываний считывает информацию о нажатой клавише. С программной точки зрения порт PS2 представляет собой два регистра, только обращение к ним происходит не по именам или адресам в памяти о по номеру порта ввода/вывода. Эти два однобайтовых регистра закреплены за портами 0x60 и 0x64, в первом (0x60) будет лежать скан-код клавиши. Второй порт используется для передачи статуса и комманд порту PS2 (не клавиатуре!). В наборе инструкций архитектуры x86 есть команда IN storeTo, fromPortNum, которая считывает значение из указанного I/O port в указанный регистр. Например IN AL, 0x60 сохранит данные с клавиатуры в регистр AL. Она может работать примерно так:
- В процессоре который мы рассматриваем есть ножки AD0-AD20, они могут использоваться для указания адреса так и данных. Это и шина данных и шина адреса. Помимо них есть ряд управляющих ножек, к примеру пин №28 (S2) значение которого укажет северному мосту откуда будет происходить чтение — из памяти или устройства ввода вывода. Команда IN выставляет сюда значение говорящее об I/O устройстве.
- На шину адреса CPU (какие-то из ножек AD0-AD20) выставляется последовательность 0110 0000, что и есть 0x60. Совместно с другими управляющими ножками CPU отправляет сообщение Северному мосту, оно направляется на Южный мост. Между чипами сообщение может передаваться по разным протоколам, где-то это последовательная шина, где-то параллельная. Южный мост приняв сообщение видит порт 0x60 и понимает что его надо направить в SuperIO чип, у того будет какое-то количество времени его обработать.
- Как мы уже говорили Super IO чип нужен для того чтобы скрыть в себе архаичный функционал старых устройств. По номеру 0x60 он понимает что это сообщение предназначено контроллеру PS2, который уже выставляет скан код на свою шину данных.
- Теперь скан код идёт обратно. От эмулятора Intel 8042, через внутреннюю схему чипа SuperIO, по шине LPC состоящей из трёх контактов в Южный мост, оттуда в Северный в виде расширенного сообщения. Т.е. к скан коду было добавлено чуть больше информации, к примеру откуда она.
- Северный мост уведомляет комбинацией сигналов CPU о прибытии данных с запрошенного порта и выставляет скан код на шину данных (какие-то 8 пинов из AD0-AD20).
- Микроархитектура CPU считывает эту последовательность высоких и низких сигналов с шины данных и и помещает её на транзисторы представляющие регистр AL. С этого момента программа может обрабатывать данные от клавиатуры.
Весь этот алгоритм работает в масштабе наносекунд и потому выполняется почти моментально, хоть процессор и провёл некоторое время в ожидании операции ввод-вывода.
Как вы теперь понимаете чтение с внешних устройств, даже таких как память RAM с т.зр. CPU достаточно медленное. Эту медлительность можно заметить написав программу которая печатает 10 000 строчек в файл построчно, вместо того чтобы скопить их в буфере и сохранить сразу. Жёсткий диск подключен к Южному мосту и внутри него так же есть контроллер управляющим непосредственным размещением данных.
Оперативная память подключается к CPU через шину и чтение с неё занимает некоторое время. Для ускорения работы CPU у него имеется кэш, т.е. область в которой расположены транзисторы представляющие данные которые скоро понадобятся или часто используются, их чтение происходит гораздо быстрее чем из платы RAM, которая общается с CPU через Северный мост. Оперативная память называется Dynamic Random Access Memory, потому как для представления данных в ней используются конденсаторы. Конденсатор это радиоэлемент который как аккумулятор держит некоторое время заряд пока полностью не разрядится. Только здесь разрядка происходит очень быстро. Поэтому конденсаторы надо перезаряжать, это происходит моментально, достаточно подать напряжение. Заряженный конденсатор — логическая 1, иначе 0. Для памяти кэша используется Static RAM, т.е. её не надо перезаряжать и поэтому она работает быстрее, но стоит дороже. Кэш делится на 3 уровня, которые последовательно проверяются в процессе поиска запрошенных данных, прежде чем процессор обратиться к RAM. На старых процессорах кэш первого уровня (L1) был частью CPU и работал с ним на одной частоте, когда как L2 и L3 кэши были внешними чипами. Сейчас они все находятся на одной микросхеме с процессором. Кэш L1 самый быстрый и самый маленький по объёму памяти, L2 имеет больше памяти но медленнее. L3 самый большой кэш и самый медленный, часто его называют shared cache, потому что он хранит данные для всех ядер CPU, в то время как L1 и L2 созданы для каждого отдельного ядра.
В следующей части поговорим как Windows принимает и обрабатывает полученные данные.
UPD: О работе ПК ч.3: От включения до полной загрузки Windows 10
Клавиатура подключена к линии прерывания
IRQ1. Этой линии соответствует прерывание
INT 09h.
Клавиатурное прерывание обслуживается
модулями BIOS. Драйверы клавиатуры и
резидентные программы могут организовывать
дополнительную обработку прерывания
INT 09h. Для этого может быть использована
цепочка обработчиков прерывания. В
первой книге первого тома мы приводили
примеры расширения обработчика прерывания
INT 09h.
Как работает стандартный обработчик
клавиатурного прерывания, входящий в
состав BIOS?
Этот обработчик выполняет следующие
действия:
-
читает из порта 60h скан-код нажатой
клавиши; -
записывает вычисленное по скан-коду
значение ASCII-кода нажатой клавиши в
специальный буфер клавиатуры,
расположенный в области данных BIOS; -
устанавливает в 1 бит 7 порта 61h, разрешая
дальнейшую работу клавиатуры; -
возвращает этот бит в исходное состояние;
-
записывает в порт 20h значение 20h для
правильного завершения обработки
аппаратного прерывания.
Обработчик прерывания INT 09h не просто
записывает значение ASCII-кода в буфер
клавиатуры. Дополнительно отслеживаются
нажатия таких комбинаций клавиш, как
Ctrl-Alt-Del, обрабатываются специальные
клавиши PrtSc и SysReq. При вычислении кода
ASCII нажатой клавиши учитывается состояние
клавиш Shift и CapsLock.
Буфер клавиатуры имеет длину 32 байта и
расположен по адресу 0000h:041Eh для машин
IBM PC/XT.
В IBM AT и PS/2 расположение клавиатурного
буфера задается содержимым двух слов
памяти с адресами 0000h:0480h (компонента
смещения адреса начала буфера) и
0000h:0482h (смещение конца буфера). Обычно
в IBM AT эти ячейки памяти содержат значения,
соответственно, 001Eh и 003Eh. Так как смещения
заданы относительно сегментного адреса
0040h, то видно, что обычное расположение
клавиатурного буфера в IBM AT и PS/2
соответствует его расположению в IBM
PC/XT.
Клавиатурный буфер организован
циклически. Это означает, что при его
переполнении самые старые значения
будут потеряны. Две ячейки памяти,
находящиеся в области данных BIOS с
адресами 0000h:041Ah и 0000h:041Ch содержат,
соответственно, указатели на начало и
конец буфера. Если значения этих
указателей равны друг другу, буфер пуст.
(Можно удалить все символы из буфера
клавиатуры, установив оба указателя на
начало буфера. Однако есть более
предпочтительный способ с использованием
прерывания BIOS INT 16h).
Указателями на начало и конец клавиатурного
буфера обычно управляют обработчики
прерываний INT 09h и INT 16h.
Программа извлекает из буфера коды
нажатых клавиш, используя различные
функции прерывания INT 16h.
Помимо управления содержимым буфера
клавиатуры, обработчик прерывания
INT 09h отслеживает нажатия на так
называемые переключающие клавиши —
NumLock, ScrollLock, CapsLock, Ins. Состояние этих
клавиш записывается в область данных
BIOS в два байта с адресами 0000h:0417h и
0000h:0418h.
Формат байта 0000h:0417h:
Биты
Значение
0
Нажата правая клавиша Shift.
1
Нажата левая клавиша Shift.
2
Нажата комбинация клавиш Ctrl-Shift с
любой
стороны.
3
Нажата комбинация клавиш Alt-Shift с
любой
стороны.
4
Состояние клавиши ScrollLock.
5
Состояние клавиши NumLock.
6
Состояние клавиши CapsLock.
7
Состояние клавиши Insert.
Формат байта 0000h:0418h:
Биты
Значение
0
Нажата левая клавиша Shift вместе с
клавишей
Ctrl.
1
Нажата левая клавиша Shift вместе с
клавишей
Alt.
2
Нажата клавиша SysReq.
3
Состояние клавиши Pause.
4
Нажата клавиша ScrollLock.
5
Нажата клавиша NumLock.
6
Нажата клавиша CapsLock.
7
Нажата клавиша Insert.
Если вы изменяете состояние светодиодов
на панели клавиатуры, не забывайте
устанавливать соответствующие биты в
байтах состояния клавиатуры.
Программой обработки прерывания INT 09h
отслеживаются некоторые комбинации
клавиш. В таблице приведены эти комбинации
и действия, выполняемые обработчиком
прерывания при их обнаружении:
Комбинация
клавиш Выполняемые действия
Ctrl-Alt-Del
Сброс и перезагрузка системы.
Ctrl-NumLock,
Перевод машины в состояние ожидания
Pause
до нажатия любой клавиши.
Shift-PrtSc
Распечатка на принтере содержимого
видеопамяти.
Ctrl-Break
Выполнение прерывания INT 1Bh,
завершающего
работу программы.
Многие типы клавиатур имеют отдельную
альтернативную цифровую панель,
напоминающую клавиатуру калькулятора.
Если одновременно с нажатием на клавишу
Alt набрать число на этой панели (не
большее, чем 255 и не равное 0), то это число
будет помещено в буфер клавиатуры, как
будто бы оно было введено нажатием на
одну клавишу. Это число будет также
записано в слове по адресу 0000h:0419h в
области данных BIOS.
При переполнении внутреннего буфера
клавиатуры или буфера, расположенного
в области данных BIOS, программа-обработчик
прерывания INT 09h генерирует звуковой
сигнал.
При составлении программ для MS-DOS у вас
едва ли появится необходимость
непосредственного манипулирования
содержимым буфера клавиатуры — вы можете
использовать прерывание BIOS INT 16h для
выполнения практически всех клавиатурных
операций.
В следующем разделе мы займемся
непосредственно изучением средств
работы с клавиатурой, предоставляемых
в распоряжение прерыванием BIOS INT 16h.
Соседние файлы в папке Периферийные устройства ПК
- #
- #
- #
- #
02.05.2014269.31 Кб21Классификация шин.vsd
- #
- #
- #
- #
02.05.2014315.39 Кб26Накопители.vsd