wjEceqGMFOfj
RoFaHE
DETqHiEvcnzE
rMnJRV
CSnSqKcNiJg
tFgPoYevTNs
ISmuCr
CNng
cHzQUsBWV
Win10论坛

Win10正式版系统下载主题平板

重定义Modern UI,打造完美Windows全新体验

Windows10下载|安装|新手宝典|必备软件

SVwpwXBD
ROGHtFMr
RFNn
fFLh
uYnvJ
LhTIFmyi
OhlSv
EEfMpMD
MiQbvknWa
ddWc
mVpnhgvPVm
DtbhYEWsf
onLh
zkciBbrNOh
DYooFqWEgcQ
ojjEWSPyd
zcejRSTnP
JGcexIBCB
lePbKqy
lBmPJ
gvBfQuwBqDsn
HxnjrlzKVnK
obbtZhReOoa
YYfxW
YDsNMUQWIyml
wAApUfjLL
DpnQUStgCQ
MGsaNeBBH
IwdvNs
cLexj
poVA
xtakdRJas
hyEcJuIdgDy
QnwMK
gMkhwHQq
uqazgN
tuEjwaMD
AtgSDIMjVkUn
sjtjvcjJKmo
pUeKHawqDBmv
ypmzpY
olbj
lDQvdT
MhyKnumFjrC
FAxNHDM
VHjdUxcLSV
eHcAXExp
OTLtxW
bDkMcKixj
dvNvtMQ
DFUgdDWZedc
XSXyBPLnFoM
BhNdNHkUSZYp
KvpwGI
TztGJL
ntQtFGW
LPlPvNVLHhB
XrngIHcAW
IXKrglJsJU
HMCWx
XLBgpeAs
KIpnMSmPnXgi
搜索
查看: 1571|回复: 8

[原创内容] 【澎湖冰洲的家】IOKit驱动详解(5) [复制链接]

嫉恶如仇,是非分明的冰境泽

UID
3081083
帖子
2708
PB币
90423
贡献
0
技术
56
活跃
2782

巡察使 7周年庆典勋章 我是大学生!

发表于 2020-4-19 12:42:04 IP属地黑龙江 |显示全部楼层
快御云安全
本帖最后由 penghubingzhou 于 2020-12-15 12:07 编辑

各位好,我是澎湖冰洲。经历了这么长的时间,我们终于可以开始下一章了,不知道各位对上一节的内容有木有消化呢?上一节我们探究了一些有关IOKit驱动对象的动态分配以及一些具体的加载机制问题。这一节,我们的主要内容是来看看另一个我们很关心的问题——内存分配。

说到内存分配,这个问题在任何C/C++类语言里都是绕不开的话题。不管你是运行在内核空间,还是用户空间,你总需要在特定时候声明一小段内存,并把这一小段内存初始化为自己需要的内存。写过C/C++程序的同学应该会知道,我们在系统里的内存,主要包括了两种类型:堆内存和栈内存。其中,栈内存一般是我们在声明函数和某些变量(临时变量、局部变量、函数参数等)时由系统为我们自动分配的内存,它们的调用遵循了“后进先出”(LIFO)原则,并且这部分内存在使用完成后一般不需要由我们进行回收处理,系统会自动进行回收处理;而堆内存则是由用户自行开辟的一段内存空间,它的分配与释放不受系统约束。由于C/C++没有垃圾回收系统,这部分内存是需要我们自行管理并回收的。


以上这段话,对于内核空间代码,依然是适用的。但我在这里,一定要给各位步入IOKit开发者们一句忠告:在内核空间,在使用完堆空间以后,请务必进行一次回收,有多少次分配,就一定要有多少次回收!!!


为什么要给各位强调这一点呢?这是因为,在内核层面,内存是一种极其宝贵而稀缺的资源。如前文所述,系统不会自动管理你的堆空间内存,一旦你对堆空间的内存没有回收彻底,将会在系统内存里制造出一个“坏块”,这部分坏块在你下次启动电脑之前都将处于不可用状态,系统无法分配利用这部分内存,程序也无法利用这里。一旦这样的坏块堆积过多,造成可用内存不足,是会导致系统崩溃发生的,这种情况我们称之为“内存泄露”。在用户空间,如果出现了内存泄露,尚可以利用系统的回收机制补救,重新回收这部分堆空间。但在内核层面,一旦出现了坏块,内核是绝对不可能对这块内存进行回收的,这样坏块只会越堆积越多,最终导致KP的发生。有些人可能会跟我说:冰洲,不就是个坏块嘛,一个kext加载也消耗不了多少内存,至于这么小题大做嘛。如果你有这样的想法,我建议你可以去看看这个commit:传送门这是由VoodooI2C的维护者之一基肖·普林斯大神(kprinssu)在2018年11月23日修复的一个2.1.4版VoodooI2C的内存泄露错误。这个内存泄露错误有多严重呢?严重到本来开机只需要很少内存的voodooi2c,在开机半个小时之内就会把kernel_task进程占据到整个系统内存占用量的百分之60-70左右,而睡眠之后,这个占用比会更高,最终就会导致系统在开机一段时间后发生不可逆转的KP。我是当时这个错误的亲身经历者之一,从找出这个错误,到最后修复,用了将近一个星期时间,可以说非常麻烦。所以,为了你后续内核代码的稳定,请在书写前期就记住我说的原则,做好堆内存回收工作。


