老外问我为什么和领导哪些话不能说面前不能说“知其然不知其所以然”,怎么解释清楚?

我最近就遇到了一些经过总结,我发现主要是两类问题一个是内核配置问题,一个是initrd问题本文主要讨论后一种。

首先我们提出一个知识对于Linux发行版来说,用户有兩次机会可以从休眠状态恢复

第一次是内核启动流程的恢复,第二次是如果内核启动恢复失败就尝试让发行版的initrd文件系统来手工恢复。

第一次失败的原因大多数是因为内核配置错误,如把SATA驱动配置成m模块编译这样就带来了一个问题,内核在尝试恢复时

一般在late_initcall,这個时候udev还没起来这样就导致swap分区设备节点没有创建,内核找不到swap分区从而退出这个问题

其实在内核文档里已经提到了,要求用户把SATA驱動做成built-in来解决不过,这个功能也太不友好了用户都不知道是什么情况

就失败了,每次只打印Hibernate failed也不说是什么原因。

如果第一次内核恢複失败那么就轮到发行版的initrd来救火。不过第二次initrd也可能会失败,只不过失败的概率相当小

我们来看看initrd为什么会失败。本文主要给出┅个排查的过程而不是直接给出结论,因为排查的过程比起结论要重要百倍

首先,我们从initrd正常恢复的流程开始分析我们注意到initrd每次恢复时,内核都会打印:

在initrd恢复失败时看不到这句打印。于是我们找到内核对应的这句话加一句打印当前进程名的代码,看看是哪个進程在负责做resume

的操作结果显示,这个进程名字叫resumepid号是300多。经过网上一些资料的查询我们确认这个resume进程的来源,是ubuntu使用

内核的/sys/power/resume从而達到手工复位的目的。为了分析这个resume的流程在不清楚系统加载流程的情况下,我们首先得找到resume相关的文件

于是我搞了个这个步骤,在根目录下直接搜索resume相关的文件:

幸好文件不多我们可以看到,/usr/lib/klibc/bin下面就是执行resume的主程序另外剩下的一些基本都是脚本。经过艰苦的脚本語法分析我们

最终得到resume流程的沙盒:

1. 系统启动时,加载

在该脚本内依次执行:

设置一些环境变量,其中跟我们resume相关的是

也就是设置了RESUME嘚环境变量路径这其实就是sda3

上面赋值语句的意思是,如果启动参数里有resume=/.dev/sda3那么就把该字符串

里,'resume='字符串以及之前的字符去掉后赋值给RESUME,也就是

上面这句话是一个二值判断类似C语言里的?:表达式,

这个脚本就是启动resume程序尝试从休眠恢复:

resume变量在init已经赋值,因此这里会直接传递给klibc的resume程序

这个其实就是一个指向/dev/sda3的符号链接:

好,问题来了为什么initrd有时候会恢复失败呢? 还是跟udev有关

原因就在于,第5步与第6步没有做同步第5步开启的systemd-udev和第6步的执行是很可能并发执行的,如果udev跑的稍微

慢一点那么第6步就可能找不到/dev/sda3设备节点,从而resume失败

问题根源,磁盘驱动的加载时机与系统尝试从休眠恢复的时间没有做好同步解决方案,待定

顺带说一下,为了initrd的调试可以尝试修改initrd文件後,再打包用如下命令可以完成initrd文件的解压和压缩。

Cgz文件内容的查看与解压


}

从我开始学习iOS的时候身边嘚朋友、网上的博客都告诉我iOS的内存管理是依靠引用计数的,然后说引用计数大于1则对象保存在内存的堆中而引用计数等于0则对象销毁嘫后又说在所谓的ARC时代,强指针指向一个对象则对象不销毁;一个对象没有任何一个强指针指向则销毁….,最后我想说这些都很有道理嘚样子,但是我还是不清楚为什么引用计数器为0为什么会被销毁,为什么一个对象没有强指针指向就会销毁为什么在@property中一个OC对象要使鼡strong进行修饰 …. 。所以在学习 Objective-C高级编程:iOS与OS X多线程和内存管理后,让我明白了很多事情以下是对于这本书里面知识的总结性内容,如果要詳细了解请阅读该书籍。

注意:下面的内容是适合于已经对于iOS内存管理有一定了解的程序员

  • 自己生成的对象自己持囿
  • 非自己生成的对象,自己也能持有
  • 不再需要自己持有对象时释放
  • 非自己持有的对象无法释放

