#import "@local/nure:0.1.0": *

#show: pz-lb.with(..yaml("doc.yaml"))

#v(-spacing)

== Мета роботи
Метою даної лабораторної роботи є вивчення особливостей використання та функції для роботи з віртуальною та фізичною пам'яттю.

== Хід роботи

Оскільки в якості операційних систем ми використовуємо дистрибутиви Linux, дану лабораторну роботу буде виконано саме для цієї ОС.

=== Розробка програми "Info". Виконання пунктів 1 -- 7 методичних вказівок.

Код складеної програми розміщено в додатку Б.

==== Скласти програму для формування системної інформації про різні типи пам'яті

В рамках цієї програми ми зосередимося на дослідженні та розробці механізму для збору системної інформації про різні типи пам'яті, використовуючи доступні системні виклики та утиліти Linux, що є аналогами функціональності GetSystemInfo та GlobalMemoryStatusEx у Windows. Зокрема, для отримання необхідних даних будуть задіяні системні виклики "sysinfo" та "sysconf", а також проаналізовано вміст файлів "/proc/meminfo" та "/proc/self/maps".

Розглянемо детальніше згадані вище засоби:
- "sysinfo": системний виклик, що надає загальну інформацію про систему, таку як час роботи, кількість процесів, використання оперативної та swap-пам'яті;
- "sysconf": системний виклик, який дозволяє отримати різні системні конфігураційні значення, включаючи розміри сторінок пам'яті;
- "/proc/meminfo": віртуальний файл у файловій системі /proc, який містить детальну інформацію про використання оперативної пам'яті, swap та інші параметри, зібрані ядром;
- "/proc/self/maps": віртуальний файл, що відображає адресний простір поточного процесу, включаючи інформацію про завантажені бібліотеки, сегменти пам'яті (heap, stack) та їхні права доступу;

==== Скомпілюйте 2 екземпляри програми, відкрийте їх за допомогою налагоджувача та перегляньте адреси початку та кінця цих програм.

Для відлагодження буде використано програму "gf", яка є графічною обгорткою для налагоджувача GNU -- "gdb".

#figure(image("img/gf1.png"), caption: [Адреси початку та кінця програми 1])
#figure(image("img/gf2.png"), caption: [Адреси початку та кінця програми 2])

Можемо побачити, що адреси початку та кінця в обох програмах однакові. Така поведінка була очікуваною, адже налагоджувач "gdb" за замовчуванням вимикає ASLR
#footnote[ASLR (Address Space Layout Randomization) -- технологія, при використанні якої розташування важливих структур даних випадковим чином змінюється в адресному просторі процесу.]
для зручності налагоджування. Увімкнемо ASLR в налагоджувачі за допомогою команди "set disable-randomisation off" та повторно виконаємо програми.

#figure(image("img/gf1-aslr.png"), caption: [Адреси початку та кінця програми 1 після увімкнення ASLR])
#figure(image("img/gf2-aslr.png"), caption: [Адреси початку та кінця програми 2 після увімкнення ASLR])

Можемо побачити, що тепер в налагоджувачі відображаються різні адреси програм.

==== Побудуйте список блоків пам'яті доступних програмі, виділіть пам'ять, та побудуйте список блоків пам'яті знову. Дослідіть зміни в списку вільних блоків.
Linux, як і інші POSIX сумісні операційні системи, на відміну від Windows, не має окремого системного виклику для отримання інформації про доступні програмі блоки пам'яті, натомість цю інформацію можна отримати із файлу "/proc/self/maps" динамічної віртуальної файлової системи процесів. Оскільки програмі доступно доволі багато блоків пам'яті, наприклад блоки пам'яті, що належать завантаженим бібліотекам на кшталт "libc", виведемо лише блоки пам'яті, що належать безпосередньо програмі, та спеціальні блоки.

#figure(image("img/mem-rg-1.png"), caption: [Частина блоків пам'яті, доступних програмі, до виділення])

