
Лабораторная работа №1
Тема: Процессы в OS Windows
Цель: Изучить основные средства получения информации о процессах операционной системы Windows. Получить практические навыки работы с процессами операционной системы.
Задание: Получить информацию обо всех процессах системы. Для текущего процесса получить информацию: время работы; текущий каталог; переменные окружения; и т.д.
Теоретические сведения:
Процесс определяют как экземпляр выполняемой программы, и он состоит из двух компонентов
объекта ядра, через который операционная система управляет процессом, там же хранится статистическая информация о процессе,
адресного пространства, в котором содержится код и данные всех EXE- и DLL модулей. Именно в нем находятся области памяти, динамически распределяемой для стеков потоков и других нужд.
Процессы инертны, чтобы процесс что-нибудь выполнил, в нем нужно создать поток. Именно потоки отвечаю за исполнение кода, содержащегося в адресном пространстве процесса Один процесс может владеть несколькими потоками, и тогда они "одновременно" исполняют код в адресном пространстве процесса.
Для этого каждый поток должен располагать собственным набором регистров процессора и собственным стеком. В каждом процессе есть минимум один поток. Если бы у процесса не было ни одного потока, ему нечего было бы делать и система автоматически уничтожила бы его вместе с выделенным ему адресным пространством.
Чтобы все эти потоки работали, операционная система отводит каждому из них определенное процессорное время. Выделяя потокам отрезки времени (называемые квантами) по принципу карусели, она создает тем самым иллюзию одновременного выполнения потоков. Если в машине установлено более одного процессора, алгоритм работы операционной системы значительно усложняется (в этом случае система стремится сбалансировать нагрузку между процессорами).
Когда поток в приложении вызывает CreateProcess, система создает объект ядра "процесс" с начальным значением счстчика числа его пользователей, равным 1. Этот объект — не сам процесс, а компактная структура данных, через которую операционная система управляет процессом. (Объект ядра "процесс" следует рассматривать как структуру данных со статистической информацией о процессе.). Затем система создает для нового процесса виртуальное адресное пространство и загружает в него код и данные как для исполняемого файла, тaк и для любых DLL (если таковые требу ются).
Далее система формирует объект ядра "поток" (со счетчиком, равным 1) для первичного потока нового процесса. Как и в первом случае, объект ядра "поток" — это компактная структура данных, через которую система управляет потоком. Первичный поток начинает с исполнения стартового кода из библиотеки С/С++, который в конечном счете вызывает функцию WinMain, wWinMain, main или wmain в программе. Если системе удастся создать новый процесс и его первичный поток, Create Process вернет TRUE
Функция CreateProcess и её параметры:ss
BOOL CreateProcess( PCTSTR pszApplicationName, //имя программы PTSTR pszCommandLine, //командная строка PSFCURITY_ATTRIBUTES psaProcess,/*атрибуты безопасности для дескриптора процесса*/ PSECURITY_ATTRIBUTES psaThiead, /*атрибуты безопасности для дескриптора потока*/ BOOL bInheritHandles, /*указывает наследует ли новый процесс дескрипторыпринадлежащие текущему*/ DWORD fdwCreate, //параметры создания процесса PVOID pvEnvironment,//значение переменных окружения PCTSTR pszCurDir, //текущий каталог PSTARTUPINFO psiStartInfo, //информация о запуске процесса PPROCESS_INFORMATION ppiProcInfo);/*возвращаемые функции, дескрипторы и идентификаторы потока*/
Завершения процесса:
Процесс можно завершить четырьмя способами:
входная функция первичного потока возвращает управление (рекомендуемый способ),
один из потоков процесса вызывает функцию ExitProcess (нежелательный способ);
поток другого процесса вызывает функцию TerminateProcess (тоже нежелательно);
все потоки процесса умирают по своей воле (большая редкость);
Возврат управления входной функцией первичного потока
Приложение следует проектировать так, чтобы его процесс завершался только после возврата управления входной функцией первичного потока. Это единственный способ, гарантирующий корректную очистку всех ресурсов, принадлежавших первичному потоку. При этом:
любые С++ объекты, созданные данным потоком, уничтожаются соответствующими деструкторами;
система освобождает память, которую занимал стек потока;
система устанавливает код завершения процесса (поддерживаемый объектом ядра "процесс") — его и возвращает Ваша входная функция;
счетчик пользователей данного объекта ядра "процесс" уменьшается на 1.
Функция ExitProcess
Процесс завершается, когда один из его потоков вызывает ExitProcess:
VOID ExitProcess(UINT fuExitCode);
Эта функция завершает процесс и заносит в параметр fuExitCode код завершения процесса. Возвращаемого значения у ExitProcess нет, так как результат ее действия — завершение процесса. Если за вызовом этой функции в программе присутствует какой-нибудь код, он никогда не исполняется.
Когда входная функция (WinMain, wWinMain, main илиwmain) в Вашей программе возвращает управление, оно передастся стартовому коду из библиотеки С/C++, и тот проводит очистку всех ресурсов, выделенных им процессу, а затем обращается кExitProcess, передавая ей значение, возвращенное входной функцией, поэтому возврат управления входной функцией первичного потока приводит к завершению всего процесса. Обратите внимание, что при завершении процесса прекращается выполнение и всех других его потоков. В приложении, написанном на С/С++, следует избегать вызова этих функций, так как библиотеке С/С++ скорее всего не удастся провести должную очистку.
Функция TerminateProcess
Вызов функции TerminateProcess тоже завершает процесс:
BOOL TerminateProcess( HANDLE hProcoss, UINT fuExitCode);
Главное отличие этой функции от ExitProcess в том, что ее может вызвать любой поток и завершить любой процесс. Параметр bProcess идентифицирует описатель завершаемого процесса, а в параметре fuExitCode возвращается код завершения процесса.
Пользуйтесь TerminateProcess лишь в том случае, когда иным способом завершить процесс не удается. Процесс не получает абсолютно никаких уведомлений о том, что он завершается, и приложение не может ни выполнить очистку, ни предотвратить свое неожиданное завершение (если оно, конечно, не использует механизмы защиты). При этом теряются все данные, которые процесс не успел переписать из памяти на диск.
Процесс действительно не проводит сам очистку, но операционная система высвобождает все принадлежавшие ему ресурсы: возвращает себе выделенную им память, закрывает любые открытые файлы, уменьшает счетчики соответствующих объектов ядра и разрушает все его User- и GDI-объекты.
Когда все потоки процесса уходят
В такой ситуации (а она может возникнуть, если все потоки вызвали ExitTbread или их закрыли вызовом TermirmteTbread) операционная система больше не считает нужным "содержать" адресное пространство данного процесса. Обнаружив, что в процессе не исполняется ни один поток, она немедленно завершает его. При этом код завершения процесса приравнивается коду завершения последнего потока.
Что происходит при завершении процесса
А происходит вот что.
Выполнение всех потоков в процессе прекращается
Все User- и GDI-объекты, созданные процессом, уничтожаются, а объекты ядра закрываются (если их не использует другой процесс).
Код завершения процесса меняется со значения STILL_ACTIVE на код, переданный в ExitProcess или TerminateProcess.
Объект ядра "процесс" переходит в свободное, или незанятое (signaled), состояние. Прочие потоки в системе могут приостановить свое выполнение вплоть до завершения данного процесса.
Счетчик объекта ядра "процесс" уменьшается на 1
Связанный с завершаемым процессом объект ядра не высвобождается, пока не будут закрыты ссылки на него и из других процессов. В момент завершения процесса система автоматически уменьшает счетчик пользователей этого объекта на 1, и объект разрушается, как только его счетчик обнуляется. Кроме того, закрытие процесса не приводит к автоматическому завершению порожденных им процессов.
По завершении процесса его код и выделенные ему ресурсы удаляются из памяти. Однако область памяти, выделенная системой для объекта ядра "процесс", не освобождается, пока счетчик числа его пользователей не достигнет нуля, а это произойдет, когда все прочие процессы, создавшие или открывшие описатели для умершего процесса, уведомят систему (вызовом CloseHandle) о том, что ссылки на этот процесс им больше не нужны.
Функции для получения информации о текущем процессе:
HANDLE GetCurrentProcessO - описатель текущего процесса;
DWORD GetCurrentProcessId - идентификатор текущего процесса;
DWORD GetCurrentDirectory (DWORD nBufferLength, LPTSTR lpBuffer) - текущий каталог текущего процесса;
LPTSTR GetCommandLine - командная строка текущего процесса;
LPVOID GetEnvironmentStrings - указатель на блок переменных окружения;
BOOL GetExitCodeProcess(HANDLE hProcess, LPDWORD lpExitCode); - код завершения процесса;
DWORD GetPriorityClass(HANDLE hProcess) - класс приоритета процесса;
BOOL GetProcessShutdownParameters(LPDWORD lpdwLevel, LPDWORD lpdwFlags) - параметры перезагрузки текущего процесса;
BOOL GetProcessTimes(HANDLE hProcess, LPFILETIME ipCreationTime, LPFILETIME IpExitTime, LPFILETIME ipKernelTime, LPFILETIME lpUserTime) - временная информация о процессе;
DWORD GetProcessVersion(DWORD Processld) - версия процесса;
BOOL WINAPI Process32First(HANDLE hSnapshot, LPPROCESSENTRY32 lppe) возвращает информацию о первом найденном процессе в списке процессов. Если функция возвратиля значение ИСТИНА, то lppe содержит адрес блока информации о процессе.
BOOL WINAPI Process32Next(HANDLE hSnapshot, LPPROCESSENTRY32 lppe) возвращает информацию о следующем найденном процессе в списке процессов. Если функция возвратиля значение ИСТИНА, то lppe содержит адрес блока информации о процессе:
cntUsage - Количество ссылок на процесс;
th32ProcesslD - Идентификатор процесса;
th32DefaultHeapID - Идентификатор кучи для процесса;
th32ModuleID - Идентификатор модуля процесса;
cntThreads - Количество потоков процесса;
th32ParentProcessID - Идентификатор родительского процесса;
pcPriClassBase - Базовый приоритет потоков процесса;
szExeFile - Имя загрузочного модуля процесса;
BOOL WINAPI Module32First(HANDLE hSnapshot, LPPROCESSENTRY32 lpme) возвращает информацию о первом найденном модуле указанного процесса. Если функция возвратиля значение ИСТИНА, то lpme содержит адрес блока информации о модуле.
Пример:
/*В программе выводится информация о текущем процессе*/
#include <windows.h>
#include <stdio.h>
main()
{
//получаем идентификатор процесса
DWORD ProcID=GetCurrentProcessId();
//получаем HANDLE процесса (открываем процесс)
HANDLE hCur=OpenProcess(PROCESS_ALL_ACCESS|PROCESS_QUERY_INFORMATION,
true, ProcID);
DWORD i,code,pr,sh,sh1;
int k;
char buf[100];
char *b;
printf("Info about current process\n");
//Текущий каталог
i=GetCurrentDirectory(100,buf);
printf("Current Directory ->%s\n",buf);
//Командная строка
b=GetCommandLine();
printf("Command Line ->%s\n",b);
//Переменніе окружения
b=GetEnvironmentStrings();
printf("Environment Strings->%s\n",b);
//Код статуса завершения
k=GetExitCodeProcess(hCur,&code);
printf("ExitCode Process ->%d\n",code);
//Параметры перезагрузки
k=GetProcessShutdownParameters(&sh,&sh1);
if (k==1)
{ printf("Shutdown Parameters->%s Flag->%s\n",sh,sh1); }
//Версия
i=GetProcessVersion(ProcID);
printf("Pracess Version ->%d\n",i);
getchar();
}
Варианты:
Создать программу, в которой с помощью функции CreateProcess порождается процесс, выполняющий проверку матрицы на симметричность относительно главной диагонали. Получить информацию о запущенном процессе.
Создать программу, в которой с помощью функции CreateProcess порождается два процесса один из процессов считает сумму в строках матрицы, другой в столбцах. Получить информацию о всех процессах системы.
Создать программу в которой с помощью функции CreateProcess порождается процесс, выполняющий чтение файла и вывод его на экран. Получить информацию о запущенном процессе.
Создать программу в которой с помощью функции CreateProcess порождается два процесса один из процессов считывает из файла данные, другой дописывает в файл строки. Получить информацию о всех процессах системы.
Создать программу в которой с помощью функции ShellExecute открывается стандартный калькулятор Windows . Получить информацию о запущенном процессе.
Создать программу в которой с помощью функции ShellExecute печатается любой текстовый файл. Получить информацию о всех процессах системы.
Создать программу порождающую поток, который копирует файлы. Получить информацию о текущем процессе.
Создать программу в которой с помощью функции CreateProcess порождается процесс выполняющий чтение файла и вывод его на экран. Получить информацию о всех процессах системы.
Создать программу в которой с помощью функции CreateProcess порождается процесс, выполняющий циклический сдвиг строк матрицы на заданное количество позиций. Получить информацию о всех процессах системы.
Создать программу порождающую несколько процессов, получить всю информацию о созданных процессах.
Лабораторная работа №2
Тема: Средства синхронизации процессов в Windows
Цель: Изучение применения средств синхронизации процессов.
Теоретические сведения:
Windows поддерживает вытесняющую многозадачность, процессорное время у потока может быть отнято в любое время. Решение этой прблемы может быть осуществленно в работе с глобальной переменной на уровне атомарного доступа, т.е. без прерывания другим потоком, для этого служит семейство функций InterLocked. Синхронизация осуществляется с помощью объектов синхронизации: событий, мьютексов, семафоров.
Общие принципы синхронизации:
Тот или иной объект синхронизации создаётся с помощью специальной функции.
Для потоков одного процесса можно использовать безимянные объекты синхронизации, а для потоков разных процессов объекты синхронизации должны иметь имена. Различные объекты синхронизации не могут иметь одинаковые имена.
Функция создающая объект синхронизации возвращает его дескриптор. Потоки одного процесса могут использовать один и тот же дескриптор, другие прцессы могут наследовать дескриптор, но для передачи дескриптора процессу-потомку необходимо использовать специальные средства.
Имея дескриптор можно определить ноходится ли объект в сигнальном состоянии.
Сигнальное состояние:
События - когда устанавливается соответствующий флаг
Мьютексы - если он никому не принадлежит
Семафоры - если его счётчик больше 0
WaitForSingleObject(…) – определение состояния объекта
C помощью этой функции можно указать период в течение которого следует ожидать переход объекта в сигнальное состояние. Если время задать равное 0, то произойдёт однократный опрос, и если интервал времени задать в виде константы INFINITE, то бедет опрашиваться постоянно.
Функции для работы с событиями:
CreateEvent – создает или открывает уже созданное событие
OpenEvent - открывет событие
SetEvent - устанавливает событие в сигнальное состояние
ResetEvent - сброс сигнального состояния события
PulseEvent - переводит в сигнальное состояние на период времени до тех пор пока на это событие не прореагируют все другие(ожидающие) потоки, после чего оно сбрасывается.
События бывают двух типов: manual (мануальное), single (еденичное)
Мануальное событие устанвливается для нескольких потоков, любой поток может установить или сбросить это событие, функция PulseEvent сообщает всем потокам ожидающим этого события, что оно произошло после чего сбрасывается, когда все потоки ожидающие этого события прореагируют, система сбросит его.
Еденичное событие используется для одного потока, т.е. если необходимо дать возможность действия всем потокам вместе - то manual, если поочерёдно - то single.
Пример 1:
/*Синхронизация процессов с помощью событий*/
/*При запуске программы без параметров argc=1,программа ждёт сигнального со- стояния события, при запуске с параметрами argc<>1, событие переходит в сигнальное состояние, при этом первая программа тоже завершается т.к. сообщение уже в сигнальном состоянии */
#include<windows.h>
#include<iostream.h>
#include<fcntl.h>
#include<stdio.h>
void main(int argc,char *argv[1])
{
//Создание или открытие уже существующего события
HANDLE ev=CreateEvent(NULL,FALSE,FALSE,"nt5bbevant");
printf("%i\n",argc);
if(argc==1)
{
//Ожидание сигнального состояния
WaitForSingleObject(ev,INFINITE);
MessageBox(NULL,"Событие произошло","Semaphore",MB_OK);
cout.flush();
}
else
{
//Установка в сигнальное состояние
SetEvent(ev);
}
//Закрытие описателя
CloseHandle(ev);
}
Функции для работы с мьютексами
CreateMutex - создание или открытие уже существующего мьютекса
OpenMutex - открыть существующий мьютекс
ReleaseMutex – освобождение мьютекса
Мьютексы защищают ресурсы процесса, поток обладающий мьютексом имеет эксклюзивное право доступа к этому ресурсу.
Ни один поток не может завладеть мьютексом принадлежащем другому потоку. Процесс может присвоить мьютекс несколько раз, но тогда он должен освободить его столько же раз. Мьюткс может защитить доступ к объекту потока лишь в том случае если тот с ним «считается».
Пример 2:
/*Синхронизация процессов с помощью мьютексов*/
/*При запуске прграммы появляется сообщение «Программа обладает мьютексом» если запустить программу второй раз, то она будет ожидать пока мы не закроем появившееся сообщение в предыдущей и так до любого количества программ */
#include <windows.h>
#include <iostream.h>
void main()
{
//Создание мьютекса
HANDLE mtx=CreateMutex(NULL,FALSE,"NT5BBMTX");
//Запрос на сигнальное состояние мьютекса
WaitForSingleObject(mtx,INFINITE);
MessageBox(NULL,"Программа обладает мьютексом","MUTEX",MB_OK);
//Освобождение мьютекса
ReleaseMutex(mtx);
//Закрытие описателя
CloseHandle(mtx);
}
Функции для работы с семафорами
CreateSemaphore - создание семафора
OpenSemaphore - открыть семафор
ReleaseSemaphore – увеличить значение счётчика
При создании семафора он работает следующим образом: в начале ему присваивается начальное значение счётчика семафора, каждое обращение процесса к семафору уменьшае значение его счётчика на 1, когда значение счётчика станет равным 0, он станет недоступен.
Пример 3:
/*Синхронизация процессов с помощью семафоров*/
/*При запуске программы появляется окно с надписью «Получен доступ к семафору» при запуске второго экземпляра программы он не будет выдавать сообщение пока мы не закроем окно предыдущего т.е. не переведём семафор в сигнальное состояние*/
#include <windows.h>
#include <iostream.h>
void main()
{
//Создание семафора
HANDLE sem=CreateSemaphore(NULL,3,3,"NT5BBSEM");
//Ожидание сигнального состояния
WaitForSingleObject(sem,INFINITE);
cout<<"Access to semaphore\n";
cout.flush();
MessageBox(NULL,"Получен доступ к семафору","Semaphore",MB_OK);
//Освобождение семафора
ReleaseSemaphore(sem,1,NULL);
CloseHandle(sem);
}
Управление несколькими объектами синхронизации
Бывают случаи, когда поток ожидает перехода в сигнальное состояние нескольких объектов синхронизации, тогда удобно использовать функцию
WaitForMultipleObjects(…)
В качестве одного из аргументов задаётся массив дискрипторов объекта синхронизации, если любой из этих объектов перешёл в сигнальное состояние, то эта функция сообщает какой из объектов изменил своё состояние. Управляя параметрами этой функции можно задать интервал времени в течение которого будет ожидаться переход объекта в сигнальное состояние, можно ожидать перехода в сигнальное состояние всех указанных объектов.
Критические секции
Использование мьютексов для синхронизации потоков нерацианально, мьютексы используются для синхронизации процессов. Для синхронизации потоков используются критические секции.
Критическая секция анализирует значение специальной переменной которая используется как флаг предотвращающий исполнение некоторого участка кода несколькими потоками одновременно.
Для реализации критической секции win32 предоставляет объект типа CRITICAL_SECTION. Объекты этого типа могут быть инициализированны и удалены, но они не имеют дескрипторов и не разделяются другими процессами(т.е. они существуют в пределах одного процесса)
VOID InitializeCriticalSection(LPCRITICAL_SECTION lpscCriticalSection) - инициализация критической секции
VOID DeleteCriticalSection(……………) – удаление критической секции
VOID EnterCriticalSection(lpscCriticalSection) – начало критической секции
VOID LeaveCriticalSection(………………) – выход из критической секции
EnterCriticalSection –блокирует поток если в заданной секции находится другой поток, для функции время ожидания неограниченно, поэтому иногда целесообразно опросить занята ли критическая секция др.потоком.
BOOL TryEnterCriticalSection(………………) – попытка войти в критическую секцию
Если true – то поток владеет критической секцией
Если false – то критической секцией владеет другой поток
Объект типа CriticalSection имеет преимущество по сравнению с другими в том что он не является объектом ядра, а распологается в пространстве задачи(пользователя).
Пример 4:
/*Программа запрашивае кА кую функцию выполнять входит в критическую секцию для выполнения, при запуске второго экземпляра будет ожидание пока предыдущий экземпляр не завершит работу в критической секции*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <conio.h>
#include <iostream.h>
#include <fcntl.h>
#include <time.h>
void proc(void);
CRITICAL_SECTION csl;
#define FF "D:\\Учёба\\Visual C++\\lr1\\lr1\\lr1b\\lll.txt"
#define PER '\n'
FILE *fd;
int i,count;
char buf[1000];
char buf1[1000];
int x;
/*ввод 0 :кол-во строк
1 :ввод строки
<2:выход*/
main()
{
InitializeCriticalSection(&csl);
/*randomize();
x=random(1);*/
scanf("%i",&x);
while (x<2)
{
proc();
scanf("%i",&x);
}
DeleteCriticalSection(&csl);
}
void proc(void)
{
EnterCriticalSection(&csl);
MessageBox(NULL,"Программа в критической секции","Critical Section",MB_OK);
if (x==0)
goto s1;
else
{
if(x==1)
goto s2;
else
goto s3;
}
s2: for(i=0;i<1000;i++)
{
buf1[i]=' ';
}
/*Программа обработки файла:Вводит в файл новую строку*/
printf("Enter the line->\n");
scanf("%s",&buf1);
count=0;
i=0;
while((buf1[i]!=' ')||(buf1[i+1]!=' ')||(buf1[i+2]!=' '))
{
count=count+1;
i=i+1;
}
buf1[i-1]=PER;
if ((fd=fopen(FF,"a"))!=NULL)
{
fwrite(&buf1,count,1,fd);
}
fclose(fd);
goto s3;
/*Программа обработки файла:Cчитает количество строк*/
s1: for(i=0;i<sizeof(buf);i++)
{
buf[i]=' ';
}
fd=fopen(FF,"r");
fread(&buf,sizeof(buf),1,fd);
count=0;
for(i=0;i<sizeof(buf);i++)
{
if(buf[i]==PER)
count=count+1;
}
printf("The number of lines=%i\n",count);
fclose(fd);
goto s3;
s3: getchar();
LeaveCriticalSection(&csl);
}