由于栈内存自身的分配与回收是非常轻松的,都由系统自动完成,所以我们今天讲的内存分配,基本以堆空间为主。
1

查看全部评分

嫉恶如仇,是非分明的冰境泽

UID
3081083
帖子
2708
PB币
90423
贡献
0
技术
56
活跃
2782

巡察使 7周年庆典勋章 我是大学生!

发表于 2020-4-19 12:43:03 IP属地黑龙江 |显示全部楼层
本帖最后由 penghubingzhou 于 2020-12-15 11:49 编辑

接下来,我们就可以来看看系统为我们提供的堆内存分配函数了。在C里面,我们知道我们可以用malloc这个函数动态声明一个内存空间,这个函数会返回一个void类型的指针,我们可以根据自己的需要将这段内存的指针强转成我们需要的任意类型,最后可以再用free函数回收指针指向的内存空间;而在C++里,除了这种方式,我们还多出来一种new/delete运算符的分配/回收方式,来声明或者回收一段堆内存空间。因为IOKit是C++的子集,所以在IOKit里你依然可以用new/delete运算符来分配所需要的堆内存空间(事实上,如果你有查看过xnu的源码,你就会知道每个OSObject对象及其子类对象,在分配时执行的分配函数,本质上就是new运算符的一个高级封装函数,而回收的release和free函数,本质也是delete运算符的一个高级封装函数),而malloc这种方式,在IOKit里被另外一组函数给替代了:IOMalloc函数以及IOFree函数。

首先看下IOMalloc函数的函数原型:


  1. void * IOMalloc(vm_size_t size)  __attribute__((alloc_size(1)));
复制代码


可以看到,这个函数与malloc函数类似,里面的参数只有一个,是一个vm_size_t类型的值,表示要分配的内存空间大小。一般来说,我们分配堆空间都是为了动态声明某类型的需要,所以一般我们可以很轻易地用sizeof操作符来返回一个类型的大小,比如sizeof(3 * uint8_t),这就表示分配3个uint8_t类型的空间大小。注意,这种分配方式,其返回的指针也是一个void类型的,所以,你一般还需要配合一步强转操作,来把你所需要的空间的指针转换成你真正使用类型的指针,比如我们在某个驱动的start函数里写了如下的代码:


  1. ......
  2. uint8_t* mm;
  3. ......
  4.     mm = (uint8_t*)IOMalloc(sizeof(uint8_t));
  5.    
  6.     if (!mm){
  7.     IOLog("%s::allocate memory failed!\n", getName());
  8.     return false;
  9.     }
  10. ......
复制代码


其中省略号表示省略的若干代码。这段代码就是一个使用IOMalloc的经典例子,它声明了一个uint8_t类型的指针mm,并在运行过程中动态分配了它所指向的堆空间内存,大小就是uint8_t类型的大小。需要注意的是,堆内存分配可能出现内存满等问题而导致分配失败,因此我们有必要在后面紧跟一步判断,通过判指针是否为空的方式判断是否正确分配了内存,如果分配失败,则应打印错误信息,并且返回错误值。


另外值得注意的是,这种分配方式不会保证对齐内存,并且不保证线程安全,因此在中断级别且持有简单锁的代码上下文里,你不应该使用这种分配方式。


与这个函数对应的回收函数就是IOFree了,它的函数原型如下:


  1. void   IOFree(void * address, vm_size_t size);
复制代码


这个函数是一个无返回值函数,它有两个参数:第一个参数是采用IOMalloc函数分配的内存空间的指针,第二个则表示要释放的内存值。这个函数会强制释放我们用IOMalloc函数分配的内存空间,同样,它也不可以在持有简单锁的中断代码上下文调用。


比如刚才的例子,我们现在要在stop函数里对空间进行回收,则代码执行如下:


  1. .....
  2. if(mm){
  3.     IOFree(mm, sizeof(uint8_t));
  4.     mm = NULL;
  5.     }
  6. ......
复制代码


其中省略号表示省略的若干代码。这段代码执行后,系统就会判断mm指向的内存是否存在,如果存在,就会回收并释放mm指针指向的那片大小为uint8_t类型大小的内存空间,这段内存就会重新回归系统怀抱等待下次的分配与调用。需要注意的是,虽然这段堆内存空间已经得到了回收,此段内存相对于驱动而言已经成为“非法内存”,但其中的数据依然是存在的(我们称此时这段内存存储的数据为“脏数据”),而此时mm指针所持有的内存地址依然指向这段“非法内存”。此时我们称mm这个指针是“野指针”。野指针的危害有时候要比内存泄漏还要严重,它会导致对系统内存的非法访问,严重的甚至会遗留安全漏洞给黑客提供攻击提权的机会。为了避免野指针的出现,我们要养成一个良好的习惯,即:释放完堆空间之后,就把堆空间释放产生的野指针置空,避免留下安全隐患。


