50495

Процеси та потоки

Лабораторная работа

Информатика, кибернетика и программирование

Крім адресного простору процесу належать такі ресурси як файли динамічні області пам’яті і потоки. Ресурси створювані за життя процесу обов’язково знищуються при його завершенні. Потік thred описує послідовність виконання коду усередині процесу. Первинний потік процесу створюється системою автоматично під час створення процесу.

Украинкский

2014-01-24

134.5 KB

1 чел.

Лабораторна робота № 1

Тема: «Процеси та потоки»

Мета: Засвоїти поняття «процесів» та «потоків» як основних компонентів сучасних операційних систем. Здобути навики створення, керування та знищення «процесів» та «потоків» в операційній системі Windows.

1. Теоретична частина

В ОС Windows XP реалізована пріоритетна (витісняюча) багатозадачність. Це означає, що ОС може тимчасово припинити виконання однієї програми і перемкнути процесор на виконання іншої. Перемикання відбувається незалежно від бажання кожної з програм, завдяки чому зависання однієї програми не приводить до зависання всієї системи.

Запускаючи на виконання яку-небудь програму, ви створюєте новий процес. Процес звичайно визначають як “екземпляр” (іноді говорять, копію) виконуваного додатку. Для управління процесом система створює об’єкт ядра “процес”. Кожному об’єкту ядра виділяється в системі блок пам’яті, ініціалізований тією або іншою управляючою інформацією, зіставляється лічильник числа користувачів і описувач — ідентифікатор об’єкту (дескриптор). Сам по собі процес нічого не виконує — він просто “володіє” чотирьох гігабайтним адресним простором, що містить код і дані для ехе-файлу програми. Крім адресного простору, процесу належать такі ресурси, як файли, динамічні області пам’яті і потоки. Ресурси, створювані за життя процесу, обов’язково знищуються при його завершенні.

Щоб процес що-небудь виконав, в ньому потрібно створити потік. Потік (thread) описує послідовність виконання коду усередині процесу. Первинний потік процесу створюється системою автоматично під час створення процесу. Саме він відповідає за виконання коду ехе-файлу, поміщеного в адресний простір процесу. Потоку в системі зіставляється об’єкт ядра “потік”, що використовується для управління потоком. Відповідно, цьому об’єкту також виділяється в системі блок пам’яті, ініціалізований деякою управляючою інформацією, лічильником числа користувачів і дескриптор. У принципі, один процес може містити декілька потоків, і тоді вони “одночасно” виконують код в адресному просторі процесу. Це можливо тому що ОС розподіляє процесорний час не між процесами, а між потоками. Тим самим, виділяючи потокам відрізки часу (кванти) ОС забезпечує виконання кожного з них, створюючи ілюзію одночасного виконання потоків.

Кількість процесорного часу, що виділяється конкретному потоку, визначається багатьма чинниками. По-перше, кожний процес володіє власним базовим рівнем пріоритету, який присвоюється всім потокам, що належить йому. По-друге, кожний з потоків володіє власним пріоритетом, який додається до базового значення.

ОС Windows підтримує 4 класи пріоритетів процесів: idle (простоюючий), normal (нормальний), high (високий) і realtime (реального часу). Програми, що запускаються користувачем, у загальному відносяться до додатків з класом пріоритету normal. Пріоритет idle ідеальний для додатків, що займаються моніторингом системи або збереженням екрану (screen saver). Клас пріоритету high слід використовувати тільки при необхідності. Клас пріоритету realtime використовують тільки: 1) в програмі, напряму що “спілкується” з устаткуванням, і 2) якщо додаток виконує швидкоплинну операцію, яку не можна переривати у жодному випадку.

Потоки мають 5 рівнів відносного пріоритету. Їх опис приведений в таблиці 1

Таблиця 1 – Рівні пріоритету потоків

Ідентифікатор рівня

Опис

THREAD_PRIORITY_LOWEST

Пріоритет потоку повинен бути на 2 одиниці менше класу пріоритету процесу

THREAD_PRIORITY_BELOW_NORMAL

Пріоритет потоку повинен бути на 1 одиницю менше класу пріоритету процесу

THREAD_PRIORITY_NORMAL

