использование крючков glibc malloc потокобезопасным способом

Я хотел бы контролировать использование mallocs и освобождает в приложении, используя malloc и свободные крючки.

вот документация http://www.gnu.org/s/libc/manual/html_node/Hooks-for-Malloc.html

на странице примера вы можете увидеть, что my_malloc_hook временно отключает крючок malloc (или на предыдущий крючок в цепи) перед повторным вызовом malloc.

это проблема при мониторинге многопоточных приложений (см. конец вопроса для объяснения).

другие примеры использования крючка malloc, которые я нашел в интернете, имеют ту же проблему.

есть ли способ перезаписать эту функцию для правильной работы в многопоточном приложении?

например, есть ли внутренняя функция libc, которую может вызвать крючок malloc, который завершает выделение, без необходимости деактивировать мой крючок.

Я не могу посмотреть исходный код libc из-за корпоративного правовая политика, поэтому ответ может быть очевиден.

моя спецификация дизайна говорит, что я не могу заменить malloc другим дизайном malloc.

Я могу предположить, что никаких других крючков в игре нет.


обновление

поскольку крючок malloc временно удаляется во время обслуживания malloc, другой поток может вызвать malloc и не получить крючок.

было предложено, что malloc имеет большой замок вокруг него, который предотвращает это, но это не документировано, и тот факт, что я эффективно рекурсивно вызываю malloc, предполагает, что любой замок должен либо существовать после крючка, либо быть очень умным:

caller -> 
  malloc -> 
    malloc-hook (disables hook) -> 
      malloc -> # possible hazard starts here
        malloc_internals
      malloc <-
    malloc-hook (enables hook) <-
  malloc
caller

4 ответов


обновлено

вы право не доверять __Танос_крюками; я взглянул на код, и они - ошеломляюще безумно - не потокобезопасными.

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

от http://manpages.sgvulcan.com/malloc_hook.3.php:

переменные крючка не являются потокобезопасными, поэтому они теперь устарели. Вместо этого программисты должны опережать вызовы соответствующих функций, определяя и экспортируя такие функции, как" malloc "и"free".

соответствующим способом внедрения функций debug malloc / realloc / free является предоставление вашей собственной библиотеки, которая экспортирует ваши "отладочные" версии этих функций, а затем переносит себя на настоящие. C связывание выполняется в явном порядке, поэтому, если две библиотеки предлагают одну и ту же функцию, используется первая указанная. Вы также можете ввести свой malloc во время загрузки в unix, используя механизмы LD_PRELOAD.

http://linux.die.net/man/3/efence описывает электрический забор, подробности обоих этих подходов.

вы можете использовать свою собственную блокировку, если в этих функциях отладки, если это необходимо.


У меня такая же проблема. Я решил это с помощью этого примера. Если мы не определяем THREAD_SAFE, у нас есть пример, приведенный человеком, и у нас есть ошибка сегментации. Если мы определяем THREAD_SAFE, у нас нет ошибки сегментации.

#include <malloc.h>
#include <pthread.h>

#define THREAD_SAFE
#undef  THREAD_SAFE

/** rqmalloc_hook_  */

static void* (*malloc_call)(size_t,const void*);

static void* rqmalloc_hook_(size_t taille,const void* appel)
{
void* memoire;

__malloc_hook=malloc_call; 
memoire=malloc(taille);    
#ifndef THREAD_SAFE
malloc_call=__malloc_hook;   
#endif
__malloc_hook=rqmalloc_hook_; 
return memoire;
}

/** rqfree_hook_ */   

static void  (*free_call)(void*,const void*);

static void rqfree_hook_(void* memoire,const void* appel)
{
__free_hook=free_call;   
free(memoire);            
#ifndef THREAD_SAFE
free_call=__free_hook;    
#endif
__free_hook=rqfree_hook_; 
}

/** rqrealloc_hook_ */

static void* (*realloc_call)(void*,size_t,const void*);

static void* rqrealloc_hook_(void* memoire,size_t taille,const void* appel)
{
__realloc_hook=realloc_call;     
memoire=realloc(memoire,taille); 
#ifndef THREAD_SAFE
realloc_call=__realloc_hook;    
#endif
__realloc_hook=rqrealloc_hook_; 
return memoire;
}

/** memory_init */

void memory_init(void)
{
  malloc_call  = __malloc_hook;
  __malloc_hook  = rqmalloc_hook_;

  free_call    = __free_hook;
  __free_hook    = rqfree_hook_;

  realloc_call = __realloc_hook;
  __realloc_hook = rqrealloc_hook_;
 }

 /** f1/f2 */

 void* f1(void* param)
 {
 void* m;
 while (1) {m=malloc(100); free(m);}
 }

 void* f2(void* param)
 {
 void* m;
 while (1) {m=malloc(100); free(m);}
 }

 /** main */
 int main(int argc, char *argv[])
 {
 memory_init();
 pthread_t t1,t2;

 pthread_create(&t1,NULL,f1,NULL);
 pthread_create(&t1,NULL,f2,NULL);
 sleep(60);
 return(0);
 }

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

[EDIT] IANAL но ... Если сможешь!--6-->использовать glibc в вашем коде, то вы можете посмотреть на код (так как это LGPL, любой, кто его использует должны разрешено иметь копию источника). Поэтому я не уверен, что вы правильно поняли юридическую ситуацию или, возможно, Вам не разрешено использовать glibc вашей компанией.

[EDIT2] после некоторого размышления я предполагаю, что эта часть пути вызова должна быть защищена какой-то блокировкой, которую glibc создает для вас. В противном случае использование крючков в многопоточном коде никогда не будет работать надежно, и я уверен, что документы упомянут об этом. С malloc() должен быть потокобезопасным, крючки должны быть хорошо.

Если вы все еще беспокоитесь, я предлагаю написать небольшую тестовую программу с двумя потоками, которые выделяют и освобождают память в цикле. Увеличьте счетчик в крючке. После миллиона раундов счетчик должен быть ровно два миллиона. Если это выполняется, то крюк защищен malloc() блокировки.

[EDIT3] если тест не удается, то из-за вашей правовой ситуации невозможно реализовать монитор. Скажи своему боссу, и пусть он примет решение.

[EDIT4] Googling обнаружил этот комментарий из отчета об ошибке:

крючки не являются потокобезопасными. Период. Что ты пытаешься исправить?

Это часть обсуждения с марта 2009 года об ошибке в libc/malloc/malloc.c, который содержит исправление. Так что, возможно, версия glibc после эта дата работает, но, похоже, нет гарантии. Это также зависит от вашей версии GCC.


нет способа использовать крючки malloc потокобезопасным способом при рекурсии в malloc. Интерфейс плохо спроектирован, вероятно, не подлежит ремонту.

даже если вы поместите мьютекс в свой код крючка, проблема в том, что вызовы в malloc не видеть эти блокировки, пока они не прошли через механизм крючка, и чтобы пройти через механизм крючка, они смотрят на глобальные переменные (указатели крючка), не приобретая ваш мьютекс. Как вы сохраняете, изменяете и восстанавливаете они влияют на эти указатели в одном потоке, вызовы распределителя в другом потоке.

основная проблема дизайна заключается в том, что крючки по умолчанию являются нулевыми указателями. Если интерфейс просто предоставил ненулевые крючки по умолчанию, которые являются собственно распределителем (распределителем нижнего уровня, который не вызывает больше крючков), то было бы просто и безопасно добавлять крючки: вы могли бы просто сохранить предыдущие крючки, а в новых крючках рекурсировать в malloc, вызвав крючки hold, без возиться с любыми глобальными указателями (кроме времени установки крючка, которое может быть сделано до запуска любых потоков).

кроме того, glibc может предоставить внутренний интерфейс malloc, который не вызывает крючки.

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

Как это стенды, то, что вы можете сделать, чтобы безопасно использовать крючок glibc malloc, - это избежать рекурсии в malloc. Не меняйте указатели крючка внутри обратных вызовов крючка и просто вызывайте свой собственный распределитель.