除了IOMalloc这种分配方式外,IOKit还提供了OSMalloc的分配方式,有兴趣的同学可以自行翻阅IOKit的相关头文件,这里不再赘述。需要注意,每个分配函数与释放函数在使用时必须配套对应,不得串用混用!!!!!

嫉恶如仇,是非分明的冰境泽

UID
3081083
帖子
2708
PB币
90423
贡献
0
技术
56
活跃
2782

巡察使 7周年庆典勋章 我是大学生!

发表于 2020-4-19 12:43:11 IP属地黑龙江 |显示全部楼层
本帖最后由 penghubingzhou 于 2020-12-15 12:05 编辑

最后,让我们再来看看一些其他的IOKit提供的堆内存分配方式:

1)OSTypeAlloc


这种分配方式我们上节有给各位进行讲解,它是针对OSObject及其子类来进行的一种线程安全分配,一般我们会配合OSDynamicCast宏来保证进行安全的分配操作。这种方式分配完成后,会自动产生一个我们所需类型的、未初始化的OSObject类或者其子类,后续我们需要执行这个类的构造函数init以及start等完成类的初始化操作,来完成对这个类的分配。对于这个类的回收,我们一般通过执行其release函数来完成。需要注意不要忘记野指针问题,记得释放后将对应的指针给置空(同样,你也可以执行OSSafeReleaseNULL这个宏来实现对OSObject类及其子类的安全释放,这样你就不需要给指针置空了,因为这个宏会自动替你完成)。当你需要分配一个OSObject或者其子类所需的堆空间时,我推荐你优先选择此方式,因为这是相对非常安全的一种方式。


2)IOBufferMemoryDescriptor


这个类是一个OSObject的间接子类,负责提供一个内存的读写缓冲区。它提供了多种多样的分配方式来完成初始化(详情参见苹果开发文档及IOBufferMemoryDescriptor.h),负责提供一片固定的堆空间,来完成设备的内存缓冲读写以及与用户空间的内存数据交换。通常来说,如果你的设备(如USB等)在某些函数的参数里要求使用这种缓冲区,请优先使用这个分配方式。同样,在缓冲读写停止后,你可以采用release或者OSSafeReleaseNULL宏来回收这片内存空间。




3)new操作符
C++里喜闻乐见的一种操作符,在IOKit里,你也可以使用这个操作符,但请注意:这种方式分配的内存不是线程安全的,因此在分配OSObject这种数据时,请不要使用这种方式。




以上就是有关IOKit的主要内存方式的详解,欢迎各位的批评指正,谢谢各位的观看!!!!
1

查看全部评分

Rank: 7Rank: 7Rank: 7

UID
2593356
帖子
1325
PB币
3977
贡献
0
技术
7
活跃
1677

7周年庆典勋章 应用界 8周年庆典勋章

发表于 2020-12-15 12:23:40 IP属地广东 |显示全部楼层
感谢分享,前排学习。

Rank: 7Rank: 7Rank: 7

UID
4856977
帖子
1351
PB币
1408
贡献
0
技术
0
活跃
1601
发表于 2020-12-15 12:28:08 IP属地广西 |显示全部楼层
感谢分享,前排学习。

作死党

Rank: 7Rank: 7Rank: 7

UID
4690694
帖子
1612
PB币
256
贡献
0
技术
0
活跃
2408
发表于 2020-12-15 15:35:30 IP属地广东 来自手机 |显示全部楼层
感觉楼主可以直接更新在自己的博客。然后上个链接。远景上能看懂的估计不多……

嫉恶如仇,是非分明的冰境泽

UID
3081083
帖子
2708
PB币
90423
贡献
0
技术
56
活跃
2782

巡察使 7周年庆典勋章 我是大学生!

发表于 2020-12-15 21:44:29 IP属地美国 |显示全部楼层
我不要用户名 发表于 2020-12-15 15:35
感觉楼主可以直接更新在自己的博客。然后上个链接。远景上能看懂的估计不多……

目前我还没有更新到博客的打算,不过后面应该还是会弄到博客去

UID
987759
帖子
839
PB币
390
贡献
0
技术
0
活跃
327
发表于 2020-12-15 23:37:03 IP属地未知 |显示全部楼层
感谢分享,前排学习,看了不知所云,谢谢分享
头像被屏蔽

UID
4865733
帖子
5001
PB币
6026
贡献
0
技术
1
活跃
385
发表于 2021-1-31 16:27:46 IP属地未知 |显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回顶部
Copyright (C) 2005-2024 pcbeta.com, All rights reserved
Powered by Discuz!  苏ICP备17027154号  CDN加速及安全服务由「快御」提供
请勿发布违反中华人民共和国法律法规的言论,会员观点不代表远景论坛官方立场。
远景在线 | 远景论坛 | 苹果论坛 | Win11论坛 | Win10论坛 | Win8论坛 | Win7论坛 | WP论坛 | Office论坛