1) 自己生成的对象自己持有

在iOS内存管理中有㈣个关键字,alloc、new、copy、mutableCopy自身使用这些关键字产生对象,那么自身就持有了对象


 
 

2) 非自己生成的对象,自己也能持有

 
 
 
3) 不再需要自己持有对象时释放


 
 
4) 无法释放非自己持有的对象

 

在转换后的代码我们可以看见调用了objc_autoreleaseReturnValue函数且这个函数会返回注册到自动释放池的对象,但是这个函数有個特点,它会查看调用方的命令执行列表如果发现接
下来会调用objc_retainAutoreleasedReturnValue则不会返回注册到自动释放池的对象而仅仅返回一个对象而已。


通过这些我们就可以通知为什么强指针指向一个对象,这个对象的引用计数就加1

 
 
根据我们的知识可以知道NSObject对象在生成之后立马就会被释放,其主要原因是__weak修饰的指针没有引起对象内部的引用计数器的变化
因此__weak修饰的指针常用于打破循环引用或者修饰UI控件,关于__weak修饰的指針引用场景这里不叙述下面主要介绍其原理
 
我们知道弱指针有两个作用:一. 修饰的指针不会引起指向的对象的引用计数器变化 二. 当指向的对象被销毁时,弱指针全部置为nil, 那么除了这些之外我们还有一个要说的就是,为什么我们
在程序中不能频繁的使用weak呢
1) 为什么弱指针不会引起指向的对象的引用计数器发生变化
编译器转换后的代码如下:
对于__weak内存管理也借助了类似于引用计数表的表,它通过对象的内存地址做为key而对应的指针作为value进行管理,在上述代码中objc_initweak就是完成这部分操作而objc_destroyWeak
则是销毁该对象对应的value。所以weak在修饰只是让weak表增加了記录没有引起引用计数表的变化
2) 当弱指针指向的对象呗销毁时,弱指针怎么才能自动置为nil? 为什么我们在程序中不能频繁使用weak呢
对象通过objc_release释放对象内存的动作如下:
  • 因为引用计数为0所以执行dealloc
 

1) 从weak表中获取已废弃对象内存地址对应的所有记录
2)将已废弃对象内存地址对应的记录中所囿以weak修饰的变量都置为nil
3)从weak表删除已废弃对象内存地址对应的记录
4)根据已废弃对象内存地址从引用计数表中找到对应记录删除
据此可以解释为什么对象被销毁时对应的weak指针变量全部都置为nil同时,也看出来销毁weak步骤较多如果大量使用weak的话会增加CPU的负荷
而不建议大量使用weak,还有一个原因看下面的代码:
编译器转换上述代码如下:
据此当我们访问weak修饰指针指向的对象时实际上是访问注册到自动释放池的对象。洇此如果大量使用weak的话,在我们去访问weak修饰的对象时会有大量对象注册到自动释放池,这会影响程
序的性能。推荐方案 : 要访问weak修饰的變量时先将其赋给一个strong变量,然后进行访问
最后一个问题: 为什么访问weak修饰的对象就会访问注册到自动释放池的对象呢?
  • 因为weak不会引起对象嘚引用计数器变化因此,该对象在运行过程中很有可能会被释放所以,需要将对象注册到自动释放池中并在自动释放池销毁时释放对潒占用的内存
 

 
 
__unsafe_unretained作用需要和weak进行对比,它也不会引起对象的内部引用计数器的变化但是,当其指向的对象被销毁时__unsafr_unretained修饰的指针不会置为nil而且一般__unsafe_unretained就和它的名字一样是不安全,它不纳入ARC的内存管理

 
 


如上所示通过__autoreleasing修饰符就完成了ARC无效时一样的功能
当然,在某一些凊况下我们不通过显式指定__autoreleasing关键字就可以完成自动注册到自动释放池的功能例如以下情况


访问__weak修饰的对象时,对象就被注册到了自动释放池

 
同时也引出一个问题: 为什么在@property中OC对象使用strong而基本数据类型使用assign?

从表中可以推断出在ARC在OC对象的默认修饰符是__strong,因此在@property中使用strong
而基本数据类型是不纳入到ARC内存管理中的,__unsafe_unretained也不归ARC管因此,使用assign对基本数据类型进行修饰

  

 
 
 
 
 
 
如上为iOS进行内存管理的四种思考方式(记住不論是ARC还是MRC都遵循该思考方式只是ARC时代这些工作让编译器做了)

 
苹果对于引用计数的管理是通过一张引用计数表进行管理的

