Rende un simbolo accessibile ai moduli caricati dinamicamente (a condizione che detti moduli aggiungano un extern
dichiarazione).
Non molto tempo fa, qualcuno ha chiesto come usarlo.
Ecco una buona spiegazione.
https://www.quora.com/Qual è-la-differenza-tra-extern-and-EXPORT_SYMBOL-in-Linux-kernel-codes
Extern è una parola chiave della classe di archiviazione C. Nel kernel, come in qualsiasi altro codice C, dice al compilatore che la definizione della variabile o funzione che qualifica è implementata in un altro "file", o meglio, più precisamente Unità di traduzione (programmazione) - Wikipedia. L'unità di traduzione che la definisce non dovrebbe usare il qualificatore static. Pertanto, la tabella dei simboli ha una voce corrispondente ad essa. Al momento del collegamento, il simbolo viene risolto normalmente. Non c'è nulla di specifico del kernel su "extern".
EXPORT_SYMBOL() è una macro definita dalle intestazioni del kernel di Linux. Non ha molto in comune con extern. Dice al meccanismo kbuild che il simbolo a cui si fa riferimento dovrebbe far parte dell'elenco globale dei simboli del kernel. Ciò, a sua volta, consente ai moduli del kernel di accedervi. Il codice integrato nel kernel stesso (al contrario di un modulo) può, ovviamente, accedere a qualsiasi simbolo non statico tramite una dichiarazione extern, in accordo con il normale C. Il meccanismo EXPORT_SYMBOL() ci consente di esportare un simbolo per l'uso da parte di moduli caricabili come bene. Una cosa interessante è che un simbolo così esportato da un modulo diventa accessibile ad un altro modulo che può dipendere da esso!
Per riassumere, extern non è specifico del kernel. Viene utilizzato per qualificare una dichiarazione a un simbolo non statico da un'altra unità di traduzione. EXPORT_SYMBOL() è specifico del kernel Linux. Viene utilizzato nell'unità di traduzione della definizione per rendere disponibile il simbolo ai moduli caricabili.
Quindi EXPORT_SYMBOL è solo un meccanismo come extern, ma è per riferimento tra moduli caricabili non file.
Per andare avanti, possiamo supporre che sia raggiunto dall'extern perché extern è la forma C che è la base.
Ecco un indizio.
https://elixir.bootlin.com/linux/v4.6.7/source/include/linux/export.h#L56
#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 }
Per prima cosa dichiara un sim extern.
Quindi una stringa __kstrtab_##sym ==VMLINUX_SYMBOL_STR(sym).
Last a extern struct kernel_symbol __ksymtab_##sym ={ (unsigned long)&sym , __kstrtab_##sym }. &sym registra l'indirizzo reale del sim come una funzione o una variabile, _kstrtab ##sym registra la stringa del nome.
Non una risposta in sé ma una dimostrazione, come promesso dal mio commento, che i simboli esportati non lo sono deve essere non statico. I 2 moduli seguenti lo dimostrano:
/* 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");
E il secondo modulo
/* 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");
Questi sono stati testati su CentOS 6 e CentOS 7:kernel 2.6.32 e 3.10 (rispettivamente). Il caricamento di mod1.ko e quindi di mod2.ko comporterà la stampa del valore passato a mod1_exp_func() nei buffer di log del kernel.