Глава 2. Многозадачное программирование
В предыдущей главе нами были рассмотрены возможности использования таймеров в прикладной задаче. Задав один или несколько таймеров, мы принуждаем систему вызывать одну или несколько процедур в автоматическом режиме. Посредством таймеров мы тем самым можем реализовать многозадачный режим в рамках одного процесса. Более того, было показано, что такие подзадачи могут взаимодействовать друг с другом. Это и понятно, ведь все эти подзадачи разделяют одно адресное пространство и, следовательно, информация от одной подзадачи к другой может передаваться через глобальные переменные. Это весьма интересный и сложный вопрос о том, как задачи и подзадачи могут взаимодействовать друг с другом. Мы рассмотрим его несколько позднее. Сейчас же рассмотрим многозадачность в операционной системе Windows с самого начала.
I
Под процессом будем понимать объект, создаваемый операционной системой Windows обычно при загрузке исполняемого модуля и получающий в единоличное пользование:
- Виртуальную память, выделяемую для него операционной системой.
- Дескрипторы открываемых им файлов.
- Список загруженных им в его собственную память динамических модулей (DLL).
- Созданные им подпроцессы или потоки, исполняемые независимо друг от друга, в собственной памяти процесса.
Думаю, данное определение весьма ясно раскрывает суть понятия процесс. Но для большинства рассматриваемых в данной главе проблем, достаточно было бы дать и более простое определение. Например, такое: всякий исполняемый модуль (.ЕХЕ), запущенный в операционной системе Windows, становится процессом.
Теперь что касается подпроцесса. Смысл его достаточно прост: каждый процесс в отведенном для него адресном пространстве может порождать еще процессы. Эти процессы выполняются независимо друг от друга и от порождающего их процесса. Однако порождающий процесс может при желании "убить" любой из порожденных им процессов. Такие процессы называют еще потоками, а также цепочками или нитями40. Теперь мы понимаем, что в предыдущей главе при помощи таймера мы создавали потоки. Однако в операционной системе Windows для создания потоков есть и специальные средства, речь о которых пойдет ниже.
Теперь немного поговорим о типах многозадачности. В старой 16-битной Windows переключение между задачами происходило только тогда, когда задача отдавала управление операционной системе. Такая многозадачность называется невытесняющей. В определенном смысле это было даже хуже, чем в операционной системе MS DOS. Там элементы многозадачности осуществлялись при помощи так называемых TSR-программ (см. [1]). Такие программы назывались еще резидентными. Они перехватывали прерывание от таймера, клавиатуры или другого устройства и имели возможность время от времени получать управление.
Положение, существовавшее в старой операционной системе Windows, требовало от
программиста выполнения джентльменского правила - не захватывать надолго время
микропроцессора. Некоторым решением проблемы являлось использование таймеров (в
чем мы уже убедились), а также использование функции PeekMessage
вместо GetMessage
. Функция PeekMessage
, в отличие от
GetMessage
, возвращает управление сразу, даже если в очереди нет ни
одного сообщения.
В 32-битных операционных системах Windows (Windows 9x, Windows NT, Windows
2000) реализована вытесняющая схема многозадачности, в которой переключением
между процессами и потоками занимается операционная система. Если процесс
слишком долго выполняет некоторую операцию, то курсор над окном процесса
преобразуется в песочные часы. При этом другие процессы будут по-прежнему
выполняться, и Вы сможете переключаться на них. А вот доступ к окну данного
процесса может оказаться затруднительным. Решить данную проблему можно уже
упомянутым способом, заменив в цикле ожидания GetMessage
на
PeekMessage
. Однако более правильным решением будет разбиение
процесса на некоторое количество потоков.
Созданием потоков мы займемся в следующих разделах, а оставшаяся часть
данного раздела будет посвящена созданию процессов. Ваше приложение может
создавать процессы, запустив ту или иную ЕХЕ-программу, которые будут работать
независимо от основного приложения. Одновременно Ваше приложение может при
необходимости удалить запущенное им приложение из памяти. Запустить приложение
(создать процесс) можно при помощи функции CreateProcess
. Сейчас мы
дадим описание этой функции. Ниже объясняются ее параметры.
- 1-й параметр - указывает на имя запускаемой программы. Имя может содержать полный путь к программе.
- 2-й параметр - его значение зависит от того, является первый параметр
NULL
(0
) или нет. Если первый параметр указывает на строку, то данный параметр трактуется как командная строка запуска (без имени программы). Если первый параметр равенNULL
, то данный параметр рассматривается как командная строка, первый элемент которой представляет собой имя программы. Если путь к программе не указан, то функцияCreateProcess
осуществляет поиск программы по определенному алгоритму:- Поиск в каталоге, откуда была запущена программа.
- Поиск в текущем каталоге.
- Поиск в системном каталоге (можно получить через
GetSystemDirectory
). Обычно системным каталогом являетсяC:\WINDOWS\SYSTEM
. - Поиск в каталоге Windows (можно получить через
GetWindowsDirectory
). Обычно этим каталогом являетсяC:\WINDOWS
. - Поиск в каталогах, перечисленных в параметре
PATH
окружения.
- 3-й и 4-й параметры. Используются для задания атрибутов доступа порождаемого
процесса. Обычно полагают равным
0
. - 5-й параметр. Если этот параметр
0
, то порождаемый процесс не наследует дескрипторы порождающего процесса, в противном случае порождаемый процесс наследует дескрипторы. - 6-й параметр. Может менять свойства порождаемого процесса. Если параметр равен нулю, то свойства задаются по умолчанию. В силу большого количества значений этого параметра, мы отсылаем желающих к соответствующим справочным руководствам.
- 7-й параметр. Является указателем на буфер, содержащий параметры среды. Если
параметр равен
0
, то порождаемый процесс наследует параметры среды порождающего процесса. - 8-й параметр. Задает текущее устройство и каталог для порождаемого процесса.
Если параметр равен
NULL
, порождаемый процесс наследует текущее устройство и каталог порождающего процесса. - 9-й параметр. Представляет указатель на структуру, которая содержит
информацию об окне создаваемого процесса. Ниже будут рассмотрены поля этой
структуры.
- 10-й параметр. Указывает на структуру, заполняемую при выполнении запуск
приложения. Вот эта структура:
PROCINF STRUC hProcess DD ? ; дескриптор созданного процесса. hThread DD ? ; дескриптор главного потока нового процесса. Idproc DD ? ; идентификатор созданного процесса. idThr DD ? ; идентификатор главного потока нового процесса. PROCINF ENDS
Основное отличие дескриптора от идентификатора заключается в том, что
дескриптор уникален лишь в пределах данного процесса, идентификатор же является
глобальной величиной. Посредством идентификатора может быть найдена база данных
текущего процесса. У читателя, я думаю, сразу возникнет вопрос: а чем дескриптор приложения, который мы получаем при помощи функции
GetModuleHandle
, от только что упомянутых величин? Дескриптор
приложения, или дескриптор модуля есть величина локальная, т.е. действующая в
пределах данного процесса и, как правило, равная адресу загрузки модуля в
виртуальное адресное пространство. Дескриптор модуля имеется у любого модуля,
загруженного в память, в том числе и у подчиненных DLL-библиотек.
Рассмотрим теперь структуру, на которую указывает 9-й параметр функции
CreateProcess
. Вот эта структура:
STARTUP STRUC cb DD 0 lpReserved DD 0 lpDesktop DD 0 lpTitle DD 0 dwX DD 0 dwY DD 0 dwXSize DD 0 dwYSize DD 0 dwXCountChars DD 0 dwYCountChars DD 0 dwFillAttribute DD 0 dwFlags DD 0 wShowWindow DW 0 cbReserved2 DW 0 lpReserved2 DD 0 hStdInput DD 0 hStdOutput DD 0 hStdError DD 0 STARTUP ENDS
Итак, разберем смысл полей этой структуры.cb
- размер данной
структуры в байтах. Заполняется обязательно.lpReserved
-
резерв, должно быть равно нулю.lpDesktop
- имя рабочего стола
(и рабочей станции). Имеет смысл только для Windows NT.lpTitle
- название окна для консольных приложений, создающих свое окно. Для остальных
приложений должно быть равно 0
.dwX
- координата X
левого верхнего угла окна.dwY
- координата Y левого верхнего
угла окна.dwXSize
- размер окна по X.dwYSize
-
размер окна по Y.dwXCountChars
- размер буфера консоли по
X.dwYCountChars
- размер буфера консоли по
Y.dwFillAttribute
- начальный цвет текста. Имеет значение
только для консольных приложений.dwFlags
- флаг значения полей.
Вот значение этого флага.
Макро-значение флага | Значение константы | Смысл значения |
---|---|---|
STARTF_USESHOWWINDOW | 1h | Разрешить поле dwShowWindow |
STARTF_USESIZE | 2h | Разрешить dwXSize и dwYSize |
STARTF_USEPOSITI0N | 4h | Разрешить dwX и dwY |
STARTF_USECOUNTCHARS | 8h | Разрешить dwXCountChars и dwYCountChars |
STARTF_USEFILLATTR1BUTE | 10h | Разрешить dwFillAttribute |
STARTF_FORCEONFEEDBACK | 40h | Включить возврат курсора |
STARTF_FORCEOFFFEEDBACK | 80h | Выключить возврат курсора |
STARTF_USESTDHANDLES | 100h | Разрешить
hStdInput |
wShowWindow
-
определяет способ отображения окна.cbReserved2
- резерв, должно
быть равно 0
.hStdInput
- дескриптор ввода (для
консоли).hStdOutput
- дескриптор вывода (для
консоли).hStdError
- дескриптор вывода сообщения об ошибке (для
консоли). Следующий пример (Рис. 3.2.1) представляет собой простейший пример создания
процесса. В качестве программы, порождающей процесс, взят редактор WINWORD.EXE.
Для проверки правильности работы примера Вам придется указать путь к WINWORD на
Вашем компьютере (переменная PATH
). Обратите внимание на то, что
приложение появляется на экране в свернутом виде и как это достигается. Как
видите, функция CreateProcess
совсем не так уж страшна, как кажется
на первый взгляд.
// файл proces.rc // определение констант #define WS_SYSMENU 0x00080000L #define WS_POPUP 0x80000000L #define DS_3DLOOK 0x0004L //ресурс - меню MENUP MENU { POPUP "&Запуск Word" { MENUITEM "&3апустить", 1 MENUITEM "&Удалить", 2 MENUITEM "Выход из &программы", 3 } } // определение диалогового окна DIAL1 DIALOG 0, 0, 240, 120 STYLE WS_POPUP | WS_SYSMENU | DS_3DLOOK CAPTION "Пример немодального диалогового окна" FONT 8, "Arial" { } ; файл proces.inc ; константы STARTF_USESHOWWINDOW equ 1h SW_SHOWMINIMIZED equ 2h ; сообщение приходит при закрытии окна WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_COMMAND equ 111h ; прототипы внешних процедур IFDEF MASM ; для MASM EXTERN TerminateProcess@8:NEAR EXTERN CreateProcessA@40:NEAR EXTERN DialogBoxParamA@20:NEAR EXTERN EndDialog@8:NEAR EXTERN MessageBoxA@16:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetModuleHandleA@4:NEAR EXTERN LoadMenuA@8:NEAR EXTERN SetMenu@8:NEAR EXTERN TranslateMessage@4:NEAR ELSE ; для TASM EXTERN TerminateProcess:NEAR EXTERN CreateProcessA:NEAR EXTERN DialogBoxParamA:NEAR EXTERN EndDialog:NEAR EXTERN MessageBoxA:NEAR EXTERN ExitProcess:NEAR EXTERN GetModuleHandleA:NEAR EXTERN LoadMenuA:NEAR EXTERN SetMenu:NEAR EXTERN TranslateMessage:NEAR TerminateProcess@8 = TerminateProcess CreateProcessA@40 = CreateProcessA DialogBoxParamA@20 = DialogBoxParamA EndDialog@8 = EndDialog MessageBoxA@16 = MessageBoxA ExitProcess@4 = ExitProcess GetModuleHandleA@4 = GetModuleHandleA LoadMenuA@8 = LoadMenuA SetMenu@8 = SetMenu TranslateMessage@4 = TranslateMessage ENDIF ; структуры ; структура сообщения MSGSTRUCT STRUC MSHWND DD ? MSMESSAGE DD ? MSWPARAM DD ? MSLPARAM DD ? MSTIME DD ? MSPT DD ? MSGSTRUCT ENDS ; структура для CreateProcess STARTUP STRUC cb DD 0 lpReserved DD 0 lpDesktop DD 0 lpTitle DD 0 dwX DD 0 dwY DD 0 dwXSize DD 0 dwYSize DD 0 dwXCountChars DD 0 dwYCountChars DD 0 dwFillAttribute DD 0 dwFlags DD 0 wShowWindow DW 0 cbReserved2 DW 0 lpReserved2 DD 0 hStdInput DD 0 hStdOutput DD 0 hStdError DD 0 STARTUP ENDS ; структура - информация о процессе PROCINF STRUC hProcess DD ? hThread DD ? Idproc DD ? idThr DD ? PROCINF ENDS ; файл proces.asm .386P ; плоская модель .MODEL FLAT, stdcall include proces.inc ; директивы компоновщику для подключения библиотек IFDEF MASM ; директивы MASM includelib c:\masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ELSE ; директивы TASM includelib c:\tasm32\lib\import32.lib ENDIF ; сегмент данных _DATA SEGMENT DWORD PUBLIC USE32 'DATA' NEWHWND DD 0 MSG MSGSTRUCT <?> STRUP STARTUP <?> INF PROCINF <?> HINST DD 0 ; дескриптор приложения PA DB "DIAL1",0 PMENU DB "MENUP",0 PATH DB "C:\Program Files\Office\WINWORD.EXE",0 _DATA ENDS ; сегмент кода _TEXT SEGMENT DWORD PUBLIC USE32 'CODE' START: ; получить дескриптор приложения PUSH 0 CALL GetModuleHandleA@4 MOV [HINST], EAX ; создать модальный диалог PUSH 0 PUSH OFFSET WNDPROC PUSH 0 PUSH OFFSET PA PUSH [HINST] CALL DialogBoxParamA@20 PUSH 0 CALL ExitProcess@4 ;----------------------------------- ; процедура окна ; расположение параметров в стеке ; [EBP+014Н] ; LPARAM ; [EBP+10Н] ; WAPARAM ; [EBP+0CH] ; MES ; [EBP+8] ; HWND WNDPROC PROC PUSH EBP MOV EBP,ESP PUSH EBX PUSH ESI PUSH EDI ; сообщение при закрытии окна CMP DWORD PTR [EBP+0CH],WM_CLOSE JNE L1 ; закрыть диалоговое окно JMP L5 L1: ; сообщение при инициализации окна CMP DWORD PTR [EBP+0CH],WM_INITDIALOG JNE L3 ; загрузить меню PUSH OFFSET PMENU PUSH [HINST] CALL LoadMenuA@8 ; установить меню PUSH EAX PUSH DWORD PTR [EBP+08H] CALL SetMenu@8 JMP FINISH ; проверяем, не случилось ли чего с пунктами ; меню в диалоговом окне L3: CMP DWORD PTR [EBP+0CH],WM_COMMAND JNE FINISH CMP WORD PTR [EBP+10H],3 JE L5 CMP WORD PTR [EBP+10H],2 JE L7 CMP WORD PTR [EBP+10H],1 JE L6 JMP FINISH ; закрыть диалоговое окно L5: PUSH 0 PUSH DWORD PTR [EBP+08H] CALL EndDialog@8 JMP FINISH ; запустить программу WORD L6: ; заполняем структуру для запуска ; окно должно появляться в свернутом виде MOV STRUP.cb,68 MOV STRUP.lpReserved,0 MOV STRUP.lpDesktop,0 MOV STRUP.lpTitle,0 MOV STRUP.dwFlags, STARTF_USESHOWWINDOW MOV STRUP.cbReserved2,0 MOV STRUP.lpReserved2,0 MOV STRUP.wShowWindow,SW_SHOWMINIMIZED ; запуск приложения Winword PUSH OFFSET INF PUSH OFFSET STRUP PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET PATH PUSH 0 CALL CreateProcessA@40 JMP FINISH ; удалить из памяти процесс L7: PUSH 0 ; код выхода PUSH INF.hProcess CALL TerminateProcess@8 FINISH: MOV EAX,0 POP EDI POP ESI POP EBX POP EBP RET 16 WNDPROC ENDP _TEXT ENDS END STARTРис. 3.2.1. Пример создания процесса.