ldd3_模块VS应用程序

本文探讨了内核模块与应用程序的区别,包括它们的启动方式、函数调用机制及运行环境的不同。此外还介绍了用户空间与内核空间的概念,以及在开发过程中如何避免命名冲突。
内核模块 VS 应用程序
起始与结束

应用程序一般从main()开始, 它执行一些指令, 再结束(返回值). 

Every module must have an entry function and an exit function. 

不同于应用程序, 模块从初始化函数开始, 这个初始化函数名可以是init_module(), 也可以是你通过module_init宏注册的其他函数名. 该初始化函数被称为模块的入口函数. 

入口函数的作用: 它告诉内核, 该模块提供了哪些功能, 并设置内核以便在需要这些功能的时候调用模块的函数. 这一过程完毕之后, 入口函数马上返回, 直到内核需要调用模块提供的函数时, 模块才开始被加载, 执行.

有入口, 就自然有出口. 出口函数可以是cleanup_module(), 也可以是你通过module_exit宏注册的其他函数名. 出口函数"undo"入口函数执行的所有工作: 清除入口函数注册的模块功能.

可调用的函数

在应用程序或模块程序中都可调用程序本身为定义的函数. 不同的是:
  • 应用程序可调用C库所提供的函数(比如libc提供的printf()).
  • 模块程序只能调用内核所提供的函数(比如内核提供的printk()).
由于内核无法访问C库, 所以模块程序中无法调用C库中的函数. 好在内核本身提供了一些C库中的函数或者替代函数.

查看 /proc/kallsyms , 可直到内核输出了那些函数. 
由于调用了函数本身未调用的外部函数, 所以应用程序和模块程序都需要解析这些外部函数, 它们也互不相同:
  • 应用程序在linking阶段, linker解析外部函数, 将C库所提供的函数添加到对应位置.
  • 模块程序在insmod时, insmod解析内核所提供的外部函数.
实际上, 上述的应用程序和模块程序的函数调用还是有联系的. 让我们来回忆一下库函数和系统调用的关系. 库函数可能最终调用一个系统调用(比如printf(), 会调用write()), 而系统调用实际上就是内核所输出的函数!

写一个hello, world的应用程序, 编译它, 并运行 $ strace ./hello 看看它调用了那些系统函数! 

用户空间 VS 内核空间

用户程序运行于用户空间, 模块运行于内核空间.

内核的功能之一就是实现资源资源分配, 资源包括CPU, 内存, 硬件资源... 在执行一个程序时, 内核为程序分配资源, 为了便于管理资源分配, 内核划分了两个运行级别: 管理员模式和用户模式, 分别对应内核空间和用户空间.

这两个运行级别由CPU来实现, 比如: X86的CPU划分了4个运行级别, 这些级别以ring命名. Linux只使用其中的两个级别: 最高的为管理员模式(ring0), 最低的为用户模式(ring3).

用户模式与管理员模式的区别在于它们具有不同的内存映射: 用户模式只能运行于受保护的用户空间中,它所能访问的资源是受限的. 而管理员模式运行于内核空间, 它能访问所有资源.

以最终调用系统调用的库函数为例, 当库函数调用系统调用时, 运行级别切换到管理员模式, 即程序运行于内核空间. 此时"内核代应用程序执行某些功能". 当系统调用返回时, 切换回用户模式. 此时的进程上下文未变, 只是运行级别发生了改变.

实际上, 在Linux中, 处理器总是处于下列三个状态之一:
1, 在内核空间中, 处于进程上下文, 内核代进程执行.
2, 在内核空间中, 处于中断上下文, 运行中断处理程序, 此时没有对应的进程.
3, 在用户空间中, 执行进程中的用户代码. 
命名空间 ( Name Space)

不管是写用户程序还是些模块程序, 都要注意变量的命名.

用户程序:

写比较小的C程序时, 你可以选择你觉得方便的变量命名. 如果程序的规模比较大, 命名就要遵循一定的标准, 如果定义的全局变量中有重名的, 就会造成"命名空间污染 (namespace pollution). 所以在开发用户程序时要注意变量的命名问题: 最好遵循一定的标准, 避免C语言的保留字.

模块程序:

开发模块程序的时候更要注意变量命名: 因为所有的模块都要和整个内核连接.

最好的方法是给你模块代码中所有的变量加上static限定符, 并给你的符号(symbol)添加合适的前缀. (所有的内核前缀应该是小写!)

如果你不想声明static, 你也可以声明一个符号表, 并想内核注册它. 符号表后面再讨论.

/proc/kallsysm列出了内核知道的符号, 而且模块于内核共享代码空间, 所以你可以在模块程序中访问它们.


代码空间 ( Code Space)

要理解代码空间就要理解Linux的内存管理. 理解MM("memeory management", 或者"美妹"ldd3笔记_4_模块VS应用程序 - challengezcy - challengezcy的博客) 难度比较大, 这里只涉及皮毛.

应用程序
当进程被创建时, 内核为该进程分配物理内存. 这些内存被进程用以存放执行代码, 变量, 堆栈... 该内存从0x00000000开始, 扩展到某个需要的地方. 每个进程只能访问一个内存空间, 任何两个进程的内存空间不能重叠. (这里不讨论访问另一个进程的地址空间的情况).

注意, 进程可见的地址和内存实际的物理地址是不相同的! 比如, 某个进程访问地址为0xbffff978的内存时, 它所访问的实际物理内存地址并不是该地址. 这都是内核的内存映射造成的. 它所访问的实际内存地址可能是以0xbffff978为名的一个索引, 它指向内存物理地址的某处.

内核
内核具有自己的内存空间. 由于内核能动态地加载到内核中或从内核中卸载, 模块与内核共享代码空间. 所以, 当模块程序段错误时, 即引发整个内核的段错误, 所以编写模块程序的时候要格外小心溢出引发段错误!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值