Что означает "EXPORT_SYMBOL" в коде ядра Linux?
48 struct snd_card *snd_cards[SNDRV_CARDS];
49 EXPORT_SYMBOL(snd_cards);
Я не понимаю, в чем смысл этого и почему это используется. Я пытался искать об этом, но не понимая смысла этого.
2 ответа
Это делает символ доступным для динамически загружаемых модулей (при условии, что указанные модули добавляют extern
декларация).
Не так давно кто-то спросил, как им пользоваться.
Вот хорошее объяснение.
https://www.quora.com/What-is-the-difference-between-extern-and-EXPORT_SYMBOL-in-Linux-kernel-codes
Extern - это ключевое слово класса хранения C. В ядре, как и в любом другом коде C, он сообщает компилятору, что определение переменной или функции, которую он квалифицирует, реализовано в другом "файле", или, точнее, в модуле перевода (программирование) - Википедии. Единица перевода, которая ее определяет, не должна использовать статический квалификатор. Следовательно, в таблице символов есть соответствующая запись. Во время связывания символ разрешается как обычно. Нет ничего специфического для ядра "extern".
EXPORT_SYMBOL() - это макрос, определяемый заголовками ядра Linux. У него мало общего с extern. Он сообщает механизму kbuild, что указанный символ должен быть частью глобального списка символов ядра. Это, в свою очередь, позволяет модулям ядра получить к ним доступ. Код, который встроен в само ядро (в отличие от модуля), конечно, может получить доступ к любому нестатическому символу через объявление extern в соответствии с обычным C. Механизм EXPORT_SYMBOL() позволяет нам экспортировать символ для использования также загружаемыми модулями. Интересно то, что символ, экспортированный таким образом одним модулем, становится доступным для другого модуля, который может от него зависеть!
Подводя итог, extern не зависит от ядра. Он используется для уточнения объявления нестатического символа из другой единицы перевода. EXPORT_SYMBOL() специфичен для ядра Linux. Он используется в единице перевода определения, чтобы сделать символ доступным для загружаемых модулей.
Таким образом, EXPORT_SYMBOL - это просто механизм, подобный extern, но он используется для справки между загружаемыми модулями, а не файлом.
Чтобы двигаться вперед, мы можем предположить, что это достигается с помощью extern, потому что extern - это форма C, которая является основой.
Вот подсказка.
https://elixir.bootlin.com/linux/v4.6.7/source/include/linux/export.h
#define EXPORT_SYMBOL(sym) \
__EXPORT_SYMBOL(sym, "")
/* For every exported symbol, place a struct in the __ksymtab section */
#define __EXPORT_SYMBOL(sym, sec) \
extern typeof(sym) sym; \
__CRC_SYMBOL(sym, sec) \
static const char __kstrtab_##sym[] __attribute__((section("__ksymtab_strings"), aligned(1))) = VMLINUX_SYMBOL_STR(sym); \
extern const struct kernel_symbol __ksymtab_##sym; \
__visible const struct kernel_symbol __ksymtab_##sym __used __attribute__((section("___ksymtab" sec "+" #sym), unused)) = { (unsigned long)&sym, __kstrtab_##sym }
Сначала объявите внешний символ.
Затем строка __kstrtab_##sym = = VMLINUX_SYMBOL_STR(sym).
Последний: extern struct kernel_symbol __ksymtab_##sym = { (беззнаковое длинное)&sym, __kstrtab _ ##sym }. &sym записывает реальный адрес символа, например функцию или переменную, _kstrtab##sym записывает строку имени.
Не ответ как таковой, а демонстрация, как и было обещано в моем комментарии, что экспортируемые символы не обязательно должны быть нестатичными. Следующие 2 модуля демонстрируют это:
/* mod1.c */
#include <linux/module.h>
static int mod1_exp_func(int i)
{
pr_info("%s:%d the value passed in is %d\n",
__func__, __LINE__, i);
return i;
}
EXPORT_SYMBOL(mod1_exp_func); /* export static symbol */
static int __init mod1_init(void)
{
pr_info("Initializing simple mod\n");
return 0;
}
static void __exit mod1_exit(void)
{
pr_info("This module is exiting\n");
}
module_init(mod1_init);
module_exit(mod1_exit);
MODULE_LICENSE("GPL v2");
И второй модуль
/* mod2.c */
#include <linux/module.h>
extern int mod1_exp_func(int);
static int __init mod2_init(void)
{
pr_info("Initializing mod2\n");
pr_info("Calling exported function in mod1\n");
mod1_exp_func(3);
return 0;
}
static void __exit mod2_exit(void)
{
pr_info("mod2 exiting\n");
}
module_init(mod2_init);
module_exit(mod2_exit);
MODULE_LICENSE("GPL v2");
Они были протестированы на CentOS 6 и CentOS 7: ядра 2.6.32 и 3.10 (соответственно). Загрузка mod1.ko, а затем mod2.ko приведет к тому, что значение, переданное mod1_exp_func(), будет напечатано в буферах журнала ядра.