#figure(image("img/mem-rg-2.png"), caption: [Частина блоків пам'яті, доступних програмі, після виділення])

Після виділення 10MB пам'яті можемо побачити, що в списку з'явився новий блок.

=== Розробка програми "MemAlloc". Виконання пункту 8 методичних вказівок.

Метою даного завдання є створення аллокатора, який буде виділяти пам'ять за стратегією "Найменший достатній".

Код складеної програми розміщено в додатку В.

==== Принцип роботи алгоритму
Даний код реалізує механізм керування пам'яттю за стратегією "Найменший достатній" (Best-Fit). Суть цієї стратегії полягає у виділенні для запиту такого вільного блоку пам'яті, розмір якого найближчий до запитуваного (але не менший).

- Ініціалізація. При створенні аллокатора за допомогою функції "bf_allocator_create" виділяється буфер пам'яті заданого розміру, який вирівнюється за вимогами системи. Спочатку цей буфер представляється як один великий вільний блок.
- Виділення пам'яті. Функція "bf_allocator_alloc" реалізує стратегію Best-Fit:
  1. проходить весь список блоків і шукає найменший вільний блок, розмір якого достатній для задоволення запиту;
  2. якщо такий блок знайдено, перевіряється, чи достатньо в ньому місця для розділення на два блоки (виділений і залишок);
  3. якщо розділення доцільне, створюється новий блок для залишку пам'яті та оновлюються зв'язки в списку;
  4. обраний блок позначається як зайнятий і повертається покажчик на його дані.
- Звільнення пам'яті. Функція "bf_allocator_free" звільняє раніше виділений блок пам'яті:
  1. за адресою знаходить відповідний блок і позначає його як вільний;
  2. перевіряє, чи можливе злиття з сусідніми вільними блоками, щоб запобігти фрагментації;
  3. якщо наступний блок вільний, він об'єднується з поточним;
  4. якщо попередній блок вільний, поточний об'єднується з попереднім.
- Знищення аллокатора. Після завершення роботи з аллокатором необхідно викликати функцію "bf_allocator_destroy" яка знищує аллокатор і звільняє всю пам'ять, пов'язану з ним.

==== Особливості реалізації
1. Вирівнювання пам'яті забезпечується для коректної роботи на різних архітектурах;
2. реалізоване злиття суміжних вільних блоків для боротьби з фрагментацією;
3. використовується мінімальний поріг розміру для розділення блоків, щоб уникнути створення занадто малих блоків;
4. додана функція відображення стану пам'яті для налагодження та аналізу.

Ця реалізація Best-Fit є оптимальною з точки зору використання пам'яті, оскільки мінімізує «відходи» -- різницю між розміром виділеного блоку та запитаним розміром. Однак, це досягається ціною повного перебору списку вільних блоків, що може бути неефективним при великій кількості блоків.

==== Приклад роботи
#figure(image("img/mem-alloc.png"), caption: [Приклад роботи алокатора])

=== Розробка системи "Calc". Виконання завдання на найвищу оцінку.

Метою даного завдання є створити 3 програми для виконання простих математичних операцій над числами використовуючи певні засоби взаємодії між процесами.

Код складених програм розміщено в додатку Г.

Система складається з 3 програм:
- "NumWriter": програма, що питає у користувача 2 числа;
- "OpWriter": програма, що питає у користувача яку операцію необхідно виконати над числами;
- "Calc": програма, яка виконує над вказаними числами вказану операцію, та виводить результат в консоль.

Для зручного обміну даними створено структуру, що зберігає числа, операцію та має додаткове бітове поле, в якому програми встановлюють певні прапорці після виконання своїх дій. "OpWriter" чекає на сигнал від "NumWriter", а "Calc" чекає на сигнали від "OpWriter" та "NumWriter".

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

== Висновки
Під час даної лабораторної роботи ми дослідили особливості використання та функції для роботи з віртуальною та фізичною пам'яттю.

#show: appendices_style

