zoukankan      html  css  js  c++  java
  • Linux驱动之内核加载模块过程分析

    Linux内核支持动态的加载模块运行:比如insmod first_drv.ko,这样就可以将模块加载到内核所在空间供应用程序调用。现在简单描述下insmod first_drv.ko的过程

    1、insmod也是一个用户进程

    2、insmod进程从命令行中读取要链接的模块名字:first_drv.ko

    3、insmod进程确定模块对象代码所在的文件在系统目录树中的位置,即first_drv.ko文件所在的位置

    4、insmod进程从文件系统所在的存储区读入存有模块目标代码的文件

    5、insmod调用init_module系统调用。函数将进入内核到达内核函数 sys_init_module,然后sys_init_module函数将模块二进制文件复制到内核,然后由内核完成剩余的任务。

    下面内容转载自https://blog.csdn.net/chrovery/article/details/51088425

    一、前言
    对于现在编译的一些module要insmod在系统上时,可能会报各种各样的错误。这些错误仔细研读内核源码,都能找出原因。2.6 内核以前的insmod部分主要依赖于modutils源码包,在用户层基本将工作完成,加载过程参考前一篇文章。2.6 内核以后的做法是将大部分的原来用户级操作纳入内核中来处理,无论是逻辑上还是代码量上都比原来减少了许多,通过busybox中的insmod命令与内核接入。把这套源代码弄明白,就不会出现加载模块时出现的各种各样的错误,可以写一些patch代码,修正这些内核要检查的项,比如vermagic和crc等等。

    二、相关结构

    1.模块依赖关系
    struct module_use {
    struct list_head source_list;
    struct list_head target_list;
    struct module *source, *target;
    };

    2.模块状态
    enum module_state {
    MODULE_STATE_LIVE, /* Normal state. */
    MODULE_STATE_COMING, /* Full formed, running module_init. */
    MODULE_STATE_GOING, /* Going away. */
    MODULE_STATE_UNFORMED, /* Still setting it up. */
    };

    3.模块计数
    struct module_ref {
    unsigned long incs;
    unsigned long decs;
    } __attribute((aligned(2 * sizeof(unsigned long))));

    4.模块结构
    struct module
    {
    enum module_state state;
    /* Member of list of modules */
    struct list_head list;
    /* Unique handle for this module */
    char name[MODULE_NAME_LEN];
    /* Sysfs stuff. */
    struct module_kobject mkobj;
    struct module_attribute *modinfo_attrs;
    const char *version;
    const char *srcversion;
    struct kobject *holders_dir;
    /* Exported symbols */
    const struct kernel_symbol *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

    #ifdef CONFIG_MODULE_SIG
    /* Signature was verified. */
    bool sig_ok;
    #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 */
    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;

    /* Size of RO sections of the module (text+rodata) */
    unsigned int init_ro_size, core_ro_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
    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

    char *args;

    #ifdef CONFIG_SMP
    /* Per-cpu data. */
    void __percpu *percpu;
    unsigned int percpu_size;
    #endif

    #ifdef CONFIG_TRACEPOINTS
    unsigned int num_tracepoints;
    struct tracepoint * const *tracepoints_ptrs;
    #endif

    #ifdef HAVE_JUMP_LABEL
    struct jump_entry *jump_entries;
    unsigned int num_jump_entries;
    #endif

    #ifdef CONFIG_TRACING
    unsigned int num_trace_bprintk_fmt;
    const char **trace_bprintk_fmt_start;
    #endif

    #ifdef CONFIG_EVENT_TRACING
    struct ftrace_event_call **trace_events;
    unsigned int num_trace_events;
    #endif

    #ifdef CONFIG_FTRACE_MCOUNT_RECORD
    unsigned int num_ftrace_callsites;
    unsigned long *ftrace_callsites;
    #endif

    #ifdef CONFIG_MODULE_UNLOAD
    /* What modules depend on me? */
    struct list_head source_list;
    /* What modules do I depend on? */
    struct list_head target_list;

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

    /* Destruction function. */
    void (*exit)(void);
    struct module_ref __percpu *refptr;
    #endif

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

    5.ELF文件信息结构
    struct load_info {
    Elf_Ehdr *hdr;//指向elf头
    unsigned long len;
    Elf_Shdr *sechdrs;//指向节区头
    char *secstrings;//指向字符串节区中节区名称所在的地址
    char *strtab;//指向字符串节区的内容所在地址
    unsigned long symoffs;
    unsigned long stroffs;
    struct _ddebug *debug;
    unsigned int num_debug;
    bool sig_ok;//模块签名检查
    struct {
    unsigned int sym;//模块的符号表节区索引号
    unsigned int str;//模块的字符串节区索引号
    unsigned int mod;//模块的".gnu.linkonce.this_module"节区索引号
    unsigned int vers;//模块的"__versions"节区索引号
    unsigned int info;//模块的.modinfo节区索引号
    unsigned int pcpu;
    } index;
    };

    三、源代码解析
    //busybox中insmod.c文件中
    int insmod_main(int argc UNUSED_PARAM, char **argv)
    {
    char *filename;
    int rc;

    IF_FEATURE_2_4_MODULES(
    getopt32(argv, INSMOD_OPTS INSMOD_ARGS);
    argv += optind - 1;
    );

    //取得要加载模块的路径名
    filename = *++argv;
    if (!filename)
    bb_show_usage();

    rc = bb_init_module(filename, parse_cmdline_module_options(argv,0));
    if (rc)
    bb_error_msg("can't insert '%s': %s", filename, moderror(rc));

    return rc;
    }

    char* FAST_FUNC parse_cmdline_module_options(char **argv, int quote_spaces)
    {
    char *options;
    int optlen;

    options = xzalloc(1);
    optlen = 0;

    //遍历模块名后边的模块参数
    while (*++argv) {
    const char *fmt;
    const char *var;
    const char *val;

    var = *argv;
    //为options分配空间
    options = xrealloc(options, optlen + 2 + strlen(var) + 2);
    fmt = "%.*s%s ";
    //若'='存在于var中,则返回'='在var中出现的第一个位置的指针,否则返回字符串var末尾的空字符。
    val = strchrnul(var, '=');
    if (quote_spaces) {//为0
    if (*val) { /* has var=val format. skip '=' */
    val++;
    if (strchr(val, ' '))
    fmt = "%.*s"%s" ";
    }
    }
    //模块参数按格式存入options,"%.*s%s "中“*”对应(int)(val - var),第一个s对应var,表示在var字符串中去除(int)(val - var)个字符显示,即=前边的字符被去除。实际上只是将val字符串(即=后边的字符串)存入options指针指向的地址。
    optlen += sprintf(options + optlen, fmt, (int)(val - var), var, val);
    }

    return options;
    }

    int FAST_FUNC bb_init_module(const char *filename, const char *options)
    {
    size_t image_size;
    char *image;
    int rc;
    bool mmaped;

    if (!options)
    options = "";

    //若是2.6以前的版本调用bb_init_module_24(),这种处理就是以前modutils对模块的加载处理。这里要研究的是2.6以后的模块加载处理
    #if ENABLE_FEATURE_2_4_MODULES
    if (get_linux_version_code() < KERNEL_VERSION(2,6,0))
    return bb_init_module_24(filename, options);
    #endif

    image_size = INT_MAX - 4095;//初始化为文件的最大值
    mmaped = 0;

    //把模块文件映射进内存,并返回映射空间的大小image_size
    image = try_to_mmap_module(filename, &image_size);
    if (image) {
    mmaped = 1;
    } else {
    errno = ENOMEM; /* may be changed by e.g. open errors below */
    image = xmalloc_open_zipped_read_close(filename, &image_size);
    if (!image)
    return -errno;
    }
    errno = 0;

    //系统调用,对应内核函数是sys_init_module()函数,进入到内核空间
    init_module(image, image_size, options);
    rc = errno;
    if (mmaped)
    munmap(image, image_size);
    else
    free(image);
    return rc;
    }

    void* FAST_FUNC try_to_mmap_module(const char *filename, size_t *image_size_p)
    {
    void *image;
    struct stat st;
    int fd;

    //打开模块.ko文件,获得文件描述符
    fd = xopen(filename, O_RDONLY);
    //由文件描述符取得文件状态
    fstat(fd, &st);
    image = NULL;

    //文件的size是否超过设定的文件最大值
    if (st.st_size <= *image_size_p) {
    size_t image_size = st.st_size;//文件size
    //以只读的方式将.ko文件映射到内存中,返回在内存中的起始地址
    image = mmap(NULL, image_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (image == MAP_FAILED) {
    image = NULL;
    }
    //检查一下.ko文件的开头是否为ELF标准格式
    else if (*(uint32_t*)image != SWAP_BE32(0x7f454C46)) {
    munmap(image, image_size);
    image = NULL;
    } else {
    *image_size_p = image_size;//返回文件size
    }
    }
    close(fd);
    return image;
    }

    //sys_init_module(void __user *umod,unsigned long len,const char __user *uargs)
    //umod : 是一个指针,指向用户地址空间中的区域,模块的二进制代码位于其中。
    //len : 该区域的长度。
    //uargs : 是一个指针,指定了模块的参数。
    SYSCALL_DEFINE3(init_module, void __user *, umod,unsigned long, len, const char __user *, uargs)
    {
    int err;
    struct load_info info = { };

    //是否有权限加载模块:capable(CAP_SYS_MODULE)
    err = may_init_module();
    if (err)
    return err;

    pr_debug("init_module: umod=%p, len=%lu, uargs=%p ",umod, len, uargs);

    //将用户空间的.ko文件的内存映像(elf格式)拷贝到内核空间,并填充info结构中
    err = copy_module_from_user(umod, len, &info);
    if (err)
    return err;

    //模块的主要操作
    return load_module(&info, uargs, 0);
    }

    static int copy_module_from_user(const void __user *umod, unsigned long len,struct load_info *info)
    {
    int err;

    //模块文件映射到用户空间的size(即.ko文件本身的size)
    info->len = len;
    if (info->len < sizeof(*(info->hdr)))
    return -ENOEXEC;

    err = security_kernel_module_from_file(NULL);
    if (err)
    return err;

    //在内核空间为模块分配内存,并将该内存的起始地址付给成员hdr,即该成员现在指向的是整个模块内存
    info->hdr = vmalloc(info->len);
    if (!info->hdr)
    return -ENOMEM;

    //将用户空间的.ko文件的内存映像(elf格式)拷贝到内核空间info->hdr处
    if (copy_from_user(info->hdr, umod, info->len) != 0) {
    vfree(info->hdr);
    return -EFAULT;
    }
    return 0;
    }

    static int load_module(struct load_info *info, const char __user *uargs,int flags)
    {
    struct module *mod;
    long err;

    err = module_sig_check(info);//检查证书
    if (err)
    goto free_copy;
    err = elf_header_check(info);//检查elf头
    if (err)
    goto free_copy;

    //布置模块,并分配相关的内存,把相关节区复制到最终镜像中
    mod = layout_and_allocate(info, flags);
    if (IS_ERR(mod)) {
    err = PTR_ERR(mod);
    goto free_copy;
    }

    //因为前边已将模块镜像复制到了内核空间的内存中,module对象也指向对应的位置,然后将此module对象添加到modules链表中
    err = add_unformed_module(mod);
    if (err)
    goto free_module;

    #ifdef CONFIG_MODULE_SIG
    mod->sig_ok = info->sig_ok;
    if (!mod->sig_ok) {
    printk_once(KERN_NOTICE
    "%s: module verification failed: signature and/or"
    " required key missing - tainting kernel ",mod->name);
    add_taint_module(mod, TAINT_FORCED_MODULE, LOCKDEP_STILL_OK);
    }
    #endif

    //为节区pcpu分配空间,用于多处理器,此处不考虑
    err = percpu_modalloc(mod, info);
    if (err)
    goto unlink_mod;

    //模块计数加1,并初始化模块链表
    err = module_unload_init(mod);
    if (err)
    goto unlink_mod;

    //找到其余节区地址,初始化module对象相关指针
    find_module_sections(mod, info);

    //检查license和version
    err = check_module_license_and_versions(mod);
    if (err)
    goto free_unload;

    //根据.modinfo段设置模块信息
    setup_modinfo(mod, info);

    //根据前边设置的模块在内核的起始地址,节区的起始地址已经更新,但是节区中符号的地址还未进行更新,这里根据节区的内存地址更新符号地址
    err = simplify_symbols(mod, info);
    if (err < 0)
    goto free_modinfo;

    //对模块中的重定位节区做重定位操作
    err = apply_relocations(mod, info);
    if (err < 0)
    goto free_modinfo;

    err = post_relocation(mod, info);
    if (err < 0)
    goto free_modinfo;

    //Flush the instruction cache,no care
    flush_module_icache(mod);

    //把可选参数从用户空间复制到内核空间
    mod->args = strndup_user(uargs, ~0UL >> 1);
    if (IS_ERR(mod->args)) {
    err = PTR_ERR(mod->args);
    goto free_arch_cleanup;
    }

    //处理用于debug节区
    dynamic_debug_setup(info->debug, info->num_debug);

    //确认是否有重定义符号,并且设置模块状态为正在运行
    err = complete_formation(mod, info);
    if (err)
    goto ddebug_cleanup;

    /* Module is ready to execute: parsing args may do that. */
    //分析参数是否有效
    err = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,-32768, 32767, unknown_module_param_cb);
    if (err < 0)
    goto bug_cleanup;

    //sysfs文件系统相关,在sysfs中创建模块相应的项
    err = mod_sysfs_setup(mod, info, mod->kp, mod->num_kp);
    if (err < 0)
    goto bug_cleanup;

    //释放临时模块镜像和其他临时辅助内存区
    free_copy(info);

    /* */
    trace_module_load(mod);

    //调用do_init_module开始运行模块
    return do_init_module(mod);

    bug_cleanup:
    /* module_bug_cleanup needs module_mutex protection */
    mutex_lock(&module_mutex);
    module_bug_cleanup(mod);
    mutex_unlock(&module_mutex);
    ddebug_cleanup:
    dynamic_debug_remove(info->debug);
    synchronize_sched();
    kfree(mod->args);
    free_arch_cleanup:
    module_arch_cleanup(mod);
    free_modinfo:
    free_modinfo(mod);
    free_unload:
    module_unload_free(mod);
    unlink_mod:
    mutex_lock(&module_mutex);
    /* Unlink carefully: kallsyms could be walking list. */
    list_del_rcu(&mod->list);
    wake_up_all(&module_wq);
    mutex_unlock(&module_mutex);
    free_module:
    module_deallocate(mod, info);
    free_copy:
    free_copy(info);
    return err;
    }

    static int module_sig_check(struct load_info *info)
    {
    //#define MODULE_SIG_STRING "~Module signature appended~ "
    int err = -ENOKEY;
    const unsigned long markerlen = sizeof(MODULE_SIG_STRING) - 1;
    const void *mod = info->hdr;
    //elf文件长度是否大于签名字符串长度,并且比较该elf格式的模块文件是否在文件末尾有signature string。若有的话则将该文件长度减去该字符串长度,并确认该签名是module类型。
    if (info->len > markerlen && memcmp(mod + info->len - markerlen, MODULE_SIG_STRING, markerlen) == 0)
    {
    info->len -= markerlen;//模块长度减去签名字符串的长度
    err = mod_verify_sig(mod, &info->len);//验证模块签名
    }
    if (!err) {
    info->sig_ok = true;
    return 0;
    }

    /* Not having a signature is only an error if we're strict. */
    if (err < 0 && fips_enabled)
    panic("Module verification failed with error %d in FIPS mode ",err);
    if (err == -ENOKEY && !sig_enforce)
    err = 0;
    return err;
    }

    struct module_signature {
    u8 algo; /* Public-key crypto algorithm [enum pkey_algo] */
    u8 hash; /* Digest algorithm [enum pkey_hash_algo] */
    u8 id_type; /* Key identifier type [enum pkey_id_type] */
    u8 signer_len; /* Length of signer's name */
    u8 key_id_len; /* Length of key identifier */
    u8 __pad[3];
    __be32 sig_len; /* Length of signature data */
    };

    int mod_verify_sig(const void *mod, unsigned long *_modlen)
    {
    //mod是模块文件起始地址,_modlen是模块文件的大小(去掉了最后的签名字符串长度)
    struct public_key_signature *pks;
    struct module_signature ms;
    struct key *key;
    const void *sig;
    size_t modlen = *_modlen, sig_len;
    int ret;

    pr_devel("==>%s(,%zu) ", __func__, modlen);

    //检查长度
    if (modlen <= sizeof(ms))
    return -EBADMSG;

    //如果.ko文件最后有签名字符串的话,则签名字符串之上有一个module_signature结构,读出这个结构的内容
    memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
    modlen -= sizeof(ms);//相应修改模块文件长度

    sig_len = be32_to_cpu(ms.sig_len);//获得签名数据长度
    if (sig_len >= modlen)
    return -EBADMSG;
    modlen -= sig_len;//模块文件长度再减去签名数据长度

    if ((size_t)ms.signer_len + ms.key_id_len >= modlen)
    return -EBADMSG;
    //模块文件长度再减去签名人名字长度和key长度
    modlen -= (size_t)ms.signer_len + ms.key_id_len;

    *_modlen = modlen;//去掉签名验证的信息后返回模块真实size
    //文件指针移到签名信息处
    sig = mod + modlen;

    /* For the moment, only support RSA and X.509 identifiers */
    if (ms.algo != PKEY_ALGO_RSA || ms.id_type != PKEY_ID_X509)
    return -ENOPKG;

    if (ms.hash >= PKEY_HASH__LAST || !pkey_hash_algo[ms.hash])
    return -ENOPKG;

    //将signer name和key identifier从文件sig处读出来,返回key
    key = request_asymmetric_key(sig, ms.signer_len,sig + ms.signer_len, ms.key_id_len);
    if (IS_ERR(key))
    return PTR_ERR(key);

    //Digest the module contents.
    pks = mod_make_digest(ms.hash, mod, modlen);
    if (IS_ERR(pks)) {
    ret = PTR_ERR(pks);
    goto error_put_key;
    }

    //Extract an MPI array from the signature data. This represents the actual signature. Each raw MPI is prefaced by a BE 2-byte value indicating the size of the MPI in bytes.RSA signatures only have one MPI, so currently we only read one.
    ret = mod_extract_mpi_array(pks, sig + ms.signer_len + ms.key_id_len,sig_len);
    if (ret < 0)
    goto error_free_pks;

    //Initiate the use of an asymmetric key to verify a signature
    //key: The asymmetric key to verify against
    //sig: The signature to check
    ret = verify_signature(key, pks);
    pr_devel("verify_signature() = %d ", ret);

    error_free_pks:
    mpi_free(pks->rsa.s);
    kfree(pks);
    error_put_key:
    key_put(key);
    pr_devel("<==%s() = %d ", __func__, ret);
    return ret;
    }

    static int elf_header_check(struct load_info *info)
    {
    if (info->len < sizeof(*(info->hdr)))
    return -ENOEXEC;

    //检查elf文件头,以及文件类型(.ko文件必是可重定位文件),架构以及节区大小是否设置正确
    if (memcmp(info->hdr->e_ident, ELFMAG, SELFMAG) != 0|| info->hdr->e_type != ET_REL
    || !elf_check_arch(info->hdr)|| info->hdr->e_shentsize != sizeof(Elf_Shdr))
    return -ENOEXEC;

    //检查节区的偏移地址
    if (info->hdr->e_shoff >= info->len
    || (info->hdr->e_shnum * sizeof(Elf_Shdr) >info->len - info->hdr->e_shoff))
    return -ENOEXEC;

    return 0;
    }

    static struct module *layout_and_allocate(struct load_info *info, int flags)
    {
    struct module *mod;
    int err;

    //设置info结构,并检查module_layout的crc值,并返回一个存储在.gnu.linkonce.this_module节区中的struct module结构,该module的起始地址正是.gnu.linkonce.this_module节区起始地址
    mod = setup_load_info(info, flags);
    if (IS_ERR(mod))
    return mod;

    //检查.modinfo节区中的信息,包括version magic
    err = check_modinfo(mod, info, flags);
    if (err)
    return ERR_PTR(err);

    //什么也不做,返回0
    err = module_frob_arch_sections(info->hdr, info->sechdrs,info->secstrings, mod);
    if (err < 0)
    return ERR_PTR(err);

    //移除SHF_ALLOC标志
    info->sechdrs[info->index.pcpu].sh_flags &= ~(unsigned long)SHF_ALLOC;

    //只有节区头部设置了SHF_ALLOC标志,才最终存于内存中,且内存分为init部分和core部分
    layout_sections(mod, info);

    //为symbol section以及跟它相关的string table布置位置,更新相关size
    layout_symtab(mod, info);

    //为mod指向的临时镜像中标记了SHF_ALLOC节区分配内存,并从临时镜像复制到最终的位置,并且修改节区的起始地址
    err = move_module(mod, info);
    if (err)
    return ERR_PTR(err);

    //因为前边已将模块靠诶到最终的内存位置,所以各个节区的起始地址已经改变,之前mod指向的地址已经无效,所以重新将新的“.gnu.linkonce.this_module”节区的起始地址(指向一个module对象)赋给mod对象
    mod = (void *)info->sechdrs[info->index.mod].sh_addr;

    //扫描检查内存泄露???
    kmemleak_load_module(mod, info);
    return mod;
    }

    static struct module *setup_load_info(struct load_info *info)
    {
    unsigned int i;
    int err;
    struct module *mod;

    //找到节区头部表开始地址
    info->sechdrs = (void *)info->hdr + info->hdr->e_shoff;
    //找到节区名称的字符串节区的起始地址
    info->secstrings = (void *)info->hdr + info->sechdrs[info->hdr->e_shstrndx].sh_offset;

    //根据elf格式的.ko文件拷贝到内核空间内存中的虚拟地址为基地址(即(void *)info->hdr),重写节区在内存映像中节区第一个字节应处的位置
    err = rewrite_section_headers(info);
    if (err)
    return ERR_PTR(err);

    /*遍历所有节区找到符号表(类型为 SHT_SYMTAB 的唯一段)和相关的符号字符串表的位置,
    前者的 sh_link 即为后者的段索引*/
    for (i = 1; i < info->hdr->e_shnum; i++) {
    if (info->sechdrs[i].sh_type == SHT_SYMTAB){
    //找到符号表节区.symtab索引
    info->index.sym = i;
    //找到符号表字符串节区索引,即.strtab节区
    info->index.str = info->sechdrs[i].sh_link;
    //字符串节区内容开始地址
    info->strtab = (char *)info->hdr + info->sechdrs[info->index.str].sh_offset;
    break;
    }
    }

    //在.gnu.linkonce.this_module节区中,有一个struct module的实例,此结构大部分成员是NULL,编译器只是初始化了name,init,exit和arch等几个成员。
    info->index.mod = find_sec(info, ".gnu.linkonce.this_module");
    if (!info->index.mod) {
    printk(KERN_WARNING "No module found in object ");
    return ERR_PTR(-ENOEXEC);
    }

    //mod指向 struct module的实例,该实例中提供了模块的名称和指向初始化以及清理函数的指针,但其他成员仍然初始化为 NULL 或 0. 将该模块的地址暂时设为临时映像中节区给出的地址,后边会对module的地址再做出修正.
    mod = (void *)info->sechdrs[info->index.mod].sh_addr;

    if (info->index.sym == 0) {
    printk(KERN_WARNING "%s: module has no symbols (stripped?) ", mod->name);
    return ERR_PTR(-ENOEXEC);
    }

    //查找".data..percpu"节区索引号
    info->index.pcpu = find_pcpusec(info);

    //检查模块中“module_layout”符号的crc值,若失败则打印著名模块加载错误“Exec format error ”
    if (!check_modstruct_version(info->sechdrs, info->index.vers, mod))
    return ERR_PTR(-ENOEXEC);

    return mod;
    }

    static int rewrite_section_headers(struct load_info *info, int flags)
    {
    unsigned int i;
    info->sechdrs[0].sh_addr = 0;//节区0地址是0,表示此节区不出现在内存映像中
    for (i = 1; i < info->hdr->e_shnum; i++) {
    Elf_Shdr *shdr = &info->sechdrs[i];
    //判断节区size
    if (shdr->sh_type != SHT_NOBITS && info->len < shdr->sh_offset + shdr->sh_size) {
    printk(KERN_ERR "Module len %lu truncated ",info->len);
    return -ENOEXEC;
    }

    //在elf文件拷贝到内核空间的基地址基础上重新设置节区在内核空间的起始地址,shdr->sh_addr表示节区在内存映像中节区第一个字节应处的位置
    shdr->sh_addr = (size_t)info->hdr + shdr->sh_offset;
    #ifndef CONFIG_MODULE_UNLOAD
    /* Don't load .exit sections */
    if (strstarts(info->secstrings+shdr->sh_name, ".exit"))
    shdr->sh_flags &= ~(unsigned long)SHF_ALLOC;
    #endif
    }

    //找到__versions和.modinfo节区,并记录节区索引
    if (flags & MODULE_INIT_IGNORE_MODVERSIONS)
    info->index.vers = 0;
    else
    info->index.vers = find_sec(info, "__versions");
    info->index.info = find_sec(info, ".modinfo");
    //这两个节区清楚SHF_ALLOC标志,表示此节区在进程执行过程中不占用内存
    info->sechdrs[info->index.info].sh_flags &= ~(unsigned long)SHF_ALLOC;
    info->sechdrs[info->index.vers].sh_flags &= ~(unsigned long)SHF_ALLOC;
    return 0;
    }

    static inline int check_modstruct_version(Elf_Shdr *sechdrs,unsigned int versindex,struct module *mod)
    {
    const unsigned long *crc;

    //查找系统内核中“module_layout”符号,并返回该符号的crc值
    if (!find_symbol(VMLINUX_SYMBOL_STR(module_layout), NULL,&crc, true, false))
    BUG();

    //系统中“module_layout”符号的crc值与模块中的“module_layout”符号crc值是否一致
    return check_version(sechdrs, versindex,VMLINUX_SYMBOL_STR(module_layout), mod, crc,NULL);
    }

    //查找符号使用的结构体
    struct find_symbol_arg {
    //输入
    const char *name;//要查找的符号名
    bool gplok;//如果为真,则表示内核模块支持GPL许可
    bool warn;//警告信息标志

    //输出
    struct module *owner;//该符号所属的模块
    const unsigned long *crc;//该符号的crc值
    const struct kernel_symbol *sym;//符号结构
    };

    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;

    //查找符号,填写符号参数结构的输出部分
    if (each_symbol_section(find_symbol_in_section, &fsa)) {
    if (owner)//NULL,下边不需赋值
    *owner = fsa.owner;

    if (crc)//得到该符号的crc值
    *crc = fsa.crc;

    //返回以name命名的内核符号结构
    return fsa.sym;
    }

    //没有找到内核符号,返回NULL
    pr_debug("Failed to find symbol %s ", name);
    return NULL;
    }

    bool each_symbol_section(bool (*fn)(const struct symsearch *arr,struct module *owner,void *data),void *data)
    {
    struct module *mod;
    //第一部分在内核导出的符号表中查找对应的符号,找到就返回对应的符号信息,否则,再进行第二部分查找.
    /*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;
    };
    */
    static const struct symsearch arr[] = {
    { __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 },
    #endif
    };

    if (each_symbol_in_section(arr, ARRAY_SIZE(arr), NULL, fn, data))
    return true;

    //在内核导出的符号表中没有找到对应的符号,则在系统已加载的模块中查找
    list_for_each_entry_rcu(mod, &modules, list) {
    struct symsearch arr[] = {
    { mod->syms, mod->syms + mod->num_syms,
    mod->crcs,NOT_GPL_ONLY, false },
    { mod->gpl_syms, mod->gpl_syms + mod->num_gpl_syms,
    mod->gpl_crcs,GPL_ONLY, false },
    { mod->gpl_future_syms,mod->gpl_future_syms + mod->num_gpl_future_syms,
    mod->gpl_future_crcs,WILL_BE_GPL_ONLY, false },
    #ifdef CONFIG_UNUSED_SYMBOLS
    { mod->unused_syms,mod->unused_syms + mod->num_unused_syms,
    mod->unused_crcs, NOT_GPL_ONLY, true },
    { mod->unused_gpl_syms,mod->unused_gpl_syms + mod->num_unused_gpl_syms,
    mod->unused_gpl_crcs,GPL_ONLY, true },
    #endif
    };

    //若模块状态为MODULE_STATE_UNFORMED,则此模块的符号不可用
    if (mod->state == MODULE_STATE_UNFORMED)
    continue;

    if (each_symbol_in_section(arr, ARRAY_SIZE(arr), mod, fn, data))
    return true;
    }
    return false;
    }

    static bool each_symbol_in_section(const struct symsearch *arr,unsigned int arrsize,struct module *owner,bool (*fn)(const struct symsearch *syms,struct module *owner,void *data),void *data)
    {
    unsigned int j;

    //调用find_symbol_in_section()对每个数组指定的符号地址范围进行查找
    for (j = 0; j < arrsize; j++) {
    if (fn(&arr[j], owner, data))//调用find_symbol_in_section()
    return true;
    }
    return false;
    }

    static bool find_symbol_in_section(const struct symsearch *syms,struct module *owner,void *data)
    {
    struct find_symbol_arg *fsa = data;
    struct kernel_symbol *sym;

    //在范围内查找符号名为fsa->name的内核符号
    sym = bsearch(fsa->name, syms->start, syms->stop - syms->start,sizeof(struct kernel_symbol), cmp_name);

    //若找到内核符号,则对其进行check是否有效
    if (sym != NULL && check_symbol(syms, owner, sym - syms->start, data))
    return true;

    return false;
    }

    void *bsearch(const void *key, const void *base, size_t num, size_t size,int (*cmp)(const void *key, const void *elt))
    {
    size_t start = 0, end = num;
    int result;

    while (start < end) {//折半查找
    size_t mid = start + (end - start) / 2;

    //调用cmp_name()函数比较符号名是否一致
    result = cmp(key, base + mid * size);
    if (result < 0)
    end = mid;
    else if (result > 0)
    start = mid + 1;
    else
    return (void *)base + mid * size;
    }
    return NULL;
    }

    static int cmp_name(const void *va, const void *vb)
    {
    const char *a;
    const struct kernel_symbol *b;
    a = va; b = vb;
    return strcmp(a, b->name);
    }

    static bool check_symbol(const struct symsearch *syms,struct module *owner,unsigned int symnum, void *data)
    {
    struct find_symbol_arg *fsa = data;

    if (!fsa->gplok) {//若未设置gplok,则必须为GPL许可
    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 ", fsa->name);
    }
    }

    #ifdef CONFIG_UNUSED_SYMBOLS
    if (syms->unused && fsa->warn) {
    printk(KERN_WARNING "Symbol %s is marked as UNUSED, "
    "however this module is using it. ", fsa->name);
    printk(KERN_WARNING"This symbol will go away in the future. ");
    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. ");
    }
    #endif

    fsa->owner = owner;//符号所属模块
    //#define symversion(base, idx) ((base != NULL) ? ((base) + (idx)) : NULL)
    fsa->crc = symversion(syms->crcs, symnum);//符号的crc值
    fsa->sym = &syms->start[symnum];//返回的符号结构
    return true;
    }

    static int check_version(Elf_Shdr *sechdrs,unsigned int versindex,const char *symname,struct module *mod, const unsigned long *crc,const struct module *crc_owner)
    {
    unsigned int i, num_versions;
    struct modversion_info *versions;

    //若系统中的该符号crc值为0,则直接返回1完事
    if (!crc)
    return 1;

    //若该模块的elf格式文件中没有__versions节区,则尝试强制加载模块
    //但是try_to_force_load()函数的实现依赖于CONFIG_MODULE_FORCE_LOAD宏是否定义。而该宏默认是没有定义的,所以这里会返回失败,看来内核并不推荐强制加载模块。
    if (versindex == 0)
    return try_to_force_load(mod, symname) == 0;

    //找到模块“__versions”节区在内存映像中的起始地址。相当于节区的contents内容
    versions = (void *) sechdrs[versindex].sh_addr;
    num_versions = sechdrs[versindex].sh_size/ sizeof(struct modversion_info);
    for (i = 0; i < num_versions; i++) {
    if (strcmp(versions[i].name, symname) != 0)//在此节区中找到要比较的符号
    continue;

    //比较该模块中的符号crc值和现在系统上的内核符号的crc值是否一致
    if (versions[i].crc == maybe_relocated(*crc, crc_owner))
    return 1;
    pr_debug("Found checksum %lX vs module %lX ",maybe_relocated(*crc, crc_owner), versions[i].crc);
    goto bad_version;
    }

    //若在“__versions”节区没有找到要比较的符号,则会给出加载模块时常见错误:“no symbol version for”
    printk(KERN_WARNING "%s: no symbol version for %s ",mod->name, symname);
    return 0;
    bad_version:
    //如果比较符号的额crc值不一致,则会给出加载模块时常见错误“disagrees about version of symbol”
    printk("%s: disagrees about version of symbol %s ",mod->name, symname);
    return 0;
    }

    static int try_to_force_load(struct module *mod, const char *reason)
    {
    #ifdef CONFIG_MODULE_FORCE_LOAD
    //若选项CONFIG_MODULE_FORCE_LOAD打开,则检查tainted_mask是否设置了TAINT_FORCED_MODULE标志,若没有给出警告信息
    if (!test_taint(TAINT_FORCED_MODULE))
    printk(KERN_WARNING "%s: %s: kernel tainted. ",mod->name, reason);

    //设置mod->taints和tainted_mask的TAINT_FORCED_MODULE标志,表示强制加载该模块,并返回正确值0
    add_taint_module(mod, TAINT_FORCED_MODULE, LOCKDEP_NOW_UNRELIABLE);
    return 0;
    #else
    //若选项CONFIG_MODULE_FORCE_LOAD未打开,则直接返回错误
    return -ENOEXEC;
    #endif
    }

    static int check_modinfo(struct module *mod, struct load_info *info, int flags)
    {
    //从模块.modinfo节区中获得version magic
    const char *modmagic = get_modinfo(info, "vermagic");
    int err;
    if (flags & MODULE_INIT_IGNORE_VERMAGIC)
    modmagic = NULL;

    //若version magic为0,则尝试强制加载
    if (!modmagic) {
    err = try_to_force_load(mod, "bad vermagic");
    if (err)
    return err;
    }
    //与内核的vermagic是否一致,若不一致,给出著名错误:“version magic ... should be ...”,返回错误码
    else if (!same_magic(modmagic, vermagic, info->index.vers)) {
    printk(KERN_ERR "%s: version magic '%s' should be '%s' ",mod->name, modmagic, vermagic);
    return -ENOEXEC;
    }

    //返回.modinfo节区中intree=“...”的内容,若不存在设置标志TAINT_OOT_MODULE
    if (!get_modinfo(info, "intree"))
    add_taint_module(mod, TAINT_OOT_MODULE, LOCKDEP_STILL_OK);

    //返回.modinfo节区中staging=“...”的内容,存在设置标志TAINT_CRAP
    if (get_modinfo(info, "staging")) {
    add_taint_module(mod, TAINT_CRAP, LOCKDEP_STILL_OK);
    printk(KERN_WARNING "%s: module is from the staging directory,"" the quality is unknown, you have been warned. ",mod->name);
    }

    //取出.modinfo节区的license字段指定的license类型,并对此license检查
    set_license(mod, get_modinfo(info, "license"));
    return 0;
    }

    static char *get_modinfo(struct load_info *info, const char *tag)
    {
    char *p;
    unsigned int taglen = strlen(tag);
    //找到模块.modinfo节区
    Elf_Shdr *infosec = &info->sechdrs[info->index.info];
    unsigned long size = infosec->sh_size;

    //查找.modinfo节区中的内容,返回"*tag"字符串=后边的内容
    for (p = (char *)infosec->sh_addr; p; p = next_string(p, &size))
    {
    if (strncmp(p, tag, taglen) == 0 && p[taglen] == '=')
    return p + taglen + 1;
    }
    return NULL;
    }

    static inline int same_magic(const char *amagic, const char *bmagic, bool has_crcs)
    {
    //从字符串中依次取出以“ ”结尾的字符串段进行比较
    if (has_crcs) {
    amagic += strcspn(amagic, " ");
    bmagic += strcspn(bmagic, " ");
    }
    return strcmp(amagic, bmagic) == 0;
    }

    static void set_license(struct module *mod, const char *license)
    {
    if (!license)
    license = "unspecified";

    //比较模块的license类型是否是内核指定的GPL类型,若不是则设置TAINT_PROPRIETARY_MODULE标志
    if (!license_is_gpl_compatible(license)) {
    if (!test_taint(TAINT_PROPRIETARY_MODULE))
    printk(KERN_WARNING "%s: module license '%s' taints kernel. ", mod->name, license);
    add_taint_module(mod, TAINT_PROPRIETARY_MODULE,LOCKDEP_NOW_UNRELIABLE);
    }
    }

    static inline int license_is_gpl_compatible(const char *license)
    {
    return (strcmp(license, "GPL") == 0
    || strcmp(license, "GPL v2") == 0
    || strcmp(license, "GPL and additional rights") == 0
    || strcmp(license, "Dual BSD/GPL") == 0
    || strcmp(license, "Dual MIT/GPL") == 0
    || strcmp(license, "Dual MPL/GPL") == 0);
    }

    static void layout_sections(struct module *mod, struct load_info *info)
    {
    //注意:节的复制是按照原来ELF的顺序,将所有标志包含SHF_ALLOC的节都复制到相应的分配空间(module_core/module_init),例外的情况是SHT_NOBITS,也就是BSS段,文件中没有分配空间,因此不需要复制.

    //sections函数首先为标记了SHF_ALLOC的section定义了四种类型:code, read-only data,read-write data和small data. 带有SHF_ALLOC的section必定属于四类中的一类。函数遍历section header table中的所有项,将section name不是以".init"开始的section划归为COREsection. 以".init"开始的section划归为INIT section.
    static unsigned long const masks[][2] = {
    { SHF_EXECINSTR | SHF_ALLOC, ARCH_SHF_SMALL },//code
    { SHF_ALLOC, SHF_WRITE | ARCH_SHF_SMALL },//read only data
    { SHF_WRITE | SHF_ALLOC, ARCH_SHF_SMALL },//read-write data
    { ARCH_SHF_SMALL | SHF_ALLOC, 0 }//small data
    };
    unsigned int m, i;

    //遍历所有节区初始化sh_entsize成员
    //某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员sh_entsize给出每个表项的长度字节数。如果节区中并不包含固定长度表项的表格,此成员取值为 0。
    for (i = 0; i < info->hdr->e_shnum; i++)
    info->sechdrs[i].sh_entsize = ~0UL;

    //划分为两部分: CORE INIT
    //第1部分CORE:查找标志中含有SHF_ALLOC的section
    for (m = 0; m < ARRAY_SIZE(masks); ++m) {
    for (i = 0; i < info->hdr->e_shnum; ++i) {
    Elf_Shdr *s = &info->sechdrs[i];
    //找到节区名
    const char *sname = info->secstrings + s->sh_name;
    //含有SHF_ALLOC的section需要加载到最终的内存
    //含有SHF_ALLOC的section并且不以init开头的节区划分到CORE部分
    if ((s->sh_flags & masks[m][0]) != masks[m][0]
    || (s->sh_flags & masks[m][1])
    || s->sh_entsize != ~0UL || strstarts(sname, ".init"))
    continue;

    //把由于对齐产生的偏移保存到节区的sh_entsize字段,后边mod通过sh_entsize就可以找到该节区存储的位置。并把符合要求的节区大小加到mod->core_size
    s->sh_entsize = get_offset(mod, &mod->core_size, s, i);
    }

    //由于我们节的复制是按顺序的,而.text节是第一个节,因此mod->module_core实际上指向的就是.text段。而mod->core_text_size中也包含了.text节的大小.
    switch (m) {
    case 0: //可执行的段,代码段都一样
    mod->core_size = debug_align(mod->core_size);
    mod->core_text_size = mod->core_size;
    break;
    case 1: //只读段
    mod->core_size = debug_align(mod->core_size);
    mod->core_ro_size = mod->core_size;
    break;
    case 3: //所有段
    mod->core_size = debug_align(mod->core_size);
    break;
    }
    }

    //第2部分INIT
    for (m = 0; m < ARRAY_SIZE(masks); ++m) {
    for (i = 0; i < info->hdr->e_shnum; ++i) {
    Elf_Shdr *s = &info->sechdrs[i];
    const char *sname = info->secstrings + s->sh_name;
    //含有SHF_ALLOC的section需要加载到最终的内存
    //含有SHF_ALLOC的section并且以init开头的划分到INIT部分
    if ((s->sh_flags & masks[m][0]) != masks[m][0]
    || (s->sh_flags & masks[m][1])
    || s->sh_entsize != ~0UL
    || !strstarts(sname, ".init"))
    continue;

    //把由于对齐产生的偏移保存到节区的sh_entsize字段,并把符合要求的节区大小加到 mod->init_size
    s->sh_entsize = (get_offset(mod, &mod->init_size, s, i) | INIT_OFFSET_MASK);
    }

    switch (m) {
    case 0://代码段
    mod->init_size = debug_align(mod->init_size);
    mod->init_text_size = mod->init_size;
    break;
    case 1://只读段
    mod->init_size = debug_align(mod->init_size);
    mod->init_ro_size = mod->init_size;
    break;
    case 3://所有段
    mod->init_size = debug_align(mod->init_size);
    break;
    }
    }
    }

    static long get_offset(struct module *mod, unsigned int *size,Elf_Shdr *sechdr, unsigned int section)
    {
    //#define ALIGN(x, a) __ALIGN_KERNEL((x), (a))
    //#define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
    //#define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask))
    long ret;
    *size += arch_mod_section_prepend(mod, section);//空函数,返回0
    ret = ALIGN(*size, sechdr->sh_addralign ?: 1);//返回*size字节对齐后的值
    *size = ret + sechdr->sh_size;//把当前节区的size也加到*size上
    return ret;
    }

    static void layout_symtab(struct module *mod, struct load_info *info)
    {
    Elf_Shdr *symsect = info->sechdrs + info->index.sym;//找到符号表节区头部
    Elf_Shdr *strsect = info->sechdrs + info->index.str;//找到字符串表节区头部
    const Elf_Sym *src;
    unsigned int i, nsrc, ndst, strtab_size = 0;

    //符号节区设置SHF_ALLOC标志,表示在内存占空间
    symsect->sh_flags |= SHF_ALLOC;

    //设置符号表节区每个表项的长度字节数,并把符号表的size加到mod->init_size
    symsect->sh_entsize = get_offset(mod, &mod->init_size, symsect,info->index.sym) | INIT_OFFSET_MASK;

    pr_debug(" %s ", info->secstrings + symsect->sh_name);
    src = (void *)info->hdr + symsect->sh_offset;//符号表节区内容起始地址
    nsrc = symsect->sh_size / sizeof(*src);//符号表项个数

    //计算该模块包含的所有符号名长度所占的空间
    for (ndst = i = 0; i < nsrc; i++) {
    //is_core_symbol检查符号是否有效且属于SHF_ALLOC
    if (i == 0 || is_core_symbol(src+i, info->sechdrs, info->hdr->e_shnum)) {
    strtab_size += strlen(&info->strtab[src[i].st_name])+1;
    ndst++;
    }
    }

    //将mod->core_size按符号节区对齐约束对齐后得到的值返回给info->symoffs
    info->symoffs = ALIGN(mod->core_size, symsect->sh_addralign ?: 1);
    //将符号表节区内容(即ndst个符号表项)添加到core部分的后边,并重新设置core部分的size。
    info->stroffs = mod->core_size = info->symoffs + ndst * sizeof(Elf_Sym);
    //core部分再把符号名的字符串所占空间加上
    mod->core_size += strtab_size;
    //注意:这里只是为符号项和符号的名称长度分配了空间,还并没有将内容拷贝过来,在下边的add_kallsyms函数中进行拷贝!!!

    //把字符串节区加到模块的init部分
    strsect->sh_flags |= SHF_ALLOC;//字符串节区也设置上需要内存空间标志
    strsect->sh_entsize = get_offset(mod, &mod->init_size, strsect,info->index.str) | INIT_OFFSET_MASK;
    pr_debug(" %s ", info->secstrings + strsect->sh_name);
    }


    static bool is_core_symbol(const Elf_Sym *src, const Elf_Shdr *sechdrs,unsigned int shnum)
    {
    const Elf_Shdr *sec;
    //未定义的符号,或者符号索引号大于符号总个数,或者符号名为空则直接返回0
    if (src->st_shndx == SHN_UNDEF|| src->st_shndx >= shnum|| !src->st_name)
    return false;

    //找到符号所在的节区,若该节区不占内存,则也返回0
    sec = sechdrs + src->st_shndx;
    if (!(sec->sh_flags & SHF_ALLOC)
    #ifndef CONFIG_KALLSYMS_ALL
    || !(sec->sh_flags & SHF_EXECINSTR)
    #endif
    || (sec->sh_entsize & INIT_OFFSET_MASK))
    return false;
    return true;
    }

    static int move_module(struct module *mod, struct load_info *info)
    {
    int i;
    void *ptr;

    //在内核空间为模块的core部分分配空间,返回地址
    ptr = module_alloc_update_bounds(mod->core_size);

    //但是要扫描这个指针所分配的内存的内容。分配数据结构那么该结构本身不打印,但是会扫描结构内部的成员变量,是否引用其他指针。
    kmemleak_not_leak(ptr);
    if (!ptr)
    return -ENOMEM;
    memset(ptr, 0, mod->core_size);
    mod->module_core = ptr;//地址赋给module_core成员

    //在内核空间为init section分配内存,初始化后存储在module对象的module_init成员中
    if (mod->init_size) {
    //为模块的init部分分配空间,返回地址
    ptr = module_alloc_update_bounds(mod->init_size);

    kmemleak_ignore(ptr);
    if (!ptr) {
    module_free(mod, mod->module_core);
    return -ENOMEM;
    }
    memset(ptr, 0, mod->init_size);
    mod->module_init = ptr;//地址赋给module_init
    } else
    mod->module_init = NULL;
    /* Transfer each section which specifies SHF_ALLOC */

    pr_debug("final section addresses: ");
    //遍历所有节区,拷贝需要占用内存(标志为SHF_ALLOC的节区)的段到init section 或core section,并且调整各个节区的运行时地址
    for (i = 0; i < info->hdr->e_shnum; i++) {
    void *dest;
    Elf_Shdr *shdr = &info->sechdrs[i];
    if (!(shdr->sh_flags & SHF_ALLOC))
    continue;

    //如果节区首部的sh_entsize的最高位设置的话,表示该段属于init section,则从module_init开始的内存中获取当前段应该存储的地址,否则从module_core开始的内存中获取当前段应该存储的地址。
    if (shdr->sh_entsize & INIT_OFFSET_MASK)
    dest = mod->module_init + (shdr->sh_entsize & ~INIT_OFFSET_MASK);
    else
    dest = mod->module_core + shdr->sh_entsize;

    //将当前节区的内容从ELF文件头拷贝到指定的段(init section或core section)中
    if (shdr->sh_type != SHT_NOBITS)
    memcpy(dest, (void *)shdr->sh_addr, shdr->sh_size);

    //更改节区的运行时地址,sh_addr原先存储的地址是相对于ELF文件头的地址,现在是在内核空间分配新的内存空间后,节区新的运行地址(即节区内容的起始地址)。
    shdr->sh_addr = (unsigned long)dest;
    pr_debug(" 0x%lx %s ",(long)shdr->sh_addr, info->secstrings + shdr->sh_name);
    }
    return 0;
    }

    static void *module_alloc_update_bounds(unsigned long size)
    {
    //在内核空间中分配size大小的空间,返回起始地址
    void *ret = module_alloc(size);

    //更新模块边界值
    if (ret) {
    mutex_lock(&module_mutex);
    if ((unsigned long)ret < module_addr_min)
    module_addr_min = (unsigned long)ret;
    if ((unsigned long)ret + size > module_addr_max)
    module_addr_max = (unsigned long)ret + size;
    mutex_unlock(&module_mutex);
    }
    return ret;
    }

    static int add_unformed_module(struct module *mod)
    {
    int err;
    struct module *old;
    mod->state = MODULE_STATE_UNFORMED;
    again:
    mutex_lock(&module_mutex);
    //在内核中查找此模块是否已加入链表
    old = find_module_all(mod->name, strlen(mod->name), true);
    if (old != NULL) {
    if (old->state == MODULE_STATE_COMING|| old->state == MODULE_STATE_UNFORMED) {
    /* Wait in case it fails to load. */
    mutex_unlock(&module_mutex);
    err = wait_event_interruptible(module_wq,finished_loading(mod->name));
    if (err)
    goto out_unlocked;
    goto again;
    }
    err = -EEXIST;
    goto out;
    }
    //若还没有,则挂入全局模块链表modules
    list_add_rcu(&mod->list, &modules);
    err = 0;
    out:
    mutex_unlock(&module_mutex);
    out_unlocked:
    return err;
    }

    static int module_unload_init(struct module *mod)
    {
    //初始化多处理下用于引用计数的refptr成员
    mod->refptr = alloc_percpu(struct module_ref);
    if (!mod->refptr)
    return -ENOMEM;

    //初始化module对象的链表
    INIT_LIST_HEAD(&mod->source_list);
    INIT_LIST_HEAD(&mod->target_list);
    //模块计数加1
    __this_cpu_write(mod->refptr->incs, 1);
    /* Backwards compatibility macros put refcount during init. */
    mod->waiter = current;

    return 0;
    }

    static void find_module_sections(struct module *mod, struct load_info *info)
    {
    mod->kp = section_objs(info, "__param",sizeof(*mod->kp), &mod->num_kp);
    mod->syms = section_objs(info, "__ksymtab",sizeof(*mod->syms), &mod->num_syms);
    mod->crcs = section_addr(info, "__kcrctab");
    mod->gpl_syms = section_objs(info, "__ksymtab_gpl",sizeof(*mod->gpl_syms),&mod->num_gpl_syms);
    mod->gpl_crcs = section_addr(info, "__kcrctab_gpl");
    mod->gpl_future_syms = section_objs(info,"__ksymtab_gpl_future",
    sizeof(*mod->gpl_future_syms),&mod->num_gpl_future_syms);
    mod->gpl_future_crcs = section_addr(info, "__kcrctab_gpl_future");

    #ifdef CONFIG_UNUSED_SYMBOLS
    mod->unused_syms = section_objs(info, "__ksymtab_unused",sizeof(*mod->unused_syms),&mod->num_unused_syms);
    mod->unused_crcs = section_addr(info, "__kcrctab_unused");
    mod->unused_gpl_syms = section_objs(info, "__ksymtab_unused_gpl",sizeof(*mod->unused_gpl_syms),&mod->num_unused_gpl_syms);
    mod->unused_gpl_crcs = section_addr(info, "__kcrctab_unused_gpl");
    #endif
    #ifdef CONFIG_CONSTRUCTORS
    mod->ctors = section_objs(info, ".ctors",sizeof(*mod->ctors), &mod->num_ctors);
    #endif

    #ifdef CONFIG_TRACEPOINTS
    mod->tracepoints_ptrs = section_objs(info, "__tracepoints_ptrs",sizeof(*mod->tracepoints_ptrs),&mod->num_tracepoints);
    #endif
    #ifdef HAVE_JUMP_LABEL
    mod->jump_entries = section_objs(info, "__jump_table",sizeof(*mod->jump_entries),&mod->num_jump_entries);
    #endif
    #ifdef CONFIG_EVENT_TRACING
    mod->trace_events = section_objs(info, "_ftrace_events",sizeof(*mod->trace_events),&mod->num_trace_events);
    #endif
    #ifdef CONFIG_TRACING
    mod->trace_bprintk_fmt_start = section_objs(info, "__trace_printk_fmt",sizeof(*mod->trace_bprintk_fmt_start),&mod->num_trace_bprintk_fmt);
    #endif
    #ifdef CONFIG_FTRACE_MCOUNT_RECORD
    /* sechdrs[0].sh_size is always zero */
    mod->ftrace_callsites = section_objs(info, "__mcount_loc",sizeof(*mod->ftrace_callsites),&mod->num_ftrace_callsites);
    #endif

    mod->extable = section_objs(info, "__ex_table",sizeof(*mod->extable), &mod->num_exentries);

    if (section_addr(info, "__obsparm"))
    printk(KERN_WARNING "%s: Ignoring obsolete parameters ",mod->name);

    info->debug = section_objs(info, "__verbose",sizeof(*info->debug), &info->num_debug);
    }

    static void *section_objs(const struct load_info *info,const char *name,size_t object_size,unsigned int *num)
    {
    //根据节区名找到节区索引值
    unsigned int sec = find_sec(info, name);
    //计算节区项目数
    *num = info->sechdrs[sec].sh_size / object_size;
    //返回节区起始地址
    return (void *)info->sechdrs[sec].sh_addr;
    }

    static int check_module_license_and_versions(struct module *mod)
    {
    if (strcmp(mod->name, "ndiswrapper") == 0)
    add_taint(TAINT_PROPRIETARY_MODULE, LOCKDEP_NOW_UNRELIABLE);

    /* driverloader was caught wrongly pretending to be under GPL */
    if (strcmp(mod->name, "driverloader") == 0)
    add_taint_module(mod, TAINT_PROPRIETARY_MODULE,LOCKDEP_NOW_UNRELIABLE);

    /* lve claims to be GPL but upstream won't provide source */
    if (strcmp(mod->name, "lve") == 0)
    add_taint_module(mod, TAINT_PROPRIETARY_MODULE,LOCKDEP_NOW_UNRELIABLE);

    #ifdef CONFIG_MODVERSIONS
    if ((mod->num_syms && !mod->crcs)|| (mod->num_gpl_syms && !mod->gpl_crcs)
    || (mod->num_gpl_future_syms && !mod->gpl_future_crcs)
    #ifdef CONFIG_UNUSED_SYMBOLS
    || (mod->num_unused_syms && !mod->unused_crcs)
    || (mod->num_unused_gpl_syms && !mod->unused_gpl_crcs)
    #endif
    ) {
    return try_to_force_load(mod,"no versions for exported symbols");
    }
    #endif
    return 0;
    }

    static struct module_attribute *modinfo_attrs[] = {
    &module_uevent,
    &modinfo_version,
    &modinfo_srcversion,
    &modinfo_initstate,
    &modinfo_coresize,
    &modinfo_initsize,
    &modinfo_taint,
    #ifdef CONFIG_MODULE_UNLOAD
    &modinfo_refcnt,
    #endif
    NULL,
    };

    static void setup_modinfo(struct module *mod, struct load_info *info)
    {
    struct module_attribute *attr;
    int i;

    for (i = 0; (attr = modinfo_attrs[i]); i++) {
    if (attr->setup)
    attr->setup(mod, get_modinfo(info, attr->attr.name));
    }
    }

    static int simplify_symbols(struct module *mod, const struct load_info *info)
    {
    Elf_Shdr *symsec = &info->sechdrs[info->index.sym];
    Elf_Sym *sym = (void *)symsec->sh_addr;
    unsigned long secbase;
    unsigned int i;
    int ret = 0;
    const struct kernel_symbol *ksym;

    //遍历模块所有符号
    for (i = 1; i < symsec->sh_size / sizeof(Elf_Sym); i++) {
    const char *name = info->strtab + sym[i].st_name;

    //不同符号类型必须进行不同的处理。
    switch (sym[i].st_shndx) {
    case SHN_COMMON://标注了一个尚未分配的公共块符号
    DEBUGP("Common symbol: %s ", name);
    printk("%s: please compile with -fno-common ", mod->name);
    ret = -ENOEXEC;
    break;

    case SHN_ABS://完全定义的符号是最容易的,因为什么也不需要做。
    DEBUGP("Absolute symbol: 0x%08lx ", (long)sym[i].st_value);
    break;

    case SHN_UNDEF://未定义的符号,会进行crc检查
    //会调用check_version检查crc值是否一致,若一致返回内核中的同名符号
    ksym = resolve_symbol_wait(mod, info, name);

    //如果符号已经解决(crc一致),则没有问题,将内核中的符号地址赋给模块中的符号地址
    if (ksym && !IS_ERR(ksym)) {
    sym[i].st_value = ksym->value;//使用内核中的符号值
    break;
    }

    //如果符号定义为弱的,也没有问题
    if (!ksym && ELF_ST_BIND(sym[i].st_info) == STB_WEAK)
    break;

    printk(KERN_WARNING "%s: Unknown symbol %s (err %li) ", mod->name, name, PTR_ERR(ksym));
    ret = PTR_ERR(ksym) ?: -ENOENT;
    break;

    //解决其他符号时(符号通过st_shndx成员指向固定的节区),根据节区新的起始地址
    default:
    if (sym[i].st_shndx == info->index.pcpu)
    secbase = (unsigned long)mod_percpu(mod);
    else
    secbase = info->sechdrs[sym[i].st_shndx].sh_addr;
    //根据符号所在节区的起始地址更改符号地址
    sym[i].st_value += secbase;
    break;
    }
    }
    return ret;
    }

    static const struct kernel_symbol *resolve_symbol_wait(struct module *mod,const struct load_info *info, const char *name)
    {
    const struct kernel_symbol *ksym;
    char owner[MODULE_NAME_LEN];

    //调用resolve_symbol()函数
    if (wait_event_interruptible_timeout(module_wq,!IS_ERR(ksym = resolve_symbol(mod, info, name, owner))|| PTR_ERR(ksym) != -EBUSY,30 * HZ) <=0) {
    printk(KERN_WARNING "%s: gave up waiting for init of module %s. ",mod->name, owner);
    }
    return ksym;
    }

    static const struct kernel_symbol *resolve_symbol(struct module *mod,const struct load_info *info,const char *name,char ownername[])
    {
    struct module *owner;
    const struct kernel_symbol *sym;
    const unsigned long *crc;
    int err;
    mutex_lock(&module_mutex);

    //从内核中查找符号名为name的符号,并返回该符号的crc值
    sym = find_symbol(name, &owner, &crc,!(mod->taints & (1 << TAINT_PROPRIETARY_MODULE)), true);
    if (!sym)
    goto unlock;

    //检查该内核中的符号和模块中此未定义的符号的crc值是否一致
    if (!check_version(info->sechdrs, info->index.vers, name, mod, crc,owner)) {
    sym = ERR_PTR(-EINVAL);
    goto getname;
    }

    //更新模块的依赖关系,即修正模块的source_list和target_list链表
    err = ref_module(mod, owner);
    if (err) {
    sym = ERR_PTR(err);
    goto getname;
    }

    getname:
    strncpy(ownername, module_name(owner), MODULE_NAME_LEN);

    unlock:
    mutex_unlock(&module_mutex);
    return sym;
    }

    static int apply_relocations(struct module *mod, const struct load_info *info)
    {
    unsigned int i;
    int err = 0;

    //遍历所有的节区
    for (i = 1; i < info->hdr->e_shnum; i++) {
    //对于重定位节区来说,其sh_info指向重定位所适用的节区的节区头部索引
    //像.rel.text节区对应.text节区
    unsigned int infosec = info->sechdrs[i].sh_info;

    //如果当前节区附加的节区的索引大于节区的数目,则info不是一个有效的索引,不做处理。
    if (infosec >= info->hdr->e_shnum)
    continue;

    //如果节区在执行过程中不占内存,则 不需要进行处理。
    if (!(info->sechdrs[infosec].sh_flags & SHF_ALLOC))
    continue;

    //此节区包含重定位表项,其中没有补齐,进行符号重定位,则调用apply_relocate来处理
    if (info->sechdrs[i].sh_type == SHT_REL)
    err = apply_relocate(info->sechdrs, info->strtab,info->index.sym, i, mod);
    //此节区包含重定位表项,其中有补齐,进行符号重定位,则调用apply_relocate_add来处理
    else if (info->sechdrs[i].sh_type == SHT_RELA)
    err = apply_relocate_add(info->sechdrs, info->strtab,info->index.sym, i, mod);
    if (err < 0)
    break;
    }
    return err;
    }

    int apply_relocate(Elf32_Shdr *sechdrs,const char *strtab,unsigned int symindex,unsigned int relsec,struct module *me)
    {
    //sechdrs表示节区头部表起始地址,strtab表示字符串节区的内容,symindex表示符号节区在节区头部表的索引值,relsec表示重定位节区的索引值(例如:.rel.text节区),该节区的内容是重定位结构的表项
    unsigned int i;
    //找到重定位节区中的重定位表项起始地址
    Elf32_Rel *rel = (void *)sechdrs[relsec].sh_addr;
    Elf32_Sym *sym;
    uint32_t *location;

    DEBUGP("Applying relocate section %u to %u ",relsec, sechdrs[relsec].sh_info);

    //遍历重定位节区中的重定位表项
    for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
    //r_offset指定在此节区的区间r_offset处需要重新填写这个符号(.r_info指定)的绝对地址。找到重定位的位置,重定位节区relsec的sh_info表示重定位所适用的节区的节区头部索引值
    //像.rel.text节区对应.text节区
    location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr+ rel[i].r_offset;
    //找到重定位的符号,ELF32_R_SYM(rel[i].r_info)表示这个符号在在符号表节区的内容中的索引值
    sym = (Elf32_Sym *)sechdrs[symindex].sh_addr+ ELF32_R_SYM(rel[i].r_info);

    //sym->st_value是符号的绝对地址
    switch (ELF32_R_TYPE(rel[i].r_info)) {//重定位类型
    case R_386_32://绝对地址修正
    //在要被修正地址处loc,把该地址下的值加上符号的绝对地址,等于这个位置处的取值就是符号地址了,一般来说*location初始值是0,加上符号绝对地址就能在此位置处找到符号地址了
    *location += sym->st_value;
    break;
    case R_386_PC32://相对地址修正
    /* Add the value, subtract its position */
    *location += sym->st_value - (uint32_t)location;
    break;
    default:
    pr_err("%s: Unknown relocation: %u ",me->name, ELF32_R_TYPE(rel[i].r_info));
    return -ENOEXEC;
    }
    }
    return 0;
    }

    static int post_relocation(struct module *mod, const struct load_info *info)
    {
    //对模块的异常表进行排序
    sort_extable(mod->extable, mod->extable + mod->num_exentries);

    //因为mod对象已经复制到了内核中的最终位置,拷贝模块的percpu数据到mod对象的内存地址处。
    percpu_modcopy(mod, (void *)info->sechdrs[info->index.pcpu].sh_addr,info->sechdrs[info->index.pcpu].sh_size);

    //初始化module对象中中字符串表、符号表相关的成员,初始化core部分中的字符串表和符号表
    add_kallsyms(mod, info);

    /* Arch-specific module finalizing. */
    return module_finalize(info->hdr, info->sechdrs, mod);//x86是空函数
    }

    static void add_kallsyms(struct module *mod, const struct load_info *info)
    {
    unsigned int i, ndst;
    const Elf_Sym *src;
    Elf_Sym *dst;
    char *s;
    Elf_Shdr *symsec = &info->sechdrs[info->index.sym];//符号表节区

    //符号表节区开始地址
    mod->symtab = (void *)symsec->sh_addr;
    //符号表项个数
    mod->num_symtab = symsec->sh_size / sizeof(Elf_Sym);
    //找到字符串节区
    mod->strtab = (void *)info->sechdrs[info->index.str].sh_addr;

    //遍历符号表节区中的符号表项,获取符号的属性(STB_LOCAL,STB_GLOBAL...)
    for (i = 0; i < mod->num_symtab; i++)
    mod->symtab[i].st_info = elf_type(&mod->symtab[i], info);

    //将core部分存储符号项的偏移地址info->symoffs加上core部分的起始地址得到存储符号项的地址。将符号表项的地址付给core_symtab成员,符号名称起始地址付给core_strtab成员
    mod->core_symtab = dst = mod->module_core + info->symoffs;
    mod->core_strtab = s = mod->module_core + info->stroffs;
    src = mod->symtab;//符号表节区开始地址

    //将符号表节区的符号项(若干的Elf_Sym结构)内容拷贝到core部分指定地址处
    //将符号项中符号的名称拷贝到core部分指定地址处,并重新设置符号项中名称在符号字符串中的索引值st_name
    for (ndst = i = 0; i < mod->num_symtab; i++) {
    if (i == 0 || is_core_symbol(src+i, info->sechdrs, info->hdr->e_shnum)) {
    //符号项拷贝到mod->module_core + info->symoffs
    dst[ndst] = src[i];
    //重新设置符号名在mod->core_strtab中的索引值
    dst[ndst++].st_name = s - mod->core_strtab;
    //符号名称拷贝到mod->module_core + info->stroffs
    s += strlcpy(s, &mod->strtab[src[i].st_name],KSYM_NAME_LEN) + 1;
    }
    }
    mod->core_num_syms = ndst;//符号个数
    }

    static int complete_formation(struct module *mod, struct load_info *info)
    {
    int err;

    mutex_lock(&module_mutex);

    //确认是否有重定义符号
    err = verify_export_symbols(mod);
    if (err < 0)
    goto out;

    //模块bug相关操作
    module_bug_finalize(info->hdr, info->sechdrs, mod);

    //设置模块的状态
    mod->state = MODULE_STATE_COMING;
    out:
    mutex_unlock(&module_mutex);
    return err;
    }

    static int verify_export_symbols(struct module *mod)
    {
    unsigned int i;
    struct module *owner;
    const struct kernel_symbol *s;
    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
    };

    //遍历模块中的符号,在内核中检查是否已经导出,若已经导出该符号,则给出著名error:“exports duplicate symbol”
    for (i = 0; i < ARRAY_SIZE(arr); i++) {
    for (s = arr[i].sym; s < arr[i].sym + arr[i].num; s++) {
    if (find_symbol(s->name, &owner, NULL, true, false)) {
    printk(KERN_ERR "%s: exports duplicate symbol %s"" (owned by %s) ",mod->name, s->name, module_name(owner));
    return -ENOEXEC;
    }
    }
    }
    return 0;
    }

    static int do_init_module(struct module *mod)
    {
    int ret = 0;

    current->flags &= ~PF_USED_ASYNC;

    //内核通知
    blocking_notifier_call_chain(&module_notify_list,MODULE_STATE_COMING, mod);

    //Set RO and NX regions for core
    set_section_ro_nx(mod->module_core,mod->core_text_size,mod->core_ro_size,mod->core_size);

    //Set RO and NX regions for init
    set_section_ro_nx(mod->module_init,mod->init_text_size,mod->init_ro_size,mod->init_size);

    //调用模块构造函数
    do_mod_ctors(mod);

    //启动模块
    if (mod->init != NULL)
    ret = do_one_initcall(mod->init);

    if (ret < 0) {
    /* Init routine failed: abort. Try to protect us from buggy refcounters. */
    mod->state = MODULE_STATE_GOING;
    synchronize_sched();
    module_put(mod);
    blocking_notifier_call_chain(&module_notify_list,MODULE_STATE_GOING, mod);
    free_module(mod);
    wake_up_all(&module_wq);
    return ret;
    }
    if (ret > 0) {
    printk(KERN_WARNING"%s: '%s'->init suspiciously returned %d, it should follow 0/-E convention ""%s: loading module anyway... ",__func__,mod->name, ret,__func__);
    dump_stack();
    }

    //模块初始化完成,更改状态,通知内核
    mod->state = MODULE_STATE_LIVE;
    blocking_notifier_call_chain(&module_notify_list,MODULE_STATE_LIVE, mod);

    if (current->flags & PF_USED_ASYNC)
    async_synchronize_full();
    mutex_lock(&module_mutex);
    /* Drop initial reference. */
    module_put(mod);
    rim_init_extable(mod);
    #ifdef CONFIG_KALLSYMS
    mod->num_symtab = mod->core_num_syms;
    mod->symtab = mod->core_symtab;
    mod->strtab = mod->core_strtab;
    #endif
    //释放初始化部分空间,这部分只是在初始化有效,初始化结束回收资源,清空
    unset_module_init_ro_nx(mod);
    module_free(mod, mod->module_init);
    mod->module_init = NULL;
    mod->init_size = 0;
    mod->init_ro_size = 0;
    mod->init_text_size = 0;
    mutex_unlock(&module_mutex);
    wake_up_all(&module_wq);
    return 0;
    }

    int __init_or_module do_one_initcall(initcall_t fn)
    {
    int count = preempt_count();
    int ret;
    char msgbuf[64];

    if (initcall_debug)
    ret = do_one_initcall_debug(fn);
    else
    ret = fn();//执行模块的init_module函数

    msgbuf[0] = 0;

    if (preempt_count() != count) {
    sprintf(msgbuf, "preemption imbalance ");
    preempt_count() = count;
    }

    if (irqs_disabled()) {
    strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
    local_irq_enable();
    }
    WARN(msgbuf[0], "initcall %pF returned with %s ", fn, msgbuf);

    return ret;
    }

  • 相关阅读:
    WPF 绘图 和动画
    BZOJ 4028 分块
    操作系统与计算机网络
    go排序-基数排序
    go排序-睡眠排序
    go排序-堆排序
    go排序-构建大顶堆
    go排序 插入排序
    go排序-选择排序
    go排序-冒泡排序
  • 原文地址:https://www.cnblogs.com/andyfly/p/9466805.html
Copyright © 2011-2022 走看看