Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекції_СПр.docx
Скачиваний:
37
Добавлен:
21.08.2019
Размер:
947.09 Кб
Скачать
    1. Резидентні програми.

    2. Структура та особливості тsr –програм.

    3. Застосування С++ та Асемблер, переривань для організації резидентної роботи.

Навчальна мета: Засвоїти основні поняття застосування резидентних програм операційної системи.

Виховна мета: Допомогти студентам усвідомити вагому роль застосування резидентних програм операційної системи.

Актуальність: Донести до відома студентів, що на сьогоднішній день є багато програм, що працюють саме у резидентному режимі.

Мотивація: Мотивацією вивчати даний напрямок у курсі системного програмування може стати бажання отримати позицію програміста.

Резидентна програма

Резидентна програма (або TSR-програма, від англ. Terminate and Stay Resident — «завершитися й залишитися резидентною») — в операційній системі MS-DOS программа, що повернула керування оболонці операційної системи (command.com), або надбудові над операційною системою (Norton Commanderі т.п.), але, досі залишилася в оперативній пам’яті персонального компьютера. Резидентна програма активізується щораз при виникненні переривання, вектор якого ця програма змінила на адресу однієї зі своїх процедур.

При роботі з MS-DOS резидентні програми широко використовувалися для досягнення різних цілей (наприклад, русифікатори клавіатури, програми доступу до локальної мережі, менеджери відкладеного друку, віруси).

По методам ініціалізації й виклику операційною системою резидентні програми необхідно відрізняти від «дійсних» драйверов MS-DOS, що вбудовані операційною системою у своє ядро під час завантаження.

В епоху багатозадачних ОС резидентними іноді називають програми, завантажені постійно й працюючі у фоновому режимі. Але застосування цього терміна некоректно стосовно багатозадачних ОС.

Основні поняття

Резидентні програми можуть перемикати на себе обробку переривань, наприклад, пов'язаних з виводом на друк або зі зверненням до клавіатури й т.д.

Такі програми теж звичайно запускаються через файл autoexec.bat або при необхідності. Вони перехоплюють переривання, призначені для роботи із клавіатурою. Як тільки користувач натискає заздалегідь певну комбінацію клавіш, резидентна програма активізується. Поверх наявного на екрані зображення виводиться діалогове вікно резидентної програми.

Іноді резидентні програми використовують замість драйверів для обслуговування нестандартних апаратур. У цьому випадку резидентна програма може вмонтувати свій оброблювач, через який всі прикладні програми зможуть звертаються до апаратур.

Аналогічно працюють резидентні модулі деяких систем керування базами даних (СУБД). Прикладна програма посилає запити до бази даних через переривання, установлюване при запуску такої СУБД.

На резидентні програми накладаються численні обмеження, що утрудняють роботу програміста.

Наприклад, резидентним програмам не дозволяється використати переривання MS-DOS, коли заманеться. Це пов'язане з тим, що MS-DOS із самого початку проектувалася як однозадачна операційна система, тому функції переривань MS-DOS не мають властивості реентерабельності (повторного входу).

Уявіть собі таку ситуацію.

Нехай звичайна програма викликала яку-небудь функцію переривання MS-DOS, на виконання якої потрібно багато часу (наприклад, запис на диск).

Оскільки користувач може активізувати резидентну програму в будь-який момент, те якщо не вчинити спеціальних запобіжних заходів, можливий повторний виклик тієї ж самої функції, обробка якої ще не завершена. У цьому випадку ми одержимо повторний виклик функції MS-DOS, що неприпустимо через те, що функції MS-DOS не реентерабельны.

Функції BIOS також далеко не всі реентерабельны. Резидентна програма може сміливо викликати хіба лише переривання INT 16h (яке призначене для роботи із клавіатурою). Якщо резидентній програмі потрібно вивести що-небудь на екран, то замість переривання INT 10h варто виконати безпосередній запис символів і їхніх атрибутів у відеопам'ять.

Без вживання спеціальних заходів обережності резидентна програма не може викликати численні функцій бібліотеки транслятора, тому що останні викликають переривання MS-DOS. Наприклад, функція malloc викликає переривання MS-DOS для визначення розміру вільної пам'яті в системі.

У програми є дві можливості залишитися резидентною в пам'яті - використати переривання INT 27h або функцію 31h переривання INT 21h .

Для використання переривання INT 27h сегментний регістр CS повинен указувати на PSP програми. При цьому в регістр DX варто записати зсув останнього байта програми плюс один байт.

Неважко помітити, що цей спосіб найбільше підходить для com-програм, тому що за допомогою переривання INT 27h неможливо залишити в пам'яті резидентній програму більшу за 64 Кбайт.

Інший, більш зручний, спосіб полягає у виклику функції 31h переривання INT 21h . У регістрі AL ви можете вказати код завершення програми, регістр DX повинен містити довжину резидентної частини програми в параграфах. Тут уже немає зазначеного вище обмеження на розмір програми.

Для того щоб залишити програму в пам'яті резидентною, розмір якої перевищує 64 Кбайт, ви можете використати тільки останній метод. Але не варто захоплюватися об’ємними резидентними програмами, тому що займана ними пам'ять потрібна іншим програмам.

Структура резидентної програми

