На сегодня общепринятым является взгляд на ОС как на систему, обеспечивающую параллельное (или псевдопараллельное) выполнение набора последовательных процессов или просто процессов. Задача ОС состоит в том, чтобы организовать их поддержку, которая подразумевает, что каждый процесс получит все необходимые ему ресурсы (место в памяти, процессорное время и т.д.). Считается также, что независимые процессы не должны влиять друг на друга, а процессы, которым необходимо обмениваться информацией, должны иметь возможность сделать это путем
Из курса теории операционных систем известно, что процесс является
Для описания столь сложного динамического объекта ОС поддерживает набор структур, главную из которых принято называть блоком управления процессом (
Классический процесс содержит в своем адресном пространстве одну программу. Однако во многих ситуациях целесообразно поддерживать в
(рис 5.1) Процесс с несколькими потокамиВ этом случае процесс можно рассматривать в качестве контейнера ресурсов, а все проблемы, связанные с динамикой исполнения, решаются на уровне потоков. Обычно каждый процесс начинается с одного потока, а остальные (при необходимости) создаются в ходе выполнения. Теперь уже не процесс, а поток характеризуется состоянием, поток является единицей
В 32-разрядной версии системы у каждого процесса есть 4-гигабайтное адресное пространство, в котором пользовательский код занимает нижние 2 гигабайта (в серверах 3 Гбайта). В своем адресном пространстве, которое представляет собой набор регионов и описывается специальными структурами данных (см. часть III "система управления памятью"), процесс содержит потоки, учетную информацию и ссылки на ресурсы, которые обобществляются всеми потоками процесса.
(рис 5.2) Управляющие структуры данных процессаСодержимое блока EPROCESS подробно описано в . Блок KPROCESS (на рис. справа), блок переменных
Идентификатор процесса кратен четырем и используется в роли байтового индекса в таблицах ядра наравне с другими объектами.
Обычно процесс создается другим процессом вызовом Win32-функции CreateProcess (а также CreateProcessAsUser и CreateProcessWithLogonW ).
На первом этапе, выполняемом библиотекой kernel32.dll в режиме пользователя, на диске отыскивается нужный файл-образ, после чего создается объект "раздел" памяти для его проецирования на адресное пространство нового процесса.
На втором этапе выполняется обращение к системному сервису NtCreateProcess для создания объекта "процесс". Формируются блоки EPROCESS, KPROCESS и блок переменных окружения
На третьем этапе в уже полностью проинициализированном объекте "процесс" необходимо создать первичный поток. Это, посредством
Затем kernel32.dll посылает подсистеме Win32 сообщение, которое содержит информацию, необходимую для выполнения нового процесса. Данные о процессе и потоке помещаются, соответственно, в список процессов и список потоков данного процесса, затем устанавливается
Наконец, запускается первичный поток, для чего формируются его начальный контекст и стек, и выполняется запуск стартовой процедуры потока режима ядра KiThreadStartup. После этого стартовый код из библиотеки C/C++ передает управление функции main() запускаемой программы.
В книге этапы
Таким образом, если приложение намерено создать новый процесс, один из его потоков должен обратиться к Win32-функции CreateProcess.
BOOL CreateProcess( PCTSTR pszApplicationName, PTSTR pszCommandLine, PSECURITY_ATTRIBUTES psaProcess, PSECURITY_ATTRIBUTES psaThread, BOOL bInheritHandles, DWORD fdwCreate, PVOID pvEnvironment, PCTSTR pszCurDir, PSTARTUPINFO psiStartInfo, PPROCESS_INFORMATION ppiProcInfo);
Описание параметров функции можно посмотреть в MSDN.
Формально ОС Windows не поддерживает какой-либо иерархии процессов, например, отношений "родительский-дочерний". Однако, негласная иерархия, заключающаяся в том, кто чьим дескриптором (описателем) владеет, все же существует. Например, владение дескриптором процесса позволяет влиять на его адресное пространство и функционирование. В данном случае описатель ppiProcInfo. Хотя он не может быть напрямую передан другому процессу, тем не менее, имеется возможность передать другому процессу его дубликат. Таким путем при необходимости в группе процессов может быть сформирована нужная иерархия.
В качестве упражнения рекомендуется осуществить прогон программы, создающей дочерний процесс.
#include <windows.h>
#include <stdio.h>
void main( VOID )
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcInfo;
TCHAR CommandLine[] = TEXT("sleep");
ZeroMemory( StartupInfo, sizeof(StartupInfo) );
StartupInfo.cb = sizeof(StartupInfo);
ZeroMemory( ProcInfo, sizeof(ProcInfo) );
if( !CreateProcess( NULL, // Не используется имя модуля
CommandLine, // Командная строка
NULL, // Дескриптор процесса не наследуется.
NULL, // Дескриптор потока не наследуется.
FALSE, // Установка описателей наследования
0, // Нет флагов создания процесса
NULL, // Блок переменных окружения родительского процесса
NULL, // Использовать текущий каталог родительского процесса
StartupInfo, // Указатель на структуру STARTUPINFO.
ProcInfo ) // Указатель на структуру информации о процессе.
)
printf( "CreateProcess failed." );
// Ждать окончания дочернего процесса
WaitForSingleObject( ProcInfo.hProcess, INFINITE );
// Закрыть описатели процесса и потока
CloseHandle( ProcInfo.hProcess );
CloseHandle( ProcInfo.hThread );
}
В приведенной программе имя запускаемого модуля передается через второй параметр функции CreateProcess. В примере в качестве дочерней программы используется простейшая программа sleep, задача которой - выдержать паузу длительностью 10 секунд.
#include <windows.h>
#include <stdio.h>
void main( VOID )
{
printf("Данная программа будет спать
в течение 10000 мс\n");
Sleep(10000);
}
Выполнение обеих программ можно проконтролировать с помощью диспетчера задач.
Завершение процесса может быть осуществлено различными способами, например, с помощью функций ExitProcess, TerminateProcess. Однако, единственным способом, гарантирующим корректную очистку всех ресурсов, является возврат управления входной функцией первичного потока. Помимо перечисленных в системе имеется много полезных функций, реализующих API для управления процессами. Их полный перечень содержится в MSDN.
При завершении процесса сопоставленный с ним объект ядра "процесс" не освобождается до тех пор, пока не будут закрыты все внешние ссылки на этот объект.
Каждый новый процесс содержит, по крайней мере, один поток, остальные потоки создаются динамически. Потоки составляют основу планирования и могут: выполняться на одном из процессоров, ожидать события или находиться в каком-то ином состоянии (см. рис 5.3 и лекцию "Планирование потоков").
(рис 5.3) Состояния потоков в ОС Windows (версии Server 2003)Обычно в состоянии "Готовности" имеется очередь готовых к выполнению (running) потоков. В данном случае это состояние распадается на три составляющих. Это, собственно, состояние "Готовности (Ready)"; состояние "Готов. Отложен (Deferred Ready)", что означает, что поток выбран для выполнения на конкретном процессоре, но пока не запланирован к выполнению; и, наконец, состояние "Простаивает (
В состоянии "Ожидания (Waiting)" поток блокирован и ждет какого-либо события, например, завершения операции ввода-вывода. При наступлении этого события поток переходит в состояние "Готовности". Этот путь может проходить через промежуточное "Переходное (Transition)" состояние в том случае, если стек ядра потока выгружен из памяти.
Код ядра выполняется в контексте текущего потока. Это означает, что при прерывании, системном вызове и т д., то есть когда процессор переходит в режим ядра и управление передается ОС, переключения на другой поток (например, системный) не происходит. Контекст потока при этом сохраняется, поскольку операционная система все же может принять решение о смене характера деятельности и переключении на другой поток. Вследствие этого в некоторых курсах по операционным системам состояние "Выполнение" разделяют на "Выполнение в режиме пользователя" и "Выполнение в режиме ядра".
Системный монитор (обкладка "Производительность") представляет собой удобное средство наблюдения за состояниями потоков. Предлагается осуществить прогон программы, которая содержит длительные циклы счета и ожидания. Например, программа, вычисляющая 5*107 значений функций sin(x):
#include <windows.h>
#include <stdio.h>
#include <math.h>
VOID main( VOID ) {
int i,N=50000000;
double a,b;
getchar();
printf("Before circle\n");
for ( i = 0; i<N; i++) {
b=(double)i / (double)N;
a=sin(b);
}
printf("After circle\n");
getchar();
}
Графическое представление предполагает присвоение цифровых значений различным состояниям потока (например, готовность - 1, выполнение - 2, ожидание - 5 и т.п.). Результат работы монитора на однопроцессорной системе для данной программы представлен на рис 5.4.
(рис 5.4) Иллюстрация перехода потока из одного состояния в другоеГоризонтальные участки со значением 5 соответствуют ожиданию нажатия клавиши ввода, а значению 1 (читателю предлагается самостоятельно ответить на вопрос, почему значению 1, а не значению 2) соответствует счетный цикл.
Идентификаторы потоков, так же как и идентификаторы процессов, кратны четырем, выбираются из того же пространства, что и идентификаторы процессов, и с ними не пересекаются.
Как уже говорилось, когда поток обращается к системному вызову, то переключается в режим ядра, после чего продолжает выполняться тот же поток, но уже в режиме ядра. Поэтому у каждого потока два стека, один работает в режиме ядра, другой - в режиме пользователя. Один и тот же стек не может использоваться и в режиме пользователя, и в режиме ядра. Любой поток может делать все что угодно со своим собственным стеком (стеком режима пользователя), в том числе организовывать несколько стеков и переключаться между ними. Поток сам может определять размер своего стека. При этом нельзя гарантировать, что стек будет иметь достаточный размер, чтобы код ядра выполнился безо всяких проблем. Поскольку возникновение исключительной ситуации в режиме ядра может привести к краху всей системы, необходимо исключить такую возможность, что и осуществляется путем организации отдельного стека для режима ядра. Так как в режиме ядра могут одновременно находиться несколько потоков и между ними может происходить переключение, у каждого из них должен быть отдельный стек режима ядра.
Помимо состояния, идентификатора и двух стеков, у каждого потока есть контекст, маркер доступа, а также небольшая собственная память для хранения локальных переменных, например, для запоминания кода ошибки. Поскольку процесс является контейнером ресурсов всех входящих в него потоков, любой поток может получить доступ ко всем объектам своего процесса, независимо от того, каким потоком данного процесса этот объект создан.
Переключение между потоками занимает довольно много времени, поэтому для облегченного ConvertThreadToFiber, CreateFiber, SwitchToFiber и т. д.
Подробнее функции, связанные с волокнами, описаны в документации Platform SDK.
В системе есть также задания (job object), которые обеспечивают управление одним или несколькими процессами как группой.
Перейдем к формальному описанию потоков. Материал этого раздела в равной мере относится как к обычным потокам пользовательского режима, так и к системным потокам режима ядра.
Подобно процессам, каждый поток имеет свой блок управления, реализованный в виде набора структур, главная из которых - ETHREAD - показана на рис 5.5.
(рис 5.5) Управляющие структуры данных потока Изображенные на рис 5.5 структуры, за исключением блоков переменных окружения потока (TEB), существуют в системном адресном пространстве. Помимо этого,
Блок потока ядра
Создание потока инициируется Win32-функцией CreateThread, которая находится в библиотеке Kernel32.dll.
При этом создается объект ядра "поток", хранящий статистическую информацию о создаваемом потоке. В адресном пространстве процесса выделяется память под пользовательский стек потока. Затем инициализируется аппаратный контекст потока (ниже имеется описание соответствующей структуры CONTEXT).
Вслед за этим создается блок управления потоком вместе с сопутствующими структурами, формируется стек ядра потока и о создании потока уведомляется подсистема Win32. Наконец, вызывающему потоку возвращается описатель создаваемого потока и передается управление, а новому потоку может быть выделено процессорное время.
Таким образом, если первичный поток процесса создается при вызове функции CreateProcess, то для создания дополнительных потоков нужно вызывать функцию CreateThread:
HANDLE CreateThread ( PSECURITY_ATTRIBUTES psa, DWORD cbStack, PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadID);
Программа, листинг которой приведен ниже, создает новый поток и передает ему параметр, числовое значение которого этот поток выводит на экран.
#include <windows.h>
#include <stdio.h>
DWORD WINAPI MyThread( LPVOID lpParam )
{
printf("Parameter = %d\n", *(DWORD*)lpParam);
return 0;
}
VOID main( VOID )
{
DWORD ThreadId, ThreadParameter = 10;
HANDLE hThread;
hThread = CreateThread(
NULL, // атрибуты безопасности по умолчанию
0, // размер стека по умолчанию
MyThread , // указатель на процедуру создаваемого потока
ThreadParameter, // аргумент, передаваемый функции потока
0, // флаги создания по умолчанию
ThreadId); // возвращаемый идентификатор потока
if (hThread == NULL) printf("CreateThread failed." );
getchar();
CloseHandle( hThread );
}
В качестве самостоятельного упражнения рекомендуется написать программу, иллюстрирующие простоту организации межпотокового обмена в рамках одного процесса, например, обмен через набор общих глобальных данных. Сравните данный способ с более громоздкими примерами из лекции "Межпроцессный обмен".
Завершение потока можно организовать разными способами, например, с помощью функций способ - возврат управления функцией потока. Это единственный способ, который гарантирует корректную очистку всех ресурсов, принадлежавших потоку.
Подобно процессам при завершении потока сопоставленный с ним объект ядра "поток" не освобождается до тех пор, пока не будут закрыты все внешние ссылки на этот объект.
Особую роль в структурах данных, описывающих потоки, играет контекст потока. Информацию, входящую в состав контекста, необходимо периодически сохранять и восстанавливать в случае возникновения различных событий, например, при переключении потоков. Обычно сохранению и последующему восстановлению подлежат:
Эта информация сохраняется в текущем стеке ядра потока.
Контекст отражает состояние регистров процессора на момент последнего исполнения потока и хранится в структуре CONTEXT, определенной в заголовочном файле WinNT.h. Элементы этой структуры соответствуют регистрам процессора, например, для процессоров x86 процессоров в ее состав входят Eax, Ebx, Ecx, Edx и т д.. Win32-функция GetThreadContext позволяет получить текущее состояние контекста, а функция SetThreadContext - задать новое содержимое контекста. Перед этой операцией поток рекомендуется приостановить.
Помимо перечисленных в системе имеется много полезных функций, реализующих API для управления потоками. Их полный перечень содержится в MSDN.
Поток представляет собой набор исполняющихся команд для текущего момента исполнения. С одним или несколькими потоками ассоциирован набор ресурсов, которые объединены в рамках процесса. Для описания процесса в системе поддерживается связанная совокупность структур, главной из которых является структура EPROCESS. В свою очередь, структура ETHREAD и связанные с ней структуры необходимы для реализации потоков. В лекции проанализированы функции CreateProcess и CreateThread и этапы создания процессов и потоков. Важными характеристиками потока являются его контекст и состояние. Наблюдение за состоянием потоков предлагается осуществить при помощи инструментальных средств системы.
На сегодня общепринятым является взгляд на ОС как на систему, обеспечивающую параллельное (или псевдопараллельное) выполнение набора последовательных процессов или просто процессов. Задача ОС состоит в том, чтобы организовать их поддержку, которая подразумевает, что каждый процесс получит все необходимые ему ресурсы (место в памяти, процессорное время и т.д.). Считается также, что независимые процессы не должны влиять друг на друга, а процессы, которым необходимо обмениваться информацией, должны иметь возможность сделать это путем
Из курса теории операционных систем известно, что процесс является
Для описания столь сложного динамического объекта ОС поддерживает набор структур, главную из которых принято называть блоком управления процессом (
Классический процесс содержит в своем адресном пространстве одну программу. Однако во многих ситуациях целесообразно поддерживать в
(рис 5.1) Процесс с несколькими потокамиВ этом случае процесс можно рассматривать в качестве контейнера ресурсов, а все проблемы, связанные с динамикой исполнения, решаются на уровне потоков. Обычно каждый процесс начинается с одного потока, а остальные (при необходимости) создаются в ходе выполнения. Теперь уже не процесс, а поток характеризуется состоянием, поток является единицей
В 32-разрядной версии системы у каждого процесса есть 4-гигабайтное адресное пространство, в котором пользовательский код занимает нижние 2 гигабайта (в серверах 3 Гбайта). В своем адресном пространстве, которое представляет собой набор регионов и описывается специальными структурами данных (см. часть III "система управления памятью"), процесс содержит потоки, учетную информацию и ссылки на ресурсы, которые обобществляются всеми потоками процесса.
(рис 5.2) Управляющие структуры данных процессаСодержимое блока EPROCESS подробно описано в . Блок KPROCESS (на рис. справа), блок переменных
Идентификатор процесса кратен четырем и используется в роли байтового индекса в таблицах ядра наравне с другими объектами.
Обычно процесс создается другим процессом вызовом Win32-функции CreateProcess (а также CreateProcessAsUser и CreateProcessWithLogonW ).
На первом этапе, выполняемом библиотекой kernel32.dll в режиме пользователя, на диске отыскивается нужный файл-образ, после чего создается объект "раздел" памяти для его проецирования на адресное пространство нового процесса.
На втором этапе выполняется обращение к системному сервису NtCreateProcess для создания объекта "процесс". Формируются блоки EPROCESS, KPROCESS и блок переменных окружения
На третьем этапе в уже полностью проинициализированном объекте "процесс" необходимо создать первичный поток. Это, посредством
Затем kernel32.dll посылает подсистеме Win32 сообщение, которое содержит информацию, необходимую для выполнения нового процесса. Данные о процессе и потоке помещаются, соответственно, в список процессов и список потоков данного процесса, затем устанавливается
Наконец, запускается первичный поток, для чего формируются его начальный контекст и стек, и выполняется запуск стартовой процедуры потока режима ядра KiThreadStartup. После этого стартовый код из библиотеки C/C++ передает управление функции main() запускаемой программы.
В книге этапы
Таким образом, если приложение намерено создать новый процесс, один из его потоков должен обратиться к Win32-функции CreateProcess.
BOOL CreateProcess( PCTSTR pszApplicationName, PTSTR pszCommandLine, PSECURITY_ATTRIBUTES psaProcess, PSECURITY_ATTRIBUTES psaThread, BOOL bInheritHandles, DWORD fdwCreate, PVOID pvEnvironment, PCTSTR pszCurDir, PSTARTUPINFO psiStartInfo, PPROCESS_INFORMATION ppiProcInfo);
Описание параметров функции можно посмотреть в MSDN.
Формально ОС Windows не поддерживает какой-либо иерархии процессов, например, отношений "родительский-дочерний". Однако, негласная иерархия, заключающаяся в том, кто чьим дескриптором (описателем) владеет, все же существует. Например, владение дескриптором процесса позволяет влиять на его адресное пространство и функционирование. В данном случае описатель ppiProcInfo. Хотя он не может быть напрямую передан другому процессу, тем не менее, имеется возможность передать другому процессу его дубликат. Таким путем при необходимости в группе процессов может быть сформирована нужная иерархия.
В качестве упражнения рекомендуется осуществить прогон программы, создающей дочерний процесс.
#include <windows.h>
#include <stdio.h>
void main( VOID )
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION ProcInfo;
TCHAR CommandLine[] = TEXT("sleep");
ZeroMemory( StartupInfo, sizeof(StartupInfo) );
StartupInfo.cb = sizeof(StartupInfo);
ZeroMemory( ProcInfo, sizeof(ProcInfo) );
if( !CreateProcess( NULL, // Не используется имя модуля
CommandLine, // Командная строка
NULL, // Дескриптор процесса не наследуется.
NULL, // Дескриптор потока не наследуется.
FALSE, // Установка описателей наследования
0, // Нет флагов создания процесса
NULL, // Блок переменных окружения родительского процесса
NULL, // Использовать текущий каталог родительского процесса
StartupInfo, // Указатель на структуру STARTUPINFO.
ProcInfo ) // Указатель на структуру информации о процессе.
)
printf( "CreateProcess failed." );
// Ждать окончания дочернего процесса
WaitForSingleObject( ProcInfo.hProcess, INFINITE );
// Закрыть описатели процесса и потока
CloseHandle( ProcInfo.hProcess );
CloseHandle( ProcInfo.hThread );
}
В приведенной программе имя запускаемого модуля передается через второй параметр функции CreateProcess. В примере в качестве дочерней программы используется простейшая программа sleep, задача которой - выдержать паузу длительностью 10 секунд.
#include <windows.h>
#include <stdio.h>
void main( VOID )
{
printf("Данная программа будет спать
в течение 10000 мс\n");
Sleep(10000);
}
Выполнение обеих программ можно проконтролировать с помощью диспетчера задач.
Завершение процесса может быть осуществлено различными способами, например, с помощью функций ExitProcess, TerminateProcess. Однако, единственным способом, гарантирующим корректную очистку всех ресурсов, является возврат управления входной функцией первичного потока. Помимо перечисленных в системе имеется много полезных функций, реализующих API для управления процессами. Их полный перечень содержится в MSDN.
При завершении процесса сопоставленный с ним объект ядра "процесс" не освобождается до тех пор, пока не будут закрыты все внешние ссылки на этот объект.
Каждый новый процесс содержит, по крайней мере, один поток, остальные потоки создаются динамически. Потоки составляют основу планирования и могут: выполняться на одном из процессоров, ожидать события или находиться в каком-то ином состоянии (см. рис 5.3 и лекцию "Планирование потоков").
(рис 5.3) Состояния потоков в ОС Windows (версии Server 2003)Обычно в состоянии "Готовности" имеется очередь готовых к выполнению (running) потоков. В данном случае это состояние распадается на три составляющих. Это, собственно, состояние "Готовности (Ready)"; состояние "Готов. Отложен (Deferred Ready)", что означает, что поток выбран для выполнения на конкретном процессоре, но пока не запланирован к выполнению; и, наконец, состояние "Простаивает (
В состоянии "Ожидания (Waiting)" поток блокирован и ждет какого-либо события, например, завершения операции ввода-вывода. При наступлении этого события поток переходит в состояние "Готовности". Этот путь может проходить через промежуточное "Переходное (Transition)" состояние в том случае, если стек ядра потока выгружен из памяти.
Код ядра выполняется в контексте текущего потока. Это означает, что при прерывании, системном вызове и т д., то есть когда процессор переходит в режим ядра и управление передается ОС, переключения на другой поток (например, системный) не происходит. Контекст потока при этом сохраняется, поскольку операционная система все же может принять решение о смене характера деятельности и переключении на другой поток. Вследствие этого в некоторых курсах по операционным системам состояние "Выполнение" разделяют на "Выполнение в режиме пользователя" и "Выполнение в режиме ядра".
Системный монитор (обкладка "Производительность") представляет собой удобное средство наблюдения за состояниями потоков. Предлагается осуществить прогон программы, которая содержит длительные циклы счета и ожидания. Например, программа, вычисляющая 5*107 значений функций sin(x):
#include <windows.h>
#include <stdio.h>
#include <math.h>
VOID main( VOID ) {
int i,N=50000000;
double a,b;
getchar();
printf("Before circle\n");
for ( i = 0; i<N; i++) {
b=(double)i / (double)N;
a=sin(b);
}
printf("After circle\n");
getchar();
}
Графическое представление предполагает присвоение цифровых значений различным состояниям потока (например, готовность - 1, выполнение - 2, ожидание - 5 и т.п.). Результат работы монитора на однопроцессорной системе для данной программы представлен на рис 5.4.
(рис 5.4) Иллюстрация перехода потока из одного состояния в другоеГоризонтальные участки со значением 5 соответствуют ожиданию нажатия клавиши ввода, а значению 1 (читателю предлагается самостоятельно ответить на вопрос, почему значению 1, а не значению 2) соответствует счетный цикл.
Идентификаторы потоков, так же как и идентификаторы процессов, кратны четырем, выбираются из того же пространства, что и идентификаторы процессов, и с ними не пересекаются.
Как уже говорилось, когда поток обращается к системному вызову, то переключается в режим ядра, после чего продолжает выполняться тот же поток, но уже в режиме ядра. Поэтому у каждого потока два стека, один работает в режиме ядра, другой - в режиме пользователя. Один и тот же стек не может использоваться и в режиме пользователя, и в режиме ядра. Любой поток может делать все что угодно со своим собственным стеком (стеком режима пользователя), в том числе организовывать несколько стеков и переключаться между ними. Поток сам может определять размер своего стека. При этом нельзя гарантировать, что стек будет иметь достаточный размер, чтобы код ядра выполнился безо всяких проблем. Поскольку возникновение исключительной ситуации в режиме ядра может привести к краху всей системы, необходимо исключить такую возможность, что и осуществляется путем организации отдельного стека для режима ядра. Так как в режиме ядра могут одновременно находиться несколько потоков и между ними может происходить переключение, у каждого из них должен быть отдельный стек режима ядра.
Помимо состояния, идентификатора и двух стеков, у каждого потока есть контекст, маркер доступа, а также небольшая собственная память для хранения локальных переменных, например, для запоминания кода ошибки. Поскольку процесс является контейнером ресурсов всех входящих в него потоков, любой поток может получить доступ ко всем объектам своего процесса, независимо от того, каким потоком данного процесса этот объект создан.
Переключение между потоками занимает довольно много времени, поэтому для облегченного ConvertThreadToFiber, CreateFiber, SwitchToFiber и т. д.
Подробнее функции, связанные с волокнами, описаны в документации Platform SDK.
В системе есть также задания (job object), которые обеспечивают управление одним или несколькими процессами как группой.
Перейдем к формальному описанию потоков. Материал этого раздела в равной мере относится как к обычным потокам пользовательского режима, так и к системным потокам режима ядра.
Подобно процессам, каждый поток имеет свой блок управления, реализованный в виде набора структур, главная из которых - ETHREAD - показана на рис 5.5.
(рис 5.5) Управляющие структуры данных потокаИзображенные на рис 5.5 структуры, за исключением блоков переменных окружения потока (TEB), существуют в системном адресном пространстве. Помимо этого,
Блок потока ядра
Создание потока инициируется Win32-функцией CreateThread, которая находится в библиотеке Kernel32.dll.
При этом создается объект ядра "поток", хранящий статистическую информацию о создаваемом потоке. В адресном пространстве процесса выделяется память под пользовательский стек потока. Затем инициализируется аппаратный контекст потока (ниже имеется описание соответствующей структуры CONTEXT).
Вслед за этим создается блок управления потоком вместе с сопутствующими структурами, формируется стек ядра потока и о создании потока уведомляется подсистема Win32. Наконец, вызывающему потоку возвращается описатель создаваемого потока и передается управление, а новому потоку может быть выделено процессорное время.
Таким образом, если первичный поток процесса создается при вызове функции CreateProcess, то для создания дополнительных потоков нужно вызывать функцию CreateThread:
HANDLE CreateThread ( PSECURITY_ATTRIBUTES psa, DWORD cbStack, PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadID);
Программа, листинг которой приведен ниже, создает новый поток и передает ему параметр, числовое значение которого этот поток выводит на экран.
#include <windows.h>
#include <stdio.h>
DWORD WINAPI MyThread( LPVOID lpParam )
{
printf("Parameter = %d\n", *(DWORD*)lpParam);
return 0;
}
VOID main( VOID )
{
DWORD ThreadId, ThreadParameter = 10;
HANDLE hThread;
hThread = CreateThread(
NULL, // атрибуты безопасности по умолчанию
0, // размер стека по умолчанию
MyThread , // указатель на процедуру создаваемого потока
ThreadParameter, // аргумент, передаваемый функции потока
0, // флаги создания по умолчанию
ThreadId); // возвращаемый идентификатор потока
if (hThread == NULL) printf("CreateThread failed." );
getchar();
CloseHandle( hThread );
}
В качестве самостоятельного упражнения рекомендуется написать программу, иллюстрирующие простоту организации межпотокового обмена в рамках одного процесса, например, обмен через набор общих глобальных данных. Сравните данный способ с более громоздкими примерами из лекции "Межпроцессный обмен".
Завершение потока можно организовать разными способами, например, с помощью функций способ - возврат управления функцией потока. Это единственный способ, который гарантирует корректную очистку всех ресурсов, принадлежавших потоку.
Подобно процессам при завершении потока сопоставленный с ним объект ядра "поток" не освобождается до тех пор, пока не будут закрыты все внешние ссылки на этот объект.
Особую роль в структурах данных, описывающих потоки, играет контекст потока. Информацию, входящую в состав контекста, необходимо периодически сохранять и восстанавливать в случае возникновения различных событий, например, при переключении потоков. Обычно сохранению и последующему восстановлению подлежат:
Эта информация сохраняется в текущем стеке ядра потока.
Контекст отражает состояние регистров процессора на момент последнего исполнения потока и хранится в структуре CONTEXT, определенной в заголовочном файле WinNT.h. Элементы этой структуры соответствуют регистрам процессора, например, для процессоров x86 процессоров в ее состав входят Eax, Ebx, Ecx, Edx и т д.. Win32-функция GetThreadContext позволяет получить текущее состояние контекста, а функция SetThreadContext - задать новое содержимое контекста. Перед этой операцией поток рекомендуется приостановить.
Помимо перечисленных в системе имеется много полезных функций, реализующих API для управления потоками. Их полный перечень содержится в MSDN.
Поток представляет собой набор исполняющихся команд для текущего момента исполнения. С одним или несколькими потоками ассоциирован набор ресурсов, которые объединены в рамках процесса. Для описания процесса в системе поддерживается связанная совокупность структур, главной из которых является структура EPROCESS. В свою очередь, структура ETHREAD и связанные с ней структуры необходимы для реализации потоков. В лекции проанализированы функции CreateProcess и CreateThread и этапы создания процессов и потоков. Важными характеристиками потока являются его контекст и состояние. Наблюдение за состоянием потоков предлагается осуществить при помощи инструментальных средств системы.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.