LINUX.ORG.RU

Как сделать автоматическое освобождение разделяемой памяти, когда её уже никто не использует?


0

0

Имеется некоторая программа-сервер, которая работает более-менее постоянно и предоставляет кусок разделяемой памяти с некоторой информацией. И есть клиенты, которые иногда запускаются и пишут/читают эту разделяемую память. При этом разделяемая память всегда отображена как минимум в процесс сервера. Но допустим что сервер и клиенты были убиты с помощью kill -9. Разделяемая память ведь так и останется висеть, я правильно понимаю? И как избавиться от такой утечки? Хочется, чтобы кусок разделяемой памяти автоматически освобождался, если он больше не отображён в адресное пространство ни одного процесса.

И вопрос вдогонку: на всех ли современных POSIX-совместимых системах поддерживается POSIX Shared Memory (shm_open/shm_unlink)?

Deleted

Запускать сервер и клиентов форком от хелпера (или клиентов форком от сервера), который делает mmap() без маппинга на файл? При убитии всех процессов, шарящих этот vma, память автоматически освободится.

mv ★★★★★
()
Ответ на: комментарий от mv

> Запускать сервер и клиентов форком от хелпера (или клиентов форком от сервера), который делает mmap() без маппинга на файл? При убитии всех процессов, шарящих этот vma, память автоматически освободится.

В моём случае этот вариант не подходит, но всё-равно - можно пример как это реализовать и как это работает (можно ссылкой)? А то я не совсем понял идею.

Чтобы отойти от абстрактных клиента и сервера: я пытаюсь допилить драйвер для тачпада xf86-input-synaptics. В самом драйвере (просто динамическая библиотека, которую загружает xorg-server) создаётся кусок разделяемой памяти через shmget/shmat/etc. А специальная утилита synclient может через эту разделяемую память мониторить изменения состояния тачпада и изменять настройки драйвера. При экспериментах с кодом я столкнулся с такой проблемой: если X-сервер падает, то разделяемая память не освобождается и при следующем запуске драйвер не может снова её выделить (ключ то один и тот же) и выдаёт ошибку. Это ИМХО не очень правильное поведение.

Сейчас пришло в голову: при инициализации драйвера через shmctl получать количество аттачей (shm_nattch), и если память никем не используется, то освобождать или просто использовать её заново. Насколько правильно делать так?

И остаётся тот же вопрос: как с поддержкой POSIX Sh. M. в разных системах? Мне нужна возможность одновременной работы нескольких экземпляров драйвера, а с System V Shared Memory и одним ключом это проблематично реализовать...

Deleted
()
Ответ на: комментарий от Deleted

#include <sys/mman.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>

int main()
{
	int pid;
	char *ptr = mmap(0, 4096, PROT_READ | PROT_WRITE,
			 MAP_SHARED | MAP_ANONYMOUS, 0, 0);
	assert(ptr != MAP_FAILED);
	if ((pid = fork ()) > 0) {
		printf("parent\n");
		while (ptr[321] != 123) {
			printf("%d\n", ptr[321]);
			usleep(250000);
		}
		printf("got 123\n");
	} else if (pid < 0) {
		printf("bug\n");
		return -1;
	} else {
		printf("child\n");
		sleep(1);
		ptr[321] = 123;
		msync(ptr, 4096, MS_SYNC);
	}
}


$ ./mmap
child
parent
0
0
0
0
got 123

mv ★★★★★
()
Ответ на: комментарий от Deleted

> В самом драйвере (просто динамическая библиотека, которую загружает xorg-server) создаётся кусок разделяемой памяти через shmget/shmat/etc. А специальная утилита synclient может через эту разделяемую память мониторить изменения состояния тачпада и изменять настройки драйвера.

А если уйти от shmem и использовать пайп или сообщения? Какого рода там обмен происходит?

mv ★★★★★
()
Ответ на: комментарий от mv

> А если уйти от shmem и использовать пайп или сообщения?

Но пайп и объект очереди сообщений тоже ведь придётся как-то за собой подчищать в случае, если сервер упадёт =).

> Какого рода там обмен происходит?


Я немного ошибся. Настройки там передаются не через разделяемую память. Через неё идёт только мониторинг состояния тачпада (раньше там всё через неё делалось).

http://cgit.freedesktop.org/xorg/driver/xf86-input-synaptics/tree/include/syn... - структура SynapticsSHM.

Deleted
()
Ответ на: комментарий от mv

> Ну тогда при запуске действительно смотри, использует ли кто shmem.

Похоже с дескрипторами POSIX Sh. M. можно делать flock/fcntl. Так что по идее с разделяемой памятью должен сработать тот же метод, что и с PID-файлами. Завтра попробую...

Deleted
()

> Но допустим что сервер и клиенты были убиты с помощью kill -9

1) не пускать на сервер людей которые такое вытворяют

2) в стартовый скрипт засунь проверку того очищена ли shmem.

> Но пайп и объект очереди сообщений тоже ведь придётся как-то за собой подчищать

Пайпы чистить не надо. Именованные каналы, наверно, тоже. Кстати, можно юзать юникс-сокеты.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

> 1) не пускать на сервер людей которые такое вытворяют

Это не отменит возможную утечку памяти. Сервер и сам ведь может упасть.

> 2) в стартовый скрипт засунь проверку того очищена ли shmem.


Не подходит.
http://www.linux.org.ru/jump-message.jsp?msgid=3731548&cid=3732306

> Пайпы чистить не надо. Именованные каналы, наверно, тоже. Кстати, можно юзать юникс-сокеты.


Пайпы и безымянные юникс-сокеты тоже не походят (см. пост выше). Именованные каналы и именованные сокеты - это объекты на файловой системе, а там тоже не хочется мусорить. Хотя в этом случае легче убирать за собой при следующем запуске.

Deleted
()
Ответ на: комментарий от Deleted

> а там тоже не хочется мусорить.

Создать один файл в /tmp который при следующей загрузке будет стёр это не значит намусорить. Оно для того специально и было создано.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

> Создать один файл в /tmp который при следующей загрузке будет стёр это не значит намусорить. Оно для того специально и было создано.

Уже сделал примерно то же самое, но с разделяемой памятью. Основная проблема была - определить что уже существующая область разделяемой памяти никем не используется. Это решилось с помощью flock. Вот тестовый примерчик:

cat both.h
================================================================================
#ifndef SHMTEST_BOTH_H
#define SHMTEST_BOTH_H

#include <stdlib.h>
#include <stdio.h>

inline static void die(const char *msg)
{
	printf("ERROR: %s\n", msg);
	exit(EXIT_FAILURE);
}

#define SHM_NAME "shmtest"
#define SHM_SIZE 128

#endif
================================================================================

cat server.c
================================================================================
#define _POSIX_C_SOURCE 200112L

#include <stdio.h>
#include <time.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/file.h>
#include <unistd.h>
#include <fcntl.h>

#include "both.h"

int main()
{
	int	shm;
	void	*shm_ptr;
	time_t	now;

	printf("---> Server <---\n");

	shm = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0600);
	if (shm == -1)
		die("shm_open()");
	if (flock(shm, LOCK_EX | LOCK_NB) == -1)
		die("Shared memory already used by other process");

	if (ftruncate(shm, SHM_SIZE) == -1)
		die("ftruncate()");
	shm_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
			shm, 0);
	if (shm_ptr == MAP_FAILED)
		die("mmap()");

	now = time(NULL);
	if (now == ((time_t) -1))
		die("time()");
	snprintf(shm_ptr, SHM_SIZE, "Hello! Current time: %lld.",
			(long long int) now);
	printf("String written: %s\n", (const char *) shm_ptr);
	if (msync(shm_ptr, SHM_SIZE, MS_ASYNC | MS_INVALIDATE) == -1)
		die("msync()");

	printf("Press Enter for normal exit or Ctrl+C for exit without "
			"cleanup\n");
	getchar();

	if (munmap(shm_ptr, SHM_SIZE) == -1)
		printf("WARNING: munmap()\n");
	if (close(shm) == -1)
		printf("WARNING: close()\n");
	if (shm_unlink(SHM_NAME) == -1)
		printf("WARNING: shm_unlink()\n");

	return 0;
}
================================================================================

cat client.c
================================================================================
#define _POSIX_C_SOURCE 200112L

#include <stdio.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

#include "both.h"

int main()
{
	int	shm;
	void	*shm_ptr;

	printf("---> Client <---\n");

	shm = shm_open(SHM_NAME, O_RDONLY, 0);
	if (shm == -1)
		die("shm_open()");

	shm_ptr = mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, shm, 0);
	if (shm_ptr == MAP_FAILED)
		die("mmap()");

	printf("SHM string: %s\n", (const char *) shm_ptr);

	if (munmap(shm_ptr, SHM_SIZE) == -1)
		printf("WARNING: munmap()\n");
	if (close(shm) == -1)
		printf("WARNING: close()\n");

	return 0;
}
================================================================================

Deleted
()
Ответ на: комментарий от Deleted

selffix

>	if (close(shm) == -1)
>		printf("WARNING: close()\n");
>	if (shm_unlink(SHM_NAME) == -1)
>		printf("WARNING: shm_unlink()\n");

Правильнее вот так:

	if (shm_unlink(SHM_NAME) == -1)
		printf("WARNING: shm_unlink()\n");
	if (close(shm) == -1)
		printf("WARNING: close()\n");

Иначе может случиться так, что свежезапущенный сервер открыл существующую SHM (которая уже не залочена, так как завершающийся сервер успел вызвать close), а после этого завершающийся сервер сделал shm_unlink. В результате клиенты не смогут открыть SHM.

Deleted
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.