Linux驱动之内核加载模块过程分析

发布时间:2024-08-20  

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=%pn',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 kerneln',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);

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>