C Общая библиотека: инициализация статической переменной + видимость глобальной переменной среди процессов - PullRequest
0 голосов
/ 06 января 2012

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

(На данный момент) будет два семейства подпрограмм управления памятью:

  • Стандартные функции malloc, calloc и т. Д.
  • специализированные версии malloc, calloc и т. Д.

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

Вот как я собираюсь реализовать модификацию:

  1. Заменить существующие вызовыmalloc / calloc и т. д. с помощью my_malloc / my_calloc и т. д. Эти новые функции будут вызывать правильно назначенные указатели функций вместо вызова жестко закодированных имен функций.

  2. Предоставляют механизм для общей библиотеки для инициализацииуказатели на функции, используемые my_malloc и т. д. для указания на стандартные подпрограммы mgmt памяти C - это позволяет мне обеспечивать обратную совместимость для приложений, которые зависят от этой разделяемой библиотеки - поэтому их не нужно изменять.В C ++ я мог бы сделать это, используя статическую переменную инициализации (например) - я не уверен, можно ли использовать тот же «шаблон» в C.

  3. Введите новыйИдемпотентная функция Функция initAPI (тип), которая вызывается (при запуске) приложением, которому необходимо использовать различные подпрограммы mem mgmt в совместно используемой библиотеке.Функция initAPI () назначает функции памяти mgmt ptrs соответствующим функциям.

Ясно, что было бы предпочтительно, если бы я мог ограничить, кто может вызывать initAPI () или когда он вызывался - например, функция НЕ должна вызываться после того, как вызовы API были сделаны длябиблиотека - как это изменит подпрограммы mgmt памяти.Поэтому я хотел бы ограничить, где и кем это называется.Это проблема доступа, которую можно решить, сделав метод закрытым в C ++, я не уверен, как это сделать в C.

Проблемы в 2 и 3 выше могут быть тривиально решены в C ++, однако яя вынужден решать эти проблемы в C.

Наконец, предполагая, что указатели функций могут быть правильно установлены во время инициализации, как описано выше - у меня есть второй вопрос, касающийся видимостиглобальные переменные в разделяемой библиотеке, через различные процессы, использующие разделяемую библиотеку.Указатели на функции будут реализованы как глобальные переменные (я не слишком обеспокоен безопасностью потоков НА СЕЙЧАС - хотя я предполагаю обернуть доступ с блокировкой мьютекса в какой-то момент) * и каждое приложение, использующее разделяемую библиотеку, не должно мешать процедурам управления памятьюиспользуется для другого приложения, использующего разделяемую библиотеку.

Я подозреваю, что это код (не данные), который разделяется между процессами, использующими shlib - однако, я хотел бы, чтобы это было подтверждено - предпочтительно, со ссылкой, которая создает резервную копиюэто утверждение.

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

Кстати, я собираю библиотеку на Linux (Ubuntu)

Ответы [ 5 ]

3 голосов
/ 06 января 2012

Поскольку я не совсем уверен в том, что задают вопрос, я постараюсь предоставить информацию, которая может оказаться полезной.

Вы указали и , вероятно, можно с уверенностью предположить, что вы также используете цепочку инструментов GNU.


GCC предоставляет конструктор атрибут функции , который вызывает автоматический вызов функции до того, как выполнение входит в main().Вы можете использовать это для лучшего контроля, когда вызывается подпрограмма инициализации библиотеки, initAPI().

void __attribute__ ((constructor)) initAPI(void);

В случае инициализации библиотеки подпрограммы конструктора выполняются до того, как dlopen() вернется, если библиотека загружена ввремя выполнения или до запуска main(), если библиотека загружается во время загрузки.


У компоновщика GNU есть опция --wrap <symbol>, которая позволяет вам предоставлять оболочки для системных функций.

Если вы свяжетесь с --wrap malloc, ссылки на malloc() будут перенаправлены на __wrap_malloc() (который вы реализуете), а ссылки на __real_malloc() перенаправят на исходный malloc() (так что вы можете вызывать его из своей оболочкиреализации).

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

#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>

void * malloc(size_t size) {
   static void * (*func)(size_t) = NULL;
   void * ret;

   if (!func) {
      /* get reference to original (libc provided) malloc */
      func = (void *(*)(size_t)) dlsym(RTLD_NEXT, "malloc");
   }

   /* code to execute before calling malloc */
   ...

   /* call original malloc */
   ret = func(size);

   /* code to execute after calling malloc */
   ...

   return ret;
}

Я предлагаю прочитать сообщение в блоге Джея Конрода под названием Учебное пособие: Вложение функций в Linux для получения дополнительной информации о замене вызовов функций в динамических библиотеках вызовами ваших собственных функций-оболочек.

1 голос
/ 06 января 2012

Достижение этого:

(На данный момент) будет два семейства процедур управления памятью:

  • Стандартные функции malloc, calloc и т. Д.
  • специализированных версий malloc, calloc и т. Д.

с динамическими библиотеками в Linux - это тривиально , и не требует сложной схемы, которую вы придумали (ни LD_PRELOAD или dlopen, предложенных @ugoren).

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

Вы также можете встроить специализированные malloc, например, в. libmymalloc.so и поместите эту библиотеку в строку ссылки перед libc, чтобы достичь того же результата.

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

UPDATE:

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

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

Когда ваша общая библиотека (та, которая предоставляет API) вызывает malloc «за кулисами», какую (из нескольких) реализаций malloc она получает? первый тот, который виден динамическому компоновщику. Если вы свяжете реализацию malloc с a.out, это будет то .

1 голос
/ 06 января 2012

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

Теперь, чтобы решить ваши проблемы:

Статические данные (что вы называете "глобальными переменными)") общей библиотеки для каждого процесса.Ваши глобальные переменные в одном процессе не будут мешать глобальным переменным в другом процессе.Нет необходимости в мьютексах.

В C вы не можете ограничить [1], кто может вызывать функцию.Он может быть вызван любым, кто знает его имя или имеет указатель на него.Вы можете кодировать initAPI() таким образом, чтобы он явно прерывал работу программы (сбивал ее), если это не первая вызванная библиотечная функция.Вы писатель библиотеки, вы устанавливаете правила игры и не имеете НИКАКОГО обязательства перед программистами, которые не соблюдают правила.

[1] Вы можете объявить функцию статической, то есть ее можно вызватьимя только по коду внутри той же единицы перевода;он по-прежнему может вызываться через указатель любым, кому удается получить указатель на него.Такие функции не «экспортируются» из библиотек, поэтому это не применимо к вашему сценарию.

0 голосов
/ 06 января 2012

Если разные приложения выполняются в разных процессах, использовать динамические библиотеки довольно просто.
Библиотека может просто вызывать malloc () и free (), и приложения, которые хотят ее переопределить, могут загрузить другую библиотеку с альтернативными реализациями для этих библиотек.
Это можно сделать с помощью переменной среды LD_PRELOAD.
Или, если ваша библиотека загружена с помощью dlopen (), сначала загрузите библиотеку malloc.

Это в основном то, что делают такие инструменты, как valgrind, которые заменяют malloc.

0 голосов
/ 06 января 2012

Вам достаточно просто потребовать, чтобы ваша функция инициализации была:

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