Смекни!
smekni.com

Драйвер клавиатуры реализующий функции музыкального синтезатора на клавиатуре для Windows NT 5 (стр. 5 из 8)

PinWriteHeader.DataUsed = 12; // 1 команда: выключаем ноту

PinWriteData(&PinWriteHeader);

Функция работает только при IRQL = PASSIVE_LEVEL.

2.9.4 Схема хранения музыкальных параметров клавиш

Во время нажатия клавиши клавиатура генерирует скан-код и флаг (эта информация находится в структуре KEYBOARD_INPUT_DATA).

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

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

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

Схема реализована в модуле keys.

Схема такова:

typedef struct _KEY_SCANS_TABLE

{UCHAR Usage;

UCHAR ScanMake;

UCHAR ScanE0;

UCHAR ScanE1;

} KEY_SCANS_TABLE, * PKEY_SCANS_TABLE;

typedef struct _KEY_MIDI_TABLE

{UCHAR Used;

UCHAR Channel;

UCHAR Instrument;

UCHAR Note;

} KEY_MIDI_TABLE, * PKEY_MIDI_TABLE;

Создаётся массив структур KEY_SCANS_TABLE, где индекс массива – реальный скан-код, генерируемый клавиатурой, а в полях ScanMake, ScanE0, ScanE1 хранится позиция клавиши, соответствующая тому или иному значению флага клавиши, генерируемого клавиатурой. В поле Usage число единичных бит равно числу установленных клавиш, соответствующих одному скан-коду.

Создаётся массив структур KEY_MIDI_TABLE, где индекс массива – позиция клавиши, в полях структуры содержится музыкальная информация о клавише:

Used – используется в схеме или нет. Если используется: нажата или отпущена.

Channel – канал, в котором звучит нота. Существует 16 каналов - 0..15.

Instrument – инструмент ноты. Существует 128 инструментов - 0..127.

Note – нота. Существует 128 нот для каждого инструмента - 0..127.

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

На обычной клавиатуре находится 104 клавиши. Поэтому в данной курсовой работе используется 104 позиции (0..103).

Функции установки и выборки музыкальной информации

NTSTATUS KeyMidiInit()

Выделяет память для двух таблиц и заполняет их нулями.

NTSTATUS KeyMidiFree()

Освобождает память, выделенную для таблиц.

NTSTATUS KeyMidiSetNote(IN UCHAR ScanCode, IN UCHAR Flag,

IN UCHAR Position,

IN UCHAR Channel, IN UCHAR Instrument,

IN UCHAR Note, IN UCHAR Used)

Устанавливает в первой таблице для скан-кода и флага позицию. Для позиции во второй таблице устанавливает музыкальные параметры.

NTSTATUS KeyMidiGetPosition(IN UCHAR ScanCode, IN UCHAR Flag,

OUT PUCHAR Position)

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

NTSTATUS KeyMidiGetNote(IN UCHAR Position, PUCHAR Channel,

OUT PUCHAR Instrument, OUT PUCHAR Note,

OUT PUCHAR Used)

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

NTSTATUS KeyMidiSetUsed(IN UCHAR Position, IN UCHAR Used)

Используя позицию клавиши, устанавливает состояние клавиши KEY_RELEASED (отпущена) или KEY_PRESSED (нажата).

2.9.5 Разделение задачи на потоки

Функция MyFilterReadComplete, которая получает буфер нажатых клавиш как массив структур KEYBOARD_INPUT_DATA, выполняется на IRQL <= DISPATCH_LEVEL.

Функция PinMidiNoteOn работает только при IRQL = PASSIVE_LEVEL.

Это значит, что функцию PinMidiNoteOn нельзя вызывать внутри функции MyFilterReadComplete.

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

Принцип синхронизации потоков

Функция MyFilterReadComplete:

(поток 1, выполняется на IRQL <= DISPATCH_LEVEL)

if(PlayCompleted)

{Подсоединить Очередь Б к Очереди А

Опустошить Очередь Б

for(все нажатые или отпущенные клавиши)

{Добавить в Очередь А позицию клавиши}

Установить Событие PlayEvent в состояние «Сигнал»}

else

{for(все нажатые или отпущенные клавиши)

{Добавить в Очередь Б позицию клавиши}}

Функция PlayThread:

(поток 2, выполняется на IRQL = PASSIVE_LEVEL)

Ждать пока Событие PlayEvent не перейдёт в состояние «Сигнал»

Установить Событие PlayEvent в состояние «Нет сигнала»

