Вы находитесь на странице: 1из 15

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

«Процессы и потоки в операционной системе Linux»

Цель работы: изучение принципов создания, работы и завершения процессов и потоков в


операционной системе Linux; изучение функций библиотеки Pthreads.

Задания:
Часть 1. Процессы

1.1. Изучите команды ps и kill. Запустите любую свою программу, используя в конце &
(например: ./a.out & ). Дайте письменно объяснение результата работы команды ps.
Опишите также, как завершить процесс a.out ?

1.2. Изучите команды ОС Linux, предназначенные для управления процессами и мониторинга


состояния операционной системы: jobs, fg, bg, top. Объясните письменно назначение и
использование каждой из команд, а также их параметров. Подробно опишите действия,
необходимые для переключения процесса в фоновый режим выполнения и возврата из
этого режима.

1.3. Изучите функции семейства exec, а именно execv(), execl(), execvp()и execlp(). Объясните
письменно разницу между этими функциями.

1.4. Напишите программу на языке С/С++, удовлетворяющую всем перечисленным условиям:


1) Программа должна создать четыре процесса и ожидать окончания их выполнения.
2) Порядок создания процессов задается с командной строки (передается в виде
параметров в функцию main), например: ./a.out 1 3 2 4
3) Каждый процесс и основная программа должны выводить на экран сообщения о
начале и завершении своей работы в следующем формате: PID PPID “сообщение”
“время сообщения”
4) Для получения текущего времени используйте функцию ctime() из <time.h>.
5) Процессы должны использовать функции, перечисленные по порядку: execv(),
execl(), execvp()и execlp() с любыми командами ОС Linux внутри. Вы можете также
использовать любые свои программы.
6) Программа должна анализировать и сообщать о причинах завершения процессов,
используя переменную errno. Все значения ошибок определены в заголовочном
файле <sys/errno.h>

Часть 2. Потоки

2.1 Запустите несколько раз программу, приведенную в примере 6. Объясните письменно:


1) Результат работы программы.
2) Почему при запуске программы несколько раз и при увеличении числа потоков
(NKIDS) получаются различные результаты?

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 1
2.2 Напишите программу на языке C/C++, в которой:
1) Запускается четное количество потоков параллельно.
2) Каждый нечетный поток (например, первый) создает файл с именем из своего PID,
записывает в него произвольное число символов (от нескольких символов до сотен
миллионов символов) и закрывает этот файл.
3) Каждый четный поток (например, второй) открывает файл, созданный предыдущим
потоком с нечетным номером (в нашем случае первым), читает его, считает
количество символов в файле и закрывает его; при этом четный поток не должен
иметь никакой информации о количестве записываемых в файл символов и о том,
закончена ли запись в файл нечетным потоком.
4) Каждый из потоков выводит следующую информацию: ThreadID, PID, PPID, время,
имя файла, количество записанных или считанных символов.
5) Количество пар создаваемых потоков передается аргументом в программу с
командной строки. Программа должна ждать завершения работы всех потоков,
анализировать и сообщать о причинах завершения потоков. Для передачи имен
файлом между потоками можно использовать символьный массив в основной
программе.

2.3 Ответьте письменно на вопрос: какие атрибуты потоков реализованы в библиотеке


Pthreads (см. <pthread.h> и файлы справки).

Ход работы:
Часть 1
Задание 1
 Команда ps выводит все процессы для текущей оболочки bash. Так же с помощью
команды ps –a можно вывести все процессы системы.

 Завершить процесс можно с помощью kill PID

Задание 2
 Команда jobs выводит список процессов, выполняемых в фоновом режиме.

 Команда fg позволяет вывести процесс из фонового режима в текущую командную


оболочку и сделать его текущим заданием.

 Команда bg – восстанавливает задачи из фонового режима, исполнение которых


было приостановлено, запуская их снова в фоновом режиме.

 Команда top используется для мониторинга процессов в реальном времени.

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 2
1 строка - общая информация (top)
 
Первая строка выводит данные по порядку:
 текущее время (06:40:39)
 время работы системы (up 1 min)
 количество открытых пользовательских сессий (1 user)
 среднюю загрузку системы (load average: 3.39, 1.60, 0.60), три значения соответствуют
загрузке в последнюю минуту, пять минут и пятнадцать минут соответственно.

2 строка - статистика процессов (task)

Вторая строка выводит следующие данные:


 общее количество процессов в системе (223 total)
 количество работающих в данный момент процессов (1 running)
 количество ожидающих событий процессов (126 sleeping)
 количество остановленных процессов (0 stopped)
 количество процессов, ожидающих родительский процесс для передачи статуса