Пріоритет потоку повинен відповідати класу пріоритету процесу

THREAD_PRIORITY_ABOVE_NORMAL

Пріоритет потоку повинен бути на 1 одиницю більше класу пріоритету процесу

THREAD_PRIORITY_HIGHEST

Пріоритет потоку повинен бути на 2 одиниці більше класу пріоритету процесу

Використовування декількох потоків в одному процесі дуже важливо з кількох причин. По-перше, це дозволяє добитися мінімального простою процесора, а значить – працювати більш ефективно. По-друге, потоки можуть виконувати які-небудь дії у фоновому режимі щодо основної програми. По-третє, потоки зручно використовувати також у випадку, якщо блокування або підвисання якої-небудь процедури не повинне стати причиною порушень функціонування основної програми.

2. Системні виклики для роботи з процесами

Простіше всього для створення нового процесу використовувати виклик WinExec(). При зверненні до цього виклику необхідно повідомити імя програми (повний шлях до ехе-файлу) і спосіб відображення вікна програми. Функція визначається таким чином:

int WinExec (const char* CmdLine, unsigned int CmdShow)

Параметр CmdLine є покажчиком на рядок, що містить ім’я виконуваного файлу, параметр CmdShow найчастіше має значення SW_RESTORE. При успішному виконанні функція повертає значення, більше 31.

Більш широкі можливості надаються функцією ShellExecute(). Вона може не тільки запускати заданий додаток, але і відкривати документ, пов’язаний з цим додатком. Для використовування функції ShellExecute() в модуль треба додати директиву препроцесора

# include <ShellAPl.h>

яка підключає модуль ShellAPI, в якому описана функція. Функція визначена таким чином:

void ShellExecute(HWnd Wnd, const char * Operation,

const char *FileName, const_ char *Parameters,

const char *Directory, unsigned int ShowCmd).

Параметр Wnd є дескриптором батьківського вікна, в якому відображаються повідомлення додатку, що запускається. Звичайно як він можна просто вказати Handle.

Параметр Operation указує на рядок з нульовим символом в кінці, яка визначає виконувану операцію. Цей рядок може містити текст “open” (відкрити), “print” (надрукувати) або “explore” (досліджувати). Якщо параметр Operation рівний NULL, то за замовчанням виконується операція “open”.

Параметр FileName указує на рядок з нульовим символом в кінці, яка визначає ім’я файлу, що відкривається, або ім’я теки, що відкривається.

Параметр Parameters указує на рядок з нульовим символом в кінці, яка визначає передавані в додаток параметри, якщо FileName визначає виконуваний файл. Якщо FileName указує на рядок, що визначає документ або теку, що відкривається, то цей параметр задається рівним NULL.

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

Параметр ShowCmd визначає режим відкриття вказаного файлу, звичайно, як і для функції WinExec, використовується значення SW_RESTORE.

Для того, щоб мати нагоду управляти створеним процесом в додатку слід використовувати виклик CreateProcess(). Функція була визначена таким чином:

BOOL CreateProcess(LPСTSTR IpszImageName

LPCTSTR IpszCommandLine

LPSECURITY_ATTRIBUTES IpsaProcess

LPSECURITY_ATTRIBUTES IpsaThread

BOOL fInheritHandles

DWORD fdwCreate

LPVOID IpvEnvironment

LPTSTR IpszCurDir

LPSTARTUPINFO IpsiStartInfo

LPPROCESS_INFORMATION IppiProcinfo);

Коли в додатку викликається CreateProcess(), система створює об’єкт ядра "процес" з початковим значенням лічильника числа користувачів, рівним одиниці. Цей об’єкт —компактна структура даних, через яку операційна система управляє процесом. Далі система переходить до створення об’єкту ядра "потік" (з лічильником числа користувачів, рівним одиниці) для управління первинним потоком нового процесу. Якщо системі вдається створити новий процес і його первинний потік, функція повертає TRUE.

Параметр lpszImageName містить адресу рядка з повним шляхом до ехе-файлу.

Параметр lpszCommandLine містить покажчик на командний рядок, але звичайно його задають NULL.