// Устанавливает значение 0 переменной PlayCompleted

// Обеспечивает монопольный доступ к этой переменной аппаратными средствами

InterlockedExchange(&PlayCompleted, 0);

PinSetState(KSSTATE_RUN);

while(Очередь А не пуста)

{Извлечь из Очереди А информацию о нажатой клавиши

if(состояние на клавиатуре = нажата и

музыкальное состояние = отпущена)

{Воспроизвести ноту

Музыкальное состояние = нажата}

else if(состояние на клавиатуре = отпущена и

музыкальное состояние = нажата)

{Выключить ноту

Музыкальное состояние = отпущена}}

PinSetState(KSSTATE_PAUSE);

PinSetState(KSSTATE_STOP);

InterlockedExchange(&PlayCompleted, 1);

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

Функция InterlockedExchange обеспечивает монопольный доступ к переменной того потока, который их вызвал. Все остальные потоки, которые используют эту переменную в это же время, будут ждать пока до конца не отработает InterlockedExchange. Использование этой функции возможно на любом уровне IRQL, т.к. она реализована аппаратно.

Данный механизм обеспечивает работу первого потока без ожидания второго. Т.е. происходит быстрая обработка возвращаемого из стека IRP-пакета с нажатыми клавишами. По сигналам из первого потока второй поток производит музыкальную обработку.


3. Технологический раздел

3.1 Выбор средств разработки программного обеспечения

Из двух языков, используемых для программирования драйверов, С и ассемблера, выбор следует остановить на С ввиду того, что это язык высокого уровня, т.е. этот язык обеспечивает уровень абстракции выше, чем ассемблер, что безусловно большой плюс. Кроме того, этот язык используется фирмой Microsoft в наиболее распространенном пакете для разработки драйверов MS DDK. Еще одним плюсом С является то, что вместе с пакетом DDK поставляется специальный компилятор для С, предназначенный для компиляции драйверов.

Ориентированный на язык С, набор DDK включает в себя справочники, все необходимые библиотеки и заголовочные файлы. Поэтому была использована технология структурного программирования. В данной работе применяется DDK для Windows Server 2003.

Для редактирования исходных текстов применяется среда Microsoft Visual Studio .NET и входящий в неё компилятор Microsoft Visual C++.

При сборке исполняемого (с расширением .sys) файла используется утилита build, входящая в состав DDK.

При разработке управляющего приложения используется среда Microsoft Visual Studio .NET с визуальным редактором форм.

В проекте используется библиотека DirectKS, которая является примером получения доступа к фильтрам Kernel Streaming в режиме пользователя. В библиотеку внесены изменения. Использование библиотеки описано ниже.

Операционной системой, в которой работает драйвер, является Windows NT 5, т.е. Windows 2000, Windows XP, Windows Server 2003.

Т.к. драйвер работает с аудиоустройством напрямую, то на компьютере должна быть установлена такая звуковая карта, которая поддерживает запросы и формат данных, которые ей отправляет драйвер. Звуковые карты, начиная с Creative Sound Blaster Live поддерживают запросы, используемые в данном курсовом проекте.

3.2 Установка драйвера в системе

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

Каждое устройство имеет свой раздел в реестре. Все эти разделы находятся в HKEY_LOCAL_MACHINE&bsol;SYSTEM&bsol;CurrentControleSet&bsol;Control&bsol;Class. Клавиатуре соответствует раздел {4D36E96B-E325-11CE-BFC1-08002BE10318}. У каждого устройства в его разделе есть ключи UpperFilters и LowerFilters. Это ключи типа MultiString. Они содержат имена верхних и нижних драйверов-фильтров данного устройства. Драйверы-фильтры загружаются в систему в том порядке, в каком они записаны в этих ключах.

Для регистрации разрабатывавемого драйвера как фильтра необходимо поместить его имя в последним в ключе UpperFilters.

Для регистрации нового драйвера необходимо создать раздел с именем этого драйвера в системном реестре по адресу HKEY_LOCAL_MACHINE&bsol;SYSTEM&bsol;CurrentControlSet&bsol;Services. Этот раздел должен содержать следующие ключи:

Type типа двойное слово

Определяет тип подключаемого модуля.

Интересует только значение SERVICE_KERNEL_DRIVER (1).

Start типа двойное слово

Определяет метод загрузки драйвера. Может принимать одно из следующих значений:

SERVICE_BOOT_START (0) – во время начальной загрузки ОС. Данное значение применяется, когда драйвер используется загрузчиком системы;