= Вміст файлу "Justfile" із інструкціями збірки розроблених програм
```make
cc := "clang"
cc_flags := "-Wall -Wextra -std=c11"

@clean:
  rm -rf build

@mkdir-build: clean
  mkdir -p build

@build-info: mkdir-build
    {{cc}} -ggdb -o build/info1 info/main.c
    {{cc}} -ggdb -o build/info2 info/main.c

@debug-info: build-info
    gf2 build/info1 & disown
    gf2 build/info2 & disown

@build-mem-alloc: mkdir-build
    {{cc}} -o build/mem-alloc mem-alloc/main.c mem-alloc/best_fit_allocator.c

@run-mem-alloc: build-mem-alloc
    build/mem-alloc

@build-calc: mkdir-build
    {{cc}} -o build/calc calc/calc.c
    {{cc}} -o build/num-writer calc/num-writer.c
    {{cc}} -o build/op-writer calc/op-writer.c
```

= Код програми "Info"
#v(-spacing)
== Вміст файлу "main.c"
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/sysinfo.h>
#include <unistd.h>

#define bool int
#define true 1
#define false 0

#define MAX_LINE_LENGTH 256
#define GB (1024 * 1024 * 1024)
#define MB (1024 * 1024)

typedef enum {
  RG_ALL,
  RG_PROGRAM_ONLY,
  RG_ANONYMOUS_ONLY,
  RG_ANONYMOUS_PROGRAM
} RG_MODE;

typedef struct {
  void *start_addr;
  void *end_addr;
  size_t size;
  char permissions[5];
  unsigned long offset;
  char device[8];
  unsigned long inode;
  char pathname[256];
} mem_rg;

void get_sys_mem_info() {
  printf("\n=== System Memory Information ===\n");

  struct sysinfo inf;
  sysinfo(&inf);

  printf("Total RAM:\t%5.2f GB\n", (float)(inf.totalram * inf.mem_unit) / GB);
  printf("Free RAM:\t%5.2f GB\n", (float)(inf.freeram * inf.mem_unit) / GB);
  printf("Shared RAM:\t%5.2f GB\n", (float)(inf.sharedram * inf.mem_unit) / GB);
  printf("Buffer RAM:\t%5.2f GB\n", (float)(inf.bufferram * inf.mem_unit) / GB);
  printf("Total Swap:\t%5.2f GB\n", (float)(inf.totalswap * inf.mem_unit) / GB);
  printf("Free Swap:\t%5.2f GB\n", (float)(inf.freeswap * inf.mem_unit) / GB);
  printf("Number of processes: %d\n", inf.procs);

  printf("\n=== Additional Memory Info (/proc/meminfo) ===\n");

  FILE *proc_meminfo = fopen("/proc/meminfo", "r");

  char line[MAX_LINE_LENGTH];
  while (fgets(line, MAX_LINE_LENGTH, proc_meminfo) != NULL) {
    if (strstr(line, "MemTotal:") || strstr(line, "MemFree:") ||
        strstr(line, "MemAvailable:") || strstr(line, "Buffers:") ||
        strstr(line, "Cached:") || strstr(line, "SwapTotal:") ||
        strstr(line, "SwapFree:") || strstr(line, "Dirty:") ||
        strstr(line, "Writeback:") || strstr(line, "Shmem:")) {
      printf("%s", line);
    }
  }

  fclose(proc_meminfo);

  printf("\n=== Memory Pages Information ===\n");

  long p_size = sysconf(_SC_PAGESIZE);
  long p_phys = sysconf(_SC_PHYS_PAGES);
  long p_avai = sysconf(_SC_AVPHYS_PAGES);

  printf("Page Size:             %8ld bytes\n", p_size);
  printf("Total Phys Pages:      %8ld\n", p_phys);
  printf("Available Phys Pages:  %8ld\n", p_avai);
  printf("Total Phys Memory:     %8.2f GB\n", (float)(p_phys * p_size) / GB);
  printf("Available Phys Memory: %8.2f GB\n", (float)(p_avai * p_size) / GB);
}