завершения (0 zombie)

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 3
3 строка - статистика использования центрального процессора (cpu)

В третьей строке приводится информация об использовании центрального процессора


 процент использования центрального процессора пользовательскими процессам (9.0%us)
 процент использования центрального процессора системными процессами (31.4%sy)
 процент использования центрального процессора процессами с приоритетом,
повышенным при помощи вызова nice (0.0%ni)
 процент времени, когда центральный процессор не используется (40.8%id)
 процент использования центрального процессора процессами, ожидающими завершения
операций ввода-вывода (17.6%wa)
 процент использования центрального процессора обработчиками аппаратных прерываний
(0.0%hi - Hardware IRQ (аппаратные прерывания))
 процент использования центрального процессора обработчиками программных
прерываний (1.2%si - Software Interrupts (программные прерывания))
 количество ресурсов центрального процессора "заимствованных" у виртуальной машины
гипервизором для других задач (таких, как запуск другой виртуальной машины); это
значение будет равно нулю на настольных компьютерах и серверах, не использующих
виртуальные машины (0.0%st - Steal Time (заимствованное время)).

4 и 5 строки - статистика использования памяти (memory usage)

В четвертой и пятой строке выводится информация об использовании физической


оперативной памяти и раздела подкачки соответственно. Значения в порядке следования:
общее количество памяти, количество используемой памяти, количество свободной
памяти, количество памяти в кэше буферов.

7 и последующие строки - список процессов

Последним источником информации является список процессов, отсортированный по


степени использования центрального процессора (по умолчанию).
 PID – идентификатор процесса
 USER - имя пользователя, который является владельцем процесса
 PR - приоритет процесса
 NI - значение "NICE", влияющие на приоритет процесса
 VIRT - объем виртуальной памяти, используемый процессом
 RES - объем физической памяти, используемый процессом
 SHR - объем разделяемой памяти процесса
 S - указывает на статус процесса: S=sleep (ожидает событий) R=running
(работает) Z=zombie (ожидает родительский процесс) (S)
 %CPU - процент использования центрального процессора данным процессом
 %MEM - процент использования оперативной памяти данным процессом
 TIME+ - общее время активности процесса
 COMMAND - имя процесса

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 4
 Для переключения процесса в фоновый режим нужно:
При запуске программы поставить в конце знак амперсанта (program &)
Или
1) Остановить процесс сочетанием Ctrl-Z
2) С помощью команды bg перевести его в фоновый режим

Затем с помощью fg процесс можно перевести обратно в обычный режим

Задание 3
Функция exec() загружает и запускает другую программу. Таким образом, новая
программа полностью замещает текущий процесс. Новая программа начинает свое выполнение с
функции main.

Суффиксы l, v, p, e в именах функций определяют формат и объем аргументов, а также


каталоги, в которых нужно искать загружаемую программу

l (список). Аргументы командной строки передаются в форме списка arg0, arg1.... argn,


NULL. Эту форму используют, если количество аргументов известно;
 v (vector). Аргументы командной строки передаются в форме вектора argv[]. Отдельные
аргументы адресуются через argv [0], argv [1]... argv [n]. Последний аргумент (argv [n])
должен быть указателем NULL;
 p (path). Обозначенный по имени файл ищется не только в текущем каталоге, но и в
каталогах, определенных переменной среды PATH;
 e (среда). Функция ожидает список переменных среды в виде вектора (envp []) и не
использует текущей среды.

Задание 4
Листинг
Base_Program
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <time.h>
#include <sys/wait.h>
#include <map>
#include <sys/errno.h>

