Московский физико-технический институт
Опубликован: 12.12.2007 | Доступ: свободный | Студентов: 5489 / 1825 | Оценка: 4.34 / 4.14 | Длительность: 13:57:00
ISBN: 978-5-94774-827-7
Лекция 5:

Реализация процессов и потоков

< Лекция 4 || Лекция 5: 123 || Лекция 6 >

Изображенные на рис. 5.5 структуры, за исключением блоков переменных окружения потока (TEB), существуют в системном адресном пространстве. Помимо этого, параллельная структура для каждого потока, созданного в Win32-процессе, поддерживается процессом Csrss подсистемы Win32. В свою очередь, часть подсистемы Win32, работающая в режиме ядра (Win32k.sys), поддерживает для каждого потока структуру W32THREAD.

Блок потока ядра KTHREAD содержит информацию, необходимую ядру для планирования потоков и их синхронизации с другими потоками. Просмотр структур данных потока может быть осуществлен отладчиком. Более подробно данный материал изложен в книге [ Руссинович ] .

Создание потоков

Создание потока инициируется Win32-функцией CreateThread, которая находится в библиотеке Kernel32.dll. При этом создается объект ядра "поток", хранящий статистическую информацию о создаваемом потоке. В адресном пространстве процесса выделяется память под пользовательский стек потока. Затем инициализируется аппаратный контекст потока (ниже имеется описание соответствующей структуры CONTEXT).

Вслед за этим создается блок управления потоком вместе с сопутствующими структурами, формируется стек ядра потока и о создании потока уведомляется подсистема Win32. Наконец, вызывающему потоку возвращается описатель создаваемого потока и передается управление, а новому потоку может быть выделено процессорное время.

Функция CreateThread

Таким образом, если первичный поток процесса создается при вызове функции 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 );
   }

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

Завершение потока можно организовать разными способами, например, с помощью функций ExitThread или TerminateThread. Рекомендуемый [ Рихтер ] способ - возврат управления функцией потока. Это единственный способ, который гарантирует корректную очистку всех ресурсов, принадлежавших потоку.

Подобно процессам при завершении потока сопоставленный с ним объект ядра "поток" не освобождается до тех пор, пока не будут закрыты все внешние ссылки на этот объект.

Контекст потока, переключение контекстов

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

  • программный счетчик, регистр состояния и содержимое остальных регистров процессора;
  • указатели на стек ядра и пользовательский стек;
  • указатели на адресное пространство, в котором выполняется поток (каталог таблиц страниц процесса).

Эта информация сохраняется в текущем стеке ядра потока.

Контекст отражает состояние регистров процессора на момент последнего исполнения потока и хранится в структуре CONTEXT, определенной в заголовочном файле WinNT.h. Элементы этой структуры соответствуют регистрам процессора, например, для процессоров x86 процессоров в ее состав входят Eax, Ebx, Ecx, Edx и т д.. Win32-функция GetThreadContext позволяет получить текущее состояние контекста, а функция SetThreadContext - задать новое содержимое контекста. Перед этой операцией поток рекомендуется приостановить.

Помимо перечисленных в системе имеется много полезных функций, реализующих API для управления потоками. Их полный перечень содержится в MSDN.

Заключение

Поток представляет собой набор исполняющихся команд для текущего момента исполнения. С одним или несколькими потоками ассоциирован набор ресурсов, которые объединены в рамках процесса. Для описания процесса в системе поддерживается связанная совокупность структур, главной из которых является структура EPROCESS. В свою очередь, структура ETHREAD и связанные с ней структуры необходимы для реализации потоков. В лекции проанализированы функции CreateProcess и CreateThread и этапы создания процессов и потоков. Важными характеристиками потока являются его контекст и состояние. Наблюдение за состоянием потоков предлагается осуществить при помощи инструментальных средств системы.

< Лекция 4 || Лекция 5: 123 || Лекция 6 >
Ирина Оленина
Ирина Оленина
Николай Сергеев
Николай Сергеев

Здравствуйте! Интересует следующий момент. Как осуществляется контроль доступа по тому или иному адресу с точки зрения обработки процессом кода процесса. Насколько я понял, есть два способа: задание через атрибуты сегмента (чтение, запись, исполнение), либо через атрибуты PDE/PTE (чтение, запись). Но как следует из многочисленных источников, эти механизмы в ОС Windows почти не задействованы. Там ключевую роль играет менеджер памяти, задающий регионы, назначающий им атрибуты (PAGE_READWRITE, PAGE_READONLY, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_NOACCESS, PAGE_GUARD: их гораздо больше, чем можно было бы задать для сегмента памяти) и контролирующий доступ к этим регионам. Непонятно, на каком этапе может включаться в работу этот менеджер памяти? Поскольку процессор может встретить инструкцию: записать такие данные по такому адресу (даже, если этот адрес относится к региону, выделенному менеджером памяти с атрибутом, например, PAGE_READONLY) и ничего не мешает ему это выполнить. Таким образом, менеджер памяти остается в стороне не участвует в процессе...