Linux模塊編程機制之hello kernel

火星人 @ 2014-03-03 , reply:0


Linux模塊編程機制之hello kernel

Linux模塊編程機制之hello kernel       

       看了那麼多理論知識,可能還是一頭霧水,是啊,純理論分析本來就不好理解。為了更好的理解Linux內核各種內部機制以及其運用,在接下來的學習中將採用理論+實驗+源碼註釋的方式進行。包括演算法、原理的實驗,內核的局部擴展與修改等。Linux內核編程有很多方法,最方便的方式是使用內核提供的模塊編程機制,另一種方式是以補丁的方式,這種方式只需要編譯一次內核,當然也可以直接修改內核源碼,但是每次修改後都需要重新編譯、引導、重啟,很麻煩,也很費時。首先,我們看看最方便快捷的一種方式——LINUX內核中模塊編程機制。

      

      還是從程序員的哪個起步程序hello world開始,但是我們這裡比一般的hello world稍微複雜一點,用兩個hello world程序。



文件hello.c

view plaincopyprint?#include    
#include    
#include    
  
MODULE_LICENSE("GPL");  
extern int hello_data;  
  
static int hello_init(void)  
{  
    printk(KERN_ERR "hello,kernel!,this is hello module\n");  
    printk(KERN_ERR "hello_data:%d\n",++hello_data);  
    return 0;  
}  
  
static void hello_exit(void)  
{  
    printk(KERN_ERR "hello_data:%d\n",--hello_data);  
    printk(KERN_ERR "Leave hello module!\n");  
}  
module_init(hello_init);  
module_exit(hello_exit);  
  
MODULE_AUTHOR("Mike Feng");  
MODULE_DESCRIPTION("This is hello module");  
MODULE_ALIAS("A simple example");  
#include
#include
#include

MODULE_LICENSE("GPL");
extern int hello_data;

static int hello_init(void)
{
        printk(KERN_ERR "hello,kernel!,this is hello module\n");
        printk(KERN_ERR "hello_data:%d\n",++hello_data);
        return 0;
}

static void hello_exit(void)
{
        printk(KERN_ERR "hello_data:%d\n",--hello_data);
        printk(KERN_ERR "Leave hello module!\n");
}
module_init(hello_init);
module_exit(hello_exit);

MODULE_AUTHOR("Mike Feng");
MODULE_DESCRIPTION("This is hello module");
MODULE_ALIAS("A simple example");對應的Makefile文件:

view plaincopyprint?obj-m +=hello.o  
CURRENT_DIR:=$(shell pwd)  
KERNEL_DIR:=$(shell uname -r)  
KERNEL_PATH:=/usr/src/kernels/$(KERNEL_DIR)  
  
all:  
    make -C $(KERNEL_PATH) M=$(CURRENT_DIR) modules  
clean:  
    make -C $(KERNEL_PATH) M=$(CURRENT_DIR) clean  
obj-m +=hello.o
CURRENT_DIR:=$(shell pwd)
KERNEL_DIR:=$(shell uname -r)
KERNEL_PATH:=/usr/src/kernels/$(KERNEL_DIR)

all:
        make -C $(KERNEL_PATH) M=$(CURRENT_DIR) modules
clean:
        make -C $(KERNEL_PATH) M=$(CURRENT_DIR) clean
文件hello_h.c:

view plaincopyprint?#include    
#include    
#include    
  
MODULE_LICENSE("GPL");  
static unsigned int hello_data=100;  
EXPORT_SYMBOL(hello_data);  
  
static int hello_h_init(void)  
{  
    hello_data+=5;  
    printk(KERN_ERR "hello_data:%d\nhello kernel,this is hello_h module\n",hello_data);  
  
    return 0;  
}  
  
static void hello_h_exit(void)  
{  
    hello_data-=5;  
    printk(KERN_ERR "hello_data:%d\nleave hello_h module\n",hello_data);  
}  
  
module_init(hello_h_init);  
module_exit(hello_h_exit);  
MODULE_AUTHOR("Mike Feng");  
#include
#include
#include

MODULE_LICENSE("GPL");
static unsigned int hello_data=100;
EXPORT_SYMBOL(hello_data);

static int hello_h_init(void)
{
        hello_data+=5;
        printk(KERN_ERR "hello_data:%d\nhello kernel,this is hello_h module\n",hello_data);

        return 0;
}

static void hello_h_exit(void)
{
        hello_data-=5;
        printk(KERN_ERR "hello_data:%d\nleave hello_h module\n",hello_data);
}