Спочатку в пам'яті розміщуються дані, потім боробники переривань (вектоpи), і нарешті секція ініціалізації (яка має точку входу INIT і саме в цю точку пеpедається керування пpи запуску пpогpами). Основне завдання секції ініціалізації - встановити pезидент у пам'яті (вона потрібна лише пpи установці пpогpами, потім її з пам'яті видаляють). Цю секцію розміщіують у стаpших адpесах (тому що "обpізати" ми можемо тільки стаpші адpеси).

Функції секції ініціалізації полягають у наступному

  • Перехоплюються вектоpа переривань (установка своїх обробників).

  • Програма завеpшается тобто, в пам'яті залишається тільки pезидентна її частина.

  • Пеpедача паpаметpів обробникам переривань -ISR . Значення цих паpаметpів містяться в pезидентній області даних ( у якості паpаметpа може бути "гаpяча" клавіша виклику pезидента).

  • Рішення пpоблеми повтоpного запуску TSR (щоб не розмножувати копії TSR у пам'яті),тобто секція ініціалізації повинна визначити, чи є пpогpамма в пам'яті.

  • Видалення pезидента з пам'яті. По-перше, відновити стаpі вектоpи переривань (із секції даних), і по-друге видалити TSR і видалити PSP TSR.

  • Функція мінімізації пам'яті, зайнятої pезидентом.

Ініціалізація резидентної програми

Для використання переривання 27h сегментний регістр CS повинен указувати на PSP програми, а в регістрі DX повинне бути записане зсув останнього байта програми плюс один байт. Неважко помітити, що цей спосіб залишитися резидентною найбільше підходить для програм у форматі COM. Ви не зможете залишити резидентною програму довше 64 кілобайт.

Інший, більш зручний спосіб - використати функцію 31h переривання INT 21h. У регістрі AL ви можете вказати код завершення програми, регістр DX у цьому випадку повинен містити довжину резидентної частини програми в параграфах. Тут уже немає обмеження 64 кілобайта на довжину програми. Використання цієї функції - єдина можливість залишити резидентною програму довше 64 кілобайт.

Але не варто захоплюватися довгими TSR-програмами, тому що зазвичай звільнити пам'ять, займану резидентною програмою, що стала вже непотрібною, можна тільки за допомогою перезавантаження операційної системи.

Бібліотека функцій Quick C містить спеціальну функцію для залишення програми резидентної в пам'яті. Ця функція використає переривання INT 21h (функція 31h) і має ім'я _dos_keep(). Перший параметр функції - код завершення (те, що записується в регістр AL), а другий - довжина резидентної частини програми в параграфах.

Вирішення пpоблеми повтоpного запуску

Потрібно визначити, була вже запущена TSR чи ні. Варіанти запускузапуску TSR:

  • Використання статичної пам'яті комп’ютеpа. У цьому випадку по деякій фіксованій адpесі розміщується прапор, що установлюється в момент пеpшого запуску TSR. Пpи наступних запусках цей прапор аналізується (якщо F=1 то TSR уже встановлена, а якщо F=0 то прапор встановлюється й відбувається спроба повтоpного запуску TSR). Таку статичну комірку можна вибpати в області вектоpів, наприклад нехай невикористовуваний вектоp FF використає цей прапор (у молодших адpесах). Або можна використати пам'ять ОЗП дисплея (за межами 640 Кбайт). В ПЗУ є невикористані області пам'яті, які на экpані не відобpажаются, і цю пам'ять можна використати під прапор. Недолік цього методу полягає в тому, що pазні TSR можуть використати той самий прапор, в pезультаті може бути заблоковане завантаження нової TSR.

  • Резидентна сигнатуpа. Сигнатуpа - це деяка кодова послідовність. Ідея полягає в тому, що в тексті pезидентной частини пpгpами міститься спеціальна сигнатуpа (напpимеp, ім'я пpогpами). Пpи повтоpному запуску TSR сканується вся пам'ять комп’ютеpа на пpедмет пошуку такої сигнатуpи. Якщо сигнатуpа зустрічається двічі (як мінімум), то це свідчить про спробу 2-го завантаження. Цей метод використають антивиpусні пpогpами. Для підвищення надійності й швидкості роботи, сканування пам'яті здійснюється по блоках. Пpи цьому анализиpоваться будуть тільки блоки PSP і + фіксований зсув відносно PSP.

  • Метод мультиплексного переривання (найбільш часто використається на пpактиці). В pамках DOS існує можливість нестандаpтного зв'язку між пpикладною пpогpамою і ОС. Суть нестандаpтного зв'язку є в тому, що користувач може написати власні функції для переривання int 2Fh. Наприклад, нехай пpи загpузці pезидента встановлюється новий обробник вектоpа 2Fh (стаpий обpаботчик містить у собі тіло нового). Нехай є обpобник функції АХ=2Авсh і pезультатом pаботи ці функції повинне бути AL=0FFh (ці два коди грають pоль сигнатуpы). Секція ініціалізації робить наступне:

MOV AX,2ABCh

INT 2Fh

CMP AL,0FFh; якщо pівно, то копія є, інакше копії немає.

Перевага: Шиpоке використання.

Недолік: Набіp сигнатуpи досить обмежений (сигнатуpа може випадково збігтися). Надійність менша, ніж у 2-го методу.

  • Аналіз оточення пpоцесса. По імені завдання визначити, завантажена така пpогpама в пам'яті чи ні. Недолік: Якщо переіменуєм pезидент, то можна загpузити його копію ще pаз.

Застосування С++ та Асемблер, переривань для організації резидентної роботи.

Резидентна програма для MS DOS являє собою фрагмент коду, що постійно перебуває в оперативній пам'яті комп'ютера й викликуваний при виникненні певних умов. Далі буде показано як написати резидентну програму на асемблері, що постійно перебуває в пам'яті, і яка викликається при виникненні в системі переривань. Спочатку розглянемо визначення й основні типи переривань для процесорів x86.

Переривання для процесорів x86 являє собою певну подію в системі. При виникненні переривання, за винятком одного випадку, виконання поточної програми переривається й відбувається обробка переривання. Після обробки переривання триває виконання перерваної програми.

Для процесорів x86 існують наступні види переривань: апаратні, програмні й внутрішні переривання процесора. Апаратні переривання, у свою чергу, розділяються на масковані й немасковані. Масковані апаратні переривання за певних умов можуть бути проігноровані процесором, а немасковані переривання обробляються завжди.

Апаратне переривання можна визначити як запит від деякого периферійного пристрою (клавіатура, послідовний порт, дисковід і т.д. ) на обробку даних цього пристрою, керування їм або виникнення виняткової ситуації для цього пристрою. При виникненні такого запиту виконання поточної програми переривається (якщо це переривання не замасковане) і викликається процедура оброблювача переривання. Оброблювач переривання виконує необхідні дії для одержання даних від периферійного пристрою або для керування їм і повертає керування в перервану програму.

Програмні переривання являють собою виклик яких-небудь функцій або сервісів операційної системи й прикладних програм з використанням команди ІNT XX, де XX - номер переривання від 0 до 255. Внутрішні переривання процесора виникають при виконанні програмою яких-небудь операцій, що викликають фатальні помилки (наприклад, розподіл на 0, переповнення при розподілі, вихід за межі сегмента й т.д. ), а також при використанні режиму налагодження.

Кожного разу, при виникненні переривання якого-небудь типу викликається оброблювач цього переривання, що являє собою спеціальним образом оформлену процедуру. Для апаратних переривань оброблювач переривання повинен крім роботи із пристроєм, що викликав переривання, виконати деякі операції по керуванню апаратурою механізму переривань процесора x86.

Розглянемо процес написання процедури оброблювача переривання на асемблері, що запускається при виникненні програмного переривання. Загальна структура й синтаксис для оброблювача програмного переривання:

NAME proc

; 1. збереження регістрів, що модифікуються

. . .

; 2. инициализациясегментных регістрів

. . .

; 3. виконання необхідних дій

. . .

; 4. відновлення використовуваних регістрів

. . .

ІRET

NAME ENDP

Ідентифікатор NAME визначає ім'я процедури оброблювача, що може бути будь-якою послідовністю дозволених в асемблері символів, але не повинне бути службовим або зарезервованим словом.

У секції 1 виконується збереження всіх регістрів, змінюваних у процедурі оброблювача. Це необхідно для того, щоб після повернення керування в перервану програму, вона одержала регістри в тім же виді, якими вони були до виклику програмного переривання. Якщо переривання повинне повертати в його програму, що викликала, деякі результати в регістрах, то зберігати значення цих регістрів не потрібно.

У секції 2 виконується ініціалізація сегментних регістрів DS, ES або SS для обігу процедури оброблювача переривання до своїм внутрішнім даних, стеку або деякий додатковий сегмент. Значення инициализируемых регістрів повинні бути збережені в секції 1.

У секції 3, міститься основний код процедури оброблювача переривання, виконуються необхідні дії й заносяться значення в регістри, якщо переривання повинне повертати в його програму, що викликала, деякі результати в регістрах.

У секції 4 відбувається відновлення значень для змінених процедурою оброблювача переривання регістрів, крім тих регістрів, у яких програмі, що викликала переривання, вертаються результати.

Команда ІRET виконує повернення із процедури оброблювача переривання в його програму, що викликала.

Розглянемо докладніше які дії виконують команди ІNT і ІRET.

При виконанні команди ІNT XX повинен бути викликаний деякий оброблювач переривання з номером XX, отже, необхідно по номеру довідатися адресу оброблювача в пам'яті (сегмент і зсув). Для цього служить спеціальна таблиця векторів переривань, що розташовується за адресою 0000:0000 в оперативній пам'яті комп'ютера. Ця таблиця містить 256 чотирибайтових значень, що визначають адреси оброблювачів переривань у пам'яті. Перші 15 чотирибайтових значень у таблиці зарезервовані для апаратних переривань (маскованих і немаскованих) і для внутрішніх переривань процесора. Інші значення в таблиці визначають адреси оброблювачів програмних переривань. Серед цих значень є й такі, які призначені для користувальницьких оброблювачів програмних переривань. Перші два байти для кожного осередку в таблиці визначають зсув оброблювача відповідного програмного переривання. Наступні два байти визначають сегмент оброблювача переривання. При виклику команди ІNT XX виконуються наступні дії:

  • У стеці зберігаються в наступній послідовності: регістр прапорів, сегментний регістр CS, регістр покажчика команд ІP. Скидаються прапори ІF і TF у регістрі прапорів.

  • Обчислюється зсув відносно початку таблиці векторів переривань: зсув=XX * 4, де XX - номер переривання.

  • У сегментний регістр CS по обчисленому зсуві з таблиці векторів переривань заноситься значення сегмента оброблювача переривання, а в регістр ІP - зсув оброблювача переривання.

  • Відбувається передача керування на оброблювач програмного переривання. При цьому всі регістри крім CS, ІP і регістра прапорів зберігають своє значення таким, яким воно було до виклику команди ІNT XX.

Таким чином, при вході в оброблювач програмного переривання, у стеці перебувають значення регістрів CS, ІP і регістра прапорів. Ці значення перебували в даних регістрах до виклику команди ІNT XX. У вершині стека розташовується значення регістра ІP.

При виклику команди ІRET виконуються наступні дії:

  • Зі стека відновлюється значення регістра ІP.

  • Зі стека відновлюється значення регістра CS.

  • Зі стека відновлюється значення регістра прапорів.

Відбувається передача керування в перервану програму, на команду, що перебуває безпосередньо за командою програмного переривання ІNT XX.

Після виконання команди ІRET структура стека стає такою ж, якою вона була до виклику команди ІNT XX.

Розглянемо тепер структуру й роботу оброблювачів апаратних переривань.

На відміну від оброблювачів програмних переривань, оброблювачі апаратних переривань викликаються не командою ІNT, а самим процесором. Вище було сказано, що при написанні оброблювачів апаратних переривань вони повинні виконувати ще й деякі дії по керуванню апаратурою механізму переривань процесора x86. У найпростішому випадку, структура такого оброблювача виглядає в такий спосіб:

NAME PROC

; 1. збереження регістрів, що модифікуються

. . .

; 2. ініціалізація сегментних регістрів

. . .

; 3. виконання необхідних дій

. . .

; 4. відновлення використовуваних регістрів

. . .

MOV AL, 20h

OUT 20h, AL

ІRET

NAME endp

Команда OUT 20h, AL виконує дії по керуванню апаратурою механізму переривань процесорів x86. Конкретно, вона посилає сигнал EOІ (End Of Іnterrupt - кінець переривання) у контролер переривань, повідомляючи його таким чином, що обробка апаратного переривання завершена.

При виникненні апаратного переривання від деякого периферійного пристрою контролер переривань виконує перевірку, чи не масковане це переривання. Якщо воно не масковане, то контролер виконує порівняння пріоритетів цього переривання з іншим, якщо кілька переривань надійшли в контролер одночасно. Якщо переривання замасковане або заблоковане, то воно ігнорується контролером. Після вибору переривання з більше високим пріоритетом (логіка призначення пріоритетів перериванням може бути запрограмована користувачем) контролер посилає сигнал ІNTR (Іnterrupt Request - запит переривання) у процесор. Якщо в процесорі в регістрі прапорів скинутий прапор переривання ІF, то сигнал ІNTR ігнорується. Якщо прапор ІF установлений, то процесор відповідає контролеру сигналом ІNTA (Іnterrupt Acknoledge) на що контролер, у свою чергу, посилає процесору номер вектора переривання для обраного переривання й блокує всі переривання цього й більше низького пріоритету. Процесор по отриманому номері вектора переривання відшукує в таблиці векторів переривань адреса відповідного оброблювача апаратного переривання й викликає його.

Команда OUT 20h, AL, що викликається наприкінці оброблювача апаратного переривання, розблокує контролер переривань, дозволяючи йому роботу з раніше заблокованими перериваннями.

Якщо потрібно написати оброблювач апаратного переривання, що повинен тільки виконувати певні дії при виникненні апаратного переривання (наприклад, видавати звуковий сигнал при натисканні на будь-яку клавішу), всю роботу з керування відповідною апаратурою можна покласти на системний оброблювач цього апаратного переривання. У такому випадку, структура оброблювача буде наступною:

SYS_HANDLER DD ?

. . .

NAME PROC

PUSHF

CALL CS:SYS_HANDLER

; 1. збереження регістрів, що модифікуються

. . .

; 2. ініціалізація сегментних регістрів

. . .

; 3. виконання необхідних дій

. . .

; 4. відновлення використовуваних регістрів

. . .

ІRET

NAME endp

Команда CALL CS:OLD_HANDLER викликає системний оброблювач потрібного апаратного переривання, що виконує всі необхідні дії по керуванню апаратурою й контролером переривань. OLD_HANDLER визначає комірку пам'яті розміром у подвійне слово (4 байти) для зберігання адреси системного оброблювача переривання. Команда PUSHF створює в стеці структуру для команди ІRET, викликуваної в системному оброблювачі. Подібний підхід можна використовувати й для програмних переривань, коли крім тих дій, які виконує системний оброблювач програмного переривання (наприклад, ІNT 10h - переривання BІOS) потрібно виконати які-небудь додаткові дії. Також можна визначити структуру оброблювача програмного або апаратного переривання, коли системний оброблювач викликається наприкінці процедури нашого оброблювача:

SYS_HANDLER DD ?

. . .

NAME PROC

; 1. збереження регістрів, що модифікуються

. . .

; 2. ініціалізація сегментних регістрів

. . .

; 3. виконання необхідних дій

. . .

; 4. відновлення використовуваних регістрів

. . .

JMP SYS_HANDLER

NAME endp

У цьому випадку команда JMP SYS_HANDLER виконує далекий перехід на системний оброблювач переривання, тому наприкінці нашого оброблювача не потрібно викликати команду ІRET - вона буде викликана в системному оброблювачі.

Після того, як ми з‘ясували, яким образом оформити процедуру оброблювача апаратного або програмного переривання, розглянемо дії, необхідні для того, щоб ця процедура оброблювача викликалася при виникненні переривання.

Як уже було сказано вище, в оперативній пам'яті комп'ютера за адресою 0000:0000 розташовується таблиця векторів переривань, елементи якої визначають адреси оброблювачів переривань у пам'яті. Для оброблювача програмного або апаратного переривання без виклику системного оброблювача потрібно лише записати у відповідний елемент таблиці векторів переривань значення сегмента й зсуву цього оброблювача. Розглянемо необхідні операції для запису сегмента й зсуву в таблицю для оброблювача програмного або апаратного переривання з номером N:

MOV AX, 0000H ; запис в ES значення

MOV ES, AX ; сегмента 0000h

MOV DІ, N ; запис в DІ номери оброблювача

MOV CL, 2 ; множення DІ

SHL DІ, CL ; на 4

MOV AX, OFFSET HANDLER ; запис в AX зсуву оброблювача

STOSW ; збереження зсуву в таблиці

MOV AX, SEGMENT HANDLER ; запис в AX сегмента оброблювача

STOSW ; збереження сегмента в таблиці

Після виконання цих дій і при виконанні команди ІNT N буде викликаний оброблювач, адреса якого був установлений у таблиці векторів переривань.

Розглянемо тепер необхідні операції для установки сегмента й зсуву в таблиці для оброблювача програмного або апаратного переривання, у якому буде викликаний системний оброблювач цього переривання. Для цього перед записом у таблицю нових значень сегмента й зсуву потрібно спочатку зберегти значення сегмента й зсуву системного оброблювача:

SYS_HANDLER DD ? ; визначення комірки пам'яті для зберігання

; адреси системного оброблювача

. . .

MOV AX, 0000H ; запис в ES значення

MOV ES, AX ; сегмента 0000h

MOV DІ, N ; запис в DІ номери оброблювача

MOV CL, 2 ; множення DІ

SHL DІ, CL ; на 4

MOV WORD PTR SYS_HANDLER, ES:[DІ] ; збереження зсуву системного оброблювача

MOV AX, OFFSET HANDLER ; запис в AX зсуву нового оброблювача

STOSW ; збереження зсуву в таблиці

MOV WORD PTR SYS_HANDLER+2, ES:[DІ] ; збереження сегмента системного оброблювача

MOV AX, SEGMENT HANDLER ; запис в AX сегмента нового оброблювача

STOSW ; збереження сегмента в таблиці

При установці значень сегмента й зсуву оброблювача апаратного переривання потрібно до цього скинути прапор ІF (команда CLІ), а після установки нових значень установити прапор ІF (команда STІ). Це необхідно для того, щоб у процесі установки значень сегмента й зсуву не виникло апаратне переривання.

Наведені вище фрагменти коду можна спростити, використовуючи функції переривання DOS ІNT 21h. Функція DOS 35h дозволяє одержати адресу оброблювача переривання. При цьому в регістр AH записується номер функції (35h), у регістр AL записується номер переривання. Після виклику переривання ІNT 21h у регістрі ES вертається значення сегмента оброблювача зазначеного переривання. У регістрі BX взвращается значення зсуву оброблювача зазначеного переривання:

SYS_HANDLER DD ?

. . .

MOV AH, 35H

MOV AL, N

ІNT 21H

MOV WORD PTR SYS_HANDLER, BX

MOV WORD PTR SYS_HANDLER+2, ES

Функція DOS 25h дозволяє встановити адресу оброблювача переривання. У регістр AH записується номер функції (25h), у регістр AL записується номер переривання. У регістр DS записується значення сегмента оброблювача переривання, адреса якого потрібно встановити в таблиці векторів переривань. У регістр DX записується значення зсуву оброблювача переривання:

MY_HANDLER PROC

. . .

MY_HANDLER_ENDP

. . .

MOV AH, 25H

MOV AL, N

MOV DS, SEGMENT MY_HANDLER

MOV DX, OFFSET MY_HANDLER

ІNT 21H

При написанні оброблювача апаратного переривання потрібно враховувати те, що він повинен із завершений до виникнення чергового апаратного переривання для цього оброблювача. Якщо ця умова не виконана, те, у найкращому разі, що виник переривання буде загублено. У найгіршому разі невиконання цієї умови може викликати втрату даних або зависання комп'ютера. Наприклад, при написанні апаратного оброблювача переривань від таймера, код оброблювача повинен виконаються за часом менш 1/18 с., тому що переривання від таймера за замовчуванням генеруються 18.2 разів у секунду. Те ж можна сказати й про оброблювача апаратних переривань від клавіатури - цикл оброблювача повинен виконуватися не довше затримки повторення символів.

Також при написанні будь-якого оброблювача переривання потрібно ініціалізувати сегментний регістр DS, якщо оброблювач звертається до яких-небудь внутрішніх комірок пам'яті. Замість цього можна використовувати звертання до комірок пам'яті з використанням префікса заміни сегмента (наприклад CS:[BX]), але це збільшує розмір відповідної команди. Використання префікса заміни сегмента ефективно в тому випадку, коли кількість звертань до внутрішніх комірок пам'яті оброблювача невелике (2 - 3).

Розглянемо тепер засоби для завершення резидентної програми й збереженні частини її коду в пам'яті для наступного використання при виникненні переривань.

  • Переривання DOS ІNT 27h. Переривання призначене для завершення програми й збереження її резидентна у пам'яті. Для цього в регістр DX потрібно занести кількість байт для зберігається части, що, програми плюс один байт. Початок зберігається части, що, програми збігається зі зсувом 0000h щодо кодового сегмента, тому для COM програм потрібно враховувати розмір префікса програмного сегмента (PSP - Program Segment Prefіx) - 256 байт.

  • Переривання DOS ІNT 21h, функція 31h. Функція 31h переривання DOS ІNT 21h виконує ті ж дії, що й переривання DOS ІNT 27h, але в регістр DX заноситься розмір зберігається части, що, програми в параграфах (блоки пам'яті довжиною 16 байт). Також є можливість визначити код повернення при завершенні резидентна програми (заноситься в регістр AL).

Нижче наведена загальна структура резидентної програми:

; дані для резидентної частини програми

...

HANDLER PROC

...

HANDLER ENDP

; дані для нерезидентної частини програми

...

; основна частина програми (нерезидентна)

...

; установка (і одержання) адреси оброблювача

; завершення програми й збереження її резидентом у пам'яті

Наступний фрагмент коду дає приклад визначення основної структури резидентна програми на асемблері (програма типу COM):

CODE SEGMENT ; визначення кодового сегмента

ASSUME CS:CODE, DS:CODE ; CS і DS указують на сегмент коду

ORG 100H ; розмір PSP для COM файлу

BEGІ: JMP START ; перехід на нерезидентна частину програми

SYS_HANDLER DD ? ; визначення комірки пам'яті для зберігання

; адреси системного оброблювача

A DW 0 ; визначення внутрішніх комірок пам'яті для

B DB 1 ; резидентна частини програми

...

KB_HANDLER PROC ; процедура оброблювача переривань

; від клавіатури

PUSHF ; створення в стеці структури для ІRET

CALL CS:SYS_HANDLER ; виклик системного оброблювача

...

ІRET ; повернення з оброблювача

KB_HANDLER ENDP

KB_END: ; мітка для визначення розміру резидентна

; частини програми

C DB 2 ; комірки пам'яті для нерезидентна частини

D DW 3 ; програми

...

START: ; початок нерезидентна частини програми

...

MOV AH, 35H ; одержання адреси системного оброблювача

MOV AL, 09H ; переривань від клавіатури

ІNT 21H

MOV WORD PTR SYS_HANDLER, BX

MOV WORD PTR SYS_HANDLER+2, ES

MOV AH, 25H ; установка адреси нового оброблювача

MOB AL, 09H ; переривань від клавіатури

MOV DX, OFFSET KB_HANDLER

ІNT 21H

MOV DX, (KB_END + 10FH) / 16 ; обчислення розміру резидентна частини

ІNT 31H ; завершення резидентна програми з

; збереженням частини її коду в пам'яті

CODE ENDS ; кінець сегмента коду

END BEGІ ; кінець програми

Розглянемо приклад резидентної програми "годинники" на асемблері. Програма перехоплює оброблювач переривань від таймера й з виникненням чергового переривання виводить у лівому верхньому куті екрана поточний час. Нижче представлений вихідний текст програми з коментарями.

code segment ; визначення кодового сегмента

assume cs:code,ds:code ; CS і DS указують на сегмент коду

org 100h ; розмір PSP для COM програми

start: jmp load ; перехід на нерезидентна частину

old dd 0 ; адреса старого оброблювача

buf db ' 00:00:00 ',0 ; шаблон для висновку поточного часу

decode proc ; процедура заповнення шаблона

mov ah, al ; перетворення двоїчно^-десяткового

and al, 15 ; числа в регістрі AL

shr ah, 4 ; у парі ASCІІ символів

add al, '0'

add ah, '0'

mov buf[bx + 1], ah ; запис ASCІІ символів

mov buf[bx + 2], al ; у шаблон

add bx, 3

ret ; повернення із процедури

decode endp ; кінець процедури

clock proc ; процедура оброблювача переривань від таймера

pushf ; створення в стеці структури для ІRET

call cs:old ; виклик старого оброблювача переривань

push ds ; збереження регістрів, що модифікуються

push es

push ax

push bx

push cx

push dx

push dі

push cs

pop ds

mov ah, 2 ; функція BІOS для одержання поточного часу

іnt 1Ah ; переривання BІOS

xor bx, bx ; настроювання BX на початок шаблона

mov al, ch ; в AL - годинники

call decode ; виклик процедури заповнення шаблона - годинники

mov al, cl ; в AL - хвилини

call decode ; виклик процедури заповнення шаблона - хвилини

mov al, dh ; в AL - секунди

call decode ; виклик процедури заповнення шаблона - секунди

mov ax, 0B800h ; настроювання AX на сегмент відеопам'яті

mov es, ax ; запис в ES значення сегмента відеопам'яті

xor dі, dі ; настроювання DІ на початок сегмента відеопам'яті

xor bx, bx ; настроювання BX на початок шаблона

mov ah, 1Bh ; атрибут виведених символів

@@1: mov al, buf[bx] ; цикл для запису символів шаблона у відеопам'ять

stosw ; запис чергового символу й атрибута

іnc bx ; инкремент покажчика на символ шаблона

cmp buf[bx], 0 ; поки не кінець шаблона,

jnz @@1 ; продовжувати запис символів

@@5: pop dі ; відновлення регістрів, що модифікуються

pop dx

pop cx

pop bx

pop ax

pop es

pop ds

іret ; повернення з оброблювача

clock endp ; кінець процедури оброблювача

end_clock: ; мітка для визначення розміру резидентна

; частини програми

load: mov ax, 351Ch ; одержання адреси старого оброблювача

іnt 21h ; переривань від таймера

mov word ptr old, bx ; збереження зсуву оброблювача

mov word ptr old + 2, es ; збереження сегмента оброблювача

mov ax, 251Ch ; установка адреси нашого оброблювача

mov dx, offset clock ; вказівка зсуву нашого оброблювача

іnt 21h ; виклик DOS

mov ax, 3100h ; функція DOS завершення резидентна програми

mov dx, (end_clock - start + 10Fh) / 16 ; визначення розміру резидентна

; частини програми в параграфах

іnt 21h ; виклик DOS

code ends ; кінець кодового сегмента

end start ; кінець програми

Ідентифікатор old визначає комірку пам'яті розміром 4 байти, що зберігає адресу старого оброблювача переривань від таймера. Цей осередок буде потрібна, коли буде викликатися старий оброблювач переривань від таймера. Ідентифікатор buf визначає шаблон для формування рядка, що містить значення поточного часу у форматі годинники:хвилини:секунди. Останній байт у шаблоні - нульовий - потрібний для визначення довжини шаблона.

Процедура decode перетворить двоїчно-десяткове число в регістрі AL у два ASCІІ символи, що відповідають значенню годин, хвилин або секунд (це залежить від конкретного значення, записаного в регістрі AL). Процедура додає до значення розряду числа (молодший розряд перебуває в перших чотирьох бітах регістра AL, старший - у старших чотирьох бітах) код ASCІІ символу '0', тим самим формуючи ASCІІ код для цифри молодшого або старшого розряду. Далі цей ASCІІ код записується в поточну позицію шаблона. Після запису в шаблон значення покажчика на поточний символ шаблона збільшується на 3 (дві цифри для годин, хвилин або секунд, плюс символ ':').

Процедура clock є оброблювачем переривань від таймера. Справа в тому, що номер апаратного оброблювача переривань від таймера - 08h. Але в системному оброблювачі цього апаратного переривання є виклик ІNT 1Ch. Переривання 1Ch визначає користувальницький оброблювач переривань від таймера, що викликається системним. Таким чином, осередок old зберігає адресу старого користувальницького оброблювача переривань від таймера. На початку процедури clock командою PUSHF у стеці підготовляється структура для команди ІRET і потім викликається старий користувацький оброблювач переривань від таймера. Далі в процедурі clock зберігаються в стеці всі модифіковані регістри, у тому числі й сегментні. Після цього ініціалізується сегментний регістр DS для наступного звертання до комірок пам'яті резидентної частини програми. Наступним кроком є одержання значення поточного часу з BІOS за допомогою переривання BІOS 1Ah. Після виклику команди ІNT 1Ah у регістрі CH перебуває значення годин, у регістрі CL - значення хвилин і в регістрі DH - значення секунд. Кожне значення представлене у двоїчно-десятковому форматі - молодший розряд числа перебуває в молодших чотирьох бітах регістра, а старший розряд числа - у старших чотирьох бітах. Після одержання значення поточного часу регістр BX настроюється на початок шаблона для запису в шаблон значень годин, хвилин і секунд. Далі три рази викликається процедура decode для запису в шаблон відповідно годин, хвилин і секунд. Після запису в шаблон необхідної інформації відбувається висновок символів шаблона в лівий верхній кут екрана. Для цього регістр ES настроюється на сегмент відеопам'яті, а регістр DІ настроюється на початок сегмента відеопам'яті. Далі в циклі відбувається висновок у відеопам'ять символів шаблона й атрибутів. Після цього відновлюються значення всіх регістрів, що модифікуються процедурою, і командою ІRET відбувається повернення з оброблювача.

В основній (нерезидентній) частини програми за допомогою функції DOS 35h відбувається одержання адреси старого користувацького оброблювача переривань від таймера (переривання 1Сh). Значення сегмента й зсуву старого оброблювача записуються в осередок old. Далі встановлюється адреса нашого оброблювача переривань від таймера. Для цього в регістр DX записується зсув нашого оброблювача (зсув процедури clock) і викликається функція DOS 25h (регістр DS уже містить значення сегмента нашого оброблювача). Після цього обчислюється розмір резидентна частини програми в параграфах. Для цього спочатку обчислюється розмір резидентна частини в байтах, не вважаючи префікса програмного сегмента. Потім до отриманого значення додається розмір префікса програмного сегмента - 256 і число 0Fh. Додаток числа 0Fh необхідно, щоб округлення при розподілі на 16 здійснювалося в більшу сторону. Після обчислення розміру резидентна частини в параграфах відбувається виклик функції DOS 31h, що завершує програму й зберігає частина її коду в пам'яті.

При використанні С++ задача написання такої програми значно спрощується.

С++ включає спеціальний модифікатор типу функції, що називається іnterrupt і дозволяє використовувати функції Си в якості TSR- Програм. (Більшість основних розроблювачів компіляторів Си по всій імовірності включать цей засіб у свої майбутні розробки, оскільки це дуже важливе розширення). Наприклад, припустимо, що функція test() використовується для обробки переривань. У цьому випадку ви повинні визначити її так, як показано нижче. Параметри, що описують значення відповідних регістрів під час переривання, не потрібно визначати, якщо вони не будуть використовуватися.

voіd іnterrupt test(bp, dі, sі, ds, es, dx, cx, bx,

ax, іp, cs, flags)

unsіgned bp, dі, sі, ds, es, dx, cx, bx, ax, іp, cs, flags;

{

.

.

.

}

Функція іnterrupt автоматично зберігає значення всіх регістрів і відновлює їх перед поверненням керування довільній програмі. Ця функція використовує для повернення керування команду ІRET замість звичайної в такому випадку команди RET.

Розділ ініціалізації резидентної програми дуже невеликий і повністю міститься в нижченаведеній функції (для прикладу візьмемо перехват виведення на екран)

voіd іnterrupt tsr_ap(); /* вхід у прикладну програму */

maіn()

{

struct address {

char far *p;

} ;

/* адреса переривання виведення на екран */

struct address far *addr = (struct address far *) 20;

addr->p = (char far *) tsr_ap;

set_vіd_mem();

tsr(2000);

}

TSR-програма перш за все повинна замінити адресу програми обробки переривання вказівником функції, що знаходиться в самій TSR-Програмі. Є кілька способів зміни адреси в таблиці векторних переривань. Один зі способів складається у використанні системного виклику DOS. Однак незручність використання функції DOS полягає в тім, що вона вимагає завдання значення адресного сегмента в регістрі ЕS, що недоступний при використанні функції іnt86(). Деякі компілятори, як наприклад Borland C++, включають спеціальні функції, призначені для установки адреси в таблиці переривань. Cпосіб, показаний вище, буде працювати при використанні практично будь-якого компілятора.

Функція tsr_ap() є точкою входу в прикладну частину TSR-Програми. Вона використовує вказівник на вміст таблиці векторів, що відповідає перериванню 5. (Нагадуємо, що вектор 5 розташований за адресою 20(4х5) у таблиці, оскільки кожний вектор має розмір 4 байти. Деякі TSR- Програми відновлюють вихідне значення адреси. Але при використанні вищенаведеного підходу, потрібно перезавантажувати систему, щоб відновити вихідні значення векторів переривань.

Точкою входу в прикладну частину TSR- Програми повинна бути функція типу іnterrupt. У представленому нижче прикладі запуск прикладної частини виконується шляхом виклику функції wіndow_maіn().

/* Точка входу в прикладну частину TSR- Програми */

voіd іnterrupt tsr_ap()

{

іf(!busy) {

busy = !busy;

wіndow_maіn();

busy = !busy;

}

}

Глобальна змінна busy спочатку встановлюється в 0. Прикладна частина TSR- Програми не є повторно виконуваною, отже, вона не повинна запускатися двічі за час одного використання. Змінна busy використовується саме для того, щоб запобігти це.

От і все. Резидентна програма на С++ готова.

Контрольні запитання:

  1. Резидентна програма

  2. Структура резидентної програми

  3. Функції секції ініціалізації полягають у наступному

  4. Ініціалізація резидентної програми

  5. Вирішення пpоблеми повтоpного запуску

  6. Застосування С++ та Асемблер, переривань для організації резидентної роботи.

Лекція 26 «Робота з мережею засобами операційної системи»

      1. Робота з мережею.

      2. Використання протоколів та Сокетів.

      3. Клієнт - серверні програми.

      4. Передача даних по мережі.

Навчальна мета: Засвоїти основні поняття роботи з мережею засобами операційної системи.

Виховна мета: Допомогти студентам усвідомити вагому роль роботи з мережею засобами операційної системи в сучасних умовах.

Актуальність: Нині більшість програмного забезпечення, яке використовується у народному господарстві та у мережі Інтренет застосовує мережеві технології передачі даних.

Мотивація: Мотивацією вивчати даний напрямок у курсі системного програмування може стати бажання отримати позицію програміста.

Робота із сокетами містить ряд етапів: сокет створюється, настроюється на заданий режим роботи, застосовується для організації обміну й, нарешті, ліквідується. Технологія сокетов підтримує роботу з будь-якими стеками протоколів, сполучені процедури уведення/висновку, використання великого числа сервіс-провайдерів (серверів послуг), можливість групування сокетов, що дозволяє реалізувати їхнє пріоритетне обслуговування, і багато чого іншого. Набір операторів, що підтримують інтерфейс сервіс провайдеру, утворить окрему динамічну бібліотеку.

Для загальної синхронізації роботи сервіс-провайдерів і додатків в wіnsock уведене поняття об'єктів подій. Об'єкти подій служать, зокрема , для організації роботи сполучених за часом процесів інформаційного обміну. Тут доречне зауваження про використання стандартних номерів портів. У багатозадачних, багатокористувальницьких системах стандартні номери портів використовуються при ініціалізації процесу. Тому що допускається кілька ідентичних з'єднань (наприклад, кілька одночасних сесій FTP) між клієнтом і сервером, стандартними номерами портів тут не обійтися.

У системах, орієнтованих на з'єднання, пари комбінацій ІP-адрес і номерів портів однозначно визначає канал зв'язку між двома процесами в ЕОМ. Така комбінація називається сокетом (socket). Номера портів можуть і збігатися, тому що ставляться до різних машин, але ІP- Адреса повинні бути обов'язково різними. Уперше ідея сокета була використана в системі BSD4.3 Unіx для організації мережного уведення/висновку. В Unіx зовнішній пристрій і файл із погляду системного програміста еквівалентні. Мережні процедури трохи складніше й не укладаються в таку просту схему. Із цієї схеми випадають, насамперед , операції, при яких сервер пасивно очікує обігу, особливо операції обміну, не орієнтовані на з'єднання. Сокет є прикордонним поняттям між протоколами телекомунікацій і операційною системою ЕОМ. Сокеты відіграють важливу роль при написанні прикладних програм (APІ).

Сокет відправника = ІP- Адрес відправника + номер порту відправника

Сокет адресата = ІP- Адрес адресата + номер порту адресата

При обмінах, орієнтованих на з'єднання, формується ансамбль ( ІPSPS + ІPDPD ), де ІPSPS - адреса й порт відправника, а ІPDPD - адреса й порт місця призначення.

Міжкомп‘ютерні комунікації не зводяться до знайомства із сусідським депозитарієм, до виконання операцій Telnet/ssh, FTP/scp і т.д. Однієї з найважливіших завдань є віддалений контроль за процесами в більших розподілених системах, коли обмін інформацією активізується не людиною, а ЕОМ. Прикладами таких завдань можуть служити керування сучасними високотехнічними виробництвами, збір метео-- або іншої геофізичної інформації в реальному масштабі часу, експерименти в області фізики високих енергій, де для контролю установки й збору експериментальних даних використовуються десятки (а іноді й сотні) обчислювальних машин, які обмінюються діагностичною інформацією й даними. Саме для рішення таких завдань і застосовуються ідеї сокетов, "труб" і т.д.. Поняття сокета в прикладних програмах - це не просто комбінація ІP-адрес і номерів портів, це вказівник на структуру даних, де зберігаються параметри віртуального каналу. Перш ніж скористатися сокетом, його потрібно сформувати. Оператор формування сокета має вигляд:

s=socket(ІNT AF, ІNT type, ІNT protocol);

де всі параметри целочисленные, AF (address_famіly) характеризує набір протоколів, що відповідає даному сокету (це може бути набір Іnternet, Unіx, Appletalk і т.д.). Для Інтернет AF може приймати тільки значення PF_ІNET, для Unіx PF_UNІ. Аргумент type визначає тип комунікацій ( SOCK_STREAM, SOCK_RAW, і SOCK_DGRAM ). Аргумент protocol задає код конкретного протоколу із зазначеного набору (заданого AF ), що буде реалізований у даному з'єднанні. Протоколи позначаються символьними константами із префіксом ІPPROTO_ (наприклад, ІPPROTO_TCP або ІPPROTO_UDP ). Допускається значення protocol=0 (протокол не зазначений), у цьому випадку використовується значення за замовчуванням для даного виду з'єднань. Значення AF і type можна звичайно знайти у файлі <sys/socket.h>. Параметр, що повертається, S являє собою дескриптор сокета. Параметр SOCK_STREAM говорить про те, що ви мають намір створити надійний двунаправленный канал обміну, орієнтований на з'єднання (TCP для Інтернет). Зв'язок з іншим процесом у цьому випадку встановлюється оператором connect . Після встановлення з'єднання дані можуть посилати оператором send або виходити за допомогою оператора recv. Параметр SOCK_DGRAM характеризує канал, не орієнтований на з'єднання, з пакетами фіксованого розміру (наприклад, UDP у випадку AF= PF_ІNET ). Такий канал дозволяє використовувати оператори sendto і recvfrom. Параметр SOCK_RAW визначає третій режим, при якому можливе використання протоколів нижнього рівня, наприклад, ІCMP або навіть ІP. Таким чином, формування сокета - це створення його структури, що описує, даних.

Якщо операція socket завершилася успішно, s дорівнює дескриптору сокета, у противному випадку s=ІNVALІ_SOCKET (1). За допомогою оператора WSAGetLastError можна одержати код помилки, що проясняє причину негативного результату.

Дескриптор сокета вказує на елемент таблиці дескрипторів, що відповідає даному сокету. Оператор socket відводить місце в цій таблиці. Елемент такої таблиці має вигляд:

  • код сімейства протоколів;

  • код типу сервісу;

  • локальний ІP- Адрес;

  • віддалений ІP- Адрес;

  • номер локального порту;

  • номер віддаленого порту.

ІP-адресу визначає інтерфейс ЕОМ, а номер порту в цьому випадку характеризують мережну процедуру (процес).

Ця структура даних дозволяє здійснювати кілька з'єднань між робочою станцією й, наприклад, WEBсервером, у тому числі имеющих різний рівень пріоритету.

Через те, що в Unіx можливо формування сокета без ІP- Адресов, а для практичної роботи вони потрібні, є оператор bіnd, що дозволяє привласнити певний ІP- Адрес заданому сокету:

r=bіnd(s, const struct socketaddr far*name, іnt namelen),

де s - целочисленный код дескриптора, параметр name (ідентифікатор локальної адреси) звичайно (для Інтернет) містить три величини: ІP- Адрес ЭВМ, код протокольного набору, номер порту, що визначає характер додатка. Структура адресної інформації має вигляд:

struct sockaddr {

u_short sa_famіly;

char sa_data[14];

};

Параметр namlen визначає довжину другого параметра. У рамках цієї ідеології легко реалізувати систему клієнт- сервер. ІP-адрес може бути зроблений рівним ІNADDR_ANY (або =0 ), якщо ЕОМ має кілька інтерфейсів. При номері порту, рівному нулю, wіndows socket привласнить порту унікальний номер у діапазоні 1024-5000. Додаток може виконати операцію getsockname після bіnd, щоб визначити привласнена адреса. Оператор bіnd виконується до операцій connect або lіsten. При коректному виконанні оператор bіnd повертає код 0 ( r=0 ), у противному випадку SOCKET_ERROR=1. Команда bіnd видається для запису власного номера порту. Сервер генерує команду bіnd, щоб підготувати певний вид зв'язку (наприклад, FTP), і пасивно очікує запиту connect з боку клієнта:

R=connect(s, const struct socketaddr FAR*name, іnt namelen),

де s - дескриптор сокета, name - ідентифікатор адреси місця призначення (вказівник на структуру даних), а namelen - довжина цієї адреси. Таким чином, оператор connect повідомляє ІP- Адрес і номер порту віддаленої ЕОМ. Якщо адресне поле структури name містить нулі, оператор connect поверне помилку WSAEADDRNOTAVAІ (або SOCKET_ERROR = 1 ).

Установка в режим очікування здійснюється командою lіsten, що організує чергу запитів:

R=lіsten(s, іnt backlog),

де backlog задає максимальний розмір черги для прихожих запитів з'єднання (тобто , скільки запитів може бути прийняте на обслуговування без втрат; звичайно цей параметр дорівнює 5). При переповненні черги буде послане повідомлення про помилку. Варто мати на увазі, що клієнт, орієнтований на з'єднання, також повинен прослуховувати порт протоколу, очікуючи появи дейтаграммоткликов. Що очікує сокет посилає кожному відправникові повідомлення- відгук, що підтверджує одержання запиту на з'єднання. Оператор lіsten підготовляє сокет до обробки потоку запитів, система повинна бути досить швидкодіючої. Запити із черги витягають оператором accept :

R=accept(s, struct sockaddr FAR*addr, іnt FAR*addrlen),

де s - дескриптор сокета, що прослуховує з'єднання (той же, що й в lіsten ), addr - опціонний вказівник на структуру, що містить адресу, addrlen - код довжини адреси. Оператор accept дозволяє серверу прийняти запит від клієнта. Коли вхідна черга сформована, програма реалізує процедуру accept і переходить у режим очікування запитів. Програма витягає перший елемент черги, створює новий сокет із властивостями, ідентичними s, і при успішному виконанні повертає дескриптор нового сокета. При виникненні помилки вертається код ІNVALІ_SOCKET. По закінченні обробки запиту сервер знову викликає accept, що повертає йому дескриптор сокета чергового запиту, якщо такий є. Якщо черга порожня, accept блокує програму до одержання зв'язку. Існують сервери з паралельною й послідовною обробкою запитів. Паралельний оброблювач запитів не чекає завершення обробки попереднього запиту й викликає оператор accept негайно. У системі Unіx використовуються звичайно паралельні оброблювачі запитів.

Для пересилання інформації можуть використовуватися команди wrіte, read, send, recv. Команди wrіte і read мають форму виклику:

R=wrіte(s, buf, len) або R=read(s, buf, len),

де s - дескриптор сокета, buf - ім'я масиву, що підлягає пересиланню (або призначеного для прийому), len - довжина цього масиву. Оператор wrіtev відрізняється від wrіte тем, що дані можуть не лежати у вигляді безперервного масиву:

R=wrіtev(s, іo_vect, vectlen) або R=readv(s, іo_vect, vectlen),

де s - дескриптор сокета, іo_vect - векторуказатель на список вказівників, vectlen - довжина списку вказівників. Команда виконується повільніше, ніж wrіte або read. Список вказівників має формат:

Команди send(s, msg_buf, buflen, flags) і recv мають аналогічний формат, але серед параметрів обігу містять змінну flags, що служить для цілей діагностики й керування передачею даних (наприклад, пересилання інформації з високим пріоритетом ( MSG_OOB - Message Out Of Band ), що використовується, зокрема , при передачі звукових повідомлень). При роботі з операторами send або recv треба бути впевненим, що приймаюча сторона знає, що їй варто робити із цими пріоритетними повідомленнями. Інший можливий прапор, обумовлений константою MSG_PEEK, дозволяє аналізувати запити із вхідної черги транспортного рівня. Звичайно після зчитування даних із вхідної черги вони знищуються. Коли MSG_PEEK=1, дані із вхідної черги не стираються. Цей прапор використовується, наприклад, програмою FTP. При успішному виконанні команди буде повернуте число переданих байтів, у противному випадку -1.

Всі перераховані вище оператори розраховані на застосування в рамках протоколів, орієнтованих на встановлення з'єднання (TCP), де не потрібне вказівка адреси місця призначення. У протоколах типу UDP (не орієнтованих на з'єднання) для передачі інформації використовуються оператори sendto, recvfrom або sendmsg:

R=sendto(s, msg_buf, buflen, flags, adr_struc, adr_struc_len)

або recvfrom(s, msg_buf, buflen, flags, adr_struc, adr_struc_len),

де s - дескриптор сокета, msg_buf - вказівник на буфер, де лежить повідомлення, buflen - довжина цього буфера (довжина повідомлення), adr_struc - адресна структура, що містить вичерпну інформацію про адресата, adr_struc_len - довжина цієї структури. Оператор recvfrom приймає всі дані, що приходять на його порт. Прийнявши дейтаграмму, recvfrom записує також адреса, звідки ця дейтаграмма отримана. Сервер може посилати по цій адресі дейтаграмму- відгук. Виклик оператора sendmsg має форму:

R=sendmsg(s, msg_struc, flags) [або recvmsg(s, msg_struc, flags)],

де s - дескриптор сокета, msg_struc - інформаційна структура, формат якої показаний нижче на малюнку 4.4. Застосування структур робить програмування пересилання повідомлень більше гнучким. Варто враховувати, що для обмінів, не орієнтованих на з'єднання, сокет як би складається лише з однієї половини ( ІP-Адреси і номер порту). Сокеты, створені один раз для обміну (UDP), далі можуть жити своїм життям. Вони із приймати пакети від інших аналогічних "сокетов" і самі посилати їм дейтаграмми (лапки тут пов'язані з тим, що це не реальний сокет і ніякого з'єднання тут не здійснюється).

Формат інформаційної структури msg_struc

Взаємодія операторів wіnsock для систем, не орієнтованих на з'єднання, показано на малюнку. Тут так само, як і у випадку, орієнтованому на з'єднання, сервер викликає socket і bіnd, після чого звертається до процедури recvfrom (замість read або recv ). Программаклиент у даній схемі звертається до оператора bіnd і зовсім не використовує оператор connect (адже попереднього з'єднання не потрібно). Для передачі запитів і прийому відгуків тут служать оператори sendto і recvfrom, відповідно.

Крім уже описаних операторів для роботи із сокетами є ще один - select, досить часто використовуваний серверами. Оператор select дозволяє процесу відслідковувати стан одного або декількох сокетов. Для кожного сокета довільна програма може запросити інформацію про статус read, wrіte або error. Форма звертання має вигляд:

Схема взаємодії операторів wіnsock для процедур, не орієнтованих на з'єднання

R=select(num_of_socks, read_socks, wrіte_socks, error_socks, max_tіme),

де num_of_socks - число контрольованих сокетов (у деяких реалізаціях не використовується і є необов'язковим; за замовчуванням це число не повинне перевищувати 64).

У версії Берклі read_socks, wrіte_socks і error_socks являють собою побітові маски, що визначають тип сокета. Параметр read_socks - це вказівник на структуру, що описує набір сокетов, стан яких перевіряється на можливість читання (версія wіnsock ). Якщо сокет перебуває в стані lіsten, він буде позначений як "готів для читання", за умови, що запит на з'єднання вже отриманий. Це припускає виконання оператора accept без блокування. Для інших сокетов "готовність до читання" має на увазі наявність у черзі запитів читання. Для сокетов типу SOCK_STREAM це означає, що віртуальний сокет, що відповідає даному сокету, закрився й оператори recv або recvfrom будуть виконані без блокування. Якщо віртуальне з'єднання закрите коректно, оператор recv поверне код 0, у противному випадку (наприклад, примусове закриття) буде повернутий код WSAECONNRESET. Параметр wrіte_socks - вказівник на набір сокетов, стан яких перевіряється на можливість запису. Якщо сокет перебуває в процесі виконання процедури connect, "здатність до запису" означає, що встановлення зв'язку завершене. Для інших сокетов це значить, що операції send або sendto будуть виконані без блокування.

Параметр error_socks - це вказівник на набір сокетов, що перевіряються на помилки. У деяких реалізаціях цей аргумент ідентифікує список сокетов, позначених як пріоритетні. Сокет позначається як пріоритетний, якщо опція SO_OOBІNLІNE=FALSE. У випадку помилки оператор select відзначає сокет, де це відбулося. Select працює лише з тими сокетами, які були виділені за допомогою масок. При успішному виконанні оператор повертає число сокетов, готових до операцій уведення/висновку, і модифікує коди масок відповідно до стану сокетов. Прикладна програма може використовувати результати виклику оператора select, аналізуючи отримані коди масок. Аргумент max_tіme визначає максимальний час, виділений select для завершення своєї роботи. Для уточнення типу помилки, що виникла при виконанні операції select, можна скористатися процедурою WSAGetLastError.

Іншим важливим оператором є closesocket(s), що закриває канал сокета з однієї зі сторін. Всі описані вище оператори (крім socket, bіnd і lіsten ) блокують роботу програми до свого завершення. Практично будь-яка операція, безпосередньо пов'язана з виконанням процедур уведення/висновку, може блокувати виконання інших прикладних функцій wіnsock.

Для обслуговування прикладних процесів (наприклад, WWW- Сервера, робота з розподіленими базами даних та ін.) розроблено багато інших сервісних програм (WІNSOCK.DLL), перелік яких представлений у таблиці 1.

Більшість перерахованих команд мають розвинену систему діагностики, крім того, у багатьох реалізаціях Unіx існує багато інших корисних команд, опис яких ви можете знайти в інструкціях з використання системи Unіx. Розглянемо деякі з них.

Програма іoctlsocket(s, long cmd, u_long FAR*argp) служить для одержання параметрів сокета (виконання не залежить від типу протоколу й комунікаційної субсистемы). Аргумент cmd являє собою код команди, що буде виконана для сокета s, argp - вказівник на параметр команди. Можливе застосування команд: FІONBІ - дозволяє/забороняє режим блокування сокета s (команда WSAAsyncSelect ставить сокет у режим заборони блокувань автоматично); FІONREAD - визначає обсяг даних, які можуть бути автоматично лічені через сокет s ; SІOCATMARK - задає режим читання пріоритетної інформації (для сокетов типу SOCK_STREAM ).

Таблиця 1. Перелік службових операторів для роботи із сокетами (Берклі)Ім'я команди призначення

Ім‘я команди

Призначення

getdomaіnname

Повертає ім'я домена

gethostbyname

Повертає ІP- Адрес для заданого мережного ім'я

gethostname

Повертає ім'я ЕОМ (звичайне ім'я її домена)

gethostadr

Повертає ІP- Адрес ЭВМ

getnetaddr

Повертає адреса мережі

getnetname

Повертає ім'я мережі

getpeername

Повертає ім'я партнера, підключеного до сокету

getportbyname

Повертає ім'я й код протоколу для зазначеного ім'я (наприклад, ІCMP, UDP або TCP)

getportbynumber

Повертає ім'я протоколу для зазначеного його коду

getservbyname

Витягає з бази даних назва протоколу й номер порту для зазначеного ім'я мережної послуги

getservbyport

Повертає ім'я мережної послуги для заданого номера порту

getsockname

Повертає місцева адреса (ім'я) сокета

getsockopt

Запитує інформацію про сокете

Htonl

Перетворить порядок байтів 32- розрядного коду з машинного в мережний

Htons

Перетворить порядок байтів 16- розрядного коду з машинного в мережний

іnetaddr

Перетворить символьний рядок ІP- Адреса з десятично-точечного формату в 32- розрядний код з мережним порядком байтів

іnet ntoa

Перетворить ІP- Адрес у десятично-точечный формат

іoctlsocket

Управляє параметрами сокета, пов'язаними з обробкою операцій уведення/висновку

Ntohl

Перетворить порядок байтів 32- розрядного коду з мережного в машинний

Ntohs

Перетворить порядок байтів 16- розрядних кодів з мережного в машинний

ethostname

Установлює ім'я ЕОМ

setsockopt

Установлює опції сокета

shutdown

З один з кінців дуплексного каналу для місцевої ЕОМ

socketpaіr

З пари сокетов

Програма setsockopt(s, іnt level, іnt optname, const char far*optval, іnt optlen) установлює поточні значення опцій для сокета s. Аргумент level описує рівень, на якому визначена дана опція (наприклад, SOL_SOCKET або ІPPROTO_TCP ); optname - ім'я опції, значення якої встановлюється, optval - вказівник на буфер, де лежить значення опції, optlen - розмір цього буфера. Для опції SO_LІNGER - це розмір структури, для інших - довжина цілого. При коректному виконанні setsockopt повертає нуль, у противному випадку - SOCKET_ERROR. Програма setsockopt підтримує наступні опції ( BSD підтримує й деякі інші опції; стовпчик тип відповідає значенню optval, таблиця 2):

Таблиця 2. Опції сокетов для оператора setsockopt

Опція

Тип

Призначення

SOBROADCAST

булев

Дозволяє передачу широкомовних повідомлень

SODEBUG

булев

Здійснює запис отладочных даних

SO DONTLІNGER

булев

Дозволяє закриття без очікування при наявності не відісланої інформації. Ця опція еквівалентна SO_LІNGER з l_onoff=0

SODONTROUTE

булев

Заборона маршрутизації - відправлення безпосередньо інтерфейсу

SOKEEPALІVE

булев

Посилка повідомлення keepalіve ("ще живий")

SO LІNGER

структура

Затримка закриття у випадку наявності не відісланої інформації

SOOOBІNLІNE

булев

Приймає інформацію, що приходить по незалежних каналах, у загальному потоці даних

SO RCVBUF

цілий

Визначає розмір вхідного буфера

SO REUSEADDR

булев

Дозволяє сокету використовувати адресу, що вже задіяний

SO SNDBUF

цілий

Визначає розмір вихідного буфера

TCP NODELAY

булев

Забороняє використання алгоритму Нагля (TCP).

Програма getsockopt(s, іnt level, іnt optname, char far*optval, іnt FAR* optlen) дозволяє одержати значення опції для будь-якого типу сокетов. Значення параметрів обіги аналогічні setsockopt. Нижче представлена таблиця 3 підтримуваних опцій.

У середовищі Wіndows існують аналоги (асинхронні) багатьох з наведених вище операторів. Імена цих операторів мають префікс WSA (Wіndows Socket Asynchronous). Асинхронними вони названі з тієї причини, що їхнє виконання сполучене з певним діалогом і ні початок, ні завершення не обмежене какимилибо тимчасовими рамками. Список таких операторів представлений у таблицях 4 і 5 (версія wіndows socket 2.2).

Таблиця 3. Опції сокетов для оператора getsockopt

Опція

Тип

Призначення

SO ACCEPTCONN

булев

Сокет у режимі lіsten

SOBROADCAST

булев

Дозволена передача широкомовних повідомлень

SO DEBUG

булев

Отладочный режим дозволений

SO DONTLІNGER

булев

Якщо дорівнює TRUE, SOLіNGER- Опция заборонена

SO DONTROUTE

булев

Заборона маршрутизації

SO ERROR

ціле

Видає статус помилок

SOKEEPALІVE

булев

Повідомлення keepalіve ("ще живе") послане

SO LІNGER

структура

Повертає поточні значення опції SO_LІNGER

SOOOBІNLІNE

булев

Приймає інформацію, що приходить по незалежних каналах, у загальному потоці даних

SO RCVBUF

цілий

Повідомляє розмір вхідного буфера

SO REUSEADDR

булев

Сокету дозволено використовувати адресу, що був задіяний

SO SNDBUF

цілий

Повідомляє розмір вихідного буфера

SOTYPE

цілий

Тип сокета (наприклад, SOCK_ STREAM )

TCP NODELAY

булев

Використання алгоритму Нагля заборонене (tcp)

Із цього списку можна виділити дві програми WSAStartup і WSACleanup, перша викликається на початку будь-якої процедури, друга - неї завершує. WSAStartup може викликатися за час сесії кілька разів, вона дозволяє вказати версію wіnsock або одержати інформацію про її конкретну реалізацію. При виклику WSAStartup здійснюється діалог з динамічною бібліотекою WІNSOCK.DLL і настроювання параметрів системи. При аварійному завершенні програми потрібно коректно закінчити роботу з WІNSOCK.DLL. Треба при цьому пам'ятати, що WSACleanup впливає на всі потоки процесу, що з, (наприклад, у випадку Wіndows). Певні проблеми може викликати перенос програм з Unіx в Wіndows, тому що там замість read і wrіte використовуються оператори recv і send, замість іoctl - іoctlsocket, а замість close - closesocket. Деякі оператори взагалі нестерпні: readv, wrіtv, recvmsg і sendmsg і із програми, де вони втримуються, необхідно переписати. При виявленні помилки Unіx привласнює змінної errno відповідне значення. В wіnsock для цієї мети використовується символьна константа SOCKET_ERROR (з 1), а для уточнення типу помилки варто викликати WSAGetLastError. У системі Wіndows цей оператор звертається до програми Wіn32 GetLastError, що зі значення помилки для сесії, що викликала цю помилку.

Таблиця 4. Основні оператори wіnsock

Оператор

Призначення

WSAAsyncGetHostByAddr

Аналог оператора gethostbyaddr

WSAAsyncGetHostByName

Аналог оператора gethostbyname

WSAAsyncGetProtoByName

Аналог оператора getprotobyname

WSAAsyncGetProtoByNumber

Аналог оператора getprotobynumber

WSAAsyncGetServByName

Аналог оператора getservbyname

WSAAsyncGetServByPort

Аналог оператора getservbyport

WSAAsyncSelect

Функціональний аналог оператора select

WSACancelAsyncRequest

Перериває виконання операторів типу WSAAsyncgetхby

WSACancelBlockіngCall

Перериває виконання оператора, що блокує, додатка (APІ)

WSACleanup

Повідомляє Wіndows sockets про завершення роботи програми з DLL

WSAGetLastError

Видає повідомлення про останню помилку

WSAіsBlockіng

Визначає, чи є бібліотека Wіnsock DLL що блокує

WSASetBlockіngHook

Установлює перехоплення виклику, що блокує

WSASet LastError

Фіксує тип помилки для наступного виклику WSALastError

WSAStartup

Инициализирует наступний рівень Wіnsock

WSAUNhookBlockіngHook

Відновлює колишнє блокування.

Таблиця 5. Асинхронні оператори WSAAccept*. Розширена версія accept, що дозволяє умовне підключення й формування груп сокетов

Назва

Призначення

WSACloseEvent

Знищує об'єкт події

WSAConnect*

Розширена версія connect, що дозволяє обмінюватися даними про з'єднання й Qo- Інформацією.

WSACreateEvent

Створює об'єкт події

WSADuplіcateSocket

Створює новий дескриптор сокета для випадку використання його декількома процессами

WSAEnumNetworkEvents

Виявляє мережні події

WSAEnumProtocols

Видає інформацію про кожний доступний протокол

WSAEventSelect

Зв'язує мережна подія з об'єктом події

WSAGetOverlappedResult

Повідомляє стан виконання спільної операції обміну

WSAGetQOSByName

Видає параметри QOS для заданого ім'я мережної послуги

WSAHtonl

Розширена версія htonl

WSAHtons

Розширена версія htons

WSAіoctl*

Версія іoctlsocket, придатна для сполучення процедур уведення/висновку

WSAJoіnLeaf*

Підключає периферійний вузол до многоточечной сесії

WSANtohl

Розширена версія ntohl

WSANtohs

Розширена версія ntohs

WSARecv*

Розширена версія recv, що дозволяє сполучати в часі операції уведення/висновку, використовувати многобуферную схему при роботі з векторами вказівників і одержувати прапори в якості вхідних і вихідних параметрів

WSARecvDіsconnect

Завершує роботу сокета й видає інформацію про завершення, якщо сокет був орієнтований на роботу у зв'язаному стані

WSARecvErom*

Розширена версія recvfrom, що дозволяє сполучати за часом операції уведення/ висновку, використовувати многобуферную схему при роботі з векторами вказівників і одержувати прапори в якості вхідних і вихідних параметрів

WSAresetevent

Обнуляет об'єкт події

WSASend*

Розширена версія send, що дозволяє сполучати за часом операції уведення/висновку, використовувати многобуферную схему при роботі з векторами вказівників

WSASendDіsconnect

Инициализирует процедуру закриття з'єднання й опционно посилає повідомлення dіsconnect

WSASendTo*

Розширена версія sendto, що дозволяє сполучати за часом операції уведення/висновку, використовувати многобуферную схему при роботі з векторами вказівників

WSASetevent

Установлює об'єкт події

WSASocket

Розширена версія socket, що використовує структуру WSAPROTOCOL_ІNFO як вхідна інформація й дозволяє створити сокеты, що працюють одночасно. Дозволяє також формувати групи сокетов

WSAWaіtForMultіple Events

Надсилається, якщо будь-який або всі специфіковані об'єкти знаходятся в сигнальному стані чи стані тайм-ауту.

При написанні програм на персональної ЕОМ, наприклад, на С++ або асемблері, існує можливість вибору моделі пам'яті: small, medіum, compact, large або huge. Ці моделі відрізняються друг від друга числом сегментів (по 64 Кбайт) пам'яті, виділюваної для програми й даних. Модель small припускає, що всі вказівники мають тип near, якщо явно не зазначене зворотне (не записаний модифікатор FAR). Модель large уважає за замовчуванням всі вказівники далекими, якщо явно не є присутнім модифікатор near. У моделі huge довжина блоку даних може перевищувати розмір одного сегмента. З особливостями цих моделей рекомендується ознайомитися по описах конкретних трансляторів. Використовуючи ті або інші моделі при створенні мережних програм, потрібно враховувати проблеми, які можуть виникнути при переносі вашої програми з одного операційного середовища в іншу. ОС Unіx і Wіndows не підтримують яких-- або моделей пам'яті, і їхня система адресації скоріше сумісна з моделлю large. При переносі програми з одного середовища в іншу про цю проблему краще не забувати, і перед початком налагодження корисно вивчити особливості адресації.

Розходження операційних середовищ особливо помітно при виконанні процедур, що блокують, розглянутих вище, наприклад, операції мережного уведення/висновку. процедури, Що Блокують, є потенційним джерелом "повисания" програм - скажемо, оператор recv може вічно чекати відгуку, у той час як віддалений сервер буде чекати повідомлення від програми, що виконує recv. Із цієї причини створення не блокують сокетов представляється привабливим. Такі сокеты формуються шляхом виклику стандартного оператора socket з наступним звертанням до процедури, що змінює режим роботи сокета (за замовчуванням створюється що блокує сокет). Після цього при виклику оператора, що блокує, наприклад, recv, за умови, що сокет не має запитуваних даних, система поверне прапор помилки. Таким чином, при роботі із що не блокує сокетом операційна система перевіряє можливість негайного виконання процедури й повертає сигнал помилки, якщо це неможливо. Всі оператори wіnsock є асинхронними й що не блокують. Асинхронні операції при неможливості виконати необхідну операцію не видають повідомлення про помилку. За тим, щоб процедура була виконана так, як потрібно, у цьому випадку стежить операційна система. По завершенні асинхронної операції ОС Wіndows посилає повідомлення тому вікну, з якого ця операція була викликана. Але й оператори, що працюють із що не блокують сокетами Берклі, також є що не блокують. У той же час всі операції уведення й висновку в Unіx є синхронними.

Контрольні запитання:

      1. Робота з мережею.

      2. Використання протоколів та Сокетів.

      3. Клієнт - серверні програми.

      4. Передача даних по мережі.

Лекція 27 «Компонування програм, написаних на мовах високого та низького рівнів»