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);