1. 程式人生 > >核心EXPORT_SYMBOL函式講解(二)

核心EXPORT_SYMBOL函式講解(二)

書接上文,在

https://blog.csdn.net/weixin_37867857/article/details/84526808

這個部落格裡面寫到EXPORT_SYMBOL函式使用就是把匯出的符號以符號值+符號的字串表示的形式表示的。只是講解了EXPORT_SYMBOL巨集的作用就是把匯出的符號以符號名稱+符號值的形式儲存在struct kernel_symbol為資料結構的檔案裡面,如果檔案載入過程中則是把資料載入在記憶體裡,具體的儲存地方則是ksymtab資料棧裡。如果模組編譯成核心,則是編譯進核心的ksymtab的資料棧裡。
但是還沒有講解模組載入過程中對於ksymtab資料棧的操作。本章就講解下該資料棧的作用以及使用。
所有的已經載入的模組在核心中都有一個struct module的表示,並且加入進去modules的全域性連結串列中。其中struct module關於匯出的符號的表示如下:

struct module{
	....
/* Exported symbols */
        const struct kernel_symbol *syms;
        /***儲存著模組匯出的符號的陣列**/
        const unsigned long *crcs;//校驗
        unsigned int num_syms;
        /***儲存著模組匯出的符號的數量****/

        /* GPL-only exported symbols. */
        unsigned int num_gpl_syms;
        /***儲存模組匯出gpl符號的陣列長***/
const struct kernel_symbol *gpl_syms; /***儲存著模組匯出GPL符號的陣列的個數***/ const unsigned long *gpl_crcs;//校驗 .... };

核心在模組載入過程中先獲取正在載入的模組的符號表

static int verify_export_symbols(struct module *mod)
{
        unsigned int i;
        struct module *owner;
        const struct kernel_symbol *
s; /***先獲取正在載入模組的符號表並且存放在arr陣列中**/ struct { const struct kernel_symbol *sym; unsigned int num; } arr[] = { { mod->syms, mod->num_syms }, { mod->gpl_syms, mod->num_gpl_syms }, { mod->gpl_future_syms, mod->num_gpl_future_syms }, #ifdef CONFIG_UNUSED_SYMBOLS { mod->unused_syms, mod->num_unused_syms }, { mod->unused_gpl_syms, mod->num_unused_gpl_syms }, #endif }; /***遍歷arr陣列中每一個struct kernel_symbol結構***/ for (i = 0; i < ARRAY_SIZE(arr); i++) { for (s = arr[i].sym; s < arr[i].sym + arr[i].num; s++) { /***查詢符號名稱是否是已知模組使用,若果是已知模組使用則把模組內容加入到owner中**/ if (find_symbol(s->name, &owner, NULL, true, false)) { printk(KERN_ERR "%s: exports duplicate symbol %s" " (owned by %s)\n", mod->name, s->name, module_name(owner)); return -ENOEXEC; } } } return 0; }

以下是根據匯出的符號名稱查詢的的過程:

	1.根據核心映象載入過程中匯出的符號查詢,儲存在:
	                { __start___ksymtab, __stop___ksymtab, __start___kcrctab,
                  NOT_GPL_ONLY, false },
                { __start___ksymtab_gpl, __stop___ksymtab_gpl,
                  __start___kcrctab_gpl,
                  GPL_ONLY, false },
                { __start___ksymtab_gpl_future, __stop___ksymtab_gpl_future,
                  __start___kcrctab_gpl_future,
                  WILL_BE_GPL_ONLY, false },
#ifdef CONFIG_UNUSED_SYMBOLS
                { __start___ksymtab_unused, __stop___ksymtab_unused,
                  __start___kcrctab_unused,
                  NOT_GPL_ONLY, true },
                { __start___ksymtab_unused_gpl, __stop___ksymtab_unused_gpl,
                  __start___kcrctab_unused_gpl,
                  GPL_ONLY, true },
	全域性變數裡;
	2.遍歷各個模組,查詢各個模組匯出的符號表。

以下程式碼是查詢符號表的過程:

const struct kernel_symbol *find_symbol(const char *name,
                                        struct module **owner,
                                        const unsigned long **crc,
                                        bool gplok,
                                        bool warn)
{
        struct find_symbol_arg fsa;

        fsa.name = name;
        fsa.gplok = gplok;
        fsa.warn = warn;
	/**each_symbol是遍歷每一個由映象或者核心模組匯出的符號表;
           find_symbol_in_section則是一個回撥函式,由each_symbol呼叫,執行真正
           的查詢過程
	   查詢的順序如下:
	   	1.查詢由於核心映象匯出的符號表;
	   	2.查詢由於各個模組匯出的符號表;
	**/
        if (each_symbol(find_symbol_in_section, &fsa)) {
                if (owner)
                        *owner = fsa.owner;
                if (crc)
                        *crc = fsa.crc;
                return fsa.sym;
        }

        DEBUGP("Failed to find symbol %s\n", name);
        return NULL;
}

以下是find_symbol_in_section執行過程:
find_symbol_in_section則是一個回撥函式,由each_symbol呼叫,執行真正
的查詢過程:

static bool find_symbol_in_section(const struct symsearch *syms,
                                   struct module *owner,
                                   unsigned int symnum, void *data)
{
        struct find_symbol_arg *fsa = data;
	/**比較開始的佇列的符號名稱,如果比較失敗則返回false*/
        if (strcmp(syms->start[symnum].name, fsa->name) != 0)
                return false;

        if (!fsa->gplok) {
                if (syms->licence == GPL_ONLY)
                        return false;
                if (syms->licence == WILL_BE_GPL_ONLY && fsa->warn) {
                        printk(KERN_WARNING "Symbol %s is being used "
                               "by a non-GPL module, which will not "
                               "be allowed in the future\n", fsa->name);
                        printk(KERN_WARNING "Please see the file "
                               "Documentation/feature-removal-schedule.txt "
                               "in the kernel source tree for more details.\n");
                }
        }

#ifdef CONFIG_UNUSED_SYMBOLS
        if (syms->unused && fsa->warn) {
                printk(KERN_WARNING "Symbol %s is marked as UNUSED, "
                       "however this module is using it.\n", fsa->name);
                printk(KERN_WARNING
                       "This symbol will go away in the future.\n");
                printk(KERN_WARNING
                       "Please evalute if this is the right api to use and if "
                       "it really is, submit a report the linux kernel "
                       "mailinglist together with submitting your code for "
                       "inclusion.\n");
        }
#endif
	/***如果比較成功則返回true,並且把模組的指標賦予fsa->owner**/
        fsa->owner = owner;
        fsa->crc = symversion(syms->crcs, symnum);
        fsa->sym = &syms->start[symnum];
        return true;
}

each_symbol執行則相對簡單不在贅述,大體上說一下執行過程:

	struct symsearch {
        const struct kernel_symbol *start, *stop;
        const unsigned long *crcs;
        enum {
                NOT_GPL_ONLY,
                GPL_ONLY,
                WILL_BE_GPL_ONLY,
        } licence;
        bool unused;
	};
	這個結構體在each_symbol函式裡面經常用到,用於臨時儲存匯出符號的符號表;
	1.先匯出核心每一個模組到struct symsearch型別的數組裡面。
	2.遍歷儲存的symsearch陣列,從start->stop過程;
	3.遍歷每一個模組,把每個模組匯出的符號表匯出到symsearch數組裡面;
	4.執行過程2