mem_rg parse_rg(char *line) {
  mem_rg region;
  memset(&region, 0, sizeof(region));

  sscanf(line, "%p-%p %4s %lx %7s %lu %255[^\n]", &region.start_addr,
         &region.end_addr, region.permissions, &region.offset, region.device,
         &region.inode, region.pathname);

  region.size = (size_t)region.end_addr - (size_t)region.start_addr;

  return region;
}

int is_valid_rg(mem_rg *region, RG_MODE mode) {
  switch (mode) {
  case RG_PROGRAM_ONLY:
    return strstr(region->pathname, "info") != NULL ||
           region->pathname[0] == '\0';

  case RG_ANONYMOUS_ONLY:
    return strstr(region->pathname, "[heap]") != NULL ||
           strstr(region->pathname, "[stack]") != NULL;

  case RG_ANONYMOUS_PROGRAM:
    return strstr(region->pathname, "[heap]") != NULL ||
           strstr(region->pathname, "[stack]") != NULL ||
           strstr(region->pathname, "info") != NULL;

  case RG_ALL:
  default:
    return 1;
  }
}

void get_prog_addr(FILE *fp) {
  printf("\n=== Program Memory Addresses ===\n");

  rewind(fp);

  char line[MAX_LINE_LENGTH];
  fgets(line, MAX_LINE_LENGTH, fp);

  mem_rg start_region = parse_rg(line);
  mem_rg end_region = {0};

  while (fgets(line, MAX_LINE_LENGTH, fp) != NULL) {
    mem_rg curr_region = parse_rg(line);

    if (is_valid_rg(&curr_region, RG_PROGRAM_ONLY) &&
        curr_region.end_addr > end_region.end_addr)
      end_region = curr_region;
  }

  printf("Program Start Address: %p\n", start_region.start_addr);
  printf("Program End Address:   %p\n", end_region.end_addr);

  printf("\n================================\n");
}

void list_rg(FILE *fp) {
  rewind(fp);

  RG_MODE mode = RG_PROGRAM_ONLY;

  char line[MAX_LINE_LENGTH];
  int included_count = 0;
  int total_count = 0;

  while (fgets(line, MAX_LINE_LENGTH, fp) != NULL) {
    total_count++;

    mem_rg region = parse_rg(line);

    if (!is_valid_rg(&region, mode))
      continue;

    included_count++;

    printf("--- Memory Region №%2d ---\n", included_count);
    printf("Start Address: %p\n", region.start_addr);
    printf("End Address:   %p\n", region.end_addr);
    printf("Size:          %zu bytes (%.2f MB)\n", region.size,
           (float)region.size / MB);
    printf("Permissions:   %s\n", region.permissions);
    printf("Offset:        0x%lx\n", region.offset);
    printf("Device:        %s\n", region.device);
    printf("Inode:         %lu\n", region.inode);
    printf("Pathname:      %s\n",
           region.pathname[0] ? region.pathname : "[anonymous]");
    printf("-------------------------\n");
  }

  printf("Blocks Total:  %d\n", total_count);
  printf("Blocks Shown:  %d\n", included_count);
}