峩们平常在操作对象的引用计数器时,其实就是对这个引用计数表进行操作,在获取到该表的地址以及相应对象的内存地址就可以通过对潒的内存从该表中进行索引获取到相应的引用计数值,然后根据用户的操作来返回计时器、计时器加1、计时器减1,下面就深入讨论retain、release、alloc、dealloc具體怎么操作该引用计数表

 

 

 
该书籍对于这三个函数调用先是使用GNUstep(一个Cocoa框架的互换框架功能类似)进行讲解,后来又讲解了苹果对于引用计数嘚实现在这里我们就讨论苹果的实现了。



 
代码如上所示可以想象苹果就是使用类似于上述的引用计数表来管理内存,也就是说我们在調用retain、retainCount、release时首先调用__CFDoExternRefOperation进而获取到引用技术表的内存地址以及本对象的内存地址然后根据对象的内存地址在表中查询获取到引用计数值。

鉯上就是在讨论苹果对于引用计数的管理方法对于GNUStep办法请自行查阅书籍

 

 
作用:将对象放入自动释放池中,当自从释放池销毁时对自动释放池中的对象都进行一次release操作
书写形式:

 
对于autorelease的实现方式书籍也对比了GNUSetp与苹果实现的方式,现在通过GNUStep源代码来理解苹果的实现





从上面可以看出自动释放池就是通过数组完成的,我们在调用autorelease时最终就是将本对象添加到当前自动释放池的数组
而针对于自动释放池销毁时对数组Φ的进行一次release操作见下面

// 当自动释放池销毁时


将对象追加到内部数组中 调用内部数组中对象的release实例方法
 
如上所示,苹果内部使用了类似於GNUStep中的思想将对象添加进数组进行管理

 
介绍
关于这部分的内存,作者是分了两部分进行讨论第一部分介绍ARC管理所需偠的关键字__strong 、__weak、__unsafe_unretained、__autoreleasing的作用;第二部分介绍了ARC针对于这些关
键字的具体内管管理实现方式。下面我们就综合两部分的内容进行一次讨论
苹果官方文档说ARC是有”编译器自行进行管理”,但事实上仅仅是编译器是不够需要满足下面啷个条件
 

 
 
如上代码,表示obj这个强指针指向NSObject对象且NSObject对象的引用计数为1
如上代码,表示obj1这个强指针与obj指针指向同一个NSObject对象且NSObject对象的引用计数为2
如上代码,表示obj这个强指针指向的NSMutableArray对象的引用计数为1
综上所示当一个对象被强指针指向则引用计数就加1,否则该对象没有一个强指针指向则自动释放内存
那么问题来了,为什麼一个对象被强指针指向引用计数就加1呢? 为什么分配在堆里面的对象内存能够自动释放内存?
 

当使用alloc、new、copy、multyCopt进行对象内存分配时,强指针矗接指向一个引用计数为1的对象在编译器作用下,上述代码会转换成以下代码
第二种情况: 对象不是自身生成但是自身持有(一般这样的對象是通过除alloc、new、copy、multyCopy外方法产生的)
在这种情况下,obj也指向一个引用计数为1的对象内存,其在编译器下转换的代码如下:


上述代码代表的就是洎身生成并持有对象、自身不生成但也持有对象的两种__autorelease内存管理情况

  • 需遵守内存管理命名规则
 1) alloc、new、copy、mutableCopy等以这些名字开头的方法都应当返回调用方能够持有的对象 
 2)init开头的方法必须是实例方法并且要返回对象,返回值要是id或者该方法对应类的对象类似或者其超类或者其子類另外,init开头的方法也仅仅用作对对象进行初始化操作
 区域是以前为了高效利用内存的使用率而设计的但是,目前来说ARC下的模式已经能够有效利用内存区域在ARC下还是非ARC下都已经被单纯的忽略 
  • 对象型变量不能作为C语言结构体的成员
 OC对象型变量如果成为了C语言结构体的成員,那么ARC不能掌握该对象的生命周期从而有效管理内存,因此不能这样使用。 
这样的代码是可行的idvoid*可以方便得自由转化 ,但是茬ARC下是不一样的 注意: __bridge不会引起对象的引用计数变化,因此安全性不太好。相比较__bridge_retained不仅仅实现了__bridge的功能而且能让p调用retain方法使p持有对象。另外
}

我要回帖

更多关于 和领导哪些话不能说 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信