module_init(hello_h_init);
module_exit(hello_h_exit);
MODULE_AUTHOR("Mike Feng");
對應的Makefile

view plaincopyprint?obj-m+=hello_h.o  
CURRENT:=$(shell pwd)  
KERNEL_PATH:=/usr/src/kernels/$(shell uname -r)  
  
all:  
    make -C $(KERNEL_PATH) M=$(CURRENT) modules  
clean:  
    make -C $(KERNEL_PATH) M=$(CURRENT) clean  
obj-m+=hello_h.o
CURRENT:=$(shell pwd)
KERNEL_PATH:=/usr/src/kernels/$(shell uname -r)

all:
        make -C $(KERNEL_PATH) M=$(CURRENT) modules
clean:
        make -C $(KERNEL_PATH) M=$(CURRENT) clean
可見,我們在hello_h.c中定義了一個靜態變數hello_data,初始值為100,並把他導出了,在hello.c中使用了該變數。這樣給出例子,後面我們會看到,是為了說明模塊依賴。



模塊信息分析初步

       當我們make后,在當前目錄下生成文件有:hello.mod.c、hello.o、hello.ko、hello.mod.o、Module.markers、modules.order、Module.symvers。上面的代碼以及編譯生成的文件後面會詳細分析。



hello.ko文件是我們需要的,用file名命令看看。


        file命令的輸出表明模塊文件是可重定位的,這是用戶空間程序設計中一個熟悉的術語。可從定位文件的函數都不會引用絕對地址,而只是指向代碼中的相對地址,因此可以在內存的任意偏移地址載入,當然,在映像載入到內存中時,映像個的地址要由動態鏈接器ld.so進行適當的修改。內核模塊同樣如此。其中的地址也是相對的,而不是絕對的。當重定位的工作由內核自身執行,而不是動態裝載器。



我們再用nm命令查看一下該目標文件的外部函數列表。nm hello.ko:00000000 r __mod_alias25
0000003c r __mod_author23
00000018 r __mod_description24
00000050 r __mod_license5
0000005c r __mod_srcversion23
0000008c r __mod_vermagic5
00000080 r __module_depends
00000000 D __this_module
00000000 T cleanup_module
         U hello_data
00000000 t hello_exit
0000002d t hello_init
0000002d T init_module
         U mcount
         U printkU代表未解決的引用,可見都為內核代碼中的導出函數,D表示符號位於數據段,T表示符號位於代碼段。內核提供了一個所有導出函數的列表。該列表給出了所有導出函數的內存地址和對應的函數名,可以通過proc文件系統訪問,即文件/proc/kallsyms。



查詢模塊信息:

        還有一些額外的信息來源,是直接存儲在模塊二進位文件中,並且指定了模塊用途的文本描述。這些可以使用modutils中的modinfo工具查詢。他們可以存儲電子郵件地址、功能簡短描述、配置參數描述、指定支持的設備、模塊按何種許可證分發等,我們對上面的hello.ko文件查看一下:



       這些額外的信息如何合併到二進位模塊文件中呢?在所有使用ELF格式的二進位文件中,有各種各種單元將二進位數據組織到不同類別中,這些在技術上稱之為段。為允許在模塊中添加信息,內核引入了一個名為.modinfo的段。

自動載入:

      通常,模塊的裝載發起於用戶空間,由用戶或自動化腳本啟動。在處理模塊時,嗚嗚i達到更大的靈活性並提高透明度,內核自身也能夠請求載入模塊。由於在用戶空間完成這些比在內核空間容易的多,內核將該工作委託給一個輔助進程kmod。要注意,kmod並不是一個永久性的守護進程,內核會按需啟動他。



       當內核請求沒有相關數據結構信息時,內核試圖使用request_module函數載入對應的模塊,該函數使用kmod機制啟動modprobe工具,modprobe插入相應的模塊。換句話說,內核依賴於用戶空間中的一個應用程序使用內核函數來添加模塊,如下圖:





       內核源代碼中,很多不同地方調用了request_module。藉助該函數,內核試圖通過在沒有用戶介入的情況下自動載入代碼,使得儘可能透明地訪問那些委託給模塊的功能。



        可能出現這樣的情況;無法唯一確定哪個模塊能夠提供所需的功能。為解決這個問題,附加到每個模塊的一個小「資料庫」。資料庫的內容描述了該模塊所支持的設備。資料庫信息通過模塊別別名提供。這些是模塊的通用標識符,其中編碼了所描述的信息。宏MODULE_ALIAS用於產生模塊別名。MODULE_ALIAS這一類的宏都由MODULE_INFO定義,我們還是從源碼中尋找出處:

view plaincopyprint?/* Generic info of form tag = "info" */  
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)   
  
/* For userspace: you can also call me... */  
#define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)   
  
  
#define __MODULE_INFO(tag, name, info)                    \   
static const char __module_cat(name,__LINE__)[]               \  
  __used                                  \  
  __attribute__((section(".modinfo"),unused)) = __stringify(tag)  
/* Generic info of form tag = "info" */
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)

/* For userspace: you can also call me... */
#define MODULE_ALIAS(_alias) MODULE_INFO(alias, _alias)


#define __MODULE_INFO(tag, name, info)                                          \
static const char __module_cat(name,__LINE__)[]                                  \
  __used                                                                  \
  __attribute__((section(".modinfo"),unused)) = __stringify(tag)
可見,MODULE_INFO內容保存在模塊二進位文件的.modinfo段中。



模塊行為

      用戶空間工具和內核的模塊實現之間的結構,包括兩個系統調用。

init_module:將一個新模塊插入到內核中。用戶空間工具只需提供二進位數據。所有其他工作(特別是重定位和解決引用)由內核自身完成。



delete_module:從內核溢出一個模塊。當然,前提是該模塊的代碼不再使用,並且其他模塊也不再使用該模塊導出的函數。



還有一個request_module函數(不是系統調用),用於從內核端載入模塊。他不經用於載入模塊。還用於實現熱插拔功能。



模塊在內核中用下面數據結構表示:

view plaincopyprint?struct module  
{  
    enum module_state state;  
  
    /* Member of list of modules */  
    /*鏈表的表頭為一個module類型的全局變數
    modules*/  
    struct list_head list;  
  
    /* Unique handle for this module */  
    char name;  
  
    /* Sysfs stuff. */  
    struct module_kobject mkobj;  
    struct module_attribute *modinfo_attrs;  
    const char *version;  
    const char *srcversion;  
    struct kobject *holders_dir;  
  
    /* Exported symbols */  
    /*syms為一個數組,共有num_syms項,
    類型kernel_symbol負責將標識符(name欄位)
    分配到內存地址(value欄位)*/  
    const struct kernel_symbol *syms;  
    /*也是num_syms個項的數組,存放了到處富豪的校驗和
    用於實現版本控制*/  
    const unsigned long *crcs;  
    unsigned int num_syms;  
  
    /* Kernel parameters. */  
    struct kernel_param *kp;  
    unsigned int num_kp;  
  
    /* GPL-only exported symbols. */  
    unsigned int num_gpl_syms;  
    const struct kernel_symbol *gpl_syms;  
    const unsigned long *gpl_crcs;  
  
#ifdef CONFIG_UNUSED_SYMBOLS   
    /* unused exported symbols. */  
    const struct kernel_symbol *unused_syms;  
    const unsigned long *unused_crcs;  
    unsigned int num_unused_syms;  
  
    /* GPL-only, unused exported symbols. */  
    unsigned int num_unused_gpl_syms;  
    const struct kernel_symbol *unused_gpl_syms;  
    const unsigned long *unused_gpl_crcs;  
#endif   
  
    /* symbols that will be GPL-only in the near future. */  
    const struct kernel_symbol *gpl_future_syms;  
    const unsigned long *gpl_future_crcs;  
    unsigned int num_gpl_future_syms;  
  
    /* Exception table */  
    unsigned int num_exentries;  
    struct exception_table_entry *extable;  
  
    /* Startup function. */  
    int (*init)(void);  
  
    /* If this is non-NULL, vfree after init() returns */  
    /*模塊二進位數據分為兩個部分:初始化部分和核心部分
    前者包含的東西在裝載結束后都可以丟棄
    後者包含了正常運行期間需要的所有數據
    初始化部分的起始地址保存在module_init*/  
    void *module_init;  
  
    /* Here is the actual code + data, vfree'd on unload. */  
    void *module_core;  
  
    /* Here are the sizes of the init and core sections */  
    unsigned int init_size, core_size;  
  
    /* The size of the executable code in each section.  */  
    unsigned int init_text_size, core_text_size;  
  
    /* Arch-specific module values */  
    struct mod_arch_specific arch;  
  
    unsigned int taints;    /* same bits as kernel:tainted */  
  
#ifdef CONFIG_GENERIC_BUG   
    /* Support for BUG */  
    unsigned num_bugs;  
    struct list_head bug_list;  
    struct bug_entry *bug_table;  
#endif   
  
#ifdef CONFIG_KALLSYMS   
    /*
     * We keep the symbol and string tables for kallsyms.
     * The core_* fields below are temporary, loader-only (they
     * could really be discarded after module init).
     */  
    Elf_Sym *symtab, *core_symtab;  
    unsigned int num_symtab, core_num_syms;  
    char *strtab, *core_strtab;  
  
    /* Section attributes */  
    struct module_sect_attrs *sect_attrs;  
  
    /* Notes attributes */  
    struct module_notes_attrs *notes_attrs;  
#endif   
  
    /* Per-cpu data. */  
    void *percpu;  
  
    /* The command line arguments (may be mangled).  People like
       keeping pointers to this stuff */  
    char *args;  
#ifdef CONFIG_TRACEPOINTS   
    struct tracepoint *tracepoints;  
    unsigned int num_tracepoints;  
#endif   
  
#ifdef CONFIG_TRACING   
    const char **trace_bprintk_fmt_start;  
    unsigned int num_trace_bprintk_fmt;  
#endif   
#ifdef CONFIG_EVENT_TRACING   
    struct ftrace_event_call *trace_events;  
    unsigned int num_trace_events;  
#endif   
#ifdef CONFIG_FTRACE_MCOUNT_RECORD   
    unsigned long *ftrace_callsites;  
    unsigned int num_ftrace_callsites;  
#endif   
  
#ifdef CONFIG_MODULE_UNLOAD   
    /* What modules depend on me? */  
    /*將依賴本模塊的模塊用module_use數據結構鏈接
    起來*/  
    struct list_head modules_which_use_me;  
  
    /* Who is waiting for us to be unloaded */  
    struct task_struct *waiter;  
  
    /* Destruction function. */  
    void (*exit)(void);  
  
#ifdef CONFIG_SMP   
    char *refptr;  
#else   
    local_t ref;  
#endif   
#endif   
  
#ifdef CONFIG_CONSTRUCTORS   
    /* Constructor functions. */  
    ctor_fn_t *ctors;  
    unsigned int num_ctors;  
#endif   
};  
struct module
{
        enum module_state state;

        /* Member of list of modules */
        /*鏈表的表頭為一個module類型的全局變數
        modules*/
        struct list_head list;

        /* Unique handle for this module */
        char name;

        /* Sysfs stuff. */
        struct module_kobject mkobj;
        struct module_attribute *modinfo_attrs;
        const char *version;
        const char *srcversion;
        struct kobject *holders_dir;

        /* Exported symbols */
        /*syms為一個數組,共有num_syms項,
        類型kernel_symbol負責將標識符(name欄位)
        分配到內存地址(value欄位)*/
        const struct kernel_symbol *syms;
        /*也是num_syms個項的數組,存放了到處富豪的校驗和
        用於實現版本控制*/
        const unsigned long *crcs;
        unsigned int num_syms;

        /* Kernel parameters. */
        struct kernel_param *kp;
        unsigned int num_kp;

        /* GPL-only exported symbols. */
        unsigned int num_gpl_syms;
        const struct kernel_symbol *gpl_syms;
        const unsigned long *gpl_crcs;

#ifdef CONFIG_UNUSED_SYMBOLS
        /* unused exported symbols. */
        const struct kernel_symbol *unused_syms;
        const unsigned long *unused_crcs;
        unsigned int num_unused_syms;

        /* GPL-only, unused exported symbols. */
        unsigned int num_unused_gpl_syms;
        const struct kernel_symbol *unused_gpl_syms;
        const unsigned long *unused_gpl_crcs;
#endif

        /* symbols that will be GPL-only in the near future. */
        const struct kernel_symbol *gpl_future_syms;
        const unsigned long *gpl_future_crcs;
        unsigned int num_gpl_future_syms;

        /* Exception table */
        unsigned int num_exentries;
        struct exception_table_entry *extable;

        /* Startup function. */
        int (*init)(void);

        /* If this is non-NULL, vfree after init() returns */
        /*模塊二進位數據分為兩個部分:初始化部分和核心部分
        前者包含的東西在裝載結束后都可以丟棄
        後者包含了正常運行期間需要的所有數據
        初始化部分的起始地址保存在module_init*/
        void *module_init;

        /* Here is the actual code + data, vfree'd on unload. */
        void *module_core;

        /* Here are the sizes of the init and core sections */
        unsigned int init_size, core_size;

        /* The size of the executable code in each section.  */
        unsigned int init_text_size, core_text_size;

        /* Arch-specific module values */
        struct mod_arch_specific arch;

        unsigned int taints;        /* same bits as kernel:tainted */

#ifdef CONFIG_GENERIC_BUG
        /* Support for BUG */
        unsigned num_bugs;
        struct list_head bug_list;
        struct bug_entry *bug_table;
#endif

#ifdef CONFIG_KALLSYMS
        /*
         * We keep the symbol and string tables for kallsyms.
         * The core_* fields below are temporary, loader-only (they
         * could really be discarded after module init).
         */
        Elf_Sym *symtab, *core_symtab;
        unsigned int num_symtab, core_num_syms;
        char *strtab, *core_strtab;

        /* Section attributes */
        struct module_sect_attrs *sect_attrs;

        /* Notes attributes */
        struct module_notes_attrs *notes_attrs;
#endif

        /* Per-cpu data. */
        void *percpu;

        /* The command line arguments (may be mangled).  People like
           keeping pointers to this stuff */
        char *args;
#ifdef CONFIG_TRACEPOINTS
        struct tracepoint *tracepoints;
        unsigned int num_tracepoints;
#endif

#ifdef CONFIG_TRACING
        const char **trace_bprintk_fmt_start;
        unsigned int num_trace_bprintk_fmt;
#endif
#ifdef CONFIG_EVENT_TRACING
        struct ftrace_event_call *trace_events;
        unsigned int num_trace_events;
#endif
#ifdef CONFIG_FTRACE_MCOUNT_RECORD
        unsigned long *ftrace_callsites;
        unsigned int num_ftrace_callsites;
#endif

#ifdef CONFIG_MODULE_UNLOAD
        /* What modules depend on me? */
        /*將依賴本模塊的模塊用module_use數據結構鏈接
        起來*/
        struct list_head modules_which_use_me;

        /* Who is waiting for us to be unloaded */
        struct task_struct *waiter;

        /* Destruction function. */
        void (*exit)(void);

#ifdef CONFIG_SMP
        char *refptr;
#else
        local_t ref;
#endif
#endif

#ifdef CONFIG_CONSTRUCTORS
        /* Constructor functions. */
        ctor_fn_t *ctors;
        unsigned int num_ctors;
#endif
};
module_state狀態:

view plaincopyprint?enum module_state  
{  
    /*正常運行*/  
    MODULE_STATE_LIVE,  
    /*裝載期間*/  
    MODULE_STATE_COMING,  
    /*正在移除*/  
    MODULE_STATE_GOING,  
};  
enum module_state
{
        /*正常運行*/
        MODULE_STATE_LIVE,
        /*裝載期間*/
        MODULE_STATE_COMING,
        /*正在移除*/
        MODULE_STATE_GOING,
}; view plaincopyprint?
struct kernel_symbol  
{  
    unsigned long value;  
    const char *name;  
};  
struct kernel_symbol
{
        unsigned long value;
        const char *name;
}; 依賴關係和引用:

如果模塊B使用了模塊A提供的函數,那麼模塊A和模塊B之間就存在關係。為正確管理這些依賴關係,內核需要引入另一個數據結構:

view plaincopyprint?/* modules using other modules */  
struct module_use  
{  
    struct list_head list;  
    struct module *module_which_uses;  
};  
/* modules using other modules */
struct module_use
{
        struct list_head list;
        struct module *module_which_uses;
}; 依賴關係的網路通過module_use和module數據結構的modules_which_use_me成員共同建立起來。對每個使用了模塊A中函數的模塊B,都會創建一個module_use的新實例。該實例將添加到模塊A的modules_which_use_me鏈表中。Module_which_uses指向模塊B的module實例。根據這些信息,內核很容易計算出使用特定模塊的其他內核模塊。我們回到前面的兩個hello kernel代碼,hello.c中用了一個外部變數hello_data,這個變數來自hello_h.c中,為hello_h.c的全局靜態導出變數。所以hello模塊依賴hello_h模塊。我們正常操作:先插入模塊hello_h然後插入hello模塊,先移除hello模塊,在移除hello_h:



很容易想到上面的操作順序是不能改變的。



上面的依賴關係可以畫出如下圖:





       當然,有數據結構必然有操作這些數據結構的函數,數據結構放這裡了,對於他的操作就不看了。無非是從hello找到hello_h。



模塊的二進位結構:

我們使用readelf –S hello.ko > readelf.txt命令來看看hello.ko模塊文件的二進位結構,輸出如下:





[火星人 via ] Linux模塊編程機制之hello kernel已經有179次圍觀

http://www.coctec.com/docs/service/show-post-883.html