void alloc_cmp(FILE *fp) {
  printf("\n=== Allocating Memory ===\n");

  printf("Before allocation:\n");
  list_rg(fp);

  size_t allocation_size = 10 * MB;
  printf("\nAllocating %zu bytes (%.2f MB) of memory...\n", allocation_size,
         (float)allocation_size / MB);

  void *memory = mmap(NULL, allocation_size, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

  printf("Memory is allocated at %p\n", memory);

  printf("\nAfter allocation:\n");
  list_rg(fp);

  if (munmap(memory, allocation_size) == 0) {
    printf("\nMemory freed.\n");
  } else {
    perror("munmap");
  }
}

int main(int argc, char *argv[]) {
  get_sys_mem_info();

  FILE *fp = fopen("/proc/self/maps", "r");
  get_prog_addr(fp);

  alloc_cmp(fp);

  fclose(fp);

  return EXIT_SUCCESS;
}
```

= Код програми "MemAlloc"
#v(-spacing)
== Вміст файлу "main.c"
```c
#include "best_fit_allocator.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
  /* small pool for clarity */
  best_fit_allocator_t *pool = bf_allocator_create(1024);
  if (!pool) {
    fprintf(stderr, "Failed to create allocator\n");
    return 1;
  }

  /* Step 1: allocate three blocks */
  void *b1 = bf_allocator_alloc(pool, 100);
  void *b2 = bf_allocator_alloc(pool, 200);
  void *b3 = bf_allocator_alloc(pool, 300);
  printf("After initial allocations (100,200,300):\n");
  bf_allocator_print(pool);

  /* Step 2: free the first two */
  bf_allocator_free(pool, b1);
  bf_allocator_free(pool, b2);
  printf("\nAfter freeing 100- and 200-byte blocks:\n");
  bf_allocator_print(pool);

  /* Step 3: allocate 150 bytes—should go into the 200-byte hole */
  void *b4 = bf_allocator_alloc(pool, 150);
  printf("\nRequesting 150 bytes: (best-fit should pick the 200-byte block)\n");
  bf_allocator_print(pool);

  /* Verify that b4 lies inside the old b2 region */
  size_t diff = (uintptr_t)b4 - (uintptr_t)b2;
  printf("\nb2 was at %p, b4 is at %p, diff = %zu bytes\n", b2, b4, diff);

  bf_allocator_destroy(pool);
}
```

== Вміст файлу "best_fit_allocator.h"
```c
#ifndef BEST_FIT_ALLOCATOR_H
#define BEST_FIT_ALLOCATOR_H

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct best_fit_allocator best_fit_allocator_t;

best_fit_allocator_t *bf_allocator_create(size_t pool_size);
void bf_allocator_destroy(best_fit_allocator_t *allocator);
void *bf_allocator_alloc(best_fit_allocator_t *allocator, size_t size);
void bf_allocator_free(best_fit_allocator_t *allocator, void *ptr);
void bf_allocator_print(const best_fit_allocator_t *allocator);

#ifdef __cplusplus
}
#endif
#endif /* BEST_FIT_ALLOCATOR_H */
```

== Вміст файлу "best_fit_allocator.c"
```c
#include "best_fit_allocator.h"
#include <assert.h>
#include <stdalign.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

static size_t align_up(size_t n, size_t align) {
  return (n + align - 1) & ~(align - 1);
}

struct block {
  size_t size;
  int free;
  struct block *next;
  struct block *prev;
};

struct best_fit_allocator {
  void *buffer;
  size_t pool_size;
  struct block *head;
};

best_fit_allocator_t *bf_allocator_create(size_t pool_size) {
  if (pool_size <= sizeof(struct block))
    return NULL;
  size_t alignment = alignof(max_align_t);

  if (pool_size % alignment != 0) {
    pool_size = ((pool_size + alignment - 1) / alignment) * alignment;
  }

  void *buf = aligned_alloc(alignment, pool_size);
  if (!buf)
    return NULL;
  best_fit_allocator_t *a = malloc(sizeof(*a));

  if (!a) {
    free(buf);
    return NULL;
  }

  a->buffer = buf;
  a->pool_size = pool_size;
  a->head = (struct block *)buf;
  a->head->size = pool_size - sizeof(struct block);
  a->head->free = 1;
  a->head->next = NULL;
  a->head->prev = NULL;

  return a;
}

void bf_allocator_destroy(best_fit_allocator_t *allocator) {
  if (!allocator)
    return;
  free(allocator->buffer);
  free(allocator);
}

