МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ “ЛЬВІВСЬКА ПОЛІТЕХНІКА”
ЗВІТ
до лабораторних робіт №1-2
з дисципліни " Системне програмне
забезпечення "
Варіант №4
Тема. «Процеси та потоки»
Мета. Засвоїти поняття «процесів» та «потоків» як основних компонентів сучасних операційних систем. Здобути навики створення, керування та знищення «процесів» та «потоків» в операційній системі Windows.
Теоретична частина
В ОС Windows XP реалізована пріоритетна (витісняюча) багатозадачність. Це означає, що ОС може тимчасово припинити виконання однієї програми і перемкнути процесор на виконання іншої. Перемикання відбувається незалежно від бажання кожної з програм, завдяки чому зависання однієї програми не приводить до зависання всієї системи.
ОС 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. Системні виклики для роботи з процесами
Функція визначена таким чином:
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 містить дескриптор об’єкту ядра, лічильник якого треба зменшити.
Володіючи дескриптором процесу, можна управляти процесом за допомогою різних викликів.
Таблиця 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.
Функція 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).
Функція не містить параметрів і повертає ідентифікатор того процесу, чий потік її викликав.
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 є покажчиком на змінну, куди функція помістить ідентифікатор потоку.
Функція повертає дескриптор створеного потоку.
Для збільшення або зменшення швидкості виконання потоку (точніше за програмний код, який він реалізує) потрібно відповідно міняти його пріоритет, використовуючи функцію SetThreadPriority(). Функція визначена таким чином:
BOOL SetThreadPriority (HANDLE hThread, int nPriority).
Параметр hThread містить дескриптор потоку, рівень пріоритету якого треба змінити, а параметр nPriority містить один з прапорів, визначених в таблиці 1.Функция повертає TRUE, якщо пріоритет вдалося встановити.
Отримати рівень пріоритету потоку можна функцією GetThreadPriority(). Функція визначена таким чином:
int GetThreadPriority (HANDLE hTherad).
Параметр hThread містить дескриптор потоку, рівень пріоритету якого треба отримати. Функція повертає один з прапорів, визначених в таблиці 1.
Для завершення потоку використовуються виклики _endthreadex() або TerminateThread(). Функція _endthreadex() викликається зсередини потоку, який треба завершити. Вона визначена таким чином:
_endthreadex(unsigned int).
Ніяких значень функція не повертає, а як параметр передається нуль.
Функція TerminateThread() дозволяє завершити будь-який потік. Вона визначена таким чином:
BOOL TerminateThread (HANDLE hThread, UINT fuExitCode).
Параметр hThread містить дескриптор потоку який необхідно завершити, а в параметр ExitCode слід передати нуль. Якщо виклик успішний, функція повертає TRUE.
Після завершення потоку слід викликати функцію CloseHandle(). Тим самим зменшивши лічильник числа користувачів об’єкту ядра “потік” на 1. При досягненні лічильником нуля об’єкт видаляється системою.
ЗАВДАННЯ
Розробити програму в середовищі Visual Studio, що демонструє використання системних викликів.
Системний
виклик
Варіант
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. Проаналізувати вміст файлу після завершення програми та порівняти записи при активному та пасивному очікуванні.
ТЕКСТ ПРОГРАМИ
#include <Windows.h>
#include <ShellAPI.h>
#include <process.h>
#include <stdio.h>
#include <conio.h>
#include <time.h>
unsigned __stdcall MyThreadFunc(void *hFile);
void main()
{
printf ("Start program\n");
printf("Create proccess\n");
HWND Handle = GetDesktopWindow(); // Адрес робочого стола
ShellExecuteA(Handle,"open","notepad.exe", "C:\\1.txt", NULL, SW_RESTORE); // запускає процес з параметром
getch();
printf("Create proccess\n");
STARTUPINFO SProcess; //Стартові налаштування процесу
ZeroMemory(&SProcess,sizeof(SProcess)); //забиває структуру 0
SProcess.cb = sizeof(SProcess); //сб повинно приймати розмір структури а всі інші поля повинні бути 0
PROCESS_INFORMATION pi; //структура процесу
CreateProcessA(NULL,"calc.exe",NULL,NULL,FALSE,0,NULL,NULL,&SProcess,&pi);
getch();
DWORD ProcessID;
ProcessID = GetCurrentProcessId(); // Повертає номер процесу
printf("Get process ID: %d\n",ProcessID);
getch();
printf("Create process handle\n");
HANDLE hdl; //Вказівник на процес
hdl = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pi.dwProcessId); // звязує вказівник з процесом, і задає доступ до пріоритету
getch();
printf("Set Priority Class\n");
SetPriorityClass(hdl,REALTIME_PRIORITY_CLASS); //Встановлює пріоритет процесу
getch();
printf("Close proccess\n");
TerminateProcess(hdl,0); // Закриває процес
getch();
printf("Close proccess handle\n");
CloseHandle(hdl); //Видаляє хендл
getch();
unsigned ThreadId;
HANDLE hFile;
DWORD dwExitCode;
hFile = CreateFileA("D:\\lab1.txt",GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); //створює новий файл
HANDLE hThread;
printf("Create new Thread\n");
hThread = (HANDLE)_beginthreadex(NULL, 0, MyThreadFunc, &hFile, 0, &ThreadId); // відкриває новий потік
int err = GetExitCodeThread(hThread,&dwExitCode); //Повертає код виходу
if( err != 0 ){
if( dwExitCode == STILL_ACTIVE )
printf("still active\n");
else
printf("Exit code %d\n",dwExitCode);
}
else
printf("error! %d\n",err);
printf("Main thread will sleep for 1 sec\n");
Sleep(1000);
DWORD ThreadPriorityClass = GetThreadPriority(hThread); // Повертає пріоритет потоку
printf("Priority Class: %x\n",ThreadPriorityClass);
for(int i = 1; i < 6; i++ ){
Sleep(100);
printf("Main thread : %d ms\n",i*100);
}
printf("Set Priority Class\n");
SetThreadPriority(hThread,REALTIME_PRIORITY_CLASS); //Встановлює пріоритет потоку//
for(int i = 1; i < 6; i++ ){
Sleep(100);
printf("Main thread : %d ms\n",i*100);
}
printf("Close Thread handle\n");
CloseHandle(hThread); //Закрити хендл потоку
printf ("End program\n");
getch();
}
unsigned __stdcall MyThreadFunc(void *hFile)
{
for(int j = 1; j < 21; j++){
Sleep(170);
printf("NewThread : %d ms\n",j*100);
DWORD dwProcId;
dwProcId = GetCurrentProcessId();
DWORD dwThreadId;
dwThreadId = GetCurrentThreadId();
char buf[20];
itoa(dwProcId,buf,10);
DWORD dwNOBW;
int i = 0;
while(buf[i] != '\0'){
WriteFile(*(HANDLE*)hFile,&buf[i],1,&dwNOBW,NULL);
i++;
}
WriteFile(*(HANDLE*)hFile," ",1,&dwNOBW,NULL);
itoa(dwThreadId,buf,10);
i = 0;
while(buf[i] != '\0'){
WriteFile(*(HANDLE*)hFile,&buf[i],1,&dwNOBW,NULL);
i++;
}
WriteFile(*(HANDLE*)hFile," ",1,&dwNOBW,NULL);
struct tm *localtime( const time_t *time );
time_t theTime;
time( &theTime );
tm *t = localtime( &theTime );
char* buffer = asctime(t);
i = 0;
while(i<=25){
WriteFile(*(HANDLE*)hFile,&buffer[i],1,&dwNOBW,NULL);
i++;
}
WriteFile(*(HANDLE*)hFile,"\n",1,&dwNOBW,NULL);
}
CloseHandle(*(HANDLE*)hFile);
_endthreadex(0); //Завершити потік
return 0;
}
РЕЗУЛЬТАТИ РОБОТИ ПРОГРАМИ
ВИСНОВОК
На лабораторній роботі засвоїла поняття “процесів” та ”потоків” як основних компонентів сучасних операційних систем. Здобула навики створення, керування та знищення “процесів” та “потоків” в операційній системі Windows.