Описание

Библиотека в составе которой расположены исходники альтернативного аллокатора памяти в пределах ранее созданного массива.

Сборка

Для сборки необходимо установленные пакеты CUnit, CUnit-devel, cmake, gcc. Приведу на примере сборки CentOS 7

Последовательность сборки

yum install CUnit CUnit-devel cmake gcc git
git clone https://github.com/bayrepo/arrayallocator.git
cd arrayallocator/build
cmake ..
make
make unit-test
make install

Установка из пакета

Последовательность установки CentOS 7

wget http://download.opensuse.org/repositories/home:/bayrepo/CentOS_7/home:bayrepo.repo -O /etc/yum.repos.d/home:bayrepo.repo
yum install inarray-allocator inarray-allocator-devel -y

установка завершена

Последовательность установки CentOS 6

wget http://download.opensuse.org/repositories/home:/bayrepo/CentOS_6/home:bayrepo.repo -O /etc/yum.repos.d/home:bayrepo.repo
yum install inarray-allocator inarray-allocator-devel -y

установка завершена

Список функций

brp_malloc_init

int brp_malloc_init(void *array_ptr, long array_size, char *_allocalg) или int brp_malloc_init(void *array_ptr, long array_size) Инициализация массива array_ptr и размером array_size, выделение памяти в массиве производить по алгоритму _allocalg. _allocalg на текущий момент может принимать только "SBRK". Вызов функции brp_malloc_init(void *array_ptr, long array_size) по умолчанию использует SBRK. Результат: 0 - успешно инициализировано, 1 - ошибка инициализации.

Пример:

brp_malloc_init

char storage[1000] = {0};
if (brp_malloc_init((void *)storage, 1000)){
  printf("Error\n");
  exit(-1);
}

brp_malloc

void *brp_malloc(void *storage, long size) Аналог malloc, только в качестве первого параметра передается массив, где происходить выделение памяти. Возвращает указатель на выделенный участок или NULL.

Пример:

brp_malloc

char storage[1000] = {0};
if (brp_malloc_init((void *)storage, 1000)){
  printf("Error\n");
  exit(-1);
}

char *data = brp_malloc((void *)storage, 100);
if (!data){
  printf("Not enough of memory\n");
  exit(-1);
}

brp_free

void brp_free(void *storage, void *ptr) Аналог классического free, только в качестве первого параметра передается массив, в котором происходило выделение памяти с помощью brp_malloc или brp_calloc.

Пример:

brp_malloc

char storage[1000] = {0};
if (brp_malloc_init((void *)storage, 1000)){
  printf("Error\n");
  exit(-1);
}

char *data = brp_malloc((void *)storage, 100);
if (!data){
  printf("Not enough of memory\n");
  exit(-1);
}

brp_free((void *)storage, data);

brp_realloc

void *brp_realloc(void *storage, void *ptr, long new_size) Аналог realloc, берет выделенный ранее участок ptr, создает в storage новый участок и копирует в новое место содержимое ptr, если ptr = NULL, то копирование не происходит, создается новый участок памяти с обнуленным содержимым. Если new_size меньше размера ptr, то содержимое ptr урезается до new_size и копируется в новое место. Функция возвращает указатель на новый блок памяти или NULL, если произошла ошибка или не хватило места.

brp_calloc

void *brp_calloc(void *storage, int elem_numbers, int elem_size) Аналог calloc, выделает elem_numbers * elem_size байт памяти, но в отличии от malloc, очищает память нулями. Возвращает NULL, если не удалось выделить участок памяти.

brp_free_null

void brp_free_null(void *storage, void *ptr) Освобождение участка памяти ptr, с предварительным обнулением участка памяти

brp_realloc_null

void *brp_realloc_null(void *storage, void *ptr, long new_size) realloc с заполнением нулями старого участка памяти ptr.

Группа отладочных функций

  • void *brp_get_next_region_info(void *storage, void *ptr, void **info) - получение следующего указателя за ptrю
  • void brp_return_allocation_picture(void *storage, char *buffer_for_picture, long size) - вывод в буфер информации о статусе и количестве участков памяти: F - свободный участок, U - занятый. Результат выглядит так: UUFFUUFUUUю
  • void *brp_get_next_elem(void *storage) - получить указатель, указывающий на участок последнего выделения памяти.
  • long brp_get_free(void *storage) - размер свободного места
  • long brp_get_inuse(void *storage) - размер занятого места
  • void brp_return_allocation_stdout(void *storage) - вывод полной информации о выделенных и свободных участках памяти во всем массиве. Информация выводится в виде:
  1. STATUS U SIZE 24 ST 40 DC 11
  2. STATUS U SIZE 7 ST 40 DC 11
  3. STATUS U SIZE 24 ST 40 DC 11
  4. STATUS U SIZE 8 ST 40 DC 11
  5. STATUS U SIZE 24 ST 40 DC 11

brp_make_pointers_table

int brp_make_pointers_table(void *storage, int pointers_number)

Функция создания таблицы указателей на участки памяти. Первый параметр - это массив куда будет сохраняться таблица, второй параметр(pointers_number) - максимальное число хранимых указателей. Возвращает 0 - когда таблица создана и 1 - когда нет. Таблица и указатели необходимы при работе нескольких процессов с одной общей памятью, например. Далее будет разобрана программа пример с использованием этой функции.

brp_get_pointer_with_number

void *brp_get_pointer_with_number(void *storage, int pointer_number) Получить указатель с номером pointer_number из таблицы указателей, если такая есть и, номер не более максимального числа указателя. или NULL, если указателя нет

brp_set_pointer_to_number

void brp_set_pointer_to_number(void *storage, int pointer_number, void *value) Записать значение указателя value в таблицу указателей под номером pointer_number. Пример использования ниже.

brp_strdup

char *brp_strdup(void *storage, const char *s) Выделить память в хранилище storage для строки s и скопировать строку в выделенную область. Если не удалось выделить то вернуть NULL.

brp_nstrdup

char *brp_strndup(void *storage, const char *s, size_t n)

Выделить память в хранилище storage для строки s, но не более n символов, и скопировать строку в выделенную область. Если не удалось выделить то вернуть NULL.

Функции и расширения

Библиотека и исходники снабжены доработанными функциями uthash библиотеки https://troydhanson.github.io/uthash/ с небольшими доработками, что вся служебная и дополнительная информация о данных для utstring, utarray, utringbuffer, uthash, utlist выделяется в указанном хранилище.

Использование

Библиотеку можно использовать:

  1. для обмена массивов данных(посредством utstring, utarray, utringbuffer, uthash, utlist) через общую память
  2. для работы в рамках одного процесса, но с ограничениями выделяемой памяти

Первый случай делает удобный обмен данными с использованием хэш-таблиц, динамических массивов, списков и прочее.

Пример 1

Программа должна создавать список пользователей вида: имя→счетчик, имя должн быть ключем хэш-таблицы и эти данные должны разделяться между процессами.

shared memory

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "bayrepomalloc.h"
#include "uthash.h"

#define ARRAY_SIZE 100000

размер хэш массива

shared memory продолжение

//Структура, хранящаяся в хэш-таблице
typedef struct _data_struct {
	char name[50]; //имя записи
	int counter;           // счетчик обращений
	UT_hash_handle hh;
} data_struct;

void* create_shared_memory(size_t size) {
	int protection = PROT_READ | PROT_WRITE;
	int visibility = MAP_ANONYMOUS | MAP_SHARED;
	return mmap(NULL, size, protection, visibility, 0, 0);
}

сама структура хранения данных и функция выделения разделяемой памяти

shared memory продолжение

int main() {

	data_struct *storage = NULL; //будущий массив структур, обязательно должен быть обнулен
	pthread_mutex_t * pmutex = NULL; //мьютекс для синхронизации процессов будет расположен в этой же общей памяти
	pthread_mutexattr_t attrmutex;

	void *hash_array = NULL;

	/*
	 * выделяем общую память разером под будущий массив с hash таблицей + область для mutex для
	 * синхронизации
	 */
	void* shmem = create_shared_memory(ARRAY_SIZE + sizeof(pthread_mutex_t));
	if (!shmem) {
		fprintf(stderr, "Error shared memeory allocation\n");
		exit(-1);
	}

	/*
	 * Инициализируем мьютекс и положим его в начало общей памяти
	 */
	pmutex = (pthread_mutex_t *) shmem;

	pthread_mutexattr_init(&attrmutex);
	pthread_mutexattr_setpshared(&attrmutex, PTHREAD_PROCESS_SHARED);

	pthread_mutex_init(pmutex, &attrmutex);

Нахожу адрес в общей памяти за мьютексом и присваиваю его будущей хэш-таблицы. Еще один момент, я здесь сразу же захватываю мьютекс. В родительском процессе произойдет наполнение таблицы, а дочерний процесс повторным pthread_mutex_lock будет ждать завершения наполнения таблицы и отпускания мьютекса со стороны родителя.

shared memory

	/*
	 * Массив хэш таблицы будет располагаться сразу после мьютекса
	 */
	hash_array = (void *) ((char *) shmem + sizeof(pthread_mutex_t));
	pthread_mutex_lock(pmutex);
	brp_malloc_init(hash_array, ARRAY_SIZE);

	int pid = fork();

	if (pid == 0) {
		printf("Child: waiting for parent array initialization\n");
		pthread_mutex_lock(pmutex);

А вот здесь как раз и пример работы с таблицей указателей. Я условился, что мне нужен один указатель, содержащий адрес начала хэш-таблицы. Т.к на момент разветвления процессов ее еще нет и этот указатель появится лишь в родительском процессе. Вот здесь я его как раз получаю из общей памяти, он расположен в таблице указателей с индексом = 0.

shared memory продолжение

		storage = brp_get_pointer_with_number(hash_array, 0);
		if (!storage) {
			fprintf(stderr, "Something wrong");
		} else {
			printf("Child: try to find 3 items TEST10, TEST15, TEST100\n");
			data_struct *item = NULL;

Далее я провожу манипуляции с найденной таблицей. Вношу изменения в нее в дочернем процессе.

shared memory продолжение

			HASH_FIND_STR(storage, "TEST10", item);
			if (item) {
				printf("Child: TEST10 found. Change counter from %d to %d\n",
						item->counter, item->counter + 1000);
				item->counter += 1000;
			}
			HASH_FIND_STR(storage, "TEST15", item);
			if (item) {
				printf("Child: TEST15 found. Change counter from %d to %d\n",
						item->counter, item->counter + 2000);
				item->counter += 2000;
			}
			HASH_FIND_STR(storage, "TEST100", item);
			if (!item) {
				printf("Child: TEST100 not found. Going to create it\n");
				item = (data_struct*) brp_malloc(hash_array,
						sizeof(data_struct));
				snprintf(item->name, 50, "TEST100");
				item->counter = 10000;
				HASH_ADD_STR(storage, name, item, hash_array);
			}

		}
		pthread_mutex_unlock(pmutex);
		pthread_mutexattr_destroy(&attrmutex);

	} else if (pid == -1) {
		fprintf(stderr, "Fork error\n");
	} else {

Заполнение таблицы значениями

shared memory продолжение

		//Родитель инициализирует массив заполняет его и отпускает мьютекс
		int index = 0;
		data_struct *item, *tmp = NULL;
		printf("Parent: add data to hash array\n");
		for (index = 0; index < 20; index++) {
			item = (data_struct*) brp_malloc(hash_array, sizeof(data_struct));
			snprintf(item->name, 50, "TEST%d", index);
			item->counter = index;
			HASH_ADD_STR(storage, name, item, hash_array);
		}
		//Создадим в памяти один указатель на таблицу, чтоб в дочернем процессе знать
		//где hash таблица начигается

А вот момент создания таблицы указателей, создается один указатель и сохраняется адрес хеш-таблицы из переменной storage.

shared memory продолжение

		brp_make_pointers_table(hash_array, 1);
		brp_set_pointer_to_number(hash_array, 0, storage);

		pthread_mutex_unlock(pmutex);
		printf("Parent: wait for children work\n");
		sleep(2);
		pthread_mutex_lock(pmutex);
		printf("Parent: read of children activity\n");
		index = 0;
		HASH_ITER(hh, storage, item, tmp)
		{
			printf("Parent: %d) %s=>%d\n", index++, item->name, item->counter);
			HASH_DEL(storage, item);
			brp_free(hash_array, item);
		}
		pthread_mutex_unlock(pmutex);

		//Освободим память
		pthread_mutex_destroy(pmutex);
		pthread_mutexattr_destroy(&attrmutex);
		munmap(shmem, ARRAY_SIZE + sizeof(pthread_mutex_t));
	}

}

вывод работы скрипта:

USER
Parent: add data to hash array Parent: wait for children work Child: waiting for parent array initialization Child: try to find 3 items TEST10, TEST15, TEST100 Child: TEST10 found. Change counter from 10 to 1010 Child: TEST15 found. Change counter from 15 to 2015 Child: TEST100 not found. Going to create it Parent: read of children activity Parent: 0) TEST0⇒0 Parent: 1) TEST1⇒1 Parent: 2) TEST2⇒2 Parent: 3) TEST3⇒3 Parent: 4) TEST4⇒4 Parent: 5) TEST5⇒5 Parent: 6) TEST6⇒6 Parent: 7) TEST7⇒7 Parent: 8) TEST8⇒8 Parent: 9) TEST9⇒9 Parent: 10) TEST10⇒1010 Parent: 11) TEST11⇒11 Parent: 12) TEST12⇒12 Parent: 13) TEST13⇒13 Parent: 14) TEST14⇒14 Parent: 15) TEST15⇒2015 Parent: 16) TEST16⇒16 Parent: 17) TEST17⇒17 Parent: 18) TEST18⇒18 Parent: 19) TEST19⇒19 Parent: 20) TEST100⇒10000