ОСлаб5_Мячина_БФИ2203
.docxМИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ, СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РОССИЙСКОЙ ФЕДЕРАЦИИ
Ордена Трудового Красного Знамени федеральное государственное бюджетное образовательное учреждение высшего образования
Московский технический университет связи и информатики
(МТУСИ)
Кафедра «Системное программирование»
Лабораторная работа №5
«ИССЛЕДОВАНИЕ МЕХАНИЗМОВ ВЫДЕЛЕНИЯ ПАМЯТИ»
по дисциплине «Операционные системы»
Выполнила:
студент группы БФИ2203
Мячина Лада Андреевна
Проверила:
Старший преподаватель
Алексанян Диана Ашотовна
Москва, 2024 г.
Цель работы:
Изучение системных функций выделения памяти в ОС GNU/LINUX
Получение практических навыков работы с динамической памятью в ОС GNU/LINUX
Задание:
1. Написать программу, работающую в одном из 2х режимов, задающимся при запуске в качестве аргумента командной строки:
Режим выделение памяти malloc() равно освобождению памяти free()
Режим выделение памяти больше malloc(), чем освобождение памяти free()
Выделение и освобождение памяти проводить параллельно с задержкой 100 – 1000 нс в бесконечном цикле. Предусмотреть прерывание процесса по сигналу, направленному асинхронно, с освобождением всей динамически выделенной памяти.
2. Написать программу для создания карты виртуальной памяти процесса, идентификатор которого передается первым аргументом, а полный путь до директории сохранения файла карты памяти – вторым аргументом. Именовать файлы карты по шаблону map_<pid>_<date&time>, где <pid> - идентификатор процесса, переданный в программу и соответствующий процессу, для которого создается карта, <date&time> - дата и время создания карты в формате yyyy-mm-dd_h:m:s. Наполнение карты взять из соответствующего процесса в /proc/ или использовать утилиту pmap.
3. Создать службу, запускающуюся каждые 30 секунд и выполняющую программу из п.2., проверить работу и получить 10-15 файлов с картами памяти для из п.1.
4. Проанализировать полученные результаты, написав программу, строящую график по данным карт виртуальной памяти из п.3 (для всех файлов карт в указанной при запуске директории). Выявить изменение размера кучи, при необходимости провести корректировку размеров выделяемой malloc() памяти для наглядного отображения (для данной программы можно использовать, например, Python).
5. Включить в отчет ответы на вопросы в свободной форме:
что такое стек и куча?
как происходит статическое и динамическое выделение памяти?
каковы плюсы и минусы статического и динамического выделения памяти?
Ход работы
Напишем программу, работающую в одном из 2х режимов, задающимся при запуске в качестве аргумента командной строки:
Режим выделение памяти malloc() равно освобождению памяти free()
Режим выделение памяти больше malloc(), чем освобождение памяти free()
Выделение и освобождение памяти проводим параллельно с задержкой 100 нс в бесконечном цикле. Предусмотрим прерывание процесса по сигналу, направленному асинхронно, с освобождением всей динамически выделенной памяти. Код программы показан на листинге 1.
Листинг 1 – Код первой программы
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#define ALLOC_COUNT 1000
volatile int running = 1;
void handle_signal(int signum) {
running = 0; // Установка флага завершения программы
}
// Функция для управления выделением и освобождением памяти
void allocate_memory(int mode) {
void* allocated[ALLOC_COUNT]; // Массив для хранения указателей на выделенную память
memset(allocated, 0, sizeof(allocated));
while (running) {
// Этап выделения памяти
for (int i = 0; i < ALLOC_COUNT && running; i++) {
allocated[i] = malloc(1024); // 1 КБ памяти
if (allocated[i] == NULL) {
fprintf(stderr, "Memory allocation failed\n");
running = 0; // Завершаем
break;
}
usleep(rand() % 901 + 100); // Задержка от 100 до 1000 мкс
// Режим A: освобождаем сразу после выделения
if (mode == 0) {
free(allocated[i]);
allocated[i] = NULL;
}
}
// Режим B: освобождаем только часть выделенной памяти
if (mode == 1) {
for (int i = 0; i < ALLOC_COUNT; i++) {
if (i % 20 == 0 && allocated[i] != NULL) {
free(allocated[i]);
allocated[i] = NULL;
}
}
}
// Очистка оставшейся памяти, чтобы избежать утечек
for (int i = 0; i < ALLOC_COUNT; i++) {
if (allocated[i] != NULL) {
free(allocated[i]);
allocated[i] = NULL;
}
}
}
}
int main(int argc, char* argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <mode>\n", argv[0]);
exit(EXIT_FAILURE);
}
int mode = atoi(argv[1]); // Режим работы
if (mode < 0 || mode > 1) {
fprintf(stderr, "Invalid mode. Use 0 or 1.\n");
exit(EXIT_FAILURE);
}
// Установка обработчика сигнала SIGINT
signal(SIGINT, handle_signal);
// Инициализация генератора случайных чисел
srand(time(NULL));
// Выполнение функции выделения памяти
allocate_memory(mode);
printf("Program terminated gracefully.\n");
return 0;
}
Написала программу для создания карты виртуальной памяти процесса, идентификатор которого передается первым аргументом, а полный путь до директории сохранения файла карты памяти – вторым аргументом. Именовать файлы карты по шаблону map_<pid>_<date&time>, где <pid> - идентификатор процесса, переданный в программу и соответствующий процессу, для которого создается карта, <date&time> - дата и время создания карты в формате yyyy-mm-dd_h:m:s.
Листинг 2 - Код второй программы
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// Функция для создания файла с картой памяти процесса
void create_map_file(int pid, const char* directory) {
// Буфер для хранения имени файла
char filename[256];
// Получение текущего времени
time_t now = time(NULL);
struct tm* t = localtime(&now); // Преобразуем текущее время в структуру tm
// Форматирование имени файла, включая директорию, PID и текущие дату и время
snprintf(filename, sizeof(filename), "%s/map_%d_%04d-%02d-%02d_%02d:%02d:%02d",
directory, pid, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
// Буфер для формирования команды
char command[512];
// Формирование команды для вызова pmap и записи вывода в файл
snprintf(command, sizeof(command), "pmap %d > %s", pid, filename);
// Выполнение команды system() для получения карты памяти процесса
if (system(command) == -1) {
// Если команда не выполнена, выводим ошибку и завершаем программу
fprintf(stderr, "Failed to execute pmap command.\n");
exit(EXIT_FAILURE);
}
// Выводим сообщение о том, что файл с картой памяти был создан
printf("Memory map saved to: %s\n", filename);
}
int main(int argc, char* argv[]) {
// Проверка корректности количества аргументов
if (argc != 3) {
// Если аргументов недостаточно или их слишком много, выводим сообщение о неправильном использовании программы
fprintf(stderr, "Usage: %s <pid> <directory>\n", argv[0]);
exit(EXIT_FAILURE);
}
// Преобразуем первый аргумент (PID) в целое число
int pid = atoi(argv[1]);
// Второй аргумент — путь к директории для сохранения файла
const char* directory = argv[2];
// Вызываем функцию для создания файла с картой памяти
create_map_file(pid, directory);
// Возвращаем 0, чтобы завершить программу без ошибок
return 0;
}
Работа представлена на рисунке 2.
Рисунок 2 - Выполнение задания 2
Создала службу, запускающуюся каждые 30 секунд и выполняющую программу из п.2., проверить работу и получить 10-15 файлов с картами памяти для из п.1.
Рисунок 3 - Создание службы
Рисунок 4 – Создание файла службы
Рисунок 5 - Таймер
Проанализировала полученные результаты, написав программу, строящую график по данным карт виртуальной памяти из п.3 (для всех файлов карт в казанной при запуске директории).
Листинг 3 – Код программы, строящий график по данным карт виртуальной памяти
import os
import re
import matplotlib.pyplot as plt
from datetime import datetime
def parse_memory_file(file_path):
размер памяти."""
heap_size = None
timestamp = None
# Открываем файл и читаем все строки
with open(file_path, 'r') as file:
lines = file.readlines()
# Ищем строку с общим размером памяти
for line in lines:
if line.strip().startswith("total"):
match = re.search(r"total\s+(\d+)K", line)
if match:
# Если нашли, извлекаем размер кучи в КБ
heap_size = int(match.group(1))
break
# Извлекаем имя файла и пытаемся получить временную метку из имени
base_name = os.path.basename(file_path)
try:
# Предполагается, что имя файла в формате map_<pid>_<date&time>.txt
name_parts = base_name.replace(".txt", "").split('_')
if len(name_parts) >= 4:
# Форматируем метку времени как "дата время"
timestamp = name_parts[2] + " " + name_parts[3]
except ValueError as e:
# В случае ошибки при разборе имени файла выводим ошибку
print(f"Error parsing timestamp from {base_name}: {e}")
return timestamp, heap_size
def plot_heap_usage(directory, output_file):
"""Строит и сохраняет график изменения размера кучи."""
timestamps = [] # Список для хранения временных меток
heap_sizes = [] # Список для хранения размеров кучи
# Получаем список всех файлов в директории
file_names = os.listdir(directory)
# Сортируем файлы по времени, извлекаем из имени файла временные метки
file_names.sort(key=lambda file: re.sub(r'(?<=\d{2}):(\d{1})\b', r':0\1', file.split('_')[2] + ' ' + file.split('_')[3]))
# Проходим по всем файлам в директории
for file_name in file_names:
file_path = os.path.join(directory, file_name)
# Если это файл и его имя начинается с "map_", обрабатываем его
if os.path.isfile(file_path) and file_name.startswith("map_"):
try:
# Парсим файл для получения метки времени и размера кучи
timestamp, heap_size = parse_memory_file(file_path)
if heap_size is not None:
# Добавляем данные в списки
timestamps.append(timestamp)
heap_sizes.append(heap_size)
except Exception as e:
# В случае ошибки при обработке файла выводим сообщение
print(f"Error processing file {file_name}: {e}")
# Если нет данных для построения графика, выводим сообщение и завершаем функцию
if not timestamps:
print("No valid data found to plot.")
return
# Построение графика с использованием matplotlib
plt.figure(figsize=(10, 6))
plt.plot(timestamps, heap_sizes, marker='o', linestyle='-', color='b') plt.title('Изменение размера кучи от времени')
plt.xlabel('Время')
plt.ylabel('Размер кучи (kB)')
plt.xticks(rotation=45)
plt.tight_layout() #
plt.grid(True)
# Сохранение графика в файл
plt.savefig(output_file)
print(f"График сохранён в файл: {output_file}")
plt.show() # Отображаем график
if __name__ == "__main__":
# Вводим путь к директории с файлами и имя для сохранения графика
directory = input("Введите путь к папке с файлами: ")
output_file = input("Введите имя файла для сохранения графика (например, graph.png): ")
# Строим график
plot_heap_usage(directory, output_file)
Рисунок 6 – Работа программы в режиме 0
Рисунок 7 – Работа программы в режиме 1
Вопросы:
Что такое стек и куча?
Стек — это область памяти, в которой происходит статическое выделение памяти, то есть размер выделения не будет изменяться, а значит должен быть известен заранее. При запуске программы в стеке выделяется память для переменных программы и по завершению программы из стека автоматически удаляются все данные.
Куча — это область памяти, в которой происходит динамическое выделение памяти, то есть размер выделения может изменяться. Программа во время работы может выделить какое-то пространство в куче и увеличивать это пространство при необходимости, а затем может освободить это пространство.
Как происходит статическое и динамическое выделение памяти?
Статическое выделение памяти происходит на этапе компиляции.
Динамическое выделение памяти происходит на этапе выполнения программы.
Каковы плюсы и минусы статического и динамического выделения памяти?
Статическое выделение происходит быстрее, а также порой требует меньшее необходимой памяти.
Динамическое выделение происходит медленнее, а также порой требует больше необходимой памяти, однако размер выделения может изменяться, что порой является необходимым для некоторых случаев.
Вывод
В результате проделанной работы мы изучили системные функций выделения памяти в ОС GNU/LINUX и получили практические навыки работы с динамической памятью в ОС GNU/LINUX.
