Драйвер
Програма віртуального драйвера. Підготовка виклику необхідної функції додатка. Додаток Windows, що обробляє апаратні переривання. Програма драйвера для обслуговування апаратних переривань. Текст віртуального драйвера, що обробляє апаратні переривання.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | реферат |
Язык | украинский |
Дата добавления | 17.06.2010 |
Размер файла | 42,1 K |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Драйвер
Драйвер - системна програма, яка використовується для управління якимось фізичним або віртуальним пристроем комп'ютера.
Віртуальні драйвери служать насамперед для віртуалізації апаратури, тобто для надання одночасно виконуваним завданням можливості спільного використання пристроїв комп'ютера. Вимірювальна або керуюча апаратури, що підключається до комп'ютера з метою створення автоматизованої установки, навряд чи буде експлуатуватися в багатозадачному режимі, однак використання для її керуванням віртуального драйвера може помітно скоротити програмні витрати й зменшити час відгуку. Розглянемо приклад віртуального драйвера, що обслуговує переривання для наприклад інтерфейсної плати таймера-лічильника. Очевидно, що до складу такого драйвера повинен входити оброблювач переривань від плати. Функції цього оброблювача визначає програміст; у найпростішому випадку оброблювач може просто прочитати дані з вихідного регістра лічильника й замаскувати переривання. Однак прикладна програма при цьому не довідається про завершення вимірювального процесу; більш природно організувати виклик з оброблювача переривань драйвера деякої функції програми, у яку можна передати прочитані дані. Фактично ця функція буде відігравати роль оброблювача переривання прикладної програми, однак викликатися вона буде не самим перериванням, а оброблювачем драйвера.
Передбачимо наступну схему взаємодії програми й драйвера. Програма за командою користувача викликає драйвер і передає йому константи налаштування таймера. Драйвер ініціалізує таймер і повертає керування у програму, що продовжує своє виконання. За сигналом переривання від таймера програма призупиняється й активізується оброблювач переривань, що перебуває у драйвері. У цьому оброблювачі виконуються: читання даних з вихідного регістра лічильника, виклик (через VMM) оброблювача переривання програми й передача у нього отриманих даних. Оброблювач переривань програми приймає із драйвера дані й, завершуючись, повертає керування в VMM, що нарешті передає керування програмі у місце її призупинення.
Оскільки оброблювач переривань програми викликається з VMM і після свого завершення знову передає керування системі, його можливості обмежені. У ньому, зокрема, неприпустимий виклик функції виводу інформації - MessageBox() - у вікно повідомлення. Тому тут, як і в попередньому прикладі, виникає проблема оповіщення головної функції WinMain() про надходження переривання. Можна вирішити цю проблему у такий спосіб: оброблювач переривання програми, одержавши керування, установлює прапор, який перевіряється головною функцією на кожному кроці циклу обробки повідомлень. Головна функція, виявивши встановлений стан прапора, посилає в основну програму повідомлення користувача, що нарівні з повідомленнями Windows обслуговується циклом обробки повідомлення. У функції обробки цього повідомлення вже можна виконувати будь-які дії без усяких обмежень.
Програма Windows, що взаємодіє з віртуальним драйвером обробки апаратних переривань
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <string.h>
//Визначення констант
#define MI_START 100 //Константи
#define MI_EXIT 101 //для ідентифікації
#define MI_READ 102 //пунктів меню
/* void Cls_OnUser(HWND hwnd) */
#define HANDLE_WM_USER(hwnd, wParam, lParam, fn) //Макрос для обробки
((fn)(hwnd), 0L) // повідомлення WM_USER
//Прототипи функцій
void Register(HINSTANCE); //Допоміжні
void Create(HINSTANCE); //функції
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); //Віконна функція
void OnCommand(HWND,int,HWND,UINT); //Функції
void OnDestroy(HWND); //обробки повідомлень Windows
void OnUser(HWND); //і користувача
void InitCard(); //Функція ініціалізації плати (через драйвер)
void GetAPIEntry(); //Функція одержання місця входу в драйвер
void isr(int,int); //Прототип оброблювача переривання додатка
//Глобальні змінні
char szClassName[]="MainWindow"; //Ім'я класу головного вікна
char szTitle[]="Керування"; //Заголовок вікна
HWND hMainWnd; //Дескриптор головного вікна
FARPROC VxDEntry; //Адреса крапки входу в драйвер
int unsigned data; //Дане із драйвера
char request; //Прапор закінчення вимірів
//Головна функція WinMain
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int){
MSG msg; //Структура для прийому повідомлень
Register(hInstance); //Реєстрація класу головного вікна
Create(hInstance); //Створення й показ головного вікна
/*Більш складний цикл обробки повідомлень, у який включені аналіз прапора request завершення вимірів і надсилання програмі повідомлення WM_USER при встановленні цього прапора*/
do{
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)){
if (msg.message == WM_QUIT)return msg.wParam;
DispatchMessage(&msg);
}//Кінець if(PeekMessage())
if(request){
request=0; //Скидання прапора
PostMessage(hMainWnd,WM_USER,0,0);
//Поставити в чергу повідомлення WM_USER без параметрів
}//Кінець if(request)
}while(1);//Кінець do
}//Кінець WinMain
//Функція Register реєстрації класу вікна
void Register(HINSTANCE hInst){
WNDCLASS wc;
memset(&wc,0,sizeof(wc));
wc.lpszClassName=szClassName;
wc.hInstance=hInst;
wc.lpfnWndProc=WndProc;
wc.lpszMenuName="Main";
wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wc.hbrBackground=GetStockBrush(WHITE_BRUSH);
RegisterClass(&wc);}
//Функція Create створення й показу вікна
void Create(HINSTANCE hInst){
hMainWnd=CreateWindow(szClassName,szTitle,WS_OVERLAPPEDWINDOW,
10,10,200,100,HWND_DESKTOP,NULL,hInst,NULL);
ShowWindow(hMainWnd,SW_SHOWNORMAL);}
//Віконна функція WndProc головного вікна
LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam){
switch(msg){
HANDLE_MSG(hwnd,WM_COMMAND,OnCommand);
HANDLE_MSG(hwnd,WM_DESTROY,OnDestroy);
HANDLE_MSG(hwnd,WM_USER,OnUser);
default:
return(DefWindowProc(hwnd,msg,wParam,lParam));}}
//Функція OnCommand обробки повідомлень WM_COMMAND від пунктів меню
void OnCommand(HWND hwnd,int id,HWND,UINT){
switch(id){
case MI_START://Ініціалізація плати
InitCard();
break;
case MI_READ:
char txt [80];
wsprintf(txt,"Накопичено %d подій",data);
MessageBox(hMainWnd,txt,"Дані",MB_ICONINFORMATION);
break;
case MI_EXIT://Завершити додаток
DestroyWindow(hwnd);}}
//Функція OnUser обробки повідомлення WM_USER
void OnUser(HWND){
MessageBox(hMainWnd,"Вимірювання закінченно","Контроль",MB_ICONINFORMATION);}
//Функція OnDestroy обробки повідомлення WM_DESTROY
void OnDestroy(HWND){
PostQuitMessage(0);}
//Функція InitCard ініціалізації (через драйвер) плати
void InitCard (){
GetAPIEntry(); //Одержання адреси місця входу в драйвер
_AX=_DS; //Передача в драйвер DS
_BX=55; //Канал 0
_CX=20000; //Канал 1; BX*CX=Тривалість інтервалу
_DX=1000; //Канал 2, внутрішній генератор
_SI=OFFSETOF(isr); //Зсув оброблювача переривань
_DI=SELECTOROF(isr); //Селектор оброблювача переривань
VxDEntry(); //Виклик драйвера}
//Функція GetAPIEntry одержання адреси місця входу в драйвер
void GetAPIEntry(){
asm{
mov AX,0x1684
mov BX,0x8000
int 0x2F
mov word ptr VxDEntry,DI
mov word ptr VxDEntry+2,ES}}
//Оброблювач переривань програми. Викликається VMM і повертає керування в VMM
void isr(int segment,int dt){
_DS=segment; //Ініціалізуємо DS селектором, отриманим із драйвера
request++; //Поставимо запит на повідомлення
data=dt; //Одержуємо із драйвера апаратні дані}
Головна функція WinMain() виконує три характерні для неї процедури: реєстрацію класу головного вікна, створення й показ головного вікна, цикл обробки повідомлень. У циклі обробки повідомлень є дві принципових відмінності: на кожному кроці циклу перевіряється стан прапора завершення вимірювання request, і якщо прапор виявляється встановленим, то викликається функція Windows PostMessage(), що ставить у чергу повідомлень наведеної вище програми наше повідомлення з кодом WM_USER. Для того щоб у цикл обробки повідомлень включити перевірку прапора, довелося замінити у ньому функцію GetMessage() на функцію PeekMessage(), що, на відміну від GetMessage(), при відсутності повідомлень у черзі повертає керування у програму, що й дає можливість включити в цикл додаткові дії. Однак PeekMessage() не аналізує повідомлення WM_QUIT про завершення програми, тому <виловлювання> цього повідомлення (і завершення програми оператором return 0 у випадку його приходу) доводиться виконувати вручну. Конструкція: do{ ... }while(1) дозволяє організувати нескінченний цикл, оскільки умова продовження циклу, аналізована оператором while, безумовно задовольняється (константа 1 ніколи не може стати рівною 0).
У віконній функції WndProc() фіксується прихід трьох повідомлень: WM_COMMAND від пунктів меню, WM_DESTROY від команд завершення програми й WM_USER, що свідчить про закінчення процесу вимірювання. Оскільки для повідомлення WM_USER у файлі windowsx.h відсутній макрос HANDLE_WM_USER, його довелося визначити на початку програми за допомогою оператора #define, побудувавши макророзширення за аналогією з яким-небудь із макросів виду HANDLE_повідомлення з файлу windowsx.h, хоча б з макросом HANDLE_WM_DESTROY.
Фрагмент програми, виконуваний при виборі користувачем пункту меню <Пуск>, містить лише виклик функції InitCard(). У ній викликом вкладеної функції GetAPIEntry визначається адреса API-процедури драйвера, а потім, після заповнення ряду регістрів параметрами, переданими в драйвер, викликається ця процедура. У драйвер передаються наступні параметри: селектор сегмента дані додатки, три константи для ініціалізації плати, а також селектор і зсув оброблювача переривань додатка isr(). Передача в драйвер умісту сегментного регістра DS (селектора сегмента даних) необхідна тому, що при виклику драйвером (точніше, VMM) нашої функції isr() не відновлюється операційне середовище додатка, зокрема регістр DS не вказує на поля дані додатки, які в результаті виявляються недоступними. Передавши в драйвер уміст DS, ми зможемо повернути його назад разом з іншими даними, переданими із драйвера в додаток, відновивши тим самим адресуемость даних.
При виборі користувачем пунктів меню <Читання> або <Вихід> виконуються ті ж дії, що й у попередньому прикладі.
У порівнянні з попереднім прикладом спростилася функція OnDestroy(). Оскільки відновлення маски в контролері переривань покладено тепер на драйвер, а вихідний вектор ми в цьому варіанті програми не відновлюємо, то у функції OnDestroy() лише викликається функція Windows PostQuitMessage(), що приводить до завершення програми.
В оброблювачі переривань додатка isr() після засилання в регістр DS нашого ж селектора сегмента даних, переданого раніше в драйвер і отриманого з нього як перший параметр функції isr(), виконується инкремент прапора request і пересилання в змінну data другого параметра функції isr() - результату вимірів.
Програма віртуального драйвера
Перейдемо до розгляду програми віртуального драйвера, що входить до складу нашого програмного комплексу.
Текст віртуального драйвера, що обробляє апаратні переривання
;При виклику AX=DS додатка, BX=C0, CX=C1, DX=C2, DI=селектор isr ,SI=зсув isr
.386p
.XLIST
include vmm.inc
include vpicd.inc
.LIST
Declare_Virtual_Device VMy,1,0,VMy_Control,8000h, \
Undefined_Init_Order,,API_Handler
;=======================
Vx_REAL_INIT_SEG
BeginProc VMy_Real_Init
;Текст процедури ініціалізації реального режиму (див. частина 2 ці цикли)
EndProc VMy_Real_Init
Vx_REAL_INIT_ENDS
;======================
Vx_DATA_SEG
Data dw 0 ;Комірка для результату вимірювань
DSseg dw 0 ; Комірка для зберігання селектора програми
Segment_Callback dw 0 ;Селектор функції isr програми
Offset_Callback dd 0 ;Зсув функції isr програми
IRQ_Handle dd 0 ;Дескриптор віртуального переривання
VMy_Int13_Desc label dword ;32-бітова адреса наступної структури
VPICD_IRQ_Descriptor <5,,OFFSET32 VMy_Int_13> ;Структура з інформацією
;про віртуалізоване переривання
Vx_DATA_ENDS
;======================
Vx_CODE_SEG
BeginProc VMy_Control
;Включимо до складу драйвера процедуру обробки системного повідомлення
;Device_Init про ініціалізації драйвера
Control_Dispatch Device_Init, VMy_Device_Init
clc
ret
EndProc VMy_Control
;----------------------
;Процедура, що викликається при ініціалізації драйвера системою
BeginProc VMy_Device_Init
mov EDI,OFFSET32 VMy_Int13_Desc;Адреса структури VPICD_IRQ_Descriptor
VxDCall VPICD_Virtualize_IRQ ;Віртуалізація пристрою
mov IRQ_Handle,EAX ;Збережемо дескриптор віртуального IRQ
clc
ret
EndProc VMy_Device_Init
;-------------------------
;API-процедура, викликувана з програми
BeginProc API_Handler
;Одержимо параметри з програми
push [EBP.Client_DI]
pop Segment_Callback
push [EBP.Client_AX];DS
pop DSseg
movzx ESI,[EBP.Client_SI]
mov Offset_Callback,ESI
;Загальне скидання
mov DX,30Ch
in AL,DX
;Демаскуємо рівень 5 у фізичному контролері переривань
mov EAX,IRQ_Handle
VxDCall VPICD_Physically_Unmask
;Засилаємо керуючі слова по каналах
mov DX,303h
mov AL,36h ;Канал 0
out DX,AL
mov AL,70h ;Канал 1
out DX,AL
mov AL,0B6h ;Канал 2
out DX,AL
;Засилаємо константи в канали
mov DX,300h ;Канал 0
mov AX,[EBP.Client_BX];Константа З0
out DX,AL ;Молодший байт частоти
xchg AL,AH
out DX,AL ;Старший байт частоти
mov DX,301h ;Канал 1
mov AX,[EBP.Client_CX];Константа З1
out DX,AL ;Молодший байт інтервалу
xchg AL,AH
out DX,AL ;Старший байт інтервалу
mov DX,302h ;Канал 2
mov AX,[EBP.Client_DX];Константа З2
out DX,AL ;Молодший байт частоти
xchg AH,AL
out DX,AL ;Старший байт частоти
;Установимо прапор S2 дозволу підрахунку
mov DX,30Bh
in AL,DX
ret
EndProc API_Handler
;-------------------------
;Процедура обробки апаратного переривання IRQ5 (вектор 13)
BeginProc VMy_Int_13, High_Freq
;Одержимо результат вимірів з вихідного регістра лічильника
mov DX,309h ;Порт старшого байта
in AL,DX ;Одержимо старший байт
mov AH,AL ;Відправимо його в AH
dec DX ;DX=308h
in AL,DX ;Одержимо молодший байт
mov Data,AX ;Весь результат в Data
;Виконаємо завершальні дії в PIC і викличемо функцію програми
mov EAX,IRQ_Handle
VxDCall VPICD_Phys_EOI;EOI у фізичний контролер переривань
VxDCall VPICD_Physically_Mask;Маскуємо наш рівень
;Перейдемо на синхронний рівень
mov EDX,0 ;Дані відсутні
mov ESI,OFFSET32 Reflect_Int;Адреса синхронної процедури
VMMCall Call_VM_Event;Установимо запит на її виклик з VMM
clc
ret
EndProc VMy_Int_13
;-------------------------
;Процедура рівня відкладених переривань
BeginProc Reflect_Int
Push_Client_State ;Виділимо місце на стеці для регістрів клієнта
VMMCall Begin_Nest_Exec;Почнемо вкладений блок виконання
mov AX,Data ;Відправимо дане
VMMCall Simulate_Push;у стек клієнта
mov AX,DSseg ;Відправимо отриманий раніше DS
VMMCall Simulate_Push;у стек клієнта
mov CX,Segment_Callback;Зашлемо отриманий раніше адресу функції isr
mov EDX,Offset_Callback;в CS:IP клієнта, щоб після повернення з VMM
VMMCall Simulate_Far_Call;у віртуальну машину викликалася ця функція
VMMCall Resume_Exec;Повернення з VMM у поточну віртуальну машину
VMMCall End_Nest_Exec;Завершимо вкладений блок виконання
Pop_Client_State ;Звільнимо місце на стеці для регістрів клієнта
clc
ret
EndProc Reflect_Int
Vx_CODE_ENDS
end VMy_Real_Init
У полях даних драйвера зарезервований ряд комірок для тимчасового зберігання отриманих з програми параметрів, а також результату вимірювань. Окремо знаходиться комірка IRQ_Handle, у якій зберігається дескриптор віртуального переривання. Цей дескриптор призначається системою на етапі ініціалізації драйвера й залишається незмінним на увесь час його життя, тобто до перезавантаження комп'ютера.
Макрос VPICD_IRQ_Descriptor дозволяє описати в полях даних структуру з інформацією про віртуалізоване переривання. Обов'язковими елементами цієї структури є номер рівня віртуалізованого переривання й адреса оброблювача апаратного переривання (VMy_Int_13 у нашому випадку), що включається до складу драйвера. Для того щоб макроси віртуального контролера переривань були доступні асемблеру, до програми необхідно підключити (оператором include) файл VPICD.INC.
Віртуалізація переривання здійснюється на етапі ініціалізації драйвера. Дотепер ми в явній формі не використали процедуру VMy_Control, у якій обробляються системні повідомлення Windows. У розглянутому драйвері до складу цієї процедури за допомогою макросу Control_Dispatch включена процедура VMy_Device_Init (ім'я довільно), що буде викликана при одержанні драйвером системного повідомлення Device_Init. Для обробки більшого числа повідомлень Windows у процедуру VMy_Control варто включити по одному макросу Control_Dispatch на кожне оброблюване повідомлення (із вказівкою імен повідомлення й процедури його обробки).
Процедура VMy_Device_Init містить виклик функції віртуального контролера переривань (VPICD) VPICD_Virtualize_IRQ. Ця функція здійснює віртуалізацію зазначеного рівня переривань і повертає дескриптор віртуального переривання, що зберігається нами в комірці IRQ_Handle з метою подальшого використання. Функція VPICD_Virtualize_IRQ фактично встановлює в системі наш оброблювач переривань, ім'я якого включене нами в структуру VPICD_IRQ_Descriptor. Починаючи із цього моменту апаратні переривання IRQ5 будуть викликати за замовчуванням, не оброблювач цього рівня, що перебуває в VPICD, а наш оброблювач. Правда, для цього треба демаскувати рівень 5 у контролері переривань, чого ми ще не зробили.
При виклику драйвера з програми керування передається API-процедурі драйвера API_Handler. У ній насамперед витягають зі структури клієнта передані в драйвер параметри. Оскільки ці параметри (вміст регістрів клієнта) зберігаються в стеці рівня 0, тобто в пам'яті, їх не можна безпосередньо перенести в комірки даних драйвера. Для переносу параметрів у деяких випадках ми використали стек, в інші - регістри загального призначення.
Виконавши команду загального скидання плати, треба демаскувати переривання у фізичному (не віртуальному) контролері переривань. Ця операція здійснюється викликом функції віртуального контролера VPICD_Physically_Unmask із вказівкою їй як параметр у регістрі EAX дескриптора віртуального переривання. Далі виконується вже розглянута в попередній частині статті процедура ініціалізації плати (причому значення констант С0, С1 і С2 витягають зі структури клієнта). Після завершення API-процедури керування повертається у програму до надходження апаратного переривання.
Апаратне переривання віртуалізованого нами рівня через дескриптор таблиці переривань IDT з номером 55h активізує оброблювач переривань, що входить до складу VPICD, що, виконавши деякі підготовчі дії (зокрема, сформувавши на стеці рівня 0 структуру клієнта), передає керування безпосередньо нашому драйверу, а саме процедурі обробки апаратного переривання VMy_Int_13. Системні витрати цього переходу становлять близько 40 команд процесора, тобто час від моменту надходження переривання до виконання першої команди нашого оброблювача складе на комп'ютері середньої швидкодії 10...15 мкс.
У процедурі VMy_Int_13 після виконання змістовної частини (у нашому випадку - читання й запам'ятовування результату вимірів) необхідно послати в контролер переривань команду EOI, як це покладається робити наприкінці будь-якого оброблювача апаратного переривання. Для віртуалізованого переривання ця дія виконується за допомогою функції VPICD_Phys_EOI, єдиним параметром якої є дескриптор переривання, збережений нами в осередку IRQ_Handle. Останньою операцією є виклик функції VPICD_Physically_Mask, за допомогою якої маскується рівень 5 у фізичному контролері переривань.
Слід зазначити, що назви функцій VPICD можуть бути оманними. Функція VPICD_Phys_EOI у дійсності не розблокує контролер переривань, а демаскує наш рівень у регістрі маски фізичного контролера. Що ж стосується команди EOI, те вона була послана в контролер по ходу виконання фрагмента VPICD ще до переходу на наш оброблювач (згадані вище 40 команд). Проте виклик функції VPICD_Phys_EOI наприкінці оброблювача переривань обов'язковий. Якщо нею зневажити, то операційна система буде поводитися точно так само, як якби в контролер не була послана команда EOI: перше переривання обробляється нормально, але всі наступні - блокуються. Так відбувається тому, що при відсутності виклику функції VPICD_Phys_EOI порушується робота функції VPICD_Physically_Unmask, що виконується в нас на етапі ініціалізації. Ця функція, виконавши аналіз системних полів і виявивши, що попереднє переривання не завершилося викликом VPICD_Phys_EOI, обходить ті свої рядки, у яких у порту 21h установлюється 0 біт нашого рівня переривань. У результаті цей рівень залишається замаскованим і переривання не проходять.
Якщо оброблювач переривань, включений у драйвер, виконує тільки обслуговування апаратур, то на цьому його програма може бути завершена. Однак ми хочемо сповістити про переривання додаток, викликавши одну з його функцій. VMM передбачає таку можливість (так зване вкладене виконання VM), але для її реалізації треба насамперед перейти з асинхронного рівня на синхронний.
Проблема полягає в тім, що VMM є нереентерабельною програмою. Якщо перехід у віртуальний драйвер здійснюється синхронним образом, викликом з поточного додатка, те, хоча цей перехід відбувається при участі VMM і, так сказати, через нього, в активізованій процедурі віртуального драйвера допустимо виклик всіх функцій VMM. Якщо ж перехід у драйвер відбувся асинхронно, у результаті апаратного переривання, то стан VMM у цей момент невідомо й у процедурі драйвера допустимо виклик лише невеликого набору функцій, що ставляться до категорії асинхронних. До них, зокрема, ставляться всі функції VPICD, а також ті функції VMM, за допомогою яких програма переводиться на синхронний рівень (його іноді називають рівнем відкладених переривань). У довіднику, що входить до складу DDK, зазначено, які функції є асинхронними, і на цю характеристику функцій варто звертати увагу.
Ідея переходу на рівень відкладених переривань полягає в тім, що в оброблювачі апаратних переривань за допомогою однієї зі спеціально призначених для цього асинхронних функцій VMM установлюється запит на виклик callback-функції (функції зворотного виклику). Ця функція буде викликана засобами VMM у той момент, коли перехід на неї не порушить працездатність VMM. Вся ця процедура зветься <обробка події>.
У поняття <подія> входить не тільки callback-функція, але й набір умов, при яких вона може бути викликана або котрими повинен супроводжуватися її виклик. Так, наприклад, можна вказати, що callback-функцію можна викликати тільки поза критичною секцією або що виклик callback-функції повинен супроводжуватися підвищенням пріоритету її виконання. Крім того, при установці події можна визначити дане (подвійне слово), що буде передано в callback-функцію при її виклику. У складі VMM є цілий ряд функцій установки подій, що розрізняються умовами їхньої обробки, наприклад Call_When_Idle, Call_When_Not_Critical, Call_Restricted_Event, Schedule_Global_Event, Schedule_Thread_Event і ін. Необхідно підкреслити, що момент фактичного виклику callback-функції заздалегідь визначити неможливо. Вона може бути викликана негайно або через деякий час, коли будуть задоволені поставлені умови.
У нашому випадку спеціальні умови відсутні й перехід на синхронний рівень можна виконати за допомогою простої функції Call_VM_Event, як параметр якої вказується 32-бітовий зсув функції зворотного виклику, розташовуваної в тексті віртуального драйвера. У розглянутому прикладі ця функція названа Reflect_Int.
Команда ret, що закінчується оброблювач переривань віртуального драйвера, передає керування VMM, що у зручний для нього момент часу викликає функцію Reflect_Int (реально до виклику може пройти 50...200 мкс). У цій функції викликом Push_Client_State вихідна структура клієнта ще раз зберігається на стеці рівня 0, після чого функцією Begin_Nest_Exec відкривається блок вкладеного виконання. Усередині цього блоку можна, по-перше, організувати перехід на певну функцію додатка, а по-друге, створити умови для передачі їй необхідних параметрів. Передача параметрів здійснюється відповідно до встановленого інтерфейсу використовуваної мови програмування. Оскільки наш додаток написаний мовою Си, для його функцій діють правила цієї мови: параметри передаються функції через стек, причому розташування параметрів у стеці повинне відповідати їхньому перерахуванню в прототипі й заголовку функції, тобто в глибині стека повинен перебувати останній параметр, а на вершині стека - перший (у функціях типу <Паскаль>, зокрема у всіх системних функціях Windows, діє зворотний порядок передачі параметрів).
Приміщення параметрів у стек поточної VM здійснюється функцією VMM Simulate_Push, що може проштовхувати в стек як одинарні, так і подвійні слова. У нашому випадку в стек містяться два слова - результат вимірів і селектор сегмента дані додатки.
Підготовка виклику необхідної функції додатка
Наступна операція - підготовка виклику необхідної функції додатка. Ця операція здійснюється за допомогою функції VMM Simulate_Far_Call, що поміщає передані їй як параметри селектор і зсув необхідної функції додатка в поля структури клієнта Client_CS і Client_IP. У результаті, коли VMM, передаючи керування додатку, зніме зі стека структуру клієнта й виконає перехід по значеннях, що залишилися в стеці, Client_CS і Client_IP, те в регістрах CS:IP виявиться адреса функції, що цікавить нас,, що і почне негайно виконуватися. Для того щоб не втратити те місце в додатку, на якому відбулося його припинення через прихід переривання, що тече вміст полів Client_CS і Client_IP зберігається в створеної перед цим копії структури клієнта.
Нарешті, викликом Resume_Exec керування передається в додаток. Ще раз підкреслимо, що цей виклик функції додатка є вкладеним в VMM і що можливості викликуваної функції досить обмежені. Фактично вона працює в далекій для додатка операційному середовищу. Зокрема, як ми вже відзначали, уміст сегментних регістрів (крім CS) не відповідає сегментам додатка. Для того щоб функція isr() могла звернутися до глобальних змінним додатка (адресуемым через регістр DS), ми передаємо їй селектор сегмента дані додатки.
Повернемося ненадовго до тексту додатка. Функція isr(), що ми викликаємо із драйвера, має такий вигляд: void isr(int segment,int dt){ ... }
Оскільки ми в драйвері проштовхнули в стек спочатку дані Data, а потім селектор DSseg, вони розташувалися в стеці додатка в правильному з погляду цієї функції порядку, тому вона може звертатися до своїм локальним змінних segment і dt, як якби була викликана звичайним образом оператором: isr(DSseg,Data);
Після завершення функції isr() керування вертається в VMM, а з нього в драйвер на команду, що випливає за викликом Resume_Exec. Цей перехід може зажадати пари сотень команд і декількох десятків мікросекунд.
Відкладена процедура драйвера завершується очевидними викликами End_Nest_Exec - закінчення вкладеного блоку виконання й Pop_Client_State - відновлення структури клієнта.
Описана методика організації взаємодії оброблювача апаратних переривань, включеного до складу драйвера, і самого додатка відносно складна й проте не забезпечує необхідні функціональні можливості оброблювачеві переривань додатка, у якому забороняється виклик функцій Windows. Для того щоб по сигналі переривання вивести на екран результати вимірів, нам довелося створювати спеціальний цикл обробки повідомлень із постійним опитуванням прапора request. Установка прапора оброблювачем переривань додатка приводив до виконання функції PostMessage() і посилці в додаток повідомлення WM_USER, у відповідь на яке ми вже могли виконувати будь-які програмні дії без усяких обмежень.
Спрощення програми
Помітного спрощення програми можна домогтися, організувавши посилку в додаток повідомлення WM_USER безпосередньо з оброблювача переривань драйвера, точніше з рівня відкладених переривань. У цьому випадку відпадає необхідність у передачі драйверу яких-небудь даних, крім дескриптора того вікна додатка, у яке посилає повідомлення WM_USER, у цьому випадку - дескриптора головного вікна. Скорочується й текст процедури відкладених переривань Reflect_Int. Додаток Windows також спрощується: відпадає необхідність у поділі функцій обробки переривань між функцією Isr(), що працює, по суті, на рівні відкладених переривань, і функцією OnUser(), виконуваної вже на звичайному рівні завдання. Оскільки результат вимірів легко передати із драйвера в додаток як параметр повідомлення WM_USER, зникає необхідність у пункті <Читання> у меню додатка.
Розглянемо зміни, внесені при використанні такого методу.
Додаток Windows, що обробляє апаратні переривання
//Оператори препроцесора #define і #include
...#define HANDLE_WM_USER(hwnd, wParam, lParam, fn) \//Макрос для обробки
((fn)(hwnd,wParam), 0L) // повідомлення WM_USER
//Прототипи функцій
...void OnUser(HWND,WPARAM); //Сигнатура функції змінилася
//Глобальні змінні
HWND hMainWnd; //Дескриптор головного вікна
...//Головна функція WinMain
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR,int){
...while(GetMessage(&msg,NULL,0,0))//Звичайний цикл
DispatchMessage(&msg);//обробки повідомлень
return 0;
}//Кінець WinMain
//Функція Register реєстрації класу вікна
...//Функція Create створення й показу вікна
void Create(HINSTANCE hInst){
hMainWnd=CreateWindow(szClassName,szTitle,WS_OVERLAPPEDWINDOW,
10,10,200,100,HWND_DESKTOP,NULL,hInst,NULL);
ShowWindow(hMainWnd,SW_SHOWNORMAL);}
//Віконна функція WndProc головного вікна
LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam){
switch(msg){
HANDLE_MSG(hwnd,WM_COMMAND,OnCommand);
HANDLE_MSG(hwnd,WM_DESTROY,OnDestroy);
HANDLE_MSG(hwnd,WM_USER,OnUser);
default:
return(DefWindowProc(hwnd,msg,wParam,lParam));}}
//Функція OnCommand обробки повідомлень WM_COMMAND від команд меню
void OnCommand(HWND hwnd,int id,HWND,UINT){
switch(id){
case MI_START://Ініціалізація плати
InitCard();
break;
case MI_EXIT://Завершити додаток
DestroyWindow(hwnd);}}
//Функція OnUser обробки повідомлення WM_USER
void OnUser(HWND,WPARAM wParam){
char txt [80];
wsprintf(txt,"Виміри кінчені\nНакоплено %d подій",(UINT)wParam);
MessageBox(hMainWnd,txt,"Контроль",MB_ICONINFORMATION);}
//Функція OnDestroy обробки повідомлення WM_DESTROY
...//Функція InitCard ініціалізації (через драйвер) плати
void InitCard (){
GetAPIEntry(); //Одержання адреси крапки входу в драйвер
_BX=55; //Канал 0
_CX=20000; //Канал 1; BX*CX=Тривалість інтервалу
_DX=1000; //Канал 2, внутрішній генератор
_DI=(UINT)hMainWnd; //Дескриптор головного вікна
VxDEntry(); //Виклик драйвера}
//Функція GetAPIEntry одержання адреси крапки входу в драйвер
void GetAPIEntry()...
У наведеному вище тексті файлу .CPP детально показані тільки змінені ділянки програми.
Змінилося визначення макросу HANDLE_WM_USER для обробки повідомлення WM_USER, що ми пошлемо в додаток із драйвера: функція обробки цього повідомлення fn (у додатку вона зветься OnUser()) приймає два параметри - hwnd і wParam. Через параметр повідомлення wParam у додаток буде переданий результат вимірів. При необхідності передавати в додаток більший обсяг даних можна було розширити состав параметрів функції третім параметром lParam.
Цикл обробки повідомлень істотно спростився й прийняв форму, звичайну для простих додатків Windows.
У функції OnCommand() вилучений фрагмент, пов'язаний з пунктом меню <Читання> (ідентифікатор MI_READ), оскільки в цьому пункті вже немає необхідності.
У функції OnUser() параметр wParam приводиться до типу цілого без знака, перетвориться в символьну форму й виводиться на екран у вікно повідомлення з відповідним текстом.
Як буде видно із програми драйвера (і як, втім, повинне бути очевидно для читача), при посилці із драйвера повідомлення WM_USER необхідно вказати системі, якому вікну адресоване це повідомлення. Однак драйверу, природно, нічого не відомо про вікна додатків; дескриптор нашого головного вікна варто передати драйверу на етапі ініціалізації плати. Ця операція виконується у функції InitCard(), де перед викликом драйвера в регістри BX, CX і DX засилаються константи настроювання таймера, а регістр DI є наведеним до цілого типу дескриптором головного вікна hMainWnd.
Зміна програми драйвера
Програма драйвера для обслуговування апаратних переривань
...WM_USER=SPM_UM_AlwaysSchedule+400h;Код повідомлення WM_USER
include shell.inc ;Додатковий включати файл
...;======================
Vx_DATA_SEG
Data dw 0,0 ;32-бітовий осередок з даним для передачі в додаток
hwnd dd 0 ;32-бітовий осередок для одержання дескриптора вікна
IRQ_Handle dd 0 ;Дескриптор віртуального переривання
VMy_Int13_Desc label dword;32-бітова адреса наступної далі структури
VPICD_IRQ_Descriptor <5,,OFFSET32 VMy_Int_13>;Структура з даними про переривання
Vx_DATA_ENDS
;======================
Vx_CODE_SEG
BeginProc VMy_Control
...EndProc VMy_Control
;----------------------
BeginProc VMy_Device_Init
...EndProc VMy_Device_Init
;-------------------------
;API-процедура, викликувана з додатка
;При виклику: BX=C0, CX=C1, DX=C2, DI=дескриптор головного вікна
BeginProc API_Handler
;Одержимо параметри з додатка
movzx EAX,[EBP.Client_DI]
mov hwnd,EAX
;Загальне скидання
...;Размаскируем рівень 5 у фізичному контролері переривань
...;Засилаємо керуючого слова по каналах
...;Засилаємо константи в канали
...;Установимо прапор S2 дозволу рахунку
...ret
EndProc API_Handler
;-------------------------
;Процедура обробки апаратного переривання IRQ5 (вектор 13)
BeginProc VMy_Int_13, High_Freq
;Одержимо результат вимірів з вихідного регістра лічильника
...mov Data,AX ;Результат у молодшій половині Data
;Виконаємо завершальні дії в PIC і викличемо функцію додатка
mov EAX,IRQ_Handle
VxDCall VPICD_Phys_EOI;EOI у фізичний контролер переривань
VxDCall VPICD_Physically_Mask;Маскуємо наш рівень
;Перейдемо на синхронний рівень. Це всі інакше
push 0 ;Таймаут
push CAAFL_RING0 ;Подія кільця 0
push 0 ;Дані для передачі в процедуру відкладених переривань
push OFFSET32 Reflect_Int;Викликувана процедура відкладених переривань
VxDCall _SHELL_CallAtAppyTime;Викликати в "час додатка"
add ESP,4*4 ;Компенсація стека
clc
ret
EndProc VMy_Int_13
;-------------------------
;Процедура рівня відкладених переривань. Це теж інакше
BeginProc Reflect_Int
push 0 ;Дані для функції зворотного виклику
push 0 ;Адреса функції зворотного виклику
push 0 ;lParam
push Data ;wParam
push WM_USER ;Код повідомлення
push hwnd ;Вікно-адресат
VxDCall _SHELL_PostMessage;Поставити повідомлення в чергу
add ESP,4*6 ;Компенсація стека
clc
ret
EndProc Reflect_Int
Vx_CODE_ENDS
end VMy_Real_Init
На початку тексту драйвера необхідно підключити ще один заголовний файл SHELL.INC і визначити значення константи WM_USER. В Windows ця константа має довжину 16 біт і дорівнює 400h, однак функції _SHELL_PostMessage необхідно передати 32-бітове слово, причому сам код повідомлення WM_USER повинен перебувати в молодшій половині цього слова, а в старшу половину варто помістити інформацію про диспетчеризації. У нашому випадку ця інформація виглядає як константа: SPM_UM_AlwaysSchedule.
У сегменті даних вилучені осередки для адреси функції зворотного виклику isr і селектора DS. Осередок для результату вимірів оголошений як два слова, оскільки всі параметри функції Shell_PostMessage мають розмір 32 біт. Додано осередок hwnd для одержання в неї з додатка дескриптора головного вікна. Сам дескриптор має розмір 16 біт, однак передавати його тієї ж функції Shell_PostMessage треба у вигляді довгого слова.
На початку API-процедури зі структури клієнта (конкретно - з регістра DI) витягає дескриптор вікна й після розширення до довгого слова міститься в осередок hwnd.
Інші зміни стосуються лише способу переходу на рівень відкладених переривань і состава процедури ReflectInt, що працює на цьому рівні.
Для переходу на синхронний рівень у цьому випадку використається системний виклик _SHELL_CallAtAppyTime, що здійснює передачу керування зазначеної у виклику процедурі ReflectInt в <час додатка>, тобто коли керування буде повернуте з VMM у додаток. У цій процедурі вже можна буде поставити повідомлення WM_USER у чергу повідомлень головного вікна нашого додатка.
У процедурі рівня відкладених переривань ReflectInt після приміщення в стек необхідних параметрів викликається системна функція _Shell_PostMessage, що і посилає в додаток повідомлення WM_USER. Легко побачити, що програміст повинен у цьому випадку повністю сформувати весь состав структури повідомлення msg - дескриптор вікна-адресата, код повідомлення, а також обидва параметри, що входять в усі повідомлення Windows, - wParam і lParam. Параметром wParam ми в даному прикладі користуємося для передачі в додаток результату виміру з осередку Data. При необхідності можна було використати й lParam.
Функція зворотного виклику, адреса якої можна було вказати в числі параметрів, викликається Windows після успішної постановки в чергу повідомлення, що посилає. Ми цю функцію не використаємо.
Для завдань керування апаратурами, що працює в режимі переривань, важливою характеристикою є час відгуку на переривання, тобто тимчасова затримка від моменту надходження переривання до виконання першої команди оброблювача. Як ми побачили, при використанні віртуального драйвера системні витрати переходу на прикладний оброблювач, включений до складу драйвера, становлять близько 40 команд, на виконання яких на машині середньої продуктивності може знадобитися 10...15 мкс. При використанні системи MS-DOS цих витрат не було б зовсім, тому що в реальному режимі перехід на оброблювач переривань процесор здійснює практично миттєво. Якщо ж реалізувати обробку переривань без допомоги віртуального драйвера, як це було зроблено в попередній частині статті, то перехід на прикладний оброблювач переривань зажадав би 200...300 команд, а час затримки збільшилося б (на такому ж комп'ютері) до 120...180 мкс, тобто більш ніж на порядок.
Подобные документы
Архитектура ввода/вывода Windows NT. Внутренняя организация шины USB. Сущностная характеристика драйверной модели WDM. Точки входа разрабатываемого драйвера, размещение кода в памяти, установка драйвера в системе. Реализация кода драйвера на языке C.
курсовая работа [1,2 M], добавлен 27.09.2014Разработка драйвера под Linux, отслеживающего выделение и освобождение процессами виртуальной памяти и выделение физических страниц при страничных отказах. Компиляция драйвера и работа с ним. Экспериментальная проверка работоспособности драйвера.
курсовая работа [43,5 K], добавлен 18.06.2009Призначення драйверів та порядок роботи з драйверами в MS-DOS. Розробка драйверів консолі. Структура драйвера та призначення компонентів. Розробка структури алгоритму, програми налагодження драйвера. Опис змінних програми та роботи модулів програми.
курсовая работа [1,0 M], добавлен 22.06.2012Использование драйвера режима ядра и управляющего приложения для создания системных потоков. Имитация обработки данных и организация задержек. Разработка драйвера на языке C++. Конфигурация тестового стенда. Точность изменения задержек и работы таймера.
курсовая работа [182,4 K], добавлен 24.06.2009Механизмы взаимодействия драйвера режима ядра и пользовательского приложения: многослойная драйверная архитектура, алгоритм сокрытия данных, взаимодействие драйвера и приложения, пользовательский интерфейс программы фильтрации доступа к файлам.
курсовая работа [1023,3 K], добавлен 23.06.2009Описание принципа работы драйвера. Установка и регистрация драйвера. Назначение и возможности утилиты TestTerminals.exe. Использование редактора форм. Создание форм с помощью редактора задач. Последовательность выполнения операций и обработок данных.
курсовая работа [843,6 K], добавлен 09.11.2011Вибір засобу виконання поставленої задачі. Функції переривання INT 21h MS DOS, що використані при роботі програм. Функції роботи із DTA та інші функції переривання INT 21h. Функція завершення програми. Розробка програми на Pascal. Допоміжні процедури.
дипломная работа [89,0 K], добавлен 20.01.2009Повышение быстродействия операционной системы. Разработка драйверов для средств хранения данных, управление работой устройства командами PnP. Создание, настройка параметров и установка классового драйвера виртуального диска, его структура и свойства.
курсовая работа [163,2 K], добавлен 18.06.2009Инициализация графического драйвера и режима. Функции доступа к видеопамяти. Подключение графической библиотеки. Инициализация графического режима. Включение драйвера, шрифтов в исполняемый файл. Рисование геометрических фигур. Вывод числовой информации.
лабораторная работа [77,2 K], добавлен 06.07.2009Архитектура Windows NT 5. Приоритеты выполнения программного кода. Описание формата MIDI-данных. Установка драйвера в системе. Выбор средств разработки программного обеспечения. Обработка запросов драйверной модели WDM. Использование библиотеки DirectKS.
курсовая работа [498,8 K], добавлен 24.06.2009