96
указателя NULL;
•free(...) — освобождает память ОЗУ, которая ранее была выделена одной из функций: malloc(...), calloc(...) или realoc(...); если аргумент функции равен NULL, то никаких действий не производится;
•calloc(...) - выделяет память ОЗУ под массив данных, размерность которого задана первым аргументом, а размер каждого элемента массива в байтах, задан вторым аргументом; при этом, значение каждого байта выделенной памяти обнуляется; возвращает указатель на выделенный массив данных; если значение любого из аргументов функции равно нулю, то память ОЗУ процессу не выделяется и возвращается значение NULL;
•realloc(...) - изменяет размер уже выделенного блока памяти, используя первый аргумент как указатель, а второй — как нужный размер; если размер области уменьшается, то значения байт оставшейся области не изменяется; если размер области увеличивается, то добавляемая область не инициализируетсся; если значение аргумента указателя равно NULL, то результат функции, соответствует действию функции malloc(...); если требование на новый размер памяти равно нулю, а указатель не равен NULL, то действие эквивалентно функции free(...).
На листинге 2.5, приведен пример программы, которая сначала выделяет блоки памяти функциями malloc(...) и calloc(...), а затем освобождает первую из этих областей.
Листинг 2.5 - Пример программы динамического выделения памяти программе
#include <stdio.h> #include <stdlib.h> #include <malloc.h>
void * ptr1 = NULL; void * ptr2 = NULL;
int main(void) {
puts("Проект lab8.5 ...");
printf("Размеры областей ptr1,ptr2 до выделения ОЗУ:\n"); printf(" ptr1 =%10lu\n", malloc_usable_size(ptr1)); printf(" ptr2 =%10lu\n", malloc_usable_size(ptr2));
printf("Выделяем 100 байт по 1 Кбайт для первой области\n"); if(!(ptr1 = (char*)malloc(100*1024))){
printf("Не могу выделить память..."); exit(EXIT_FAILURE);
}
printf("Размеры ptr1,ptr2 после выделения ОЗУ malloc():\n"); printf(" ptr1 =%10lu\n", malloc_usable_size(ptr1)); printf(" ptr2 =%10lu\n", malloc_usable_size(ptr2));
printf("Выделяем ОЗУ из, 10 элементов массива по 1 Кбайт, для второй области\n");
ptr2 = calloc(10, 1024);
printf("Размеры ptr1,ptr2 после выделения ОЗУ calloc():\n"); printf(" ptr1 =%10lu\n", malloc_usable_size(ptr1)); printf(" ptr2 =%10lu\n", malloc_usable_size(ptr2));
97
printf("Освобождаем 1-ю область ОЗУ:\n"); free(ptr1);
printf("Размеры ptr1,ptr2 после освобождения ОЗУ:\n"); printf(" ptr1 =%10lu\n", malloc_usable_size(ptr1)); printf(" ptr2 =%10lu\n", malloc_usable_size(ptr2));
puts("Завершение проекта lab8.5 ..."); return EXIT_SUCCESS;
} // Конец листинга 2.5
Задание 2.5
С помощью утилиты man, провести изучение функций: malloc(...), calloc(...), realloc(...) и free(...).
В среде разработки Eclipse, создать проект с именем lab8.5 и набрать текст программы, руководствуясь текстом листинга 2.5.
Отладить и провести исследование работы полученной программы. Отразить краткое описание работы данного примера в личном отчете.
Кроме перечисленных, имеются также системные функции brk(...) и sbrk(...), с помощью которых можно непосредственно изменять конец сегмента неинициализированных данных.
Синтаксис этих системных вызовов:
#include <unistd.h>
int brk (void *addr); void *sbrk(intptr_t increment);
Семантика системных вызовов:
•brk(...) - устанавливает конец сегмента данных в значение, указанное в аргументе addr, когда это значение является приемлимым; указание адреса памяти должно быть в пределах максимально возможного размера сегмента данных (см. setrlimit(2));
•sbrk(...) - увеличивает пространство данных программы на increment байт; sbrk(...) не является системным вызовом, он просто является оберткой (wrapper), которую использует библиотека C для вызова brk(...); вызов sbrk(...) с инкрементом 0 может быть использован, чтобы найти текущее местоположение прерывания программы.
Вслучае успеха brk(...) возвращает ноль, а sbrk(...) возвращает указатель на начало новой области. В случае ошибки, возвращается -1 и значение errno устанавливается в значение ENOMEM.
На листинге 2.6 приведен пример программы, которая с помощью функции sbrk(...) три раза изменяет границу сегмента неинициализированных данных.
98
Листинг 2.6 - Пример использования функции sbrk(...)
#include <stdio.h> #include <stdlib.h> #include <unistd.h>
char * ptr = NULL;
int main(void) {
puts("Проект lab8.6 ..."); ptr = sbrk(0);
if (ptr == (char *)-1)
{
perror("Нет доступного пространства для sbrk(...)\n"); exit(EXIT_FAILURE);
}
printf("Первый адрес после добавляемой области: %23lp\n", (char *)ptr);
ptr = sbrk(4096);
if (ptr == (char *)-1)
{
perror("Нет доступного пространства для sbrk(...)\n"); exit(EXIT_FAILURE);
}
printf("Первый адрес после первого добавления 4096 байт: %14lp\n", (char *)ptr);
ptr = sbrk(4096);
if (ptr == (char *)-1)
{
perror("Нет доступного пространства для sbrk(...)\n"); exit(EXIT_FAILURE);
}
printf("Первый адрес после второго добавления 4096 байт: %14lp\n", (char *)ptr);
ptr = sbrk(0);
if (ptr == (char *)-1)
{
perror("Нет доступного пространства для sbrk(...)\n"); exit(EXIT_FAILURE);
}
printf("Первый адрес после добавленной области: %23lp\n", (char *)ptr);
ptr = sbrk(8192); ptr = sbrk(8192);
if (ptr == (char *)-1)
{
perror("Нет доступного пространства для sbrk(...)\n"); exit(EXIT_FAILURE);
}
printf("Первый адрес после двойного добавления по 8192 байт: %10lp\n", (char *)ptr);
puts("Завершение проекта lab8.6 ..."); return EXIT_SUCCESS;
} // Конец листинга 2.6
99
Задание 2.6
В среде разработки Eclipse, создать проект с именем lab8.6. Набрать текст программы, руководствуясь текстом листинга 2.6. Отладить и провести исследование работы полученной программы.
Отразить краткое описание работы данного примера в личном отчете.
Замечание
При выполнении задания 2.6 необходимо внимательно отслеживать: когда функция sbrk(...) показывает на начало выделенной области, а когда — за конец выделенной области.
Этим заданием, лабораторная работа №8 — заканчивается.
Не забудьте оформить отчет о проделанной работе.
100
В первой части нашей дисциплины, мы использовали понятие процесса как минимальную программную единицу, управляемую ядром ОС, но при более подробном рассмотрении оказывается, что:
•современные ОС, наряду с управлением отдельными процессами, управляют
также потоками (нитями, threads) процессов, обеспечивая им более широкие функциональные возможности;
•наряду с разделением времени между процессами, ядро ОС учитывает также
приоритетность процессов;
•имеются также разные подходы и к общему стилю управления процессами,
что демонстрируется разными способами организации запуска и контроля работы процессов, которые представлены ПО: init, upstart и systemd.
Замечание
Далее предполагается, что студент уже хорошо усвоил учебный материал «Темы 6» данной дисциплины. Также настоятельно рекомендуется изучить второй раздел учебника [1].
В целом, изучене данной темы предполагают последовательное рассмотрение следующих вопросов:
•общие вопросы управления процессами содержат перечень основных качественных характеристик, которые учитываются при разработке подсистемы;
•вопрос синхронизация процессов описывает проблематику взаимодействия
активных элементов ПО, необходимые для реализации их прикладных алгоритмов работы;
•стандарты POSIX отражают стремление унифицировать средства взаимо-
действия процессов, необходимые для адекватного отражения их в инструментальные средства языка С;
•системные вызовы ОС по управлению процессами содержат перечень наиболее важных функций, предоставляемых ядром ОС;
•системные вызовы fork() и каналы процессов представляют самый главный инструмент взаимодействия процессов;
•потоки (нити, threads) описывают наиболее совершенные средства взаимо-
действия «родственных» процессов, разделяющих общее адресное пространство команд и данных программы;
•сигналы POSIX отражают проблематику взаимодействия процессов, которые
теоретически исполняются одновременно, но реально - активен толко один процесс, а остальные находятся или в состоянии «Готовность» или в состоянии «Ожидание».