char *ChildProgramPath = "/home/artem/Labs/ChildProcess";

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 5
using namespace std;
int main(int argc, char *argv[])
{
map<int, int> pID_To_Number;
int BaseProgram_pID = getpid();
time_t Time = time(0);
cout << '\n' << "Base Program With pID = " << BaseProgram_pID << " And ppID = " << getppid() <<
" Launched at " << ctime(&Time) << '\n';
////////
if (argv[1][0] != 'd' || argv[1][1] != 0)
{
ChildProgramPath = argv[1];
}
for (int cntr = 2; cntr <= argc - 1; cntr++)
{
int cpID = 0;
////////
if ((cpID = fork()) > -1)
{
int LaunchingProcessNumber = atoi(argv[cntr]);
char *ArgumentsForNewProcess[2];
////////////
if (getpid() != BaseProgram_pID)
{
cout << '\n' << "Process number " << LaunchingProcessNumber << " with ID " << getpid() <<
" and parent ID "
<< getppid() << " started execution at " << ctime(&Time);
ArgumentsForNewProcess[0] = argv[cntr];
switch (LaunchingProcessNumber)
{
case 1:
{
if (execv(ChildProgramPath, ArgumentsForNewProcess) != 0)
{
return errno;
}
}
break;
case 2:
Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 6
{
if (execl(ChildProgramPath, argv[cntr], NULL)!= 0)
{
return errno;
}
}
break;
case 3:
{
if (execvp(ChildProgramPath, ArgumentsForNewProcess)!= 0)
{
return errno;
}
}
break;
case 4:
{
if (execlp(ChildProgramPath, argv[cntr], NULL)!= 0)
{
return errno;
}
}
break;
}
}
else
{
pID_To_Number.insert(pair<int, int>(cpID, LaunchingProcessNumber));
}
}
else
{
cout << "Could not fork a new process " << '\n';
}
}
int Status;
int JustFinished_pID;
int JustFinished_ProcessNumber;
//Waiting For ALL Child Processes To Finish
Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 7
while ((JustFinished_pID = waitpid(-1, &Status, 0)) != -1)
{
auto it = pID_To_Number.find(JustFinished_pID);
if (it != pID_To_Number.end())
{
JustFinished_ProcessNumber = it->second;
}
cout << "Process number " << JustFinished_ProcessNumber << " finished at " << ctime(&Time) <<
" with " <<
((Status != 0) ? strerror(WEXITSTATUS(Status)) : " no errors.") << '\n';
Status = 0;
errno = 0;
}
cout << '\n' << "Base Program With pID = " << BaseProgram_pID << " And ppID = " << getppid() <<
" Stopped at " << ctime(&Time) << '\n';
}
Child_Process
#include <stdlib.h>
#include <unistd.h>

using namespace std;

int main (int argc, char* argv[])


{
sleep(1);
exit(0);
}

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 8
Результат работы:

Часть 2
Задание 1
1) Программа создает NKIDS параллельных процессов, каждому из которых передается
число на 1 большее предыдущего. Далее каждый процесс прибавляет свое полученное число к
сумме, состоящей из чисел, полученных выполненными потоками. В результате печатается сумма
этих чисел.
2) При запуске программы несколько раз с сохранением числа NKIDS результат зависит от
того в какой последовательности будут выполнены потоки.
При изменении числа NKIDS будет создано разное количество потоков и им будут
переданы разные числа. В результате сумма изменится.

Задание 2
Листинг
Threads
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 9
#include <iostream>
#include <fstream>
#include <string.h>
#include <cstdlib>
#include <math.h>
#include <sys/errno.h>

using namespace std;

bool PrintFileToTerminal = false;


bool DeleteFilesOnExit = false;
time_t Time = time(0);

struct ThreadData
{
int ThreadNumber;
ThreadData* PreviousThread;
pthread_t ThreadID;
string FileToOperateOn;
bool WorkFinished = false;
};

void WriteToFile(ThreadData *RecievedThreadData)


{
srand(time(0) + pthread_self());
int SymbolToWriteCount = rand() % 100000000 + 10;
int SymbolsWritten = 0;
ofstream OutputFile(RecievedThreadData->FileToOperateOn);
if (OutputFile.is_open())
{
while (SymbolsWritten < SymbolToWriteCount)
{
OutputFile << '1';
SymbolsWritten++;
}

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 10
OutputFile.flush();
OutputFile.close();
RecievedThreadData->WorkFinished = true;
cout << "Thread Number " << RecievedThreadData->ThreadNumber << ", With ThreadID
= " << pthread_self() << ", pID = " << getpid() <<
", ppID = " << getppid() << ", Finished Writing " << SymbolToWriteCount << " Symbols
To File " << RecievedThreadData->FileToOperateOn <<
" at " << ctime(&Time) << endl;
}
else
{
cout << "Thread number " << RecievedThreadData->ThreadNumber << ": Failed to open
file " << RecievedThreadData->FileToOperateOn << endl;
}
}

void ReadFromFile(ThreadData *RecievedThreadData)


