Понедельник, 28.07.2025, 14:24 Приветствую Вас Гость

On-line: Книги, учебники, статьи

Главная | Регистрация | Вход | RSS

Глава 2. Многозадачное программирование(1)

Глава 2. Многозадачное программирование

В предыдущей главе нами были рассмотрены возможности использования таймеров в прикладной задаче. Задав один или несколько таймеров, мы принуждаем систему вызывать одну или несколько процедур в автоматическом режиме. Посредством таймеров мы тем самым можем реализовать многозадачный режим в рамках одного процесса. Более того, было показано, что такие подзадачи могут взаимодействовать друг с другом. Это и понятно, ведь все эти подзадачи разделяют одно адресное пространство и, следовательно, информация от одной подзадачи к другой может передаваться через глобальные переменные. Это весьма интересный и сложный вопрос о том, как задачи и подзадачи могут взаимодействовать друг с другом. Мы рассмотрим его несколько позднее. Сейчас же рассмотрим многозадачность в операционной системе Windows с самого начала.

I

Под процессом будем понимать объект, создаваемый операционной системой Windows обычно при загрузке исполняемого модуля и получающий в единоличное пользование:

  1. Виртуальную память, выделяемую для него операционной системой.
  2. Дескрипторы открываемых им файлов.
  3. Список загруженных им в его собственную память динамических модулей (DLL).
  4. Созданные им подпроцессы или потоки, исполняемые независимо друг от друга, в собственной памяти процесса.

Думаю, данное определение весьма ясно раскрывает суть понятия процесс. Но для большинства рассматриваемых в данной главе проблем, достаточно было бы дать и более простое определение. Например, такое: всякий исполняемый модуль (.ЕХЕ), запущенный в операционной системе 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 осуществляет поиск программы по определенному алгоритму:
    1. Поиск в каталоге, откуда была запущена программа.
    2. Поиск в текущем каталоге.
    3. Поиск в системном каталоге (можно получить через GetSystemDirectory). Обычно системным каталогом является C:\WINDOWS\SYSTEM.
    4. Поиск в каталоге Windows (можно получить через GetWindowsDirectory). Обычно этим каталогом является C:\WINDOWS.
    5. Поиск в каталогах, перечисленных в параметре 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_USESHOWWINDOW1hРазрешить поле dwShowWindow
STARTF_USESIZE2hРазрешить dwXSize и dwYSize
STARTF_USEPOSITI0N4hРазрешить dwX и dwY
STARTF_USECOUNTCHARS8hРазрешить dwXCountChars и dwYCountChars
STARTF_USEFILLATTR1BUTE10hРазрешить dwFillAttribute
STARTF_FORCEONFEEDBACK40hВключить возврат курсора
STARTF_FORCEOFFFEEDBACK80hВыключить возврат курсора
STARTF_USESTDHANDLES100hРазрешить 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. Пример создания процесса.


Вход на сайт
Поиск
Календарь
«  Июль 2025  »
ПнВтСрЧтПтСбВс
 123456
78910111213
14151617181920
21222324252627
28293031
Архив записей
Наш опрос
Как Вам удобнее??
Всего ответов: 341
Мини-чат
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Статистика

    Онлайн всего: 1
    Гостей: 1
    Пользователей: 0