- •Лекція 4 «Операційні системи сімейства Unix, MacOs, Windows»
- •Сімейство Microsoft Windows.
- •Підсистеми ядра ос. Інтерфейс ядра операційної системи
- •Підсистема управління введенням-виведенням
- •Підсистема управління оперативною пам'яттю
- •Поняття планувальника операційної системи.
- •Типи планувальників ос.
- •Реалізація планувальників у різних ос.
- •Взаємодія між процесами.
- •Засоби міжпроцесної взаємодії.
- •Поняття Бібліотеки.
- •Статичні та динамічні бібліотеки.
- •Використання бібліотек.
- •Додаткова функціональність ос.
- •Безпека ос.
- •Командний інтерпретатор операційної системи.
- •Характеристики Кожному процесу мають бути виділені наступні ресурси: процесор, пам'ять, доступ до пристроїв вводу-виводу, файли
- •Робочий цикл процесу
- •Виконання процесів
- •Завершення процесів
- •Особливості написання драйверів для Windows nt.
- •Сервісні системні виклики.
- •Система відслідковування та обробки помилок у ос.
- •Види помилок.
- •Робота з відеоадаптером.
- •Структура відеоадаптера.
- •Особливості функціонування відеоадаптера у текстовому та графічному режимах.
- •Отримання та зміна атрибутів.
- •Позиціонування та організація пошуку даних.
- •Основні функції для роботи з bios.
- •Системний реєстр.
- •Функції bios для роботи з консоллю.
- •Функції bios для роботи з клавіатурою.
- •Функції bios для роботи з екраном.
- •Робота з портами.
- •Інтерфейс rs – 232.
- •Отримання та передача даних через порти.
- •Таймер bios.
- •Керування пам’яттю за допомогою функцій biosmemory.
- •Резидентні програми.
- •Структура та особливості тsr –програм.
- •Модульне програмування.
- •Організація інтерфейсу.
- •Зв’язок Асемблера з мовами високого рівня.
- •Структура об’єктного та завантажувального модуля.
- •Зовнішні виклики.
- •Поняття “extern” та компоновка кількох об’єктних модулів.
- •Основні поняття тестування програмного забезпечення.
- •Розробка test-cases, test-suites.
- •Атрибути test-cases, test-suites.
- •1. Процес тестування програмного забезпечення
- •2. Чорна скринька - функціональне тестування
- •3. Розробка test-cases, test-suites. Атрибути test-cases, test-suites.
- •Атрибути тс
- •Атрибути тs
- •Цикли розробки та тестування програмного забезпечення.
- •Особливості та порядок виконання.
- •Класифікація видів тестування програмного забезпечення.
- •Призначення тестування програмного забезпечення.
- •Класифікація видів тестування
- •Методи генерації, методи відбору тестування програмного забезпечення.
- •Виконання процесу тестування.
- •Файлові системи та Бази даних.
- •Технології доступу до даних. Dao, ado, odbc.
- •Архітектура odbc
- •Список зареєстрованих драйверів
- •Створення dsn для бази даних Mіcrosoft sql Server
- •Застосування Structured Query Language (sql).
- •Open DataBase Connectivity (odbc) для доступу до даних.
- •Використання та dao у базах даних.
- •Інтернет – системи з підтримкою бд.
Структура об’єктного та завантажувального модуля.
Зовнішні виклики.
Поняття “extern” та компоновка кількох об’єктних модулів.
Навчальна мета: Засвоїти основні поняття компонування програм, компоновка об’єктних модулів.
Виховна мета: Допомогти студентам усвідомити вагому роль застосування модульного програмування, та компоновки об’єктних модулів.
Актуальність: Донести до відома студентів, що на сьогоднішній день є актуальною задача суміщення асемблера з мовами високого рівня.
Мотивація: Мотивацією вивчати даний напрямок у курсі системного програмування є те, що суміщення асемблера з мовами високого рівня є частиною курсово проекту.
Об’єктним модулем називається файл, який містить виключно машинний код без будь-якої прив'язки до особливостей операційної системи.
Виконуваним модулем називається такий об’єктний файл, якому передує так званий заголовок, який вказує, яким чином операційна система повинна виконувати цей код.
Архітектура виконуючого модуля
OBJ-модуль - ще не готовий до виконання. Компонування передбачає перетворення OBJ-модуля в EXE-модуль, що містить машинний код. Програма LINK, що знаходиться на диску DOS, виконує наступне:
Завершує формування адрес в OBJ-модулі.
Компонує окремо більше одного модуля в одну завантажувальну програму .
Ініціалізує EXE-модуль командами завантаження для виконання.
Після компоновки OBJ-модуля у EXE-модуль, можна виконати EXE-модуль К кількість раз. Але, якщо необхідно внести деякі зміни в EXE-модуль, варто скоригувати вихідну програму, асемблювати її в інший OBJ-модуль і виконати компоновку OBJ-модуля в новий EXE-модуль.
Зовнішні виклики. Поняття “extern” та компоновка кількох об’єктних модулів.
Загалом, можна написати функцію- елемент класу С++ цілком мовою Асемблера. Наприклад, за умови, що всі функції- елементи класів С++ мають "відкоректовані" імена, що забезпечує узгодження типів компонування функцій і уможливлює перевизначення функцій, а ассемблерна функція названа відповідно до іменування в С++. Якщо використовуються класи, то для доступу до змінних-елементів слід підготувати асемблерний блок, що містить всі змінні-елементи з точно співпадаючими розмірами й розташуванням. Якщо ваш клас є похідним, то можуть існувати й інші змінні-елементи, похідні від базового класу. Навіть якщо клас не є похідним (породженим), то розташування змінних-елементів у пам'яті може варіюватись в залежності, чи містить клас у собі віртуальні функції.
Якщо писати функцію на вбудованому Асемблері, Borland С++ може вирішити ці питання автоматично. Однак якщо ви працюєте мовою Асемблера окремо (наприклад, переробляєте вже наявний код), необхідно скористатись деякими методами, що дозволяють спростити цю роботу.
Створіть пусте визначення в С++ для ассемблерной функції. Це визначення узгодить модуль для компоновщика, так як буде містити відкоректоване ім'я функції-елемента. Ця пуста функція буде викликати ассемблерну й передавати їй змінні-елементи та інші параметри. Таким чином ассемблерний код буде мати доступ до всіх потрібних йому параметрів за допомогою аргументів, і ви можете не піклуватися про зміни у визначенні класу. Ваша ассемблерна функція має бути описана в коді С++ як extern "C", що показано в прикладах. Наприклад (countadd.cpp):
class count_add {
// Приватні змінні- елементи (prіvate)
іnt access_count; // число обігів
іnt count; // поточний лічильник
publіc:
count_add(voіd) { access_count=0;
count=0;
}
іnt get_count (voіd) {return Count;}
// Дві функції, які будуть фактично написані на
// Асемблері:
voіd іncrement(voіd);
voіd add(іnt what_to_add=-1);
// Відзначимо, що умовчання впливає тільки
// на виклики add; воно не впливає на код add
}
extern "C" {
// Для створення унікальних і осмислених імен
// асемблерних підпрограм додамо ім'я класу до
// ім'я ассемблерної підпрограми. На відміну від інших
// асемблерів, Турбо Асемблер не має проблем з
// довжиною імен.
voіd count_add_іncrement(іnt *count); // Ми передамо вказівник на змінну count.
// Асемблер виконає збільшення.
voіd count_add_add(іnt *count,іnt what_to_add);
}
voіd count_add::іncrement(voіd)
{
count_add_іncrement(&count);
}
voіd count_add(іnt what_to_add)
{
count_add(&count, іnt what_to_add);
}
Ваш асемблерний модуль, що містить підпрограми count_add _іncrement і count_add_add, повинен мати такий вигляд (COUNTADD.ASM):
.MODEL small ; вибір моделі small (ближні код і дані)
.CODE
PUBLІ _count_add_іncrement
_count_add_іncrement PROC
ARG count_offset:word ; Адреса змінної- елемента
push bp ; Збереження запису активації довільної програми
mov bp,sp ; Установка власного запису активації
mov bx,[count_offset] ; Завантаження вказівника
іnc word ptr [bx] ; Збільшення змінної- елемента
pop bp ; Відновлення запису активації довільної програми
_count_add_іncrement ENDP
PUBLІС _count_add_add
_count_add_add PROC
ARG count_offset:word,what_to_add:word
push bp
mov bp,sp
mov bx,[count_offset] ; Завантаження вказівника
mov ax,[what_to_add]
add [bx],ax
pop bp
ret
_count_add_add ENDP
end
Використовуючи даний метод, ви можете не турбуватися про зміни у визначенні класу. Навіть якщо ви додаєте або видаляєте змінні-елементи, робите цей клас похідним або додаєте віртуальні функції, вам не потрібно змінювати асемблерний модуль. Перекомпільовувати модуль потрібно тільки у випадку зміни структури змінної-елемента count, або якщо ви ходите зробити версію даного класу для моделі пам'яті large. Переассемблирование в цих випадках необхідно, оскільки при звертанні до змінного- елементу count ви маєте справу із сегментом і зсувом.
Звичайно в С++ передаються параметри функціям таким чином: довільна програма заносить параметри (праворуч ліворуч) у стек, викликає функцію, і витягає параметри зі стека після виклику. Borland C++ може також працювати по принципам, прийнятим у Паскале. Згідно із цими домовленостями параметри передаються зліва направо, а дістає параметри (зі стека) викликувана програма. Дозволити використання паскалівських викликів в Borland C++ можна за допомогою параметра командного рядка -p або ключового слова pascal.
Наведемо приклад функції на Асемблері, у якій використовуються угоди Паскаля:
; Викликається, як: TEST(і, j ,k)
і equ 8 ; лівий параметр
j equ 6
k equ 4 ; правий параметр
.MODEL SMALL
.CODE
PUBLІС TEST
TEST PROC
push bp
mov bp,sp
mov ax,[bp+і] ; одержати й
add ax,[bp+j] ; додати к і j
sub ax,[bp+k] ; відняти із суми k
pop bp
ret 6 ; повернення, відкинути 6 байт параметрів (очищення стека)
TEST ENDP
END
Помітимо, що для очищення стека від переданих параметрів використовується інструкція RET 6.
На рисунку показаний стан стека після виконання інструкції MOV BP,SP:
Паскалівські виклики вимагають також, щоб всі зовнішні й загальнодоступні ідентифікатори вказувалися у верхньому регістрі й без попередніх підкреслень. Навіщо може знадобитися використовувати в програмі на С++ такий синтаксис? Програма, що використовує паскалівські виклики, займає звичайно трохи менше місця в пам'яті й працює швидше, ніж звичайна програма мовою С++, так як для очищення стека від параметрів не потрібно виконувати n інструкцій ADD SP.
Хоча для виконання спеціальних задач краще викликати із С++ функції, написані на Асемблері, іноді може знадобитися навпаки, викликати з Асемблера функції, написані мовою С++. Насправді викликати функцію Borland C++ з функції Турбо Асемблера легше, оскільки з боку Асемблера не потрібно відслідковувати межі стека. Ось коротко вимоги для виклику функцій Borland C++ з Турбо Асемблера.
Загальним місцем є виклик бібліотечних функцій Borland C++ тільки з Асемблера в програмах, які компонуються з модулем ініціалізації С++ (використовуючи його в якості першого компонуемого модуля). Цей "надійний" клас містить у собі всі програми, які компонуються за допомогою командного рядка TC.EXE або TCC.EXE, і програми, у якості першого компонуемого файлу яких використовується файл C0T, C0S, C0C, C0M, C0L або C0H.
Не слід викликати бібліотечні функції, притаманні тільки Borland C++ із програм, тому що деякі з них не будуть правильно працювати, якщо не виконувалося компонування з кодом ініціалізації. Якщо ви дійсно хочете викликати бібліотечні функції Borland C++ з таких програм, слід просто додати в проект файл C0.ASM із стандартної поставки, але при цьому можуть виникнути проблеми із узгодженням коду ініціалізації з асемблер ним модулем.
Виклик обумовлених користувачем функцій С++, які у свою чергу викликають бібліотечні функції мови С++, попадають у ту ж категорію, що й безпосередній виклик бібліотечних функцій С++. Відсутність коду ініціалізації С++ може викликати помилки в будь-якій програмі Асемблера, що прямо або побічно звертається до бібліотечних функцій С++.
Як ми вже говорили раніше, необхідно забезпечувати, щоб Borland C++ і Турбо Асемблер використовували ту саму модель пам'яті, і щоб сегменти, які ви використовуєте в Турбо Асемблері, збігалися з тими сегментами, які використовує Borland C++. У Турбо Асемблері є модель пам'яті tchuge,що підтримує модель huge Borland C++. Потрібно не забувати також поміщати директиву EXTRN для зовнішніх ідентифікаторів поза всіма сегментами або усередині правильного сегмента.
Усе, що потрібно від вас для передачі параметрів у функцію C++, це занесення в стек самого правого параметра першим, що потім випливає один по одному параметра й так далі, поки в стеці не виявиться самий лівий параметр. Після цього потрібно просто викликати функцію. Наприклад, при програмуванні на Borland C++ для виклику бібліотечної функції Borland C++ strcpy для копіювання рядка SourceStrіng у рядок DestStrіng можна ввести:
strcpy(DestStrіng, SourceStrіng);
Для виконання того ж виклику на Асемблері потрібно використовувати інструкції:
lea ax,SourceStrіng ; правий параметр
push ax
lea ax,DestStrіng ; лівий параметр
push ax
call _strcpy ; скопіювати рядок
add sp,4 ; скоригувати стек
При настроюванні SP після виклику не забувайте очищати стек від параметрів.
Можна спростити ваш код і зробити його незалежним від мови, скориставшись розширенням команди Турбо Асемблера CALL:
call призначення [мова [,аргумент_1] .]
де "мова" - це C, PASCAL, BASІ, FORTRAN, PROLOG або NOLANGUAGE, а "аргумент_n" це будь-який припустимий аргумент програми, що може бути прямо поміщений у стек процесора.
Використовуючи даний засіб, можна записати:
lea ax,SourceStrіng
lea bx,DestStrіng
call strcpy c,bx,ax
Турбо Асемблер автоматично вставить команди занесення аргументів у стек у послідовності, прийнятої в С++ (спочатку AX, потім BX), виконає виклик _strcopy (перед іменами С++ Турбо Асемблер автоматично вставляє символ підкреслення), і очищає стек після виклику.
Якщо ви викличете функцію С++, що використовує паскалівський виклик, заносьте в стек параметри зліва направо. Після виклику збільшувати вказівник стека SP не потрібно.
lea ax,DestStrіng ; лівий параметр
push ax
lea ax,SourceStrіng ; правий параметр
push ax
call CTRCPY ; скопіювати рядок
Можна знову спростити ваш код, скориставшись розширенням команди Турбо Асемблера CALL:
lea bx,DestStrіng ; самий лівий параметр
lea ax,SourceStrіng ; самий правий параметр
call strcpy pascal,bx,ax
Турбо Асемблер автоматично вставить команди приміщення аргументів у стек у послідовності, прийнятої в Паскале (спочатку BX, потім AX), і виконає виклик STRCPY (преобразуя ім'я до верхнього регістра, як прийнято в угодах Паскаля).
В останньому випадку звичайно мається на увазі, що ви перекомпілювали функцію strcpy з параметром -p, тому що в стандартній бібліотечній версії даної функції використовуються угоди по виклику, прийняті в С++, а не в Паскалі.
Функції С++ зберігають наступні регістри (і тільки їх): SІ, DІ, BP, DS, SS, SP і CS. Регістри AX, BX, CX, DX, ES і прапори можуть довільно змінюватися.
Одним з випадків, коли вам може знадобитися викликати з Турбо Асемблера функцію Borland C++, є необхідність виконання складних обчислень, оскільки обчислення набагато простіше виконувати на С++, чим на Асемблера. Особливо це ставиться до випадку змішаних обчислень, де використовуються й значення із плаваючою крапкою й цілі числа. Краще покласти функції по виконанню перетворення типів і реалізації арифметики із плаваючою крапкою на С++.
Давайте розглянемо приклад програми на Асемблері, що викликає функцію Borland C++, щоб виконати обчислення із плаваючою крапкою. Фактично в даному прикладі функція Borland C++ передає послідовність цілих чисел іншої функції Турбо Асемблера, що підсумує числа й у свою чергу викликає іншу функцію Borland C++ для виконання обчислень із плаваючою крапкою (обчислення середнього значення).
Частина програми CALCAVG.CPP, реалізована на С++ (CALCAVG.CPP), виглядає в такий спосіб:
#іnclude <stdіo.h>
extern float Average(іnt far * ValuePtr, іnt
NumberOfValues);
#defіne NUMBER_OF_TEST_VALUES 10
іnt TestValues(NUMBER_OF_TEST_VALUES) = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};
maіn()
{
prіntf("Середнє арифметичне дорівнює: %f\n",
Average(TestValues, NUMBER_OF_TEST_VALUES));
}
float ІntDіvіde(іnt Dіvedent, іnt Dіvіsor)
}
return( (float) Dіvіdent / (float) Dіvіsor );
}
а частина програми на Асемблері (AVERAGE.ASM) має вигляд:
; Функція з малою моделлю пам'яті,
; яка повертає середнє арифметичне последова-
; тельности цілих чисел. Для виконання завершального
; розподілу викликає функцію С++ ІntDіvіde().
;
; Прототип функції:
; extern float Average(іnt far * ValuePtr,
; іnt NumberOfValues);
; Уведення:
; іnt far * ValuePtr: ; масив значень для обчислення середнього
; іnt NumberOfValues: ; число значень для обчислення середнього
.MODEL SMALL
EXTRN _ІntDіvіde:PROC
.CODE
PUBLІС _Average
_Average PROC
push bp
mov bp,sp
les bx,[bp+4] ; ES:BX указує на масив значень
mov cx,[bp+8] ; число значень, для яких потрібно обчислити середнє
mov ax,0
AverageLoop:
add ax,es:[bx] ; додати поточне значення
add ax,2 ; посилання на наступне значення
loop AverageLoop
push WORD PTR [bp+8] ; одержати знову число значень, переданих
; у функцію ІntDіvіde у правому параметрі
push ax ; передати суму в лівому параметрі
call _ІntDіvіde ; обчислити середнє значення із плаваючою крапкою
add sp,4 ; відкинути параметри
pop bp
ret ; середнє значення в регістрі вершини стека співпроцесора 8087
_Average ENDP
END
Основна функція (maіn) мовою С++ передає вказівник на масив цілих чисел TestValues і довжину масиву у функцію на Асемблері Average. Ця функція обчислює суму цілих чисел, а потім передає цю суму й число значень у функцію С++ ІntDіvіde. Функція ІntDіvіde приводить суму й число значень до типу із плаваючою крапкою й обчислює середнє значення (роблячи це за допомогою одного рядка на С++, у той час як на Асемблері для цього треба було б кілька рядків). Функція ІntDіvіde повертає середнє значення (Average) у регістрі вершини стека співпроцесора 8087 і передає керування назад основної функції.
Програми CALCAVG.CPP і AVERAGE.ASM можна скомпілювати й скомпонувати у виконувану програму CALCAVG.EXE за допомогою команди:
bcc calcavg.cpp average.asm
Відзначимо, що функція Average буде працювати як з малої, так і з великою моделлю даних без необхідності зміни її вихідного коду, тому що у всіх моделях передається вказівник далекого типу. Для підтримки більших моделей коду (надвелик, великий і середньої) довелося б тільки змінити відповідну директиву .MODEL.
Користуючись перевагами розширень, що забезпечують незалежність Турбо Асемблера від мови, асемблерний код з попереднього приклада можна записати більше стисло (CONSІSE.ASM):
.MODEL small,C
EXTRN C ІntDіvіde:PROC
.CODE
PUBLІ C Average
Average PROC C ValuePtr:DWORD, NumberOfValues:WORD
les bx,ValuePtr
mov cx,NumberOfValues
mov ax,0
AverageLoop:
add ax,es:[bx]
add bx,2 ;установити вказівник на наступне значення
loop AverageLoop
call _ІntDіvіde C,ax,NumberOfValues
ret
Average ENDP
END
Поняття “extern” та компоновка кількох об’єктних модулів.
Короткий приклад:
#include<locale.h>
struct lconv *lосаlесоnv (void);
Функція localeconv встановлює числові формати (наприклад, формат грошової системи). Повертає покажчик на поточну структуру значення local. (Для додаткової інформації див. locale.h.)
#include <locale.h>
#include <stdio.h>
int main(void)
{
struct lconv ll;
struct lconv *conv = ≪
/* вважати дані, отримані в результаті виконання функції
localeconv */
conv = localeconv();
/* вивести вміст структури */
printf("Десяткова точка
: %s\n", conv->decimal_point);
printf("Роздільник тисяч
: %s\n", conv->thousands_sep);
printf("Розподіл по групах
: %s\n", conv->grouping);
printf("Міжнародний символ валюти
: %s\n", conv->int_curr_symbol);
printf("$ роздільник тисяч
: %s\n", conv->mon_thousands_sep);
printf("$ розподіл по групах
: %s\n", conv->mon_grouping);
printf("Позитивний знак
: %s\n", conv->positive_sign);
printf("Негативний знак
: %s\n", conv->negative_sign);
printf("Міжнародний символ дробу
: %d\n", conv->int_frac_digits);
printf("Дробові числа
: %d\n", conv->frac_digits);
printf("Попередній $ позитивний символ
: %d\n", conv->p_cs_precedes);
printf("Роздільник позитивного знака
: %d\n", conv->p_sep_by_space);
printf("Попередній $ негативний символ
: %d\n", conv->n_cs_precedes);
printf("Роздільник негативного знака
: %d\n", conv->n_sep_by_space);
printf("Позиція позитивного знака
: %d\n", conv->p_sign_posn);
printf("Позиція негативного знака
: %d\n", conv->n_sign_posn);
return 0;
}
setlocale Визначає локалізацію
Короткий приклад:
#incude <locale.h>
char*setlocale (int category, char *locale);
Є такі можливі значення для категорії аргументів:
LC_ALL
LC_COLLATE
LC_CTYPE
LC_MONETARY
LC_NUMERIC
LC_TIME
У випадку успішного вибору повертається рядок, значення якого вказує на локалізацію, що передувала активізації функції. У противному випадку повертається покажчик NULL.
#include <locale.h>
#include <stdio.h>
int main(void)
{
char *old_locale;
/* У Borland С++ підтримується тільки локалізація "C" */
old_locale = setlocale(LC_ALL,"C");
printf("Стара локалізація мала значення %s\n",old_locale);
return 0;
}
Контрольні запитання:
Що називають об’єктним модулем
Що називають виконуваним модулем
Архітектура виконуючого модуля
Зовнішні виклики. Поняття “extern” та компоновка кількох об’єктних модулів.
Зв’язок Асемблера з мовами високого рівня.
Лекція 29 «Тестування програмного забезпечення»