
- •Операционные системы. Часть 2 Учебное пособие
- •Содержание
- •6.1.1. Общие понятия 58
- •4.Память
- •4.1.Управление оперативной памятью
- •4.1.1.Виртуальная и физическая память
- •4.1.2.Схема управления памятью
- •4.1.3.Управление памятью в однозадачной системе
- •4.1.4.Управление памятью в многозадачной системе
- •4.1.5.Подкачка процессов целиком
- •4.1.6.Страничная подкачка
- •4.1.7.Управление виртуальной памятью
- •4.2.Носители памяти для долговременного хранения данных
- •4.2.1.Накопители на жестких дисках
- •4.2.2.Оптические диски
- •4.2.3.Голографические диски
- •4.2.4.Флэш-память
- •4.2.5.Перспективные носители информации
- •Контрольные вопросы
- •5.Разработка надстроек к операционным системам
- •5.1.Общие понятия
- •5.2.Многозадачная оболочка с синхронной заменой контекста
- •5.3.Многозадачная система с принудительной заменой контекста
- •5.4.Дополнения к асинхронной надстройке
- •Контрольные вопросы
- •6.Краткий обзор операционных систем
- •6.1. Операционная система unix
- •6.1.1.Общие понятия
- •6.1.2. Структура unix
- •6.2.Операционные системы реального времени
- •6.2.1.Понятие об операционной системе qnx
- •6.2.2.Введение в ос VX Works
- •6.3.Особенности ос для универсальных многопроцессорных систем
- •6.3.1.Операционная система helios
- •6.4.Операционная система Windows 2000
- •Варианты выпуска Windows 2000
- •Контрольные вопросы
- •Литература
- •Предметный указатель
5.2.Многозадачная оболочка с синхронной заменой контекста
Рассмотрим основные приемы построения многозадачной надстройки сначала на примере синхронной замены контекста (см. разд. 3.3), распространив затем их на асинхронную (принудительную) замену контекста. Чтобы осуществить переключение на другой процесс, достаточно перейти на его контекст (см. разд. 3.3). При этом при синхронной замене контекста, для которой характерно переключение (в явном или неявном виде) непосредственно из активного процесса, запоминание и восстановление регистров общего назначения не представляет трудности. Самое сложное – это запомнить и восстановить «нить управления» (в реальном режиме – CS:IP). Для запоминания регистров, связанных с управлением программы, можно воспользоваться тем обстоятельством, что во время вызова подпрограммы «адрес точки возврата» находится в стеке и доступ к нему можно получить через адрес SS:SP.
На Рис. 5 .13 отображена схема реализации этого механизма. Каждой пользовательской программе сопоставляется стек, в области памяти которого отводится место для записи его текущего контекста. Откладывая на некоторое время обсуждение вопроса о первоначальном запуске прикладной программы, ограничимся пока рассмотрением процесса переключения с одного процесса на другой. Пусть, для определённости, первым начинает работать Процесс 1. Когда будет вызвана подпрограмма Перек (ук1, ук2), назначение которой заключается в переключении управления на Процесс 2, то в стек основной программы записываются: передаваемые параметры (ук1 и ук2) и адрес точки «нити управления», из которой была вызвана эта подпрограмма. В тексте подпрограммы Перек (ук1, ук2) через адрес возврата обновляется состояние контекста первого процесса, а адрес точки возврата заменяется на ук2, через который можно получить доступ к значению контекста Процесса 2 и восстановить его. В результате осуществляется не возращение в программу, из которой был вызван переключатель, а переход в другой процесс. При вызове Перек (ук2, ук1), чтобы добиться такого же эффекта, достаточно поменять передаваемые параметры местами, введя несколько указателей и варьируя ими, нетрудно распространить этот прием на любое число процессов.
Теперь рассмотрим ситуацию, возникающую с первым запуском каждого процесса. Суть проблемы заключается в том, что программные единицы, которые должны быть независимыми, описываются как пользовательские подпрограммы, которые по статусу должны вызываться оператором Call (см. разд. 1.7), возвращающий управление в точку вызова подпрограммы. Чтобы обойти это препятствие, можно например запускать прикладную подпрограмму по Jmp, что позволит избавиться от подчиненного вызова и сделать подпрограмму независимой, переведя её в статус сопрограммы. для этого в область, отведенную под запись контекста каждой сопрограммы, записать адрес её начала. Теперь запуск первой сопрограммы можно осуществить из основной программы (ядра) по вызову Перек (указОснПрог, ук1), где указОснПрог – указатель на точку вызова из основной программы. Остальные программы запускаются из сопрограмм при первом к ним обращении. Такой порядок инициализации сопрограмм обладает и другим преимуществом: после окончания сеанса можно по вызову переключателя процессов возвратить в точку указОснПрог, находящуюся в ядре, после этого без всяких проблем закончить работу всей системы.
Многозадачная оболочка может быть написана как на языке высокого уровня, так и на ассемблере. При использовании ЯВУ приходиться учитывать особенности компилятора. Например, в Паскале при вызове подпрограммы запоминаются все регистры, тогда как в СИ только те, которые используются в подпрограмме, и учет этого факта приводит к различному написанию Переключателей контекста. Если выбрать в качестве языка ассемблер, то роль посредника (компилятора) практически сводится к нулю, и вся инициатива находится в руках программистов. С практической точки зрения целесообразно использовать для создания оболочки язык СИ.
Однако лучше оставить такую возможность студентам в лабораторном практикуме по курсу «Операционные системы» и в качестве примера построим эту оболочку на ассемблере [11] (см. Текст библиотеки макросов и Пример программы для многозадачной надстройки с синхронной заменой контекста).
Переключатель контекста. Эта процедура написана в виде макроса, внутри которого осуществляется вызов соответствующей подпрограммы. Данный приём, который дает возможность передать параметры в подпрограмму, применяется в компиляторах для языков высокого уровня. Однако на ассемблере коды, которые содержатся в интервале от начала макроса до вызова подпрограммы Trancfer, можно написать по своему усмотрению (см. «Текст библиотеки макросов»). В данном случае запоминаются состояния регистров, в процессе, который отдает управление другому процессу, и подготавливаются параметры макроса des1 и des2 для передачи в окружение подпрограммы Trancfer. Далее в этой подпрограмме из стека получаются адреса параметров des1 и des2. Эти адреса используются в рамках схемы, отображенной на Рис. 5 .13:
для запоминания «нити управления» в контексте процесса, отдающего управление;
для замены адреса точки возврата на «нить управления», содержащуюся в контексте процесса, которому передаётся управление;
для восстановления состояний регистров нового процесса.
Следует отметить, что далеко не всякая ОС даёт так вольготно «переиначивать» действия системы, что порой огорчает «самостоятельных пользователей». Однако, учитывая, что не всякий прикладной программист, достигнув некоторого уровня самостоятельности, благоразумен и доброжелателен, защита ОС от посягательств на её функции является необходимой. Однако ничто не мешает использовать редкие «лазейки» для пользы дела.
Создание нового процесса. Цель этой процедуры – придать некоторой программной единице статус процесса. Это дало бы возможность ей попеременно находиться в активной стадии и в «замороженном» состоянии. Дополнительная сложность заключается в том, что будущие процессы, являющиеся независимыми программными единицами, первоначально представляют собой подпрограммы, т. е. подчинённые программные единицы.
Рис. 5.13
Операцию «Создание нового процесса» проще всего представить на ассемблере в виде макроса, который описан вторым в библиотеке macro.lib (см. «Текст библиотеки макросов»). В этом макросе:
первый параметр ar представляет собой область памяти в виде статического массива (см. пример программы);
параметр pr_ds предназначен для записи контекста процесса, включающего и «нить управления»;
pr – имя подпрограммы, которой предстоит «переродиться» в процесс.
При создании нового процесса на основе подпрограммы выполняются следующие действия:
определяется адрес (ar);
область памяти для записи контекста (pr_ds) погружается внутрь статического массива (практически это превращает массив в стек);
записывается в область памяти, отведенной для контекста адрес начала соответствующей подпрограммы, что позволяет запустить каждую подпрограмму при первом к ней обращении по Переключателю контекста.
Текст библиотеки макросов
(библиотека macro.lib)
;****************************************************************************************************
_Trancfer macro des1,des2 ;Макрос вместе с подпрограммой Trancfer
;осуществляет переключение контекста
;****************************************************************************************************
;запоминание регистров в области памяти,
;выделенной под контекст процесса, отдающего управление
mov des1+4, dx
mov des1+6,si
mov des1+8,ax
mov des1+10,bx
mov des1+12,cx
; конец запоминания регистров
; адрес параметра des1 заталкивается в стек
mov des1+14,di
lea si,des1
mov ax, seg des1
push ax
push si
; адрес параметра des2 заталкивается в стек
lea di,des2
mov ax, seg des2
push ax
push di
Call Trancfer
endm
; ******************* конец макроса********************
После выполнения макроса _Trancfer стек будет иметь содержимое как на Рис. 5 .14. Далее разбирая программу рекомендуется самостоятельно проследить за изменением содержимого стека.
Рис. 5.14. Состояние стека после запоминания регистров
; Создание нового процесса
NewProc macro ar, pr_ds, pr
;ar – массив (область памяти) выделяемая в т. ч. под запись контекста
;pr_ds – указатель на область памяти с контекстом
;pr – имя процесса
; процедура связывает область памяти с указателем
mov bx, seg ar
mov es, bx
mov word ptr [pr_ds+2], bx
mov bx, offset ar
add bx, 1012
mov word ptr [pr_ds], bx
mov word ptr es:[bx+2], offset pr
mov word ptr es:[bx+4], seg pr
endm
;****************************************************************************************************
Программа для многозадачной оболочки написана в формате для исполняемого файла типа .exe (её объем может быть больше 64 Кб). Основная программа (main) представляет собой простейшее ядро. В нем подпрограммы трансформируются в процессы. Далее переключателем контекста запускается первый процесс (при этом в cont_main запоминается контекст основной программы). В силу наличия в пользовательских программах соответствующих переключателей контекста поочередно осуществляется переключение программ до тех пор, пока не срабатывают условия выхода из этой «вертушки», и выполнение программы не передаётся в main. Это обстоятельство, как уже указывалось раньше, позволяет корректно завершить выполнение многозадачной надстройки с синхронной заменой контекста.
В подпрограмме Trancfer, вызываемой из макроса (библиотека макросов подключена в начале программы), из стека выталкиваются адреса параметров макроса (des1, des2). Эти адреса служат для решения следующих задач:
сохранения в контексте исходного процесса «нити управления»;
подмены адреса возврата «нитью управления» замещающего процесса;
восстановления значений регистров процесса, который должен стать активным.
В данном примере работают два пользовательских процесса (что достаточно для иллюстрации к устройству этого механизма). Необходимые пояснения по их работе приведены в комментариях к программным кодам.
;****************************************************************************************************
; Пример программы для многозадачной надстройки с синхронной заменой контекста
; (КООПЕРАТИВНОЕ ЗАМЕЩЕНИЕ КОНТЕКСТА)
;****************************************************************************************************
include macro.lib
st1 segment stack
db 100h dup (?)
st1 ends
dan segment
ar_1 db 1024 dup (?) ;выделение памяти
ar_2 db 1024 dup (?) ;под стеки и дескрипторы
ar_g db 1024 dup (?) ;для всех
cont_1 dw 8 dup (0) ;процессов в том
cont_2 dw 8 dup (0) ;числе и основную
cont_main dw 8 dup (0) ;программу
count db 2 ;переменные для
count1 db 100 ;прикладных
count2 db 100 ;процессов
dan ends
cd_1 segment
assume ss:st1, cs:cd_1, ds:dan
;****************************************************************************************************
;переключение и запоминание : передаем параметры через переменные
Trancfer proc far ;переключатель
push bp ;формируем базу для отсчета
mov bp, sp ;от вершины стека
mov si,[bp+10] ;засылаем адрес
mov ax,[bp+12] ;переменной des1 (область памяти для контекста)
mov es, ax ;в пару регистров es:[si]
mov es:[si], sp ;записываем «нить управления»
mov es:[si+2], ss ;в первые 4 байта
mov dx,[bp+6] ;засылаем адрес
mov ax,[bp+8] ;переменной des2 (область памяти для контекста)
mov es, ax ;в пару регистров es:[si]
mov sp, es:[di] ;берём из первых полей des2 «нить управления»
mov ss,es:[di+2] ;и перекрываем ей адрес возврата подпрограммы
;****************************************************************************************************
;восстанавливаем регистры
mov dx, es:[di+4]
mov si, es:[di+6]
mov ax, es:[di+8]
mov bx, es:[di+10]
mov cx, es:[di+12]
mov di, es:[di+14]
;****************************************************************************************************
pop bp
ret 8
trancfer endp
;****************************************************************************************************
;установка положения курсора (требует предварительной установки регистра dх)
set_kyr proc
push ax
push bx
mov ah, 02h ;функция установки курсора
mov bh, 0h ;экранная страница
int 10h
pop bx
pop ax
ret
set_kyr endp
;****************************************************************************************************
pr1 proc
mov dl, 0 ;данные для функции вывода символа
mov dh, 1
mov ax, 0931h
mov bh, 0h
mov cx, 3
mov bl, 44
@5:
inc dl ;колонка для положения курсора
inc dh ;строка для положения курсора
call set_kyr
int 10h ;вызов функции вывода символа
push cx
push si
mov cx, 64000
@8:
mov si, 150
@10:
dec si
cmp si,0
jnz @10
loop @8
pop si
pop cx
_Trancfer cont_1,cont_2 ;переключение на второй процесс
dec count1
cmp count1, 0
jne @5
ret
pr1 endp
;****************************************************************************************************
pr2 proc
mov dl,40 ;данные для функции вывода символа
mov dh,1
mov ax,0932h
mov bh,0h
mov cx, 4
mov bl, 54
@1:
inc dl ;колонка для положения курсора
inc dh ;строка для положения курсораh
call set_kyr
int 10h ;вызов функции вывода символа
push cx
push si
mov cx,64000
@7:
mov si,150
@9:
dec si
cmp si,0
jnz @9
loop @7
pop si
pop cx
inc count
cmp count,25
jne @3
_Trancfer cont_2,cont_main ;выход в main
jmp @4
@3:
_Trancfer cont_2,cont_1 ;переключение на второй процесс
;
dec count2
cmp count2,0
je @4
jmp @1
@4:
ret
pr2 endp
;****************************************************************************************************
main proc
begin:
mov ax,dan
mov ds,ax
;очистка экрана c помощью функции заполнения указанной области символом
;****************************************************************************************************
mov ax,0600h
mov bh,07h ;верхний левый угол
mov cx,00h ;правый нижний угол
mov dx,184fh
int 10h
;****************************************************************************************************
Newproc ar_1,cont_1, pr1 ;создание первого процесса
Newproc ar_2,cont_2, pr2 ;создание второго процесса
Newproc ar_g,cont_main,main ;создание главного процесса
_Trancfer cont_main,cont_1
mov ah,4ch
int 21h
main endp
cd_1 ends
end begin