Параметри lpsaProcess і lpsaThread визначають потрібні атрибути захисту для об’єктів “процес” і “потік” відповідно. У ці параметри частіше за все заносять NULL, і тоді система закріплює за даними об’єктами дескриптори захисту за умовчанням.

Параметр fInheritHandles указує, чи успадковує новий процес дескриптори, що належать поточному процесу. Звичайно цей параметр рівний FALSE.

Параметр fdwCreate визначає прапори, що впливають на спосіб створення нового процесу, але на практиці вони використовуються не часто, тому значення цього параметра слід задати рівним нулю.

Параметр lpvEnvironment указує на блок пам’яті, що містить рядки змінних оточення, якими користуватиметься новий процес. Звичайно замість нього передається NULL, внаслідок чого породжуваний процес успадковує рядки змінних оточення від батьківського процесу.

Параметр lpszCurDir дозволяє батьківському процесу встановити в "дочірньому" поточний диск і каталог. Якщо його значення — NULL, робочий каталог нового процесу буде розташований там же, де і у додатку, що його породив.

Параметр lpsiStartInfo містить інформацію про запуск процесу і указує на структуру STARTUPINFO. При створенні процесу її слід визначити таким чином:

STARTUPINFO SProcess;

//ініціалізація

ZeroMemory(&SProcess,sizeof(SProcess));

SProcess.cb = sizeof(SProcess);

Параметр lppiProcInfo вказує на структуру PROCESS_INFORMATION, яку потрібно заздалегідь створити; її елементи ініціалізувалися самою функцією CreateProcess(). Структура є наступною:

typedef struct _PROCESS_INFORMATION {

HANDLEhProcess;

HANDLE hTh read;

DWORD dwProcessId;

DWORD dwThreadId;

} PROCESS_INFORMATION;

Як наголошувалося, створення нового процесу викликає і створення об’єктів ядра "процес" і "потік". У момент створення система присвоює кожному об’єкту початкове значення лічильника числа користувачів — одиницю. Далі функція CreateProcess() — перед самим поверненням — відкриває об’єкт "процес" і об’єкт "потік" і заносить їх описувачі (дескриптори) в елементи hProcess і hThread структури PROCESS_INFORMATION. Коли функція CreateProcess() відкриває ці об’єкти, лічильники кожного з них збільшуються до 2. Це означає, що — перш ніж система зможе вивільнити з пам’яті об’єкт "процес" — процес повинен бути завершений (лічильник зменшується до 1), а батьківський процес — викликати функцію CloseHandle() (і тим самим скинути лічильник до 0). Те ж саме відноситься і до об’єкту "потік": потік повинен бути завершений, а батьківський процес — закрити описувач об'єкту "потік".

Створеному процесу присвоюється унікальний ідентифікатор; ні у яких процесів, що виконуються в системі, не може бути однакових ідентифікаторів. Те ж стосується і потоків. Завершуючи свою роботу, CreateProcess() заносить значення ідентифікаторів в елементи dwProcessId і dwThreadId структури. Щоб надалі управляти створеними процесом і його потоком, використовуються їх дескриптори.

Коли відпадає необхідність у використовуванні об’єктів ядра "потік" і "процес" необхідно з потоків, що їх створили, викликати функцію CloseHandle(). Тим самим зменшується лічильник числа їх користувачів на 1. При досягненні лічильником нуля об’єкти видаляються системою. Функція визначена таким чином:

CloseHandle (HANDLE hObject);

Параметр hObject містить дескриптор об’єкту ядра, лічильник якого треба зменшити.

Володіючи дескриптором процесу, можна управляти процесом за допомогою різних викликів.

Звичайно процес створюється з класом пріоритету normal. Отримати клас пріоритету процесу можна використовуючи функцію GetPriorityClass().Функція визначена таким чином:

DWORD GetPriorityClass (HANDLE hProcess).

Параметр hProcess містить дескриптор процесу, клас пріоритету якого треба визначити. Функція повертає один з прапорів, перерахованих в таблиці 2.

Таблиця 2 – Рівні пріоритету процесу

Прапор

Клас пріоритету

IDLE_PRIORITY_CLASS

Idle

NORMAL_PRIORITY_CLASS

