diff --git a/semester-4/ОС/lb-7/README.md b/semester-4/ОС/lb-7/README.md new file mode 100644 index 0000000..db06797 --- /dev/null +++ b/semester-4/ОС/lb-7/README.md @@ -0,0 +1,7 @@ +> [!NOTE] +> Викладач: Мельникова Р. В. +> +> Оцінка: in progress + +> [!TIP] +> Виконано для Linux в команді. diff --git a/semester-4/ОС/lb-7/doc.yaml b/semester-4/ОС/lb-7/doc.yaml new file mode 100644 index 0000000..7730c2c --- /dev/null +++ b/semester-4/ОС/lb-7/doc.yaml @@ -0,0 +1,21 @@ +title: Керування процесами та потоками +subject: ОС +doctype: ЛБ +worknumber: 7 +mentors: + - name: Мельнікова Р. В., + gender: f, + degree: доц. каф. ПІ, +edu_program: &EDU ПЗПІ +university: ХНУРЕ +authors: + - name: Ситник Є. С. + course: 2 + edu: *EDU + gender: m + group: 23-2 + - name: Малишкін. А. С. + course: 2 + edu: *EDU + gender: m + group: 23-2 diff --git a/semester-4/ОС/lb-7/img/getenv.png b/semester-4/ОС/lb-7/img/getenv.png new file mode 100644 index 0000000..f8af46c Binary files /dev/null and b/semester-4/ОС/lb-7/img/getenv.png differ diff --git a/semester-4/ОС/lb-7/img/matrix-result.png b/semester-4/ОС/lb-7/img/matrix-result.png new file mode 100644 index 0000000..f5770ff Binary files /dev/null and b/semester-4/ОС/lb-7/img/matrix-result.png differ diff --git a/semester-4/ОС/lb-7/img/text-files.png b/semester-4/ОС/lb-7/img/text-files.png new file mode 100644 index 0000000..c8dca43 Binary files /dev/null and b/semester-4/ОС/lb-7/img/text-files.png differ diff --git a/semester-4/ОС/lb-7/img/text-result.png b/semester-4/ОС/lb-7/img/text-result.png new file mode 100644 index 0000000..10f64be Binary files /dev/null and b/semester-4/ОС/lb-7/img/text-result.png differ diff --git a/semester-4/ОС/lb-7/img/threads-nowait.png b/semester-4/ОС/lb-7/img/threads-nowait.png new file mode 100644 index 0000000..4d33302 Binary files /dev/null and b/semester-4/ОС/lb-7/img/threads-nowait.png differ diff --git a/semester-4/ОС/lb-7/src/Justfile b/semester-4/ОС/lb-7/src/Justfile new file mode 100644 index 0000000..9d1c8f8 --- /dev/null +++ b/semester-4/ОС/lb-7/src/Justfile @@ -0,0 +1,19 @@ +cc := "clang" +cc_flags := "-Wall -Wextra -ggdb" + +@clean: + rm -rf build + +@mkdir-build: clean + mkdir -p build + +@build-editor: mkdir-build + {{cc}} {{cc_flags}} -o build/main editor/main.c + {{cc}} {{cc_flags}} -o build/editor editor/editor.c + {{cc}} {{cc_flags}} -o build/analizer editor/analizer.c + +@build-threads: mkdir-build + {{cc}} {{cc_flags}} -o build/main threads/main.c + +@build-matrix-calc: mkdir-build + {{cc}} {{cc_flags}} -o build/main matrix-calc/main.c diff --git a/semester-4/ОС/lb-7/src/editor/analizer.c b/semester-4/ОС/lb-7/src/editor/analizer.c new file mode 100644 index 0000000..35aa6d8 --- /dev/null +++ b/semester-4/ОС/lb-7/src/editor/analizer.c @@ -0,0 +1,224 @@ +#define _XOPEN_SOURCE +#include + +#include +#include +#include +#include +#include + +#include "shared.h" + +typedef enum { ENC_UTF8, ENC_UTF16LE, ENC_UTF16BE, ENC_CP1251 } Encoding; + +Encoding get_encoding(char *input, size_t length) { + unsigned char *data = (unsigned char *)input; + + if (length >= 2) { + if (data[0] == 0xFE && data[1] == 0xFF) { // UTF-16 BE BOM + return ENC_UTF16BE; + } else if (data[0] == 0xFF && data[1] == 0xFE) { // UTF-16 LE BOM + return ENC_UTF16LE; + } + } + + size_t i = 0; + while (i < length) { + unsigned char c = data[i]; + + if (c < 0x80) { // ASCII + i++; + continue; + } + + if ((c & 0xE0) == 0xC0) { // 2-byte UTF8 + if (i + 1 >= length || (data[i + 1] & 0xC0) != 0x80 || c < 0xC2) + return ENC_CP1251; // overflow + i += 2; + continue; + } + + if ((c & 0xF0) == 0xE0) { // 3-byte UTF8 + if (i + 2 >= length || (data[i + 1] & 0xC0) != 0x80 || + (data[i + 2] & 0xC0) != 0x80 || (c == 0xE0 && data[i + 1] < 0xA0)) + return ENC_CP1251; // overflow + i += 3; + continue; + } + + if ((c & 0xF8) == 0xF0) { // 4-byte UTF8 + if (i + 3 >= length || (data[i + 1] & 0xC0) != 0x80 || + (data[i + 2] & 0xC0) != 0x80 || (data[i + 3] & 0xC0) != 0x80 || + (c == 0xF0 && data[i + 1] < 0x90)) + return ENC_CP1251; // overflow + i += 4; + continue; + } + + return ENC_CP1251; // Invalid utf-8 byte + } + + return ENC_UTF8; +} + +char *to_utf8(char *input, size_t length, Encoding encoding) { + if (!input || length == 0) + return NULL; + + char *encstr; + int skip_bom = 0; + switch (encoding) { + case ENC_CP1251: + encstr = "CP1251"; + break; + case ENC_UTF8: + encstr = "UTF-8"; + break; + case ENC_UTF16LE: + encstr = "UTF-16LE"; + skip_bom = 2; + break; + case ENC_UTF16BE: + encstr = "UTF-16BE"; + skip_bom = 2; + break; + } + + iconv_t cd = iconv_open("UTF-8", encstr); + if (cd != (iconv_t)-1) { + size_t out_size = length * 4 + 1; + char *output = malloc(out_size); + if (output) { + char *in_ptr = (char *)input + skip_bom; + size_t in_left = length - skip_bom; + char *out_ptr = output; + size_t out_left = out_size - 1; + + if (iconv(cd, &in_ptr, &in_left, &out_ptr, &out_left) != (size_t)-1) { + *out_ptr = '\0'; + iconv_close(cd); + return output; + } + free(output); + } + iconv_close(cd); + } + + return NULL; +} + +int main(int argc, char *argv[]) { + if (argc > 2) { + fprintf(stderr, "Error: too many arguments.\n"); + return 1; + } + + if (argc < 2) { + fprintf(stderr, "Error: too few arguments.\n"); + return 1; + } + + struct tm tm = {0}; + if (strptime(argv[1], "%s", &tm) == NULL) { + fprintf(stderr, "Error: please provide time in UNIX timestamp format.\n"); + return 1; + } + time_t norm_time = mktime(&tm); + + char *paths[] = {CWD, NULL}; + FTS *fts = fts_open(paths, FTS_NOCHDIR, NULL); + if (fts == NULL) { + err("Can't initialise FTS", __LINE__ - 1); + return 2; + } + + char *buf = (char *)malloc(1024 * sizeof(char)); + + for (FTSENT *ent = fts_read(fts); ent != NULL; ent = fts_read(fts)) { + switch (ent->fts_info) { + case FTS_F: + if (ent->fts_statp->st_mtime > norm_time) { + printf(RED); + printf("--------------------------------\n"); + + printf(GREEN); + printf("File: %s\n", ent->fts_name); + + char mtime[1024] = {0}; + strftime(mtime, 1024, "%T", localtime(&ent->fts_statp->st_mtime)); + printf("Modified at: %s\n", mtime); + + printf("Size: %ld\n", ent->fts_statp->st_size); + + FILE *file = fopen(ent->fts_path, "r"); + if (file == NULL) { + err("Can't open the file", __LINE__ - 1); + break; + } + + size_t bytes_read = fread(buf, 1, 1023, file); + if (ferror(file) != 0) { + err("Can't read the file", __LINE__ - 2); + fclose(file); + break; + } + buf[bytes_read] = '\0'; + + Encoding enc = get_encoding(buf, bytes_read); + + printf("Lines length:"); + size_t lines = 0; + size_t line_len = 0; + for (size_t i = (enc == 2 || enc == 1) ? 2 : 0; i < bytes_read; i++) { + line_len++; + + int newline_detected = 0; + switch (enc) { + case ENC_UTF16BE: + if (i + 1 < bytes_read && buf[i] == '\0' && buf[i + 1] == '\n') { + newline_detected = 1; + line_len++; + i++; + } + break; + case ENC_UTF16LE: + if (i + 1 < bytes_read && buf[i] == '\n' && buf[i + 1] == '\0') { + newline_detected = 1; + line_len++; + i++; + } + break; + case ENC_CP1251: + case ENC_UTF8: + default: + if (buf[i] == '\n') + newline_detected = 1; + break; + } + + if (newline_detected) { + printf(" %zu", line_len); + lines++; + line_len = 0; + } + } + printf("\nTotal lines: %zu\n", lines); + + if (enc != ENC_UTF8) + buf = to_utf8(buf, bytes_read, enc); + + printf(NORMAL); + printf("%s\n", buf); + + fclose(file); + } + break; + default: + break; + } + } + + free(buf); + + return 0; +} diff --git a/semester-4/ОС/lb-7/src/editor/editor.c b/semester-4/ОС/lb-7/src/editor/editor.c new file mode 100644 index 0000000..a027b6c --- /dev/null +++ b/semester-4/ОС/lb-7/src/editor/editor.c @@ -0,0 +1,31 @@ +#include +#include +#include + +#include "shared.h" + +int main() { + char *editor = getenv("EDITOR"); + if (editor == NULL) + editor = "vi"; + + struct stat st = {0}; + if (stat(CWD, &st) == -1) { + if (mkdir(CWD, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) { + err("Can't create directory for files", __LINE__ - 1); + return 1; + } + } + + if (chdir(CWD) == -1) { + err("Can't change working directory", __LINE__ - 1); + return 2; + } + + if (execlp(editor, editor, (char *)NULL)) { + err("Can't launch editor", __LINE__ - 1); + return 3; + } + + return 0; +} diff --git a/semester-4/ОС/lb-7/src/editor/main.c b/semester-4/ОС/lb-7/src/editor/main.c new file mode 100644 index 0000000..905edcf --- /dev/null +++ b/semester-4/ОС/lb-7/src/editor/main.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#include "shared.h" + +int main(void) { + time_t t = time(NULL); + char s[256]; + strftime(s, 256, "%s", localtime(&t)); + + __pid_t e_pid = fork(); + if (e_pid == -1) { + err("Can't fork process for editor", __LINE__ - 1); + return 1; + } + + if (!e_pid && execl("build/editor", "build/editor", (char *)NULL)) { + err("Can't launch build/editor", __LINE__ - 1); + return 2; + } + + waitpid(e_pid, 0, 0); + + __pid_t a_pid = fork(); + if (a_pid == -1) { + err("Can't fork process for analizer", __LINE__ - 1); + return 3; + } + + if (!a_pid && execl("build/analizer", "build/analizer", s, (char *)NULL)) { + err("Can't launch build/analizer", __LINE__ - 1); + return 4; + } + + waitpid(a_pid, 0, 0); + + return 0; +} diff --git a/semester-4/ОС/lb-7/src/editor/shared.h b/semester-4/ОС/lb-7/src/editor/shared.h new file mode 100644 index 0000000..299c34a --- /dev/null +++ b/semester-4/ОС/lb-7/src/editor/shared.h @@ -0,0 +1,16 @@ +#include +#include +#include + +#define CWD "/tmp/files-dir" + +#define RED "\x1b[31m" +#define GREEN "\x1b[32m" +#define NORMAL "\x1b[0m\n" + +#define err(msg, line) \ + do { \ + char buf[256]; \ + snprintf(buf, sizeof(buf), "%s:%d: error: %s", __FILE__, line, msg); \ + perror(buf); \ + } while (0) diff --git a/semester-4/ОС/lb-7/src/matrix-calc/main.c b/semester-4/ОС/lb-7/src/matrix-calc/main.c new file mode 100644 index 0000000..e26e18a --- /dev/null +++ b/semester-4/ОС/lb-7/src/matrix-calc/main.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include + +#define MATRIX_SIZE 512 + +typedef struct { + double **a; + double **b; + double **r; + int start_row; + int end_row; + int size; +} ThreadArgs; + +void mul_seq(double **a, double **b, double **r, int size) { + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + r[i][j] = 0; + for (int k = 0; k < size; k++) + r[i][j] += a[i][k] * b[k][j]; + } + } +} + +void *mul_par_worker(void *args) { + ThreadArgs *t_args = (ThreadArgs *)args; + double **a = t_args->a; + double **b = t_args->b; + double **r = t_args->r; + int start_row = t_args->start_row; + int end_row = t_args->end_row; + int size = t_args->size; + + for (int i = start_row; i < end_row; i++) { + for (int j = 0; j < size; j++) { + r[i][j] = 0; + for (int k = 0; k < size; k++) + r[i][j] += a[i][k] * b[k][j]; + } + } + + pthread_exit(NULL); +} + +void mul_par(double **a, double **b, double **r, int size, int n) { + pthread_t threads[n]; + ThreadArgs args[n]; + int rows_per_thread = size / n; + int rem_rows = size % n; + int curr_row = 0; + + for (int i = 0; i < n; i++) { + args[i].a = a; + args[i].b = b; + args[i].r = r; + args[i].start_row = curr_row; + args[i].end_row = curr_row + rows_per_thread + (i < rem_rows ? 1 : 0); + args[i].size = size; + + if (pthread_create(&threads[i], NULL, mul_par_worker, &args[i]) != 0) { + perror("Can't creat thread"); + exit(EXIT_FAILURE); + } + curr_row = args[i].end_row; + } + + for (int i = 0; i < n; i++) { + if (pthread_join(threads[i], NULL) != 0) { + perror("Can't join thread"); + exit(EXIT_FAILURE); + } + } +} + +double **alloc_matrix(int size) { + double **matrix = (double **)malloc(size * sizeof(double *)); + for (int i = 0; i < size; i++) + matrix[i] = (double *)malloc(size * sizeof(double)); + + return matrix; +} + +void free_matrix(double **matrix, int size) { + for (int i = 0; i < size; i++) + free(matrix[i]); + + free(matrix); +} + +void init_matrix(double **matrix, int size) { + for (int i = 0; i < size; i++) + for (int j = 0; j < size; j++) + matrix[i][j] = (double)rand() / RAND_MAX * 10.0; +} + +typedef void (*mult_func_seq)(double **, double **, double **, int); +typedef void (*mult_func_par)(double **, double **, double **, int, int); + +double measure_time_seq(mult_func_seq func, double **a, double **b, double **r, + int size) { + struct timespec start, end; + clock_gettime(CLOCK_MONOTONIC, &start); + + func(a, b, r, size); + + clock_gettime(CLOCK_MONOTONIC, &end); + return (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9; +} + +double measure_time_par(mult_func_par func, double **a, double **b, double **r, + int size, int n) { + struct timespec start, end; + clock_gettime(CLOCK_MONOTONIC, &start); + + func(a, b, r, size, n); + + clock_gettime(CLOCK_MONOTONIC, &end); + return (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9; +} + +int main() { + double **a = alloc_matrix(MATRIX_SIZE); + double **b = alloc_matrix(MATRIX_SIZE); + double **r_seq = alloc_matrix(MATRIX_SIZE); + double **r_par = alloc_matrix(MATRIX_SIZE); + + srand(time(NULL)); + init_matrix(a, MATRIX_SIZE); + init_matrix(b, MATRIX_SIZE); + + printf("performing sequentional multiplication...\n"); + double sequential_time = measure_time_seq(mul_seq, a, b, r_seq, MATRIX_SIZE); + printf("time: %f secinds\n", sequential_time); + + int num_threads = 32; + printf("\nperforming parrallel multiplication (%d threads)...\n", + num_threads); + double parallel_time = + measure_time_par(mul_par, a, b, r_par, MATRIX_SIZE, num_threads); + printf("time: %f seconds\n", parallel_time); + + free_matrix(a, MATRIX_SIZE); + free_matrix(b, MATRIX_SIZE); + free_matrix(r_seq, MATRIX_SIZE); + free_matrix(r_par, MATRIX_SIZE); + + return 0; +} diff --git a/semester-4/ОС/lb-7/src/threads/main.c b/semester-4/ОС/lb-7/src/threads/main.c new file mode 100644 index 0000000..41c93d7 --- /dev/null +++ b/semester-4/ОС/lb-7/src/threads/main.c @@ -0,0 +1,29 @@ +#include +#include +#include + +#define NUM_THREADS 10 + +void *thread_function(void *thread_id) { + long tid = (long)thread_id; + printf("Begin\t%ld\n", tid); + for (int i = 0; i < 100000; ++i) + ; + printf("End\t%ld\n", tid); + pthread_exit(NULL); +} + +int main() { + pthread_t threads[NUM_THREADS]; + long t; + + for (t = 0; t < NUM_THREADS; t++) + pthread_create(&threads[t], NULL, thread_function, (void *)t); + + void *status; + pthread_join(threads[0], &status); + + printf("Main thread completed execution\n"); + + return 0; +} diff --git a/semester-4/ОС/lb-7/Лр_7_Ситник_Малишкін_ПЗПІ_23_2.pdf b/semester-4/ОС/lb-7/Лр_7_Ситник_Малишкін_ПЗПІ_23_2.pdf new file mode 100644 index 0000000..a3b6023 Binary files /dev/null and b/semester-4/ОС/lb-7/Лр_7_Ситник_Малишкін_ПЗПІ_23_2.pdf differ diff --git a/semester-4/ОС/lb-7/Лр_7_Ситник_Малишкін_ПЗПІ_23_2.typ b/semester-4/ОС/lb-7/Лр_7_Ситник_Малишкін_ПЗПІ_23_2.typ new file mode 100644 index 0000000..0fd1a80 --- /dev/null +++ b/semester-4/ОС/lb-7/Лр_7_Ситник_Малишкін_ПЗПІ_23_2.typ @@ -0,0 +1,425 @@ +#import "@local/nure:0.1.0": * + +#show: pz-lb.with(..yaml("doc.yaml")) + +#v(-spacing) + +== Мета роботи +Метою даної лабораторної роботи є вивчення створення процесів та потоків при виконанні +програм. + +== Хід роботи +Дану лабораторну роботу нами було виконано для операційної системи Linux із +використанням POSIX API. +=== Завдання 1 -- програмний комплекс для створення та аналізу текстових файлів. +Метою цього завдання є дослідження створення дочірніх процесів із своєї програми. + +Завдання полягає у розробці трьох програм: ++ програма 1 запускає текстовий редактор у заданій папці; ++ програма 2 аналізує файли у заданій директорії, створені після вказаного часу, +визначаючи для них розмір, кількість рядків та довжину кожного рядка, з підтримкою +ASCII та UNICODE кодувань; ++ програма 3 запускає спочатку програму 1, а потім програму 2, передаючи час запуску +першої програми як параметр для другої. + +==== Розробка програми 1 +Програма 1 повинна запустити системний текстовий редактор за замовчуванням у вказаній +директорії. + +Для цього необхідно: ++ визначити редактор за замовчуванням; ++ створити вказану директорію, якщо вона не існує; ++ запустити редактор у вказаній директорії. + +Назва текстового редактору за замовчуванням на POSIX сумісних операційних системах +зазвичай зберігається в змінній оточення "EDITOR", отримати її значення можна за +допомогою функції "getenv" стандартної бібліотеки. Ця функція повертає вказівник на +рядок, що відповідає значенню змінної, або "NULL", якщо ця змінна відсутня. +Скористаємося цим, щоб встановити редактор за замовчуванням. + +Щоб визначити чи директорія для файлів існує скористаємося системним викликом +"stat" -- якщо він завершиться із помилкою, значить директорії не існує, та її +треба створити. Для створення директорії в разі її відсутності використаємо +системний виклик "mkdir". + +В POSIX сумісних операційних системах для запуску зовнішніх програм використовується +системний виклик "exec". Цей системний виклик замінює образ поточного процесу на новий, +а не створює новий процес, тому щоб відкрити редактор у вказаній директорії необхідно +зміни робочу директорію поточного процесу. Для цього скористаємося системним викликом +"chdir". + +```c +#include +#include +#include +#include "shared.h" + +int main() { + char *editor = getenv("EDITOR"); + if (editor == NULL) + editor = "vi"; + + struct stat st = {0}; + if (stat(CWD, &st) == -1) { + if (mkdir(CWD, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) { + err("Can't create directory for files", __LINE__ - 1); + return 1; + } + } + + if (chdir(CWD) == -1) { + err("Can't change working directory", __LINE__ - 1); + return 2; + } + + if (execlp(editor, editor, (char *)NULL)) { + err("Can't launch editor", __LINE__ - 1); + return 3; + } + + return 0; +} +``` + +==== Розробка програми 2 +Друга програма в якості аргументу отримує час, файли відредаговані після якого +необхідно обробити. Перевіримо кількість аргументів, що були передані програмі, +та перетворимо перший з них в потрібний формат, для цього скористаємося функцією +"strptime" стандартної бібліотеки. + +```c +if (argc > 2) { + fprintf(stderr, "Error: too many arguments.\n"); + return 1; +} + +if (argc < 2) { + fprintf(stderr, "Error: too few arguments.\n"); + return 1; +} + +struct tm tm = {0}; +if (strptime(argv[1], "%s", &tm) == NULL) { + fprintf( + stderr, + "Error: please provide time in UNIX timestamp format.\n" + ); + return 1; +} +time_t norm_time = mktime(&tm); +``` + +Для зручного перебору всіх файлів в директорії скористаємося засобами "FTS" +#footnote([ + FTS (File Tree Traversal) в POSIX — це набір функцій, + що дозволяють послідовно обходити файлову ієрархію (директорії та файли). + FTS надає структурований спосіб для навігації по файловій системі, + отримуючи інформацію про кожен знайдений елемент. +]) +, що надає стандарт POSIX. + +Створимо об'єкт FTS із коренем в директорії для файлів. +```c +char *paths[] = {CWD, NULL}; +FTS *fts = fts_open(paths, FTS_NOCHDIR, NULL); +if (fts == NULL) { + err("Can't initialise FTS", __LINE__ - 1); + return 2; +} +``` + +Виділимо буфер, в який будемо зберігати вміст файлів. +```c +char *buf = (char *)malloc(1024 * sizeof(char)); +``` + +Та обробимо всі файли, що були змінені після вказаного часу, не заглиблюючись +на інші рівні ієрархії файлової системи. +```c +for (FTSENT *ent = fts_read(fts); ent != NULL; ent = fts_read(fts)) { + switch (ent->fts_info) { + case FTS_F: + if (ent->fts_statp->st_mtime > norm_time) { + ... + } + break; + default: + break; + } +} + +free(buf); +``` + +Відкриємо кожен файл та визначимо його кодування (UTF-8, UTF-16LE, UTF-16BE чи CP1251). +```c +FILE *file = fopen(ent->fts_path, "r"); +if (file == NULL) { + err("Can't open the file", __LINE__ - 1); + break; +} + +size_t bytes_read = fread(buf, 1, 1023, file); +if (ferror(file) != 0) { + err("Can't read the file", __LINE__ - 2); + fclose(file); + break; +} +buf[bytes_read] = '\0'; + +Encoding enc = get_encoding(buf, bytes_read); +``` + +В залежності від кодування порахуємо символи нових рядків в файлі. Для UTF-8 та CP1251 +новий рядок позначається як один байт "\\n", а для UTF-16LE та UTF-16BE як 2 байти +"\\n\\0" та "\\0\\n" відповідно. +```c +printf("Lines length:"); +size_t lines = 0; +size_t line_len = 0; +for (size_t i = (enc == 2 || enc == 1) ? 2 : 0; i < bytes_read; i++) { + line_len++; + + int newline_detected = 0; + switch (enc) { + case ENC_UTF16BE: + if (i + 1 < bytes_read && buf[i] == '\0' && buf[i + 1] == '\n') { + newline_detected = 1; + line_len++; + i++; + } + break; + case ENC_UTF16LE: + if (i + 1 < bytes_read && buf[i] == '\n' && buf[i + 1] == '\0') { + newline_detected = 1; + line_len++; + i++; + } + break; + case ENC_CP1251: + case ENC_UTF8: + default: + if (buf[i] == '\n') + newline_detected = 1; + break; + } + + if (newline_detected) { + printf(" %zu", line_len); + lines++; + line_len = 0; + } +} +printf("\nTotal lines: %zu\n", lines); +``` + +Надрукуємо вміст файлу в консоль. Для зручнішого друку якщо текст має будь-яке +кодування крім UTF-8 перетворимо його в UTF-8 скориставшись засобами "iconv" +#footnote([ + iconv в POSIX -- це бібліотека, яка надає API для зручного перетворення будь-яких + кодувань символів. +]) +, що надає стандарт POSIX. +```c +if (enc != ENC_UTF8) + buf = to_utf8(buf, bytes_read, enc); + +printf(NORMAL); +printf("%s\n", buf); +``` +==== Розробка програми 3 +Третя програма повинна по черзі запустити 2 інші, при цьому зберігши час запуску +першої, та передавши його другій. +```c +time_t t = time(NULL); +char s[256]; +strftime(s, 256, "%s", localtime(&t)); +``` + +Як було зазначено раніше, системний виклик "exec" в POSIX сумісних операційних системах +заміняє образ процесу в якому він викликається новим, але в даному випадку нам +необхідно створити новий процес. Для цього скористаємося системним викликом "fork", +який створює точну копію поточного процесу. В скопійованому процесі викличемо "exec", +а в оригінальному дочекаємося завершення виконання копії за допомогою "waitpid". +```c +__pid_t e_pid = fork(); +if (e_pid == -1) { + err("Can't fork process for editor", __LINE__ - 1); + return 1; +} + +if (!e_pid && execl("build/editor", "build/editor", (char *)NULL)) { + err("Can't launch build/editor", __LINE__ - 1); + return 2; +} + +waitpid(e_pid, 0, 0); +``` + +Повторимо те саме для другої програми. +```c +__pid_t a_pid = fork(); +if (a_pid == -1) { + err("Can't fork process for analizer", __LINE__ - 1); + return 3; +} + +if (!a_pid && execl("build/analizer", "build/analizer", s, (char *)NULL)) { + err("Can't launch build/analizer", __LINE__ - 1); + return 4; +} + +waitpid(a_pid, 0, 0); +``` + +==== Тестування +Скомпілюємо всі 3 програми та запустимо головну. +Перед нами відкриється редактор у вказаній директорії, створимо в ньому 4 файли з +різним кодуванням. +#figure(image("img/text-files.png"), caption: [Файли з різним кодуванням]) + +Після закриття редактору можемо побачити наступний результат аналізу файлів. +#figure(image("img/text-result.png"), caption: [Результат аналізу файлів]) + +=== Завдання 2 -- аналіз поведінки потоків. +Метою цього завдання є дослідження поведінки потоків. + +Завдання полягає у створенні 10 потоків, та спостереженні за ними. + +В POSIX для керування потоками використовується "pthreads". + +Створимо 10 потоків, в кожному з них виведемо в консоль повідомлення про початок +виконання, виконаємо затратну по часу операцію, та виведемо повідомлення про +завершення виконання. +```c +#include +#include +#include + +#define NUM_THREADS 10 + +void *thread_function(void *thread_id) { + long tid = (long)thread_id; + printf("Begin\t%ld\n", tid); + for (int i = 0; i < 100000; ++i) + ; + printf("End\t%ld\n", tid); + pthread_exit(NULL); +} + +int main() { + pthread_t threads[NUM_THREADS]; + long t; + + for (t = 0; t < NUM_THREADS; t++) + pthread_create(&threads[t], NULL, thread_function, (void *)t); + + void *status; + pthread_join(threads[0], &status); + + printf("Main thread completed execution\n"); + + return 0; +} +``` +#figure(image("img/threads-nowait.png"), caption: [Результат виконання]) + +Можемо побачити, що потоки починають виконання в хаотичному порядку. +Це пояснюється тим, що операційна система передає потоку виконання незважаючи на час, +коли потік було створено. Також можемо побачити, що серед виводу нема повідомлень про +завершення потоків під номерами 7, 8 та 9. Оскільки головна програма чекає на +завершення лише одного потоку, всі потоки, що не встигли завершити своє виконання до +виходу першого автоматично завершуються операційною системою. Щоб це виправити +необхідно або від'єднувати потоки від головної програми, або чекати на завершення +всіх потоків, а не тільки першого. + +=== Завдання 3 -- порівняння швидкодії послідовних та паралельних обчислень. +Метою цього завдання є порівняння швидкодії послідовних та паралельних обчислень. + +Завдання полягає у створенні програм для паралельного та послідовного множення матриць. + +Множення матриць це дуже ресурсо-затратна операція, настільки затратна, що для її +виконання використовується спеціалізоване обладнання -- графічні прискорювачі, які +можуть виконувати тисячі паралельних операцій. + +Створимо функції для послідовного та паралельного множення матриць. +```c +void mul_seq(double **a, double **b, double **r, int size) { + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + r[i][j] = 0; + for (int k = 0; k < size; k++) + r[i][j] += a[i][k] * b[k][j]; + } + } +} + +void *mul_par_worker(void *args) { + ThreadArgs *t_args = (ThreadArgs *)args; + double **a = t_args->a; + double **b = t_args->b; + double **r = t_args->r; + int start_row = t_args->start_row; + int end_row = t_args->end_row; + int size = t_args->size; + + for (int i = start_row; i < end_row; i++) { + for (int j = 0; j < size; j++) { + r[i][j] = 0; + for (int k = 0; k < size; k++) + r[i][j] += a[i][k] * b[k][j]; + } + } + + pthread_exit(NULL); +} + +void mul_par(double **a, double **b, double **r, int size, int n) { + pthread_t threads[n]; + ThreadArgs args[n]; + int rows_per_thread = size / n; + int rem_rows = size % n; + int curr_row = 0; + + for (int i = 0; i < n; i++) { + args[i].a = a; + args[i].b = b; + args[i].r = r; + args[i].start_row = curr_row; + args[i].end_row = curr_row + rows_per_thread + (i < rem_rows ? 1 : 0); + args[i].size = size; + + if (pthread_create(&threads[i], NULL, mul_par_worker, &args[i]) != 0) { + perror("Can't creat thread"); + exit(EXIT_FAILURE); + } + curr_row = args[i].end_row; + } + + for (int i = 0; i < n; i++) { + if (pthread_join(threads[i], NULL) != 0) { + perror("Can't join thread"); + exit(EXIT_FAILURE); + } + } +} +``` + +Перевіримо швидкодію обох функцій, проведемо паралельні обчислення із використанням +4 потоків. +#figure(image("img/matrix-result.png"), caption: [Результати тестів]) + +Можемо побачити, що паралельне обчислення із використанням 4 потоків швидше +а послідовне приблизно в $4.4$ рази. + +Додаткове збільшення кількості потоків не змінює час виконання в кращу сторону, +а завелика кількість потоків навіть може погіршити результати. Використовувати більше +потоків ніж одночасно підтримує процесор нема сенсу, адже в такому випадку одному +потоку доведеться чекати поки виконується інший, і в такому разі час затрачений +на створення потоку та перемикання контексту процесора може навіть перевищити час, +що було зекономлено за допомогою паралелізації обчислень. + +== Висновки +Під час даної лабораторної роботи ми навчились використовувати процеси та потоки при виконанні програм. + +#show: appendices_style