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

发布者:upsilon30最新更新时间:2024-08-20 来源: cnblogs关键字: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=%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);

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

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

推荐阅读最新更新时间:2024-11-16 15:25

Linux的I2C 设备驱动 -- mini2440 上i2c接口触摸屏驱动
本篇记录在友善之臂 mini2440 平台上挂载I2C接口触摸屏的驱动开发过程。 内核版本linux-2.6.32.2, 平台是ARM9 S3C2440+I2C接口的触摸屏 如上篇 Linux的I2C驱动体系结构讲述 http://www.lupaworld.com/273398/viewspace-204237.html 要挂载新的I2C设备,需要实现3部分: 1) 适配器的硬件驱动: 内核中已经实现mini2440,i2c适配器驱动,可以在如下目录i2c-s3c2410.c中看到相关代码 linux-2.6.32.2/drivers/i2c/busses/i2c-s3c2410.c 2) I2C 设配器的algorithm 同
[单片机]
linux下编写I2C驱动与stm32通信(二)
接上一篇,linux下GPIO模拟I2C驱动完成后,就是stm32的i2c配置了,由于hi3518e作为i2c的主设备,stm32则作为从设备,由于GPIO模拟i2c的从时序比模拟主时序要麻烦很多,所以采用stm32的硬件I2C。(stm32官网i2c例程主模式会莫名的卡死,从模式比较好用) 下载官网例程,将之设置为从模式,使用i2c2,将SCL,SDA,GND与hi3518e板子上GPIO模拟的SCL,SDA和GND连起来,写一个测试例程来验证双方的通信。 首先在linux下加载驱动,然后调用打开驱动,调用编写的驱动接口函数,读数据和写数据。驱动接口代码如下: #include stdio.h #include
[单片机]
Linux下USB从(USB gadget) 驱动配置与使用
S3C2440提供了一个USB从接口,我们可以使用它来把2440模拟为一个U盘,直接从电脑上以盘符的形式访问2440 一、内核配置 USB SUPPORT * USB Gadget Support --- USB Peripheral Controller (S3C2410 USB Device Controller) --- S3C2410 USB Device Controller S3C2410 udc debug messages M USB Gadget Drivers M File-backed Storage Gadget 二、make zImage 生成z‫Im
[单片机]
ARM矩阵键盘设计及其linux驱动实现
在嵌入式系统开发中,经常通过键盘来实现人机交互。本文介绍了一种直接利用ARM的I/O口扩展矩阵键盘的方法。同时以TQ2440开发板为例,对硬件电路连接和相应的linux驱动设计方法都作了详细说明。 1.引言 ARM微处理器已广泛应用于工业控制、消费类电子产品、通信系统等领域。矩阵键盘是一种常用的键盘形式,它将按键设计成M行N列,这样共需M+N根信号线,却可驱动M×N个按键,大大节约了I/O资源。本文介绍了一种利用TQ2440开发板的GPIO口扩展5×4矩阵键盘的方法,并将所有按键重新布局成手持终端的键盘形式,方便操作。 2.硬件设计 本设计扩展5行4列的矩阵键盘,如图1所示。其中行线ROW1-ROW5连接S3C24
[单片机]
ARM矩阵键盘设计及其<font color='red'>linux</font><font color='red'>驱动</font>实现
基于Linux的MISC类设备AD7859L的驱动程序开发
  1 引言   在嵌入式系统中基于ARM微核的嵌入式处理器已经成为市场主流。随着ARM技术的广泛应用,建立面向ARM构架的嵌入式操作系统成为测量行业的热点问题。在 LINUX 操作系统中添加新的外部设备时,只需为其添加对应的驱动程序即可。介绍另一种驱动程序的编写方式,即采用MISC类设备。其实质也是一个字符设备。可将用户各种不同的驱动设备类型合成到一种类型中,共用一个主设备号,通过不同的次设备号和设备节点名来区分。可方便管理这些驱动模块。字符型的驱动设备模块在挂载时都要分配主设备号、次设备号和创建设备节点名,在卸载驱动设备时还必须同时删掉设备节点名。通过采用MISC类设备,在挂载设备驱动时无须再用到mknod命令分配主设备号、次
[单片机]
基于<font color='red'>Linux</font>的MISC类设备AD7859L的<font color='red'>驱动</font>程序开发
浅谈分析Arm linux 内核移植及系统初始化的过程
学习嵌入式ARM linux ,主要想必三个方向发展: 1 、嵌入式linux 应用软件开发 2 、linux 内核的剪裁和移植 3 、嵌入式linux 底层驱动的开发 主 要介绍内核移植过程中涉及文件的分布及其用途,以及简单介绍系统的初始化过程。整个arm linux内核的启动可分为三个阶段:第一阶段主要是进行 cpu和体系结构的检查、cpu本身的初始化以及页表的建立等;第二阶段主要是对系统中的一些基础设施进行初始化;最后则是更高层次的初始化,如根设备和 外部设备的初始化。了解系统的初始化过程,有益于更好地移植内核。 1. 内核移植 2. 涉及文件分布介绍 2.1. 内核移植 2.2. 涉及的头文件 /linux-2.6.1
[单片机]
LRF020 DRIVER FOR LINUX(BASED ON TQ2440/ARM9)
LRF020 DRIVER FOR LINUX ======================= using includes/linux/spi/spidev.h,driver/spi/spidev.c 2012-3-23 xiaoyang@HIT Kernel Version: linux2.6.30 Board Info: tq2440 arm9(S3C2440) http://www.armbbs.net/forum.php LRF020: 2.4GHZ RF module:http://www.lustech.com.cn/index.php?case=archive&act=show&aid=24 Source@Git:
[单片机]
嵌入式Linux下IC卡接口设计与驱动开发
引 言 随着现代工业社会逐步向信息社会的过渡,信息将扮演愈来愈重要的角色,成为现代经济生活中的成功要素。IC卡作为卡基应用系统中的一种卡型,是利用安装在卡中的集成电路(IC)来记录和传递信息的;具有存储量大、数据保密性好、抗干扰能力强、存储可靠、读写设备简单、操作速度快、脱机工作能力强等优点,其应用范围极为广泛。 我们基于公用电话IC卡的应用,开发了多媒体信息终端产品,在传统公用IC卡电话功能的基础上增加了上网、邮件、电子支付、信息浏览等各种多媒体功能,统一采用公用电话IC卡进行收费。目前设计的IC卡读写器和驱动软件已经应用于我们的多媒体终端产品中。 1 嵌入式Linux下设备驱动模块简介 Linux系统将设备分成三种
[嵌入式]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 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