Normal

HIGH_PRIORITY_CLASS

High

REALTIME_PRIORITY_CLASS

Realtime

Змінити клас пріоритету процесу можна функцією SetPriorityClass().Функция визначена таким чином:

DWORD GetPriorityClass (HANDLE hProcess, DWORD fdwPriority).

Параметр hProcess містить дескриптор процесу, клас пріоритету якого треба змінити, параметр fdwPriority містить один з прапорів, перерахованих в таблиці 2.

Щоб визначити момент завершення процесу, можна використовувати виклик GetExitCodeProcess(). Цей виклик повертає або значення STILL_ACTIVE (якщо процес все ще продовжує роботу), або код завершення процесу (якщо процес був завершений). Функція визначена таким чином:

BOOL GetExitCodeProcess (HANDLE hProcess, DWORD ExitCode).

Параметр hProcess містить дескриптор процесу, а в параметр ExitCode після виконання функції записується код завершення процесу. Якщо виклик успішний, функція повертає TRUE.

Для завершення процесу використовується виклики ExitProcess() або TerminateProcess(). Виклик ExitProcess() використовується, якщо потрібно завершити поточний процес, для чого цю функцію повинен викликати один з потоків процесу. Функція визначена таким чином:

ExitProcess (UINT fuExitCode).

Ніяких значень функція не повертає, а як параметр передається нуль.

Функція TerminateProcess() дозволяє завершити будь-який процес. Вона була визначена таким чином:

BOOL TerminateProcess (HANDLE hProcess, UINT fuExitCode).

Параметр hProcess містить дескриптор завешуваного процесу, а в параметр ExitCode слід передати нуль. Якщо виклик успішний, функція повертає TRUE.

Щоб мати нагоду використовувати перераховані функції управління процесом, необхідний відповідний рівень доступу до дескриптора процесу. Цей рівень доступу можна отримати застосувавши функцію OpenProcess(). Ця функція використовується також отримання дескриптора вже створеного процесу по відомому ідентифікатору. Функція визначена таким чином:

HANDLE OpenProcess (DWORD dwFlag, DWORD dwProcessId).

Параметр dwFlag містить необхідний рівень або комбінацію рівнів, представлених в таблиці 3, а в параметрі dwProcessId указується ідентифікатор процесу. Функція повертає дескриптор процесу із заданими рівнями доступу.

Таблиця 3 – Рівні доступу до процесу

Рівень доступу

Призначення

PROCESS_TERMINATE

Для завершення роботи процесу

PROCESS_QUERY_INFORMATION

Для отримання класу пріоритету і коду завершення процесу

PROCESS_SET_INFORMATION

Для встановлення класу пріоритету процесу

PROCESS_ALL_ACCESS

Для отримання повного доступу

Отримати ідентифікатор процесу можна за допомогою функції GetCurrentProcessId(). Функція визначена таким чином:

DWORD GetCurrentProcessId (VOID).

Функція не містить параметрів і повертає ідентифікатор того процесу, чий потік її викликав.

Приклад створення процесу з отриманням всіх рівнів доступу до дескриптора цього процесу.

//дескриптор процесу

HANDLE hProc=NULL;

//установки нового процесу

STARTUPINFO SProcess;          

//структура з інформацією про створений процес

PROCESS_INFORMATION ProcInfo;

//ініціалізація

ZeroMemory(&SProcess,sizeof(SProcess));

SProcess.cb = sizeof(SProcess);

//створення дочірнього процесу

CreateProcess(NULL, ”Notepad.exe” ,NULL, NULL, FALSE, 0, NULL, NULL

&SProcess, &ProcInfo);

//відособлення процесу

CloseHandle(ProcInfo.hThread);

CloseHandle(ProcInfo.hProcess);

//отримання необхідних рівнів доступу для управління процесом

hProc = OpenProcess(PROCESS_ALL_ASSECC, FALSE, ProcInfo.dwProcessId);

3. Системні виклики для роботи з потоками

Якщо ви для розробки своїх програм використовуєте С++, то для використовування стандартною функцією створення потоку в модуль треба додати директиву препроцесора

# include <process.h>.

Бібліотечна функція створення потоку була визначена таким чином:

unsigned long beginthreadex(void *security, unsigned stack_side

unsigned (*start_address)(void*), void* arglist,

unsigned initflag, unsigned *thrdaddr).

Функція містить декілька параметрів. Розглянемо послідовно кожний з них. Параметр security є покажчиком на структуру SECURITY_ATTRIBUTES, яка визначає потрібні атрибути захисту для створюваного об’єкту “потік. Передайте в цей параметр NULL.

Параметр stack_side визначає яку частину потік може використовувати під свій стек. В цей параметр потрібно передати нуль, тоді система виділить 1Мб, що цілком достатньо для потоку.

Третій параметр є покажчиком на адресу функції потоку, з якою він почне свою роботу.

Параметр arglist використовується для передачі у функцію потоку якого-небудь простого 32-бітового покажчика на структуру даних або просто 32-бітове число.

Параметр initflag визначає додаткові прапори, що управляють створенням процесу. Якщо він рівний нулю, то виконання потоку починається негайно. Можна присвоїти йому ідентифікатор CREATE_SUSPENDED, тоді система створить потік, але притримуватиме його виконання до наступних вказівок.

Параметр thrdaddr є покажчиком на змінну, куди функція помістить ідентифікатор потоку.

Функція повертає дескриптор створеного потоку.

Приклад створення потоку з передачею в його функцію деякого цілого числа.

//------------------------------------------------------------------

//функція потоку

static unsigned __stdcall ThreadFunc(void *Num)

{

int Number = *(int*) Num;   //число, що приймається

Number++;

return(0);

}

//------------------------------------------------------------------

//функція створення потоку по натисненню кнопки

void __fastcall TForm1::Button1Click(TObject *Sender)

{

DWORD ThreadId;             // ідентифікатор потоку

HANDLE hThread;  // дескриптор потоку

int i = 5;             // передаване число

 

//створення потоку

hThread=(HANDLE)_beginthreadex(NULL, 0, ThreadFunc(void *), &i,

  0, &ThreadId);

}

//-----------------------------------------------------------------

Після створення потоку можна виконувати які-небудь операції управління потоком, використовуючи його дескриптор.

Для збільшення або зменшення швидкості виконання потоку (точніше за програмний код, який він реалізує) потрібно відповідно міняти його пріоритет, використовуючи функцію SetThreadPriority(). Функція визначена таким чином:

BOOL SetThreadPriority (HANDLE hThread, int nPriority).

Параметр hThread містить дескриптор потоку, рівень пріоритету якого треба змінити, а параметр nPriority містить один з прапорів, визначених в таблиці 1.Функция повертає TRUE, якщо пріоритет вдалося встановити.

Отримати рівень пріоритету потоку можна функцією GetThreadPriority(). Функція визначена таким чином:

int GetThreadPriority (HANDLE hTherad).

Параметр hThread містить дескриптор потоку, рівень пріоритету якого треба отримати. Функція повертає один з прапорів, визначених в таблиці 1.

Виконання потоку можна припинити і знову відновити. Це може бути потрібно у випадку, якщо первинний потік виконує деякі процедури ініціалізацій глобальних змінних, що використовуються в обох потоках. Припинити виконання потоку можна при його створінні, використовуючи прапор CREATE_SUSPENDED, про що вже згадувалося, або використовуючи функцію SuspendThread(). Функція визначена таким чином:

DWORD SuspendThread (HANDLE hThread).

Параметр hThread містить дескриптор потоку, чиє виконання треба припинити. У випадку, якщо виклик невдалий, функція повертає 0xFFFFFFFF.

Продовжити виконання потоку можна використовуючи функцію ResumeThread(). Функція визначена таким чином:

DWORD ResumeThread (HANDLE hThread).

Параметр hThread містить дескриптор потоку, чиє виконання треба відновити. У випадку, якщо виклик невдалий, функція повертає 0xFFFFFFFF.

Щоб визначити момент завершення потоку, використовується виклик GetExitCodeThread(). Цей виклик повертає або значення STILL_ACTIVE (якщо потік все ще продовжує роботу), або код завершення потоку. Функція визначена таким чином:

BOOL GetExitCodeThread (HANDLE hThread, DWORD ExitCode).

Параметр hThread містить дескриптор потоку, а в параметр ExitCode після виконання функції записується код завершення потоку. Якщо виклик успішний, функція повертає TRUE.

Для завершення потоку використовуються виклики _endthreadex() або TerminateThread(). Функція _endthreadex() викликається зсередини потоку, який треба завершити. Вона визначена таким чином:

_endthreadex(unsigned int).

Ніяких значень функція не повертає, а як параметр передається нуль.

Функція TerminateThread() дозволяє завершити будь-який потік. Вона визначена таким чином:

BOOL TerminateThread (HANDLE hThread, UINT fuExitCode).

Параметр hThread містить дескриптор потоку який необхідно завершити, а в параметр ExitCode слід передати нуль. Якщо виклик успішний, функція повертає TRUE.

Після завершення потоку слід викликати функцію CloseHandle(). Тим самим зменшивши лічильник числа користувачів об’єкту ядра “потік” на 1. При досягненні лічильником нуля об’єкт видаляється системою.

4. Завдання на лабораторну роботу

1. Розробити програму в середовищі Visual Studio, що демонструє використання системних викликів, відзначених символом “+”, відповідно до варіанту (таблиця 4).

Таблиця 4 – Варіанти завдань

Системний

виклик

Варіант

1

2

3

4

5

6

7

8

9

10

WinExec

+

+

+

+

+

ShellExecute

+

+

+

+

+

CreateProcess

+

+

+

+

+

+

+

+

+

+

OpenProcess

+

+

+

+

+

+

+

+

+

+

GetCurrentProcessId

+

+

+

+

+

+

CloseHandle

+

+

+

+

+

+

+

+

+

+

GetExitCodeProcess

+

+

+

+

+

TerminateProcess

+

+

+

+

+

+

+

GetPriorityClass

+

+

+

+

+

+

SetPriorityClass

+

+

+

+

+

+

_beginthreadex

+

+

+

+

+

+

+

+

+

+

_endthreadex

+

+

+

+

+

+

GetExitCodeThread

+

+

+

+

+

+

TerminateThread

+

+

+

+

+

+

+

+

SetThreadPriority

+

+

+

+

+

GetThreadPriority

+

+

+

+

+

SuspendThread

+

+

+

+

+

ResumeThread

+

+

+

+

+

2. Написати функцію потоку, яка як вхідний параметр приймає дескриптор відкритого текстового файлу. Функція повинна здійснювати посимвольне виведення у файл номер процесу, номер потоку та системний час. Передбачити достатню кількість ітерації запису в файл з одної функції потоку. Відкриття файлу слід виконувати до створення потоку, використовуючи функцію FileOpen(), а після завершення роботи з файлом слід викликати функцію FileClose().

3. Щоб продемонструвати паралельне виконання створеного потоку з первинним потоком процесу, в цикл запису з файлу потрібно додати виклик Sleep(Num), де Num – час в мілісекундах, на яке слід призупинити виконання потоку. Затримку також можна організувати за допомогою лічильника до якогось досить великого числа.

4. Проаналізувати вміст файлу після завершення програми та порівняти записи при активному та пасивному очікуванні.


 

А также другие работы, которые могут Вас заинтересовать

76045. Расчёт технико-экономических показателей сборочного участка 350 KB
  Основным исходным материалом для разработки курсовой работы служит производственная программа. Она определяет также важные количественные характеристики участка как: необходимое количество рабочих мест по операциям; производственные площади; численность работающих; затраты на выпуск продукции.
76048. Цифровой измеритель температуры и давления 187 KB
  В данной курсовой работе разрабатывается цифровой измеритель температуры и давления, а также программное обеспечение для данного устройства. Приводятся структурная, функциональная и принципиальная схемы разрабатываемого устройства. Выполняется описание заданных процедур программного обеспечения.
76050. Организация работы кафе-мороженого на 50 мест 91 KB
  Универсальные кафе с самообслуживанием реализуют несложные прозрачные бульоны из первых блюд, вторые блюда несложного приготовления: блинчики с различными начинками, яичница, сосиски, сардельки с несложным гарниром.