Вы находитесь на странице: 1из 49

03_01

Постановка задания: Создание потоков (03_01)

Провести исследования по базовому варианту


Теория вопроса:
Создается поток функцией CreateThread, которая имеет следующий прототип:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // атрибуты защиты
DWORD dwStackSize, // размер стека потока в байтах
LPTHREAD_START_ROUTINE IpStartAddress, // адрес функции
LPVOID lpParameter, // адрес параметра
DWORD dwCreationFlags, // флаги создания потока
LPDWORD lpThreadId // идентификатор потока);
При успешном завершении функция CreateThread возвращает дескриптор созданного потока и его
идентификатор, который является уникальным для всей системы. В противном случае эта функция возвращает
значение NULL.
Рассмотрим некоторые параметры подробнее:
Параметр dwstacksize определяет размер стека, который выделяется потоку при запуске. Если этот параметр
равен нулю, то потоку выделяется стек, размер которого по умолчанию равен 1 Мбайт
Параметр lpstartAddress указывает на исполняемую потоком функцию. Эта функция должна иметь следующий
прототип:
DWORD WINAPI имя_функции_потока(LPVOID lpParameters);
Видно, что функции потока может быть передан единственный параметр lpParameter, который является
указателем на пустой тип. Это ограничение следует из того, что функция потока вызывается операционной
системой, а не прикладной программой. Программы операционной системы являются исполняемыми модулями и
поэтому они должны вызывать только функции, сигнатура которых заранее определена. Поэтому для потоков
определили самый простой список параметров, который содержит только указатель.

Постановка задачи:
Понять принцип параллелизма работы потоков в рамках одного процесса
Выяснить, как будет завершена работа процесса, если поток Add не сможет выполнить свои задачи
Выяснить, что будет происходить, если поток Add не сможет получить управление
Описание ключевых модулей:

11000000000
t1
00100000000
t2
00010000000
t3
00001001000
t4
00000101000
t5
00000011000
t6
00000000100
t7
00000000010
t8
00000000001

Здесь представлены два потока Main и Add, каждый из которых представляет отдельный объект ядра и
функционирует самостоятельно. После запуска приложения (позиции 1,2) первым начинает работу поток Main,
где вначале происходит инициализация переменной inc = 10 (позиция 3), а также объявление дескриптора hThread
и идентификатора IDThread (позиция 3), относящихся к потоку Add. Далее поток Main создает поток Add и передает
ему в качестве параметра значение переменной inc, на которое нужно увеличить значение переменной n. После
создания потока Add, Main передает управление Add и ожидает завершения его работы (позиция 8).
Последовательно поток Add выполняет действия:
1. Выводит сообщение о том, что он создан (позиция 6);
2. Инкрементирует значение переменной (позиция 6);
3. Выводит сообщение о том, что поток завершил свою работу (позиция 6).
4. Поток Add завершает свою работу и передает обратно управление потоку main (позиция 7).
Далее поток Main выводит значение инкрементируемой переменной (позиция 10) и завершает свою работу
(позиция 11).
Поток main создает поток add вызовом
функции

(P1)

Где Add - имя функции, которую должен


выполнить поток, (void*)inc – передаваемый
параметр (должен быть приведен к типу
void*. Если все прошло хорошо, то получим
дескриптор созданного потока в переменной
hThread

Потока Main, продолжает работу и переходит


к выполнению системного вызова
WaitForSingleObject(hThread, INFINITE)(P2). Но,
выполнение функции WaitForSingleObject с
P4 дескриптором hThread потока Add
приостанавливает работу потока Main, и
управление получит поток Add. Далее поток
Main ждет завершения его работы

(P2)
P3 Поток Add получает в качестве параметра
переменную inc = 10 (P3) и увеличивает
значение глобальной переменной n на 10.
Вначале своей работы выводит
диагностическое сообщение “Thread is
P1
started.” о начале своей работы, затем после
выполнения инкрементации сообщает
"Thread is finished." и о завершении своей
P2 работы

P5
P6
(P4)

После чего работа потока Add завершается


естественным путем, что становится сигналом
для продолжения работы потока Main. А
именно функция WaitForSingleObject (P2)
выводит его из состояния ожидания и
передает управление следующим за ней
операторам.

(P5)

который выводит на консоль итоговое


значение переменной n, которое в штатном
варианте будет равно 10.

Далее Main закрывает дескриптор потока


Add (P6)

После всего поток Main завершает свою


Конкретная задача:
Пускай поток Main не будет ожидать выполнения работы потока Add (P1)

Р1

P2

Поток Add (P1) не успел выполнить свою работу, так как main сделал return 0 – процесс завершил свою работу
Достигли мы этого путем изменения второго аргумента функции WaitForSingleObject, с infinite на 0 (P2).
Если этот аргумент равен нулю, функция не переходит в состояние ожидания, если объект не получил сигнала (в
нашем случае он его не получил) он всегда возвращается немедленно.
P3

Завершим работу потока main вызовом функции ExitThread с кодом ноль (P3). Поток thread успел запуститься и
отработать. Поскольку поток main прекратил работу раньше чем вывод в консоль нового значения переменной n,
то мы не увидели строку “n = 10”.
P4

В данном эксперименте поток thread не сможет полностью выполнить свою работу, поскольку его работу
прерывает вызов функции TerminateThread() (P4), в которую первым аргументом передаётся текущего потока
thread, а вторым аргументом является код возвращаемый потоком thread при завершении.
03_03

Завершение работы потоков (03_03), завершение работы потока main

Теория вопроса:

Завершить работу потока можно 4-мя способами: функция потока возвращает управление (рекомендуемый
способ), поток самоуничтожается вызовом функции ExitThread (нежелательный способ); один из потоков данного
или стороннего процесса вызывает функцию TerminateThread (нежелательный способ); завершается процесс,
содержащий данный поток (тоже нежелательно).

1. Функцию потока следует проектировать так, чтобы поток завершался только после того, как она
возвращает управление. Это единственный способ, гарантирующий корректную очистку всех ресурсов,
принадлежавших Вашему потоку.
2. Поток можно завершить принудительно, вызвав: VOID ExitThread(DWORD dwExitCоde); При этом
освобождаются все ресурсы операционной системы, выделенные данному потоку, но C/C++ – ресурсы
(например, объекты, созданные из С++-классов) не очищаются. Именно поэтому лучше возвращать
управление из функции потока, чем самому вызывать функцию ExitThread.
3. Завершение работы потока можно сделать, вызвав: BOOL TerminateThread(HANDLE hThread, DWORD
dwExitCode); В отличие от ExitThread, которая уничтожает только вызывающий поток, эта функция
завершает поток, указанный в параметре hThread. В параметр dwExitCode Вы помещаете значение,
которое система рассматривает как код завершения потока. После того как поток будет уничтожен,
счетчик пользователей его объекта ядра «поток» уменьшится на 1.

В некоторых случая потоку необходимо знать свой дескриптор, для изменения некоторых своих характеристик.
Для этих целей в WinAPI существует функция GetCurrentThread, которая имеет следующий прототип:

HANLDE GetCurrentThread(VOID);

Псевдодескриптор текущего потока отличается от настоящего дескриптора потока тем, что он может
использоваться только самим текущим потоком.

Постановка задачи:

Проверить, что происходит, когда потоки Main и Thread завершают работу самостоятельно, а также и в
случаях вызова функций их принудительного завершения.

Выяснить, как может быть завершена работа потока Main путем применения функции TerminateThread, и
что в это время будет происходить с потоком Thread, если процесс еще не завершил свою работу.
Описание ключевых модулей:

В данном задании присутствуют ключевых объекта поток Main и поток Thread.


Рассмотрим потоки Main и Thread. Поток Main создает поток Thread, путем вызова
функции CreateThread (переход t2). После создания потока Thread поток Main впадает в
ожидание до тех пор, пока мы не введем “y” (позиция 8). В свою очередь поток Thread
увеличивает счетчик count с небольшим перерывом в 100 мс(позиция 7). При вводе “y”
поток main прерывает работу потока thread с помощью TerminateThread(переход t7).
Затем закрывает дескриптор потока Thread (переход t8). Процесс завершает свое
выполнение (переход t9).
Программные элементы:
Создаем поток thread, в hThread
записывается дескриптор потока (P1).

Поток thread начинает свою работу.


Имеется бесконечный цикл, где
P2 инкрементируется значение переменной
(P2).

После введения любого символа кроме y


P1
(P3), main вызывает функцию
TerminateThread, в параметрах которого
передаем дескриптор потока thread(P4).

P3

Поток завершает свою работу, затем


закрывается дескриптор hThread (P5)

P4

P5
Конкретная задача:

Изменения в программе:

1. Увеличение времени приостановки работы потока, вывод переменной (для того, чтобы понять завершил свою
работу поток или нет.

2. Замена дескриптора thread на псевдодескриптор main

В потоке main создаётся и запускается поток-счётчик thread. После создания потока Thread поток Main впадает в
ожидание до тех пор, пока мы не введем “y” (позиция 8). В свою очередь поток Thread увеличивает счетчик count
с небольшим перерывом в 1000 мс (позиция 7). При вводе “y” поток main прерывает работу самого себя с
помощью TerminateThread(переход t7), первым аргументом которого является псевдодескриптор, что получаем
вызовом функции GetCurrentThread(). Как можем заметить, поток main завершил свою работу, но поток thread
продолжил свою работу, после перехода (t8) позиция 7 не перешла в 0.

Процесс продолжает работать после завершения работы потока main функцией TerminateThread, и поток-счётчик
thread продолжает работать.
1

Изменения в программе:

1. Увеличение времени приостановки работы потока, вывод переменной (для того, чтобы понять завершил
свою работу поток или нет.
2. Добавили вызов функции ExitThread().

В потоке main создаётся и запускается поток-счётчик thread. После создания потока Thread поток Main впадает в
ожидание до тех пор, пока мы не введем “y” (позиция 8). В свою очередь поток Thread увеличивает счетчик count
с небольшим перерывом в 1000 мс (позиция 7). При вводе “y” поток main прерывает работу самого себя с
помощью ExitThread с кодом выхода 0(переход t7. Как можем заметить, поток main завершил свою работу, но
поток thread продолжил свою работу, после перехода (t8) позиция 7 не перешла в 0.

Процесс продолжает работать после завершения работы потока main функцией ExitThread, и поток-счётчик thread
продолжает работать.
03_04

Приостановка и возобновление потоков (03_04)

Теория вопроса.

Каждый созданный поток имеет счетчик приостановок, максимальное значение которого равно
MAXIMUM_SUSPEND_COUNT. Счетчик приостановок показывает, сколько раз исполнение потока было
приостановлено. Поток может выполняться только при значении счетчика равным 0. Функция suspendThread
увеличивает значение счетчика на 1. ResumeThread уменьшает значение счетчика на 1 при условии, что до этого
значение было больше 0.

Также каждый поток может задержать свое исполнение вызовом функции sleep, параметр которой есть
число миллисекунд, на которые поток приостанавливает свое исполнение. При значении параметра 0,
исполнение потока просто прерывается.

Постановка задачи

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

Проверить варианты приостановки и возобновления работы потока Main. Выяснить, что в это время будет
происходить с потоком Thread, если процесс еще не завершил свою работу.
Описание ключевых модулей
В данном задании присутствуют ключевых объекта поток Main и поток Thread.
Рассмотрим потоки Main и Thread. Поток Main создает поток Thread, путем вызова
функции CreateThread (переход t2). Поток Main с помощью дескриптора потока Thread
может управлять этим потоком: останавливать и возобновлять его работу. В свою
очередь поток Thread увеличивает счетчик nCount с небольшим перерывом в 100
мс(позиция 7). После создания потока Thread поток Main ожидает ввода клавиши
(позиция 5). После ввода клавиши поток main может завершить работу thread (переход
t12, позиции 15,16, позиция 7 переходит в 0); затем завершить свою работу (позиция 17)
или сделать с ним различные манипуляции. Позиция 10 – вывод значения переменной,
позиции 11,12 – уменьшение значения счетчика приостановок и вывод его на экран,
позиции 13-14 – увеличение значения счетчика приостановок и вывод его на экран. Далее
возвращаемся к 4 позиции и вновь ожидаем ввода клавиши. При вводе клавиши “n”
поток main прерывает работу потока thread с помощью TerminateThread (переход t12).
Затем закрывает дескриптор потока Thread (переход t13). Процесс завершает свое
выполнение (переход t14).
Программные элементы В потоке main создается поток
thread (P1)

Поток thread начинает свою работу.


P2 Имеется бесконечный цикл, где
инкрементируется значение
переменной (P2).

P1

Далее с помощью вызовов этих


функций (передавая в виде
параметра дескриптор потока
thread; SuspendThread – P3,
ResumeThread – P4)

P3

P4
Мы увеличиваем или уменьшаем
значение счетчика приостановок.

TerminateThread, в параметрах
которого передаем дескриптор
P5
потока thread(P5).
P6

Поток завершает свою работу,


затем закрывается дескриптор
hThread (P6)
Конкретная задача

Ситуация с двумя приостановками, сначала с одним восстановлением, потом с полным восстановлением


работы потока

В начале работы программы поток thread смог увеличить значение инкрементируемой переменной.
После двух вызовов suspendThread счетчик приостановок потока thread увеличился до 2. Увеличение значения
переменной не происходит. После двух вызовов resumeThread (после первого вызова поток thread не возобновил
свою работу) счетчик обратно уменьшился до 0. Поток thread возобновил свою работу.

Функции suspendThread и resumeThread возвращают предыдущее значение счётчика приостановок.


1

Изменения в программе:

1. Добавлен дополнительный функционал в программу: после ввода символа ‘v’ происходит остановка
потока main, вызовом функции SuspendThread(), агрументом которой является псевдодескриптор потока
main, получаемый функцией GetCurrentThread().

В потоке main создаётся и запускается поток-счётчик thread. Затем поток main ожидает ввода символа. Вначале
был введён символ ‘y’, отобразилось текущее значение счётчика. После был введен символ ‘s’, что
приостановило работу потока thread вызовом функции SuspendThread(). Далее мы вводим символ ‘y’, чтобы
увидеть текущее значение счётчика. Поскольку значение счётчика не изменилось, то можно сделать вывод, что
работа потока-счётчика thread была приостановлена. Затем вводится символ ‘v’, что приводит к приостановке
работы потока main путем вызова функции SuspendThread(), аргументом которого является псевдодескриптор
потока main, получаемый функцией GetCurrentThread().

Наш процесс завис: оба потока находятся в приостановленном состоянии.


1

Изменения в программе:

1. Изменено время задержки в потоке-счётчике thread на 1000мс и добавлен вывод текущего значения
счётчика после каждой инкрементации.
2. Добавлен дополнительный функционал в программу: после ввода символа ‘v’ происходит остановка
потока main, вызовом функции SuspendThread(), агрументом которой является псевдодескриптор потока
main, получаемый функцией GetCurrentThread().

В потоке main создаётся и запускается поток-счётчик thread. Затем поток main ожидает ввода символа. Вводится
символ ‘v’, что приводит к приостановке работы потока main путем вызова функции SuspendThread(), аргументом
которого является псевдодескриптор потока main, получаемый функцией GetCurrentThread(). Поток-счётчик
thread продолжает свою работу, поскольку его работу никто не приостанавливал. Мы наблюдаем как
продолжается вывод итераций счётчика. Также консоль больше не осуществляет ввод символов с клавиатуры.
03_05

Псевдодескрипторы потоков (03_05)

Теория вопроса:

Иногда потоку требуется знать свой дескриптор, чтобы изменить какие-то свои характеристики. Например,
поток может изменить свой приоритет. Для этих целей в WinAPI существует функция GetCurrentThread, которая
имеет следующий прототип:

HANLDE GetCurrentThread(VOID);

и возвращает псевдодескриптор текущего потока. Псевдодескриптор текущего потока отличается от настоящего


дескриптора потока тем, что он может использоваться только самим текущим потоком и, следовательно, может
наследоваться другими процессами. Псевдодескриптор потока не нужно закрывать после его использования.

Постановка задачи:

Получить псевдодескриптор потока и определить смысл выводимой информации

Описание ключевых модулей:

110000
t1
001000
t2
000100
cin
000110
t3
000001

Имеется поток main. Вызывая функцию GetCurrentThread() получаем псевдодескриптор потока (3). Полученный
псевдодескриптор выводим на консоль и ждем ввода клавиши (позиции 4, 5). Далее завершаем работу потока
(позиция 6).
Программные элементы:
Получаем псевдодескриптор
запущенного потока(P1) и выводим его
значение на экран(P2)

Выводимая информация есть указатель


P1 на адрес памяти, где находится все
информация про данный поток, его
P2
данные, его свойства.
04_01, 04_02, 04_03
Перечень поставленных вопросов. Создание процессов (4.1, 4.2, 4.3). Провести
исследование по базовому варианту.
Теория вопроса.
Новый процесс в Windows создается вызовом функции CreateProcess, которая имеет
следующий прототип:
BOOL CreateProcess (
LPCTSTR lpApplicationName, // имя исполняемого модуля
LPTSTR lpCommandLine, // командная строка
LPSECURITY_ATTRIBUTES lpProcessAttributes, // защита процесса
LРSECURITY_ATTRIBUTES lpThreadAttributes, // защита потока
BOOL blnheritHandles, // признак наследования дескриптора
DWORD dwCreationFlags, // флаги создания процесса
LPVOID IpEnvironment, // блок новой среды окружения
LPCTSTR lpCurrentDirectory, // текущий каталог
LPSTARTUPINFO IpStartUpInfo, // вид главного окна
LPPROCESS_INFORMATION lpProcessInformation // информация о процессе
);
Функция CreateProcess возвращает ненулевое значение, если процесс был создан
успешно. Если процесс не был создан, то функция возвращает значение false.
Первый параметр lpApplicationName определяет строку с именем исполняемого файла,
который имеет тип ехе и будет запускаться при создании нового процесса.
Параметр lpCommandLine определяет строку с именем командной строки.
Атрибуты lpProcessAttributes, lpThreadAttributes определяют защиту процесса и защиту
потока соответственно.
Параметр blnheritHandles определяет будут ли передаваться наследуемые дескрипторы
родительского процесса дочернему: true – будут, false – нет.
Параметр dwCreationFlags определяет создание процесса: true – да, false – нет.
Параметр IpEnvironment определяет блок новой среды.
Параметр lpCurrentDirectory определяет текущий каталог.
Параметр pStartUpInfo определяет вид главного окна.
Параметр lpProcessInformation определяет информацию о создаваемом процессе.
Постановка задачи. В рамках выполняемого задания необходимо изучить и проверить
следующее:
 Проверить, что происходит, когда процессы завершают работу.
 Выяснить роль дескрипторов и назначение функции CloseHandle.
 Выяснить, как и какие параметры используются при различных системных вызовах
CreateProcess.
Описание ключевых модулей.

Граф для 4.1, 4.2 Граф для 4.1, 4.3

110000000000000000000 000000000000001100000
T12
T1
001000000000000000000 000000000000000010000
T2 T13
000100100000000000000 000000100000000001000
T5 T14
T5
000100010000000000000 000000010000000001000 000000100000000000100
T6
T6 Ввели символ

0001000010000000000000 000000001000000001000 000000100000000000110


T7 T7 T15
0001000000100000000000 000000000100000001000 000000100000000000001
T9 T8 T10
T9 T8 T10
000100000010000000000 000100000001000000000
Ввели символ
000100000001100000000 00000000010000001000 000000000001000001000
T11 Ввели символ

000100000000010000000 000000000001100001000
T3 T11
000010000000000000000 000000000000010001000
T4
000001000000000000000
В данном задании присутствует 3 ключевых объекта: процесс 4.1, процесс 4.2, процесс
4.3.
Рассмотрим процессы 4.1 - дочерний, 4.2 - родительский. Процесс 4.2 является
родительским, т.е. он создает дочерний процесс, которым является 4.1, путем вызова
функции CreateProcess (переход T2). В данном случае в процесс 4.1 передается имя
исполняемого модуля, без переменных. После создания процесса 4.1 процесс 4.2 впадает
в ожидания до тех пор, пока процесс 4.1 не завершится (позиция 4 в графе). В свою очередь
процесс 4.1. выводит сообщения о своем создании (позиция 8 в графе) и т.к. входные
параметры отсутствуют, то он переходит в режим ожидания ввода символа (переход Т10),
после ввода которого процесс 4.1 завершает свою работу (переход Т11). После завершения
работы процесса 4.1 и получения управления (переход Т3) процесс 4.2 закрывает
дескрипторы процесса 4.1, а после завершает свою работу.
Рассмотрим процессы 4.1 - дочерний, 4.3 - родительский. Процесс 4.3 является
родительским процесса 4.1, который он создает путем вызова функции CreateProcess
(переход Т13). В этом случае в качестве параметра передается командная строка с
переменными . После создания процесса 4.1, процесс 4.3
выводит сообщение о создании процесса и переходит в режим ожидания ввода символа
(переход Т14), после ввода которого процесс 4.3 завершает свою работу (переход Т15), т.е
процесс 4.3 можно завершить без ожидания завершения работы процесса 4.1. В свою
очередь после завершения работы процесса 4.3, процесс 4.1 продолжит свою работу.
Процесс 4.1. выводит сообщения о своем создании (позиция 8 в графе) и, т.к. теперь в
качестве входных параметров передавались переменные, то они все выводятся (переходы
Т8, Т9), после вывода всех переменных, процесс 4.1 переходит в режим ожидания ввода
символа (переход Т10), после ввода которого процесс 4.1 завершает свою работу (переход
Т11).
Программные элементы.
В функцию main процесса 4.1
могут передаваться
некоторые параметры, а
именно имя процесса и
Р1
значения переменных (Р1).

Процесс 4.1 выводит свое


имя. Также посредством
выполнения цикла (Р2)
выводит все значения
передаваемых переменных в
Р2
процесс.

Далее процесс переходит в


ожидание символа, после
ввода которого процесс
завершает свою работу.

После начала работы процесса 4.2,


определяется имя исполняемого
модуля для процесса 4.1 (Р3).

Далее определяются параметры


Р3
процесса 4.1, которые будут
содержать информацию о нем.

В P4 путем вызова функции


Р4 ZeroMemory все поля структуры si
типа STARTUPINFO заполняются
нулями.

Далее путем вызова функции


Р5 CreateProcess, одним из
параметров является имя
исполняемого модуля, создается
процесс 4.1, а в случае провала
выводятся сообщения о его не
Р6
создании (Р5).

Далее процесс 4.2 впадает в


ожидание завершения работы
процесса 4.1 путем вызова
функции WaitForSingleObject с
параметром дескриптора процесса
4.1 (Р6).

После завершения работы


процесса 4.1, процесс 4.2
закрывает дескрипторы процесса
4.1 и завершает свою работу.
После начала работы процесса
4.3, определяется имя командной
строки с некоторыми
передаваемыми переменными
Р7 (Р7).

Далее аналогично программе


процесса 4.2 определяются
параметры процесса 4.1 которые
будут содержать информацию о
Р8 нем.

Далее путем вызова функции


ZeroMemory все поля структуры si
типа STARTUPINFO заполняются
нулями.

Путем вызова функции


CreateProcess, одним из
параметров является командная
строка с некоторыми
передаваемыми переменными,
создается процесс 4.1 (Р8).

Далее процесс 4.3 закрывает


дескрипторы процесса 4.1 и
ожидает ввода символа для
завершения своей работы.

Конкретные задачи.
Запустим программу 4.2 и добавим к имени исполняющего модуля несколько
переменных (P9).

Р9

Как видно на картинке процесс 4.2 вывел


что процесс 4.1 не был создан, т.к.
неверно введено имя процесса 4.1.
Запустим программу 4.2 и передадим в функцию CreateProcess имя переменной процесса
в качестве второго параметра, т.е. как консольную строку (Р10).

Как видно на картинке, процесс 4.1 был


создан и, т.к. входные переменные
отсутствуют, вывел только свое имя.
Р10

Запустим программу 4.3 и передадим в функцию CreateProcess вместо параметра


CREATE_NEW_CONSOLE NULL (P11).

Как видно на картинке, процесс 4.1 был


Р11 создан, но т.к. новое консольное окно не
создавалось, то вся информация была
выведена в окне процесса 4.3.

Запустим программу 4.3 и не будем занулять поля структуры si (P12).

P12 Как видно на картинке, процесс 4.1 был создан,


но консольное окно для нее не было создано.
04_04
Перечень поставленных вопросов. Запуск процессов (04_04).
Теория вопроса.
Новый процесс в Windows создается вызовом функции CreateProcess, которая
имеет следующий прототип:
BOOL CreateProcess (
LPCTSTR lpApplicationName, // имя исполняемого модуля
LPTSTR lpCommandLine, // командная строка
LPSECURITY_ATTRIBUTES lpProcessAttributes, // защита процесса
LРSECURITY_ATTRIBUTES lpThreadAttributes, // защита потока
BOOL blnheritHandles, // признак наследования дескриптора
DWORD dwCreationFlags, // флаги создания процесса
LPVOID IpEnvironment, // блок новой среды окружения
LPCTSTR lpCurrentDirectory, // текущий каталог
LPSTARTUPINFO IpStartUpInfo, // вид главного окна
LPPROCESS_INFORMATION lpProcessInformation // информация о процессе
);
Функция CreateProcess возвращает ненулевое значение, если процесс был создан
успешно. Если процесс не был создан, то функция возвращает значение false.
Первый параметр lpApplicationName определяет строку с именем исполняемого файла,
который имеет тип ехе и будет запускаться при создании нового процесса.
Параметр lpCommandLine определяет строку с именем командной строки.
Атрибуты lpProcessAttributes, lpThreadAttributes определяют защиту процесса и защиту
потока соответственно.
Параметр blnheritHandles определяет будут ли передаваться наследуемые дескрипторы
родительского процесса дочернему: true – будут, false – нет.
Параметр dwCreationFlags определяет создание процесса: true – да, false – нет.
Параметр IpEnvironment определяет блок новой среды.
Параметр lpCurrentDirectory определяет текущий каталог.
Параметр pStartUpInfo определяет вид главного окна.
Параметр lpProcessInformation определяет информацию о создаваемом процессе.
Постановка задачи. В рамках выполняемого задания необходимо изучить следующее:
 Выяснить, что необходимо сделать, чтобы запускать дочерние процессы с рабочего
стола.
Описание ключевых модулей.

В данном задании присутствует 2 ключевых объекта: процесс 04_04, процесс


Notepad.exe.
Рассмотрим процессы Notepad.exe - дочерний, 04_04 - родительский. Процесс 04_04
является родительским, т.к. он создает дочерний процесс, которым является Notepad.exe,
путем вызова функции CreateProcess (переход T2). После создания процесса Notepad.exe,
процесс 04_09 ждет 1000 мс, после чего закрывает дескриптор процесса Notepad.exe
(позиция 5 в графе) и на этом завершается работа процесса 04_04 (позиция 6 в графе).
После запуска, процесс Notepad.exe работает самостоятельно: состояние процесса 04_04
никак на него не влияет. Процесс Notepad.exe завершается (позиция 10 в графе).
Программные элементы:

В родительском процессе работает поток


main и создает дочерний процесс (P1).

Используем для корректного запуска


Notepad.exe вторым аргументом функции
CreateProcess без указывания параметров
командной строки (Notepad.exe
находится в основном каталоге Windows,
поэтому нам не нужно указывать полный
P1 путь). Значение первого аргумента ставим
в NULL (P1).

После запуска Notepad.exe, закрываем


дескрипторы дочернего процесса
Notepad.exe, вызывая CloseHandle и
завершаем его работу (P2).

P2

Примечание:

Для запуска некоторого процесса с рабочего стола мы должны в значение второго поля указать полный путь до
приложения C:\Document and Settings\Administrator\Desktop\ConsoleApp.exe, где ConsoleApp.exe – пример
приложения, которое расположено на рабочем столе. При инициализации символьной переменной, содержащей
абсолютный путь к приложению, стоит писать \\ вместо \, потому-что символ \ используется для escape-
последовательностей, специальных символов в языках C/C++.
04_05

Завершение работы процессов (04_05)

Теория вопроса.

Процесс можно завершить четырьмя способами:

 входная функция первичного потока возвращает управление (рекомендуемый способ),

 один из потоков процесса вызывает функцию ExitProcess (нежелательный способ);

 поток другого процесса вызывает функцию TerminateProcess (тоже нежелательно);

 все потоки процесса умирают по своей воле (большая редкость).

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

 любые С++-объекты, созданные данным потоком, уничтожаются соответствующими деструкторами;

 система освобождает память, которую занимал стек потока;

 система устанавливает код завершения процесса (поддерживаемый объектом ядра "процесс") — его и
возвращает Ваша входная функция;

 счетчик пользователей данного объекта ядра "процесс" уменьшается на 1.

Функция ExitProcess. Процесс завершается, когда один из его потоков вызывает ExitProcess:

VOID ExilProcess(UINT fuExitCode);

Эта функция завершает процесс и заносит в параметр fuExitCode код завершения процесса. Возвращаемого
значения у ExitProcess нет, так как результат ее действия — завершение процесса. Если за вызовом этой функции
в программе присутствует какой-нибудь код, он никогда не исполняется.

Функция TerminateProcess. Вызов функции TerminateProcess тоже завершает процесс:

BOOL TerminateProcess(HANDLE hProcoss, UINT fuExitCode);

Главное отличие этой функции от ExitProcess в том, что ее может вызвать любой поток и завершить любой
процесс. Параметр hProcess идентифицирует описатель завершаемого процесса, а в параметре fuExitCode
возвращается код завершения процесса

Постановка задачи

- Проверить, что происходит, когда потоки Main и Thread завершают работу самостоятельно, а также и в случаях
вызова функций их принудительного завершения.
- Выяснить роль дескрипторов, а также и назначение функции CloseHandle.
- Выяснить, как может быть завершена работа потока Main путем применения функции TerminateThread, и что в
это время будет происходить с потоком Thread.
Описание ключевых модулей

11000000000
T1
00100000000
t2
00010000010
t8
00010000001
cin
00011000001
t3
t5 00000100001
t4 t6
00000010001 00000001001
t7
00000000100

В данном задании присутствует 2 ключевых объекта: поток main, поток-счётчик thread.


Рассмотрим потоки main и thread. Поток main создает поток-счётчик thread, путем вызова функции
CreateThread (переход T2). Затем поток main выводит сообщения о том, что для отображения текущего
значения счётчика нужно ввести символ ‘y’, а для завершения работы процесса требуется ввести не
символ ‘y’ (позиция 4 в графе). После ввода не символа ‘y’ поток main завершает работу процесса 04_05
(переход Т7) при помощи функции ExitProcess. При завершении работы процесса 04_05 останавливается
работа всех потоков – main и thread (позиция 9 на графе).
Программные элементы

Поток-счётчик thread совершает


инкрементацию счётчика (переменной
P1 count) каждые 100 мс(P1).

В потоке main создаётся и запускается


поток-счетчик thread (P2).
P2
Поток main выводит в консоль
сообщение, которое сообщает
пользователю, что при вводе символа
‘y’ выведется текущее значение
P3 счетчика в консоль, а при вводе любого
другого символа – произойдет
P4 остановка процесса (P3).

После ввода не символа ‘y’,


завершается работа процесса 04_05,
посредством функции ExitProcess.
Конкретная задача:

Завершим работу процесса вызовом функции TerminateProcess (P5) в потоке main. Для этого сделаем вызов
GetCurrentProcess – для получения псевдодескриптора текущего процесса, и передадим его в качестве параметра
в функцию TerminateProcess. Код выхода оставим 1.

Р5

Программа отработала, как и предполагалось – процесс завершил свою работу.


Завершим работу процесса вызовом функции TerminateProcess (P6) в потоке thread. Для этого сделаем
вызов GetCurrentProcess – для получения псевдодескриптора текущего процесса, и передадим его в качестве
параметра в функцию TerminateProcess. Код выхода оставим 1.

P6

Поток thread успел сделать только одну инкрементацию. Процесс завершился мгновенно, так как
TerminateProcess выполняется в самом начале работы потока thread.
После ввода любого символа, кроме y, завершим работу потока main вызовом функции TerminateThread (P7). В
качестве параметров передадим псведодескриптор потока main, который получим с помощью вызова
GetCurrentThread.

Для определения состояния потока thread: увеличим время приостановки работы потока до 1000мс и после
каждого инкремента будем выводит значение переменной на консоль (P8).

P8

P7

Как видим поток main завершил свою работу, но так как поток thread продолжил свою работу (его никто не
завершал), то процесс продолжил функционировать.
04_06, 04_07

Завершение процессов (04_06, 04_07)

Теория вопроса
Процесс можно завершить четырьмя способами:

 входная функция первичного потока возвращает управление (рекомендуемый способ),


 один из потоков процесса вызывает функцию ExitProcess (нерекомендуемый способ);
 поток другого процесса вызывает функцию TerminateProcess (нерекомендуемый способ);
 все потоки процесса умирают по своей воле (нерекомендуемый способ)

TerminateProcess может вызвать любой поток и завершить любой процесс. Если функция TerminateProcess
выполнилась успешно, то она возвращает ненулевое значение. В противном случае возвращаемое значение
равноe false. Функция TerminateProcess завершает работу процесса, но не освобождает все ресурсы,
принадлежащие этому процессу. Параметр hProcess идентифицирует описатель завершаемого процесса, а в
параметре fuExitCode возвращается код завершения процесса. TerminateProcess –функция асинхронная, т. e. она
сообщает системе, что Вы хотите завершить процесс, но к тому времени, когда она вернет управление, процесс
может быть еще не уничтожен.

При завершении процесса, выполнение всех потоков в процессе прекращается.

 Все User- и GDI-объекты, созданные процессом, уничтожаются, а объекты ядра закрываются (если их не
использует другой процесс).

 Код завершения процесса меняется со значения STILL_ACTIVE на код, переданный в ExitProcess или
TerminateProcess.

 Объект ядра "процесс" переходит в свободное, или незанятое (signaled), состояние. Прочие потоки в системе
могут приостановить свое выполнение вплоть до завершения данного процесса.

 Счетчик объекта ядра "процесс" уменьшается на 1.( и объект разрушается, как только его счетчик обнуляется.)

Связанный с завершаемым процессом объект ядра не высвобождается, пока не будут закрыты ссылки на него и
из других процессов. Однако область памяти, выделенная системой для объекта ядра "процесс", не
освобождается, пока счетчик числа его пользователей не достигнет нуля. А это произойдет, когда все прочие
процессы, создавшие или открывшие описатели для ныне – покойного процесса, уведомят систему (вызовом
CloseHandle) о том, что ссылки па этот процесс им больше не нужны.

Постановка задачи
В рамках выполняемого задания необходимо изучить и проверить следующее:
 работу некоторых функции API Windows
 принцип работы параллелизма процессов
 завершение работы процессов
Описание ключевых модулей

В данном задании присутствует 2 ключевых объекта: процесс 4.6, процесс 4.7 Процесс 4.7
является родительским, т.е. он создает дочерний процесс, которым является 4.6, путем
вызова функции CreateProcess (переход t2). В данном случае в процесс 4.6 передается имя
исполняемого модуля, без переменных.
Следует отметить, что для начала работы нужно получить исполняемый файл 4.6, назвать
его ConsoleProcess.exe и поместить его в корень диска C:.
После запуска приложения (позиции 1,2) первым начинает работу процесс 4.7, где вначале
происходит инициализация переменной char lpszAppName[] (позиция 3). После создаем
новый консольный процесс(дочерний 4.6).(позиции после перехода t2). Начало
выполнения дочернего процесса 4.6 процесса (позиция 11). Этот процесс выполняет роль
бесконечно работающего счетчика, что ежесекундно производит инкрементацию и
выводит текущее значение в консоль своего процесса. Процесс 4.7 переходит в режим
ожидания ввода символа (позиция 5), и если введен символ “t” процесс 4.7 завершает
работу процесса 4.6 (позиция 8), Затем закрывает дескрипторы дочернего
процесса(позиция 9). После процесс 4.7 завершает свою работу (позиция 10).
Описание программы

Данная программа реализует


процесс, бесконечно работающий
счетчик

(P1) , что ежесекундно


производит инкрементацию
и выводит текущее
значение в консоль.
P1
,
после небольшой задержки в
P2
1000 мс (P2)
Задаем место расположение
исполняемого файла (P3)

Создаем новый процесс (P4)


P3

При вводе символа ‘t’ завершаем новый


процесс (P5)

Используя дескриптор.
P4
Далее закрываем дескрипторы (P6)

И родительский процесс завершает свою


работу (P7)

P5

P6

P7
Конкретная задача:
Завершим работу родительского
процесса (4.7) вызовом функции
TerminateProcess (P8), в качестве
передаваемого параметра используем
псевдодескриптор родительского
процесса, который получаем вызовом
GetCurrentProcess.

Родительский процесс завершил свою


работу. Процесс 4.6 как видно на
скриншотах ниже продолжил свое
функционирование.

P8
Попробуем завершить работу
дочернего процесса 4.6 вызовом
функции TerminateThread, где мы
передаем в качестве параметра
дескриптор главного потока дочернего
процесса (P9).

Так как это был единственный поток,


который работал в дочернем
процессе, то дочерний процесс
завершил свою работу

P9
04_08, 04_09
Наследование дескрипторов: 04_08, 04_09.
Общее описание задачи.
Краткие теоретические сведения.
Новый процесс в Windows создается вызовом функции CreateProcess, которая имеет
следующий прототип:
BOOL CreateProcess (
LPCTSTR lpApplicationName, // имя исполняемого модуля
LPTSTR lpCommandLine, // командная строка
LPSECURITY_ATTRIBUTES lpProcessAttributes, // защита процесса
LРSECURITY_ATTRIBUTES lpThreadAttributes, // защита потока
BOOL blnheritHandles, // признак наследования дескриптора
DWORD dwCreationFlags, // флаги создания процесса
LPVOID IpEnvironment, // блок новой среды окружения
LPCTSTR lpCurrentDirectory, // текущий каталог
LPSTARTUPINFO IpStartUpInfo, // вид главного окна
LPPROCESS_INFORMATION lpProcessInformation // информация о процессе
);
Функция CreateProcess возвращает ненулевое значение, если процесс был создан
успешно. Если процесс не был создан, то функция возвращает значение false.
Первый параметр lpApplicationName определяет строку с именем исполняемого файла,
который имеет тип ехе и будет запускаться при создании нового процесса.
Параметр lpCommandLine определяет строку с именем командной строки.
Атрибуты lpProcessAttributes, lpThreadAttributes определяют защиту процесса и защиту
потока соответственно.
Параметр blnheritHandles определяет будут ли передаваться наследуемые дескрипторы
родительского процесса дочернему: true – будут, false – нет.
Параметр dwCreationFlags определяет создание процесса: true – да, false – нет.
Параметр IpEnvironment определяет блок новой среды.
Параметр lpCurrentDirectory определяет текущий каталог.
Параметр pStartUpInfo определяет вид главного окна.
Параметр lpProcessInformation определяет информацию о создаваемом процессе.
Постановка задачи. В рамках выполняемого задания нам необходимо изучить
следующие вопросы:
 Выяснить роль дескрипторов, а также их назначение. Роль функции CloseHandle;
 Выяснить, как может быть завершена работа потока Main путем применения
функции TerminateThread, и что в это время будет происходить с потоком Thread,
если процесс еще не завершил свою работу.
Описание ключевых модулей. Сеть Петри, с помощью которой происходит описание
выполнения поставленной задачи, и табличный граф состояний:

В данном задании присутствует 2 ключевых объекта: процесс 04_08, процесс 04_09.


Рассмотрим процессы 04_08 - дочерний, 04_09 - родительский. Процесс 04_09 является
родительским, т.к. он создает дочерний процесс, которым является 04_08, путем вызова
функции CreateProcess (позиции после перехода T6). В данном случае в процесс 04_08
передается дескриптор потока-счётчика thread 04_09. После создания процесса 04_08
процесс 04_09 впадает в ожидание до тех пор, пока поток-счетчик thread не завершится
(позиция 9 в графе). В свою очередь, процесс 04_08 выводит сообщения о том, что для
завершения потока требуется ввести символ ‘t’ (позиция 17 в графе). После ввода символа
‘t’ процесс 04_08 завершает работу потока-счётчика thread процесса 04_09 (позиция 21).
После завершения работы потока-счётчика thread процесса 04_09 и получения управления
(позиция 11, 12) процесс 04_09 закрывает дескрипторы потока-счётчика thread и далее
завершает свою работу (позиция 13).

Программные элементы.

В функцию main процесса


P1 04_08 передаётся как
параметр дескриптор потока-
счётчика thread из процесса
04_09 (Р1).

P2 Полученный дескриптор
потока-счётчика thread из
процесса 04_09 декодируется
(P2).
P3 Процесс 04_08 переходит в
ожидание символа (P3). После
ввода символа ‘t’ процесс
04_08 завершает работу
потока-счётчика thread из
процесса 04_09, используя
функцию TerminateThread в
которую передаётся
P4 дескриптор потока-счётчика
thread из процесса 04_09.
После этого процесс завершает
свою работу (Р4).
Поток-счётчик thread совершает
инкрементацию счётчика каждые 500
мс(P5).

P5

Процесс 04_09 выводит строку, что


уведомляет пользователя о
необходимости ввести символ для
запуска потока-счётчика thread.

Устанавливается флаг
в значение ,
что делает дескриптор потока
наследуемым (P6).

После этого с помощью функции


P6 CreateThread создаётся поток-
счетчик thread, с наследуемым
дескриптором (P7).
P7
Создаётся и запускается процесс 04_08 и
ему передается дескриптор потока-
счётчика thread, что был преобразован в
целочисленное представление (P8)
P8

Ожидается завершение потока-счётчика


thread с помощью функции
WaitForSingleObject.

Затем выводится строка, что уведомляет


пользователя о необходимости ввести
символ для завершения работы процесса
P9 04_09. Ожидается ввод символа и
завершается процесс 04_09 (P9)
Конкретные задачи

04_09

Р10

Передадим в командную строку псевдодескриптор


потока main родительского процесса 04_09 (Р10). Так
как любой поток будет рассматривать любой
псевдодескриптор как собственный, то после ввода
‘t’ дочерний процесс 04_08 завершил свой
единственный поток и завершил работу (P11).
Родительский процесс и все его потоки продолжили
функционировать.
04_08

P11
P12

При установке флага bInheritHandles в состояние не TRUE (P12) в параметрах функции CreateProcess в процессе
04_09, все наследуемые дескрипторы текущего процесса не будут наследоваться дочерним процессом. Любое
взаимодействие с этими дескрипторами в дочернем процессе ни к чему не приведет.

Как видим дочерний процесс завершил свою работу, не завершив работу потока родительского процесса.
Родительский процесс так же выполняет свои функции.

Р13

Абсолютно схожая ситуация, если сам создаваемый поток не является наследуемым. При установке в
SECURITY_ATTRIBUTES поля bInheritHandle в значение FALSE (Р13) новый создаваемый поток будет не
наследуемым в процессе 04_09. Мы увидим аналогичную картину, которая была в предыдущем случае.