{
ifstream InputFile(RecievedThreadData->FileToOperateOn);
string Line;
int TotalSymbols = 0;
while (getline(InputFile, Line))
{
int CurrentLineLength = Line.length();
int CurrentSymbol = 0;
while (CurrentSymbol < CurrentLineLength)
{
if (PrintFileToTerminal)
{
cout << Line[CurrentSymbol];
}
CurrentSymbol++;
TotalSymbols++;
}
}

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 11
RecievedThreadData->WorkFinished = true;
cout << "Thread Number " << RecievedThreadData->ThreadNumber << ", With ThreadID = "
<< pthread_self() << ", pID = " << getpid() << ", ppID = " << getppid()
<< ", Finished Reading File " << RecievedThreadData->FileToOperateOn << " at " <<
ctime(&Time) << ". It Contained "
<< TotalSymbols << " Symbols." << endl;
}

void *Thread_StartingPoint(void *RecievedThreadData)


{
ThreadData *ThreadData_P = (ThreadData *)RecievedThreadData;
//Setting Object's ThreadID After Thread Has Been Created And Threads[cntr] Now Holds
Valid ThreadID;
ThreadData_P->ThreadID = pthread_self();
//Odd Number
if ((ThreadData_P->ThreadNumber % 2) == 1)
{
ThreadData_P->FileToOperateOn = to_string(pthread_self()) + ".txt";
WriteToFile(ThreadData_P);
}
//Even Number
else
{
while (!ThreadData_P->PreviousThread->WorkFinished)
{
//Waiting For Previous (Writing) Thread To Finish It's Work.
}
ThreadData_P->FileToOperateOn = to_string(ThreadData_P->PreviousThread->ThreadID)
+ ".txt";
ReadFromFile(ThreadData_P);
}
}

int main(int argc, char *argv[])


{

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 12
int ThreadPairCount = atoi(argv[1]);
PrintFileToTerminal = atoi(argv[2]);
DeleteFilesOnExit = atoi(argv[3]);
pthread_t *ThreadIDs = new pthread_t[ThreadPairCount * 2];
ThreadData *DataForThreads = new ThreadData[ThreadPairCount * 2];
for (int cntr = 0; cntr < ThreadPairCount * 2; cntr++)
{
//Setting Thread Number (For Thread To Decide: Write To File Or Read It?)
DataForThreads[cntr].ThreadNumber = cntr + 1;
if (cntr > 0)
{
DataForThreads[cntr].PreviousThread = &DataForThreads[cntr - 1];
}
//Creating Threads
int ThreadCreationStatus = pthread_create(&ThreadIDs[cntr], NULL,
Thread_StartingPoint, &DataForThreads[cntr]);
if (ThreadCreationStatus != 0)
{
cout << "Failed To Create Thread Number " << cntr + 1 << " : " << strerror(errno);
errno = 0;
ThreadCreationStatus = 0;
}
}
for (int cntr = 0; cntr < ThreadPairCount * 2; cntr++)
{
int ThreadEndStatus = pthread_join(ThreadIDs[cntr], NULL);
if (ThreadEndStatus != 0)
{
cout << "Thread Number " << DataForThreads[cntr].ThreadNumber << " Exited With
Failure: " << strerror(errno) << endl;
}
}
if (DeleteFilesOnExit)
{
for (int cntr = 0; cntr < ThreadPairCount * 2; cntr++)

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 13
{
remove(DataForThreads[cntr * 2].FileToOperateOn.c_str());
}
}
}
Результат выполнения

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 14
Задание 3
Атрибуты являются способом определить поведение потока, отличное от поведения по
умолчанию. При создании потока с помощью pthread_create() или при инициализации
переменной синхронизации может быть определен собственный объект атрибутов. Атрибуты
определяются только во время создания потока; они не могут быть изменены в процессе
использования.

Функция pthread_attr_init() используется, чтобы инициализировать объект атрибутов


значениями по умолчанию. Память распределяется системой потоков во время выполнения.

Значения по умолчанию для атрибутов:

Атрибут Значение Смысл


scope PTHREAD_SCOPE_PROCESS Новый поток не
ограничен - не
присоединен ни к одному
процессу
detachstate PTHREAD_CREATE_JOINABLE Статус выхода и поток
сохраняются после
завершения потока

stackaddr NULL Новый поток получает


адрес стека, выделенного
системой
stacksize 1 Мбайт Новый поток имеет
размер стека,
определенный системой
inheritsched PTHREAD_INHERIT_SCHED Поток наследует
приоритет
диспетчеризации
родительского потока
schedpolicy SCHED_OTHER Новый поток использует
диспетчеризацию с
фиксированными
приоритетами. Поток
работает, пока не будет
прерван потоком с
высшим приоритетом или
не приостановится

Функция возвращает 0 после успешного завершения. Любое другое значение указывает,


что произошла ошибка.

Лист
Лабораторная работа №2 ТОГУ, ИС-51, Храмцов А.А.
по Системному ПО 15