void *bf_allocator_alloc(best_fit_allocator_t *allocator, size_t size) {
  if (!allocator || size == 0)
    return NULL;

  size = align_up(size, alignof(max_align_t));
  struct block *best = NULL;

  for (struct block *it = allocator->head; it; it = it->next) {
    if (it->free && it->size >= size) {
      if (!best || it->size < best->size) {
        best = it;
        if (best->size == size)
          break;
      }
    }
  }

  if (!best)
    return NULL;

  if (best->size >= size + sizeof(struct block) + alignof(max_align_t)) {
    uint8_t *raw = (uint8_t *)best;
    struct block *next_blk =
        (struct block *)(raw + sizeof(struct block) + size);

    next_blk->size = best->size - size - sizeof(struct block);
    next_blk->free = 1;
    next_blk->next = best->next;
    next_blk->prev = best;

    if (best->next)
      best->next->prev = next_blk;

    best->next = next_blk;
    best->size = size;
  }

  best->free = 0;
  return (uint8_t *)best + sizeof(struct block);
}

void bf_allocator_free(best_fit_allocator_t *allocator, void *ptr) {
  if (!allocator || !ptr)
    return;

  uint8_t *start = (uint8_t *)allocator->buffer + sizeof(struct block);
  uint8_t *end = (uint8_t *)allocator->buffer + allocator->pool_size;

  assert(ptr >= (void *)start && ptr < (void *)end);
  struct block *blk = (struct block *)((uint8_t *)ptr - sizeof(struct block));
  assert(!blk->free);
  blk->free = 1;

  if (blk->next && blk->next->free) {
    blk->size += sizeof(struct block) + blk->next->size;
    blk->next = blk->next->next;
    if (blk->next)
      blk->next->prev = blk;
  }

  if (blk->prev && blk->prev->free) {
    blk->prev->size += sizeof(struct block) + blk->size;
    blk->prev->next = blk->next;
    if (blk->next)
      blk->next->prev = blk->prev;
  }
}

void bf_allocator_print(const best_fit_allocator_t *allocator) {
  if (!allocator)
    return;

  printf("Block list:\n");
  for (const struct block *it = allocator->head; it; it = it->next) {
    printf("  Block at %p | size: %zu bytes | %s\n", (void *)it, it->size,
           it->free ? "FREE" : "ALLOCATED");
  }
}
```

= Код програм "Calc", "NumWriter" та "OpWriter"
#v(-spacing)
== Вміст файлу "shared.h"
```c
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define SF_NAME "/tmp/calc_data"
#define SF_SIZE 256

typedef struct {
  float num1;
  float num2;
  char operation;
  int ready_flags; // 1 - nums-ready, 2 - op-ready
} calc_data;

#define err(exit_code, msg, line)                                              \
  do {                                                                         \
    EXIT_CODE = exit_code;                                                     \
    char buf[256];                                                             \
    snprintf(buf, sizeof(buf), "%s:%d: error: %s", __FILE__, line, msg);       \
    perror(buf);                                                               \
  } while (0)

#define SPINNER "|/-\\"
#define spin(msg, delay_ms)                                                    \
  do {                                                                         \
    static int i = 0;                                                          \
    printf("\r%s %c ", msg, SPINNER[i % (sizeof(SPINNER) - 1)]);               \
    fflush(stdout);                                                            \
    usleep(delay_ms * 1000);                                                   \
    i++;                                                                       \
  } while (0)
```

== Вміст файлу "calc.c"
```c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

#include "shared.h"

int EXIT_CODE = 0;

int main() {
  int fd;
  calc_data *data;

  fd = open(SF_NAME, O_CREAT | O_RDWR, 0666);
  if (fd == -1) {
    err(EXIT_FAILURE, "open", __LINE__ - 1);
    goto exit;
  }

  if (ftruncate(fd, SF_SIZE) == -1) {
    err(EXIT_FAILURE, "ftruncate", __LINE__ - 1);
    goto fd_close;
  }

  data = (calc_data *)mmap(NULL, SF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
                           fd, 0);
  if (data == MAP_FAILED) {
    err(EXIT_FAILURE, "mmap", __LINE__ - 1);
    goto fd_close;
  }

  while (!(data->ready_flags & 1))
    spin("Waiting for numbers", 250);
  printf("\rNumbers are '%.2f' and '%.2f'.\n", data->num1, data->num2);

  while (!(data->ready_flags & 2))
    spin("Waiting for operation", 250);
  printf("\rOperation is '%c'.       \n", data->operation);

  double result = 0;

  switch (data->operation) {
  case '+':
    result = data->num1 + data->num2;
    break;
  case '-':
    result = data->num1 - data->num2;
    break;
  case '*':
    result = data->num1 * data->num2;
    break;
  case '/':
    if (data->num2 != 0) {
      result = data->num1 / data->num2;
    } else {
      fprintf(stderr, "Division by zero is not allowed!\n");
      goto munmap;
    }
    break;
  default:
    fprintf(stderr, "Unknown operation: %c\n", data->operation);
  }

  printf("Result: %.2f %c %.2f = %.2f\n", data->num1, data->operation,
         data->num2, result);

munmap:
  munmap(data, SF_SIZE);

fd_close:
  close(fd);

  unlink(SF_NAME);
exit:
  return EXIT_CODE;
}
```

== Вміст файлу "num-writer.c"
```c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

#include "shared.h"

int EXIT_CODE = 0;

int main() {
  int fd;
  calc_data *data;

  fd = open(SF_NAME, O_CREAT | O_RDWR, 0666);
  if (fd == -1) {
    err(EXIT_FAILURE, "open", __LINE__ - 1);
    goto exit;
  }

  if (ftruncate(fd, SF_SIZE) == -1) {
    err(EXIT_FAILURE, "ftruncate", __LINE__ - 1);
    goto fd_close;
  }

  data = (calc_data *)mmap(NULL, SF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
                           fd, 0);
  if (data == MAP_FAILED) {
    err(EXIT_FAILURE, "mmap", __LINE__ - 1);
    goto fd_close;
  }

  printf("Enter number 1: ");
  if (scanf("%f", &data->num1) != 1) {
    err(EXIT_FAILURE, "Getting number 1", __LINE__ - 1);
    goto munmap;
  }

  printf("Enter number 2: ");
  if (scanf("%f", &data->num2) != 1) {
    err(EXIT_FAILURE, "Getting number 2", __LINE__ - 1);
    goto munmap;
  }

  __sync_or_and_fetch(&data->ready_flags, 1);

  printf("Numbers '%.2f' and '%.2f' are written to the shared memory.\n",
         data->num1, data->num2);

munmap:
  munmap(data, SF_SIZE);

fd_close:
  close(fd);

exit:
  return EXIT_CODE;
}
```

== Вміст файлу "op-writer.c"
```c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

#include "shared.h"

int EXIT_CODE = 0;

int main() {
  int fd;
  calc_data *data;

  fd = open(SF_NAME, O_CREAT | O_RDWR, 0666);
  if (fd == -1) {
    err(EXIT_FAILURE, "open", __LINE__ - 1);
    goto exit;
  }

  if (ftruncate(fd, SF_SIZE) == -1) {
    err(EXIT_FAILURE, "ftruncate", __LINE__ - 1);
    goto fd_close;
  }

  data = (calc_data *)mmap(NULL, SF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
                           fd, 0);
  if (data == MAP_FAILED) {
    err(EXIT_FAILURE, "mmap", __LINE__ - 1);
    goto fd_close;
  }

  while (!(data->ready_flags & 1))
    spin("Waiting for numbers", 250);

  printf("\rEnter operation (+, -, *, /):   ");
  if (scanf(" %c", &data->operation) != 1) {
    err(EXIT_FAILURE, "Getting operation", __LINE__ - 1);
    goto munmap;
  }

  __sync_or_and_fetch(&data->ready_flags, 2);

  printf("Operation '%c' is written to the shared memory.\n", data->operation);

munmap:
  munmap(data, SF_SIZE);

fd_close:
  close(fd);

exit:
  return EXIT_CODE;
}
```
