LINUX.ORG.RU

Библиотеки .so, их использование и разработка


0

0

Всем доброго дня!

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

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

Во-вторых, исследуя получающиеся на выходе компилятора .so с помощью readelf обнаружил, что среди символов лежат все присутствующие в коде библиотеки функции. Это не есть хорошо: они занимают лишнее место, да и незачем выставлять на показ внутренние функции. В этом вопросе мне вспоминается опыт написания dll в NT - там я помечал функции, которые я хочу экспортировать. Более того, мне вспоминается, что при ручном импортировании функций из библиотеки в NT как-то можно было вообще не использовать имена функций, а лишь ее номер. Мне кажется в данном случае это было бы даже оправданно. И выкинуть имена всех символов, чтобы кто не знает - не пользовался. Можно ли сделать экспорт только нужных функций и по возможности с доступом не по именам, а по номерам?

В-третьих, в статье "Анатомия динамических библиотек Linux" прочитал, что при подгрузке библиотеки, у нее автоматически вызывается функция инициализации. Иногда ее иметь действительно полезно. Но, действуя по описанию, фактически я только добавил ключ компиляции у gcc и не создавал никаких процедур. Компилятор не заругался. Можно ли добавить процедуру инициализации и деинициализации в библиотеку или в статье наврали?

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

во-первых:

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

во-вторых:

для неэкспортирования существует static

в-третьих:

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

И:

да, вопрос тупой.

Deleted
()

1 и 3 делается так.

struct CoreSingleton {
   virtual void core_function1() = 0;
   virtual void core_function2() = 0;
...
};


...
void load_module(const char * module_name)
{
   void * h = dlopen(module_name);
   func_t init = dlsym(h, "init_function");
   init(core_singleton);
....
}

И тем самым мы убили сразу двух зайцев.

2. делается установкой атрибута visibility. Но тем самым ты привязываешь себя к gcc. http://gcc.gnu.org/wiki/Visibility

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

Спасибо, действительно интересное решение со структурой! Насчет visibility нигде не читал - буду изучать тему, уверен, что поможет!:)

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

а static теперь не в моде?

А если so линкуется из нескольких объектных файлов, использующих одну и туже функцию, но которая не должна быть видна в наружу so?

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

>А если so линкуется из нескольких объектных файлов, использующих одну и туже функцию, но которая не должна быть видна в наружу so?

в этом случае нужно избавляться от причины, а не бороться со следствием.

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

в этом случае нужно избавляться от причины, а не бороться со следствием.

а что в этом порочного?

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

> а что в этом порочного?

Видимо, тогда будет слишком мало копипасты... 8))

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

Брррр... или я чего-то не понял, или Вы :) если будет две одинаковые ф-ции в разных объектниках и при этом они собираются в одну .so, это разве не грабли? или это нормально по-вашему?

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

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

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

Брррр... или я чего-то не понял, или Вы :) если будет две одинаковые ф-ции в разных объектниках и при этом они собираются в одну .so, это разве не грабли? или это нормально по-вашему?

Мне кажется, вы не совсем верно поняли вопрос топикпастера. Ему нужен был аналог директивы dllexport из винды. static вещь полезная, но она не совсем для этого.

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

Во-первых мы говорим о разных вещах, а во-вторых это надо еще умудриться так сделать:

$ g++ file1.cpp file2.cpp -shared -o 1.so -fpic
/tmp/ccQSTXzO.o: In function `func()':
file2.cpp:(.text+0x0): multiple definition of `func()'
/tmp/cc4WBWbh.o:file1.cpp:(.text+0x0): first defined here
collect2: ld returned 1 exit status
Reset ★★★★★
()

Можно ли добавить процедуру инициализации и деинициализации в библиотеку или в статье наврали?

можно

m.h

#include <stdio.h>

typedef struct {
  char *name;
} plugin_data_t;

void add_plugin (plugin_data_t *data);
void del_plugin (plugin_data_t *data);

static plugin_data_t *plugin_data;

