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

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

|| (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('t%sn', 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('t%sn', 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:n');

//遍历所有节区,拷贝需要占用内存(标志为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('t0x%lx %sn',(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 parametersn',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);

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

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

推荐阅读最新更新时间:2024-11-16 17:23

linux i2c 设备测试,i2c-dev驱动测试代码
embed-linux version:linux-2.6.39-exp vmware-linux:ubuntu14.04 hardware: core chip at91sam9x25 cross-compile:arm-none-linux-gnueabi-gcc 4.5.2 code: #include stdio.h #include stdlib.h #include unistd.h #include sys/ioctl.h #include sys/types.h #include sys/stat.h #include fcntl.h //#include sys/select.h #include sys/tim
[单片机]
Linux驱动之一个简单的输入子系统程序编写
在Linux驱动之输入子系统简析已经分析过了输入子系统的构成,它是由设备层、核心层、事件层共同组成的。其中核心层提供一些设备层与事件层公用的函数,比如说注册函数、反注册函数、事件到来的处理函数等等;事件层其实在Linux内核里面已经帮我们写好了很多有关的事件;而设备层就跟我们新添加到输入系统的具体设备相关了。这里以JZ2440开发板上的4个按键作为输入子系统的按键:它定义的功能分别为:KEY_L、KEY_S、KEY_ENTER、KEY_LEFTSHIFT。这几个值是在includelinuxinput.h中被定义的。接下来就是编写程序: 直接贴出源程序: #include linux/module.h #include
[单片机]
<font color='red'>Linux</font><font color='red'>驱动</font>之一个简单的输入子系统程序编写
Linux驱动入门
阅读此文的方法: 阅读以下2个文件: hello.c,asdf.c。 此文假设读者: 已经能用C语言编写Linux应用程序, 理解"字符设备文件, 块设备文件, 主设备号, 次设备号", 会写简单的Shell脚本和Makefile。 1. "hello.c" -------------------------------- /* * 这是我们的第一个源文件, * 它是一个可以加载的内核模块, * 加载时显示"Hello,World!", * 卸载时显示"Bye!"。 * 需要说明一点,写内核或内核模块不能用写应用程序时的系统调用或函数库, * 因为我们写的就是为应用程序提供系统调用的代码。 * 内核有专用的函数库,如 linux/
[嵌入式]
XScale PXA270在Linux下的FPGA设备驱动
引言   Intel公司推出的XScale采用ARM V5TE结构,是Strong ARM的升级换代产品。XScale PXA270处理器最高主频可达624 MHz,加入了Wireless MMX、Intel SpeedStep等新技术,以其高性能、低功耗、多功能等特点在信息家电、工业控制等领域得到了广泛的应用。在嵌入式控制中,“微处理器+FPGA”是一种常用的解决方案。FPGA(现场可编程门阵列)有编程方便、集成度高、速度快等特点,电子设计人员可以通过硬件编程的方法来实现FPGA芯片各种功能的开发。在我们的一个数控平台的研究项目中,采用XScale PXA270作为主CPU,并对其进行FPGA扩展,使其具有插补、电机驱动、信
[嵌入式]
基于ARM9+Linux的DS18B20驱动程序设计
引言 随着嵌入式技术的发展,基于ARM和Linux的嵌入式产品越来越多, DS18B20 温度采集传感器在工业和生活上应用广泛,研究开发基于ARM9和Linux的DS18B20的驱动程序可以满足大部分温度采集平台的应用。 1 Linux设备驱动的开发过程 Linux操作系统通过各种驱动程序来操作硬件设备,它屏蔽了各种设备,设备驱动程序是操作系统内核和硬件之间的接口。从应用程序来看,硬件只是一个设备文件,应用程序可以像操作普通文件一样操作硬件设备。 1.1 设备的分类 Linux看待设备可区分为3种基本设备类型,分别为字符设备、块设备和网络设备: ①字符设备:字符设备是一种可以当作一个字节流来存取的设备,相当于一个文件,字符设备驱动
[电源管理]
基于ARM9+<font color='red'>Linux</font>的DS18B20<font color='red'>驱动</font>程序设计
基于内核对象的Linux输入子系统触摸屏的驱动设计
随着人们对操控要求的不断提高,电容触摸屏因为能支持多点触摸而得到广泛使用。本文基于Nokia和Intel公司合作开发的开源操作系统MeeGo,采用基于内核对象的Linux输入子系统来设计触摸屏的驱动。该方案极大地方便了触摸屏的驱动开发,可应用在车载娱乐、上网本、智能手机等电子产品上。 随着人们对操控要求的不断提高,市场上出现了越来越多的高端手机、平板电脑,这些产品共同的特点就是给人们提供了非常便利的操控方式,尤其是电容触摸屏的使用,它能很好地实现多点触控功能。多点触控技术是当今炙手可热的技术,它让人们的生活方式得到了前所未有的改变。电容触摸屏已经成为高端手机的标配,如苹果的iPhone以及HTC Motorola的一些高端
[单片机]
基于<font color='red'>内核</font>对象的<font color='red'>Linux</font>输入子系统触摸屏的<font color='red'>驱动</font>设计
利用频域时钟抖动分析加快设计验证过程
   简介   随着数据速率的提高,时钟抖动分析的需求也在与日俱增。在高速串行数据链路中,时钟抖动会影响发射机、传输线和接收机的数据抖动。保证时钟质量的测量也在不断发展。目前的重点是针对比特误码率,在时钟性能和系统性能之间建立直接联系。我们将探讨参考时钟的作用和时钟抖动对数据抖动的影响,并讨论在E5052B信号源分析仪(SSA)上运行的Agilent E5001A精确时钟抖动分析应用软件所配备的全新测量技术。该应用软件提供了前所未有的强大能力,可以对随机抖动(RJ)和周期抖动(PJ)分量超低 RJ测量和实时抖动频谱分析,使您能够提高设计质量。我们还将对新解决方案的实时测量功能进行讨论,这一功能能够加快设计验证过程。    参考
[工业控制]
数字电源市场发展过程中存在的问题分析
  随着节能成为当今世界的重大课题,电源 管理 也越来越受重视,技术水平的提升正不断优化系统的能效。但是,在“高集成度”、“数字化”等概念面前,企业更关注的还是市场和成本等现实问题。      电源管理芯片曾被形容为半导体行业的灰姑娘,这表明电源管理芯片长期以来被认为是半导体业界一个不起眼的配角。然而,与数字电路市场的大起大落不同,电源管理芯片保持着长期、稳定的增长势头。Databeans公司的 市场调查 结果显示,2007年电源管理芯片全球销售额超过80亿美元,预计今年的增长可达7%。随着原油价格的不断上升,节能领域的应用无疑是给灰姑娘穿上了红舞鞋,电源管理应用正迈步走向半导体产业的前台。      技术创新促进能效提
[电源管理]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
  • ARM裸机篇--按键中断
    先看看GPOI的输入实验:按键电路图:GPF1管教的功能:EINT1要使用GPF1作为EINT1的功能时,只要将GPFCON的3:2位配置成10就可以了!GPF1先配 ...
  • 网上下的--ARM入门笔记
    简单的介绍打今天起菜鸟的ARM笔记算是开张了,也算给我的这些笔记找个存的地方。为什么要发布出来?也许是大家感兴趣的,其实这些笔记之所 ...
  • 学习ARM开发(23)
    三个任务准备与运行结果下来看看创建任务和任运的栈空间怎么样的,以及运行输出。Made in china by UCSDN(caijunsheng)Lichee 1 0 0 ...
  • 学习ARM开发(22)
    关闭中断与打开中断中断是一种高效的对话机制,但有时并不想程序运行的过程中中断运行,比如正在打印东西,但程序突然中断了,又让另外一个 ...
  • 学习ARM开发(21)
    先要声明任务指针,因为后面需要使用。 任务指针 volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • 学习ARM开发(20)
  • 学习ARM开发(19)
  • 学习ARM开发(14)
  • 学习ARM开发(15)
何立民专栏 单片机及嵌入式宝典

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

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

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