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

发布者:upsilon30最新更新时间:2024-08-20 来源: cnblogs关键字:Linux驱动  过程分析 手机看文章 扫描二维码
随时随地手机看文章

if (owner)//NULL,下边不需赋值

*owner = fsa.owner;


if (crc)//得到该符号的crc值

*crc = fsa.crc;


//返回以name命名的内核符号结构

return fsa.sym;

}


//没有找到内核符号,返回NULL

pr_debug('Failed to find symbol %sn', 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 futuren', 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.n', fsa->name);

printk(KERN_WARNING'This symbol will go away in the future.n');

printk(KERN_WARNING

'Please evalute if this is the right api to use and if '

'it really is, submit a report the linux kernel '

'mailinglist together with submitting your code for '

'inclusion.n');

}

#endif


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 %lXn',maybe_relocated(*crc, crc_owner), versions[i].crc);

goto bad_version;

}


//若在“__versions”节区没有找到要比较的符号,则会给出加载模块时常见错误:“no symbol version for”

printk(KERN_WARNING '%s: no symbol version for %sn',mod->name, symname);

return 0;

bad_version:

//如果比较符号的额crc值不一致,则会给出加载模块时常见错误“disagrees about version of symbol”

printk('%s: disagrees about version of symbol %sn',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.n',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'n',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.n',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.n', 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]

[1] [2] [3] [4] [5] [6]
关键字:Linux驱动  过程分析 引用地址:Linux驱动之内核加载模块过程分析

上一篇:Linux驱动之建立一个hello模块
下一篇:S3C2440看门狗定时器原理

推荐阅读最新更新时间:2026-03-20 16:14

系统分析STM32的上电启动过程
上一篇文章我写了STM32的RAM和Flash,文章最后我建议大家来深入研究一下STM32上电启动过程。同时有小伙伴留言说想让我讲一下IAP(在线升级程序)。其实如果搞懂STM32的上电启动过程,那么IAP就可以信手拈来了。下面我们一起来研究研究。 1、先说启动文件 我们正常在操作一款单片机的时候,都是从main函数开始进行编程的,但是单片机上电是从main函数开始执行的吗?答案当然是否定的,在main函数之前单片机最先执行的是硬件设置SP、PC然后是“启动文件”,一般主要是项目文件里面的startup_xxxxx.s文件。 其实不光STM32系列单片机是这样,我们接触的NXP的微控制器、TI的MSP430以及51单片机等等
[单片机]
汽车ECU的Bootloader升级过程分析
前言 最近负责的ECU报了CAN升级失败的问题,反馈到开发这边就是问题描述和一堆的Error log,因为发生问题的车辆在外地,这就需要我们从Error Log中找到问题所在(起码找到是上位机问题还是ECU端问题,如果是ECU的问题还要继续分析ECU为啥故障),因为以前的Bootloader升级知识还停留在理论阶段,到真正找问题的时候还是有很多模糊的地方的,终归还是对一些基础试着掌握的不牢固,这里把分析过程中需要的基础知识都列出来,同时把升级的分析过程也记录下来,希望以后分析Bootlodaer的升级问题时能更加得心应手。 正文 1.什么是Bootloader MCU正常运行时总是从固定地方取指令,顺序运行,程序更新时需要使用
[嵌入式]
汽车ECU的Bootloader升级<font color='red'>过程</font><font color='red'>分析</font>
u-boot之make _config执行过程分析
从网上下载uboot源码之后需要对源码作相应修改来支持自己的开发板,更改完源码之后需要配置。uboot(make board_name _config)。这里以百问网的开发板jz2440为例子,配置命令为make 100ask24x0_config。这条命令的执行过程按以下几步分析: 1、u-boot-1.1.6/Makefile简单分析 2、u-boot-1.1.6/mkconfig详细分析 3、总结make 100ask24x0_config这条命令执行后会发生什么 1、u-boot-1.1.6/Makefile简单分析。Makefile的最简单的规则如下(摘超自博客https://blog.csdn.net/
[单片机]
Linux移植之配置过程分析
在Linux移植之移植步骤中已经将Linux移植的过程罗列出来了,现在分析一下Linux的配置过程,将分析以下两个配置过程: 1、make s3c2410_defconfig分析 2、make menuconfig分析 1、make s3c2410_defconfig分析 首先从顶层Makefile开始分析,找到类似smdk2410_defconfig的目标。找到了%config目标。表示后缀为config的目标遵循这个规则,config %config前面的config是一个Kconfig关键字,表示一个配置选项的开始。 416 config %config: scripts_basic outputmak
[单片机]
STM32H7的启动过程分析
本章 教程 主要跟大家讲 STM32 H7的启动过程,这里的启动过程是指从 CPU 上电复位执行第1条指令开始( 汇编 文件)到进入C程序main()函数入口之间的部分。 启动过程相对来说还是比较重要的,理解了这个过程,对于以后分析程序还是有些帮助的,要不每次看到这个启动过程都会跳过,直接去看主程序了。 还有就是以后打算学习 RTOS 的话,对于这个过程必须有个了解,因为移植的时候涉及到中断向量表。 对初学者来说,看这个可能有些吃力,不过不要紧,随着自己做过一些简单的应用之后再来看这章,应该会有很多的帮助,由于我们的V7板子是基于STM32H7XXX,所以我们这里主要针对H7系列的启动过程做一下分析,对于F1,F4系列也是大致
[单片机]
STM32H7的启动<font color='red'>过程</font><font color='red'>分析</font>
Fluke 725S多功能过程校验仪的主要特性和功能应用分析
一、Fluke 725S多功能过程校验仪产品概述: Fluke 725S多功能过程校验仪测试和校准几乎所有的过程参数。它可以测量和输出毫安、伏特、温度(RTD和热电偶)、频率、脉冲、欧姆,及压力信号(需外接可选的F750P压力模块)。需要检修校准变送器吗?725S的分割显示屏,可同时观察输入和输出。对于阀门和I/P测试,在测量压力的同时还可以输出mA。725S具有自动步进和自动斜坡功能,可用于远程测试,25%步进功能可用于快速线性度检测。 当您拿到Fluke 725S时,即可马上投入工作。中文显示使仪器控制起来更加简单。其存储功能使得设置更加快捷,且非常坚固。是现场最理想的检修维护工具。使用简单,功能强大! 二、Fluke 7
[测试测量]
754/754PLUS多功能过程校验仪的功能及特点分析
754/754PLUS 多功能过程校验仪是一款强大的多功能记录过程校准仪,使用该校准仪,您可以下载用软件创建的程序、列表和说明,或者上传数据进行打印、归档和分析。754/754PLUS 还特别内置了功能强大的 HART 接口,您现在通过单独的通信器执行的所有日常任务几乎都可以使用该接口来完成。 ● 全面升级了语言操作系统,提供简洁友好的中文操作界面,更便于中国用户操作,极大地提升了专业人员进行现场校准和故障诊断的工作效率。 ● 测量电压、电流 (mA)、RTD、热电偶、频率和电阻,以测试传感器、变送器和其他仪器。 ● 输出/模拟电压、电流 (mA)、热电偶、RTD、频率、电阻和压力以校准变送器。 ● 热工信号校验仪测量电流的同
[测试测量]
利用CSR8350测试LOOPBACK过程分析
内容简介 本文介绍如何利用Bluesuite的Bluetest3让CSR8350进入AUDIO LOOPBACK测试,直接将MIC的输入音频从SPEAKER输出。 测试工具 MDE:NULL Toolkit:NULL QACT:NULL ADK:NULL Bluesuite:2.6.9 Hardware:CSR8350 Software:NULL Here we go 在开始之前,需要确认一点,如果CSR8350有外挂的EEPROM,请先用pstool进行factory restore: 这样做的目的是为了防止原先存储的信息对芯片的干扰。 测试需要一个1kHz的正玄波信号,可以用信号发生器,也可以用开发板自己产生一个1kH
[测试测量]
利用CSR8350测试LOOPBACK<font color='red'>过程</font><font color='red'>分析</font>
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved