Міністерство освіти, науки, молоді та спорту України
Національний університет “Львівська політехніка”
Кафедра ЕОМ
Звіт
до лабораторної роботи №1
з дисципліни: «Системне програмне забезпечення»
Варіант №2
Підготував:
Прийняв:
Львів – 2016
Тема. «Процеси та потоки»
Мета. Засвоїти поняття «процесів» та «потоків» як основних компонентів сучасних операційних систем. Здобути навики створення, керування та знищення «процесів» та «потоків» в операційній системі 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>
#include <iostream>
using namespace std;
unsigned __stdcall MyThreadFunc(void *hFile);
void main()
{
cout<<"LabWork N1. Variant 2. KI-34. KVM"<<endl;
cout<<"Start program"<<endl;
/***********************************************************************
Функції для роботи з процесами
************************************************************************/
cout<<"ShelExecute -> Notepad.exe"<<endl;
HWND Handle = GetDesktopWindow(); // Адрес робочого стола
ShellExecuteA(Handle,"open","notepad.exe", "d:\\1.txt", NULL, SW_RESTORE); // запускає процес з параметром
Sleep(1000);
cout<<"Create proccess -> Calc.exe"<<endl;
STARTUPINFO SProcess; //Стартові налаштування процесу
ZeroMemory(&SProcess,sizeof(SProcess)); //забиває структуру 0
SProcess.cb = sizeof(SProcess); //сб повинно приймати розмір структури а всі інші поля повинні бути 0
PROCESS_INFORMATION pi; //структура процесу
CreateProcess(NULL,"calc.exe",NULL,NULL,FALSE,0,NULL,NULL,&SProcess,&pi);
Sleep(3000);
cout<<"OpenProcess -> Handle"<<endl;
HANDLE hdl; //Вказівник на процес
hdl = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pi.dwProcessId); // звязує вказівник з процесом, і задає доступ до пріоритету
cout<<"GetExitCodeProcess"<<endl;
DWORD Exit;
GetExitCodeProcess(hdl,&Exit); // Чи запущений рпроцес, в Exit записується код роботи процесу
cout<<"Exit Code: "<<Exit<<endl;
if (Exit == STILL_ACTIVE)
cout<<"process is running"<<endl;
else
cout<<"process isn't running"<<endl;
cout<<"Set Priority Class"<<endl;
SetPriorityClass(hdl,REALTIME_PRIORITY_CLASS); //Встановлює пріоритет процесу
cout<<"Priority Class: "<<GetPriorityClass(hdl)<<endl;
cout<<"Terminate Proccess"<<endl;
TerminateProcess(hdl,0); // Закриває процес
cout<<"Close Handle"<<endl;
CloseHandle(hdl); //Видаляє хендл
Sleep(3000);
/***********************************************************************
Функції для роботи з потоками
***********************************************************************/
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;
cout<<"Create new Thread _beginthreadex()"<<endl;
//Функція створення потоку з передачею в його функцію імені створеного файла для проведення в нього запису
hThread = (HANDLE)_beginthreadex(NULL, 0, MyThreadFunc, &hFile, 0, &ThreadId); // відкриває новий потік
cout<<"GetExitCodeThread"<<endl;
int err = GetExitCodeThread(hThread,&dwExitCode); //Повертає код завершення потоку
if( err != 0 ){
if( dwExitCode == STILL_ACTIVE )
cout<<"Still active"<<endl; //Потік продовжує роботу
else
cout<<"Exit code: "<<dwExitCode<<endl; //Код завершення потоку
}
else
cout<<"Error! "<<err<<endl;
cout<<"Main thread will sleep for 1 sec"<<endl;
Sleep(1000);
DWORD ThreadPriority = GetThreadPriority(hThread); // Повертає пріоритет потоку
cout<<"Get Priority Class: "<<ThreadPriority<<endl;
for(int i = 1; i < 6; i++ ){
Sleep(100);
cout<<"Main thread: "<<i*100<<" ms"<<endl;
}
cout<<"Set Priority Class -> REALTIME_PRIORITY_CLASS"<<endl;
SetThreadPriority(hThread,REALTIME_PRIORITY_CLASS); //Встановлює пріоритет потоку//
for(int i = 6; i < 12; i++ ){
Sleep(100);
cout<<"Main thread: "<<i*100<<" ms"<<endl;
}
cout<<"Stop new Thread"<<endl;
SuspendThread(hThread); // призупинити виконання потоку
for(int i = 12; i < 18; i++ ){
Sleep(100);
cout<<"Main thread: "<<i*100<<" ms"<<endl;
}
cout<<"Resume new Thread"<<endl;
ResumeThread(hThread); //Відновити виконання потоку
for(int i = 18; i < 24; i++ ){
Sleep(100);
cout<<"Main thread: "<<i*100<<" ms"<<endl;
}
cout<<"Close new Thread"<<endl;
TerminateThread(hThread,0); // Закрити потік
cout<<"Close Thread handle"<<endl;
CloseHandle(hThread); //Закрити хендл потоку
//Вивід результату, записаного у файл lab1.txt з допомогою нового процесу - "notepad.exe d:\\lab1.txt"
cout<<"REZULTAT -> Notepad.exe(lab1.txt)"<<endl;
STARTUPINFO SProcess1; //Стартові налаштування процесу
ZeroMemory(&SProcess1,sizeof(SProcess1)); //забиває структуру 0
SProcess1.cb = sizeof(SProcess1); //сб повинно приймати розмір структури, а всі інші поля повинні бути 0
PROCESS_INFORMATION pi1; //структура процесу
CreateProcess(NULL,"notepad.exe d:\\lab1.txt",NULL,NULL,FALSE,0,NULL,NULL,&SProcess1,&pi1);
cout<<"End program"<<endl;
Sleep(30000);
//Завершення процесу та видалення його хендлу
HANDLE hdl1; //Вказівник на процес
hdl1 = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pi1.dwProcessId); // звязує вказівник з процесом, і задає доступ до пріоритету
TerminateProcess(hdl1,0); // Закриває процес
CloseHandle(hdl1); //Видаляє хендл
}
unsigned __stdcall MyThreadFunc(void *hFile)
{
for(int j = 1; j < 25; j++){
Sleep(150);
cout<<"NewThread: "<<j*100<<" ms"<<endl;
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,"\r\n",2,&dwNOBW,NULL);
}
CloseHandle(*(HANDLE*)hFile);
_endthreadex(0); //Завершити потік
return 0;
}
Результат виконання програми:
/
Висновок: На лабораторній роботі засвоїв поняття “процесів” та ”потоків” як основних компонентів сучасних операційних систем. Здобув навики створення, керування та знищення “процесів” та “потоків” в операційній системі Windows.