static void ctor (void) __attribute__ ((constructor));
static void ctor (void) { add_plugin (plugin_data); }
static void dtor (void) __attribute__ ((destructor));
static void dtor (void) { del_plugin (plugin_data); }

m.c

#include <dlfcn.h>

#include "m.h"

void 
add_plugin (plugin_data_t *data)
{
  if (data)
    printf ("add plugin %s\n", data->name);
}

void 
del_plugin (plugin_data_t *data)
{
  if (data)
    printf ("remove plugin %s\n", data->name);
}

int 
main ()
{
  void *h;

  h = dlopen ("p.so", RTLD_LAZY);
  dlclose (h);
  
  return 0;
}

p.c

#include "m.h"

static plugin_data_t descr = {
  .name = "SimplePlugin"
};

static plugin_data_t *plugin_data = (plugin_data_t *) & descr;

(victor@toshiba)~/ttt $> gcc -l dl -rdynamic -o m m.c                                                [sh]
(victor@toshiba)~/ttt $> gcc -shared -o p.so p.c                                                     [sh]
(victor@toshiba)~/ttt $> ./m                                                                         [sh]
add plugin SimplePlugin
remove plugin SimplePlugin
(victor@toshiba)~/ttt $>                                                                             [sh]
ananas ★★★★★
()
Ответ на: комментарий от ananas

>static void ctor (void) __attribute__ ((constructor));
>static void ctor (void) { add_plugin (plugin_data); }

>static void dtor (void) __attribute__ ((destructor));

>static void dtor (void) { del_plugin (plugin_data); }


абалдеть :) и не знал. я правильно понимаю, что сие точит приложение под gcc? или так это где-то еще есть?

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

> я правильно понимаю, что сие точит приложение под gcc?

правильно понимаешь. хотя, по-идее, подобные фичи должны быть и в других компиляторах.

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

>загрузка библиотеки не влечет за собой автоматический вызов какого-либо функции-инициализатора, происходит попросту "расшаривание" экспортированных функций на адресное пространство процесса, загружающего эту библиотеку.

в http://ladweb.net/ говорится, что если экспортируется _init, то он выполнится до возвращения управления от dlopen

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

Это как раз-таки то, о чем привел пример ananas. Вот выдержка из мана по dlopen:

The obsolete symbols _init and _fini

The linker recognizes special symbols _init and _fini. If a dynamic library exports a routine named _init, then that code is executed after the loading, before dlopen() returns. If the dynamic library exports a routine named _fini, then that routine is called just before the library is unloaded. In case you need to avoid linking against the system startup files, this can be done by giving gcc the "-nostartfiles" parameter on the command line. Using these routines, or the gcc -nostartfiles or -nostdlib options, is not recommended. Their use may result in undesired behavior, since the constructor/destructor routines will not be executed (unless special measures are taken).

Instead, libraries should export routines using the __attribute__((constructor)) and __attribute__((destructor)) function attributes. See the gcc info pages for information on these. Constructor routines are executed before dlopen() returns, and destructor routines are executed before dlclose() returns.

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

Спасибо за ответы! с использованием функции _init и методики из http://www.linux.org.ru/view-message.jsp?msgid=3984098 получается действительно довольно элегантное решение! Про _init почему-то в документации проглядел, виноват:(. Останется только видимость функций прикрутить и будет загляденье.

codergeneration
() автор топика
Ответ на: комментарий от Reset

>1 и 3 делается так.

Не понял, ты хочешь задействовать плюсовый name mangling? или все-таки где-то будет extern "C" CoreSingleton core?

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

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

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

То есть ты своими руками предложил человеку не написать extern "C"? Он же следующим ходом спросит: "А почему dlsym("core") возвращает NULL?".

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

extern "C" пишется для функций. описания init_function у меня вообще нет, естественно оно должно быть с extern "C"

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

А адрес экземпляра структуры получается при помощи святого духа? Или ты просто демонстрировал ручную инициализацию? Но тогда к чему фрагмент виртуальных методов тут?

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

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

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

Конечно спрошу. Но это не большая проблема - readelf все расставляет на свои места.

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