我为热插拔SCSI设备编写了一个设备驱动程序kext,它在某种程度上基于Wagerlabs代码(使用驱动程序-用户-客户端应用程序模型),一切正常。唯一剩下的问题是驱动程序似乎没有得到一致释放,尤其是在应用程序崩溃的情况下。例如,当我尝试卸载kext时,即使设备断开连接,应用程序关闭,仍然存在驱动程序和用户客户端的未完成实例(驱动程序的数量通常超过用户客户端)。
我已经登录了free()
等驱动程序功能,当我关闭计算机时,我可以看到这些功能正在执行,所以实例显然仍然可以终止。哪种"正确"的方法可以确保驱动程序实例被终止和释放,即使主机应用程序崩溃、终止不当或事情通常没有按计划进行?
如果在没有用户客户端应用程序运行的情况下拥有用户客户端类实例,那么保留用户客户端实例的频率肯定高于释放用户客户端实例。例如,您可能会在主驱动程序类中保留对客户端实例的保留引用。在用户客户端类的stop()
方法中,确保从驱动程序中删除该客户端实例。
另一件需要注意的事情是:确保从内置IOService方法(如stop()
、free()
等)的重写版本调用超类实现。不这样做通常会使IO Kit处于不一致的状态。
最后,调试I/O工具包驱动程序中保留泄漏的一种有用技术是,通过用日志版本覆盖方法来实际记录保留和释放:
void MyClass::taggedRetain(const void* tag) const
{
OSReportWithBacktrace(
"MyClass" CLASS_OBJECT_FORMAT_STRING "::taggedRetain(tag=%p)n", CLASS_OBJECT_FORMAT(this), tag);
IOService::taggedRetain(tag);
}
void MyClass::taggedRelease(const void * tag) const
{
OSReportWithBacktrace(
"MyClass" CLASS_OBJECT_FORMAT_STRING "::taggedRelease(tag=%p)n", CLASS_OBJECT_FORMAT(this), tag);
int count = getRetainCount();
IOService::taggedRelease(tag);
if (count == 1)
printf(
"MyClass::taggedRelease(tag=%p) final donen", tag);
else
printf(
"MyClass" CLASS_OBJECT_FORMAT_STRING "::taggedRelease(tag=%p) donen", CLASS_OBJECT_FORMAT(this), tag);
}
此代码中的宏在标题中定义如下:
#define CLASS_OBJECT_FORMAT_STRING "[%s@%p:%dx]"
#define CLASS_OBJECT_FORMAT(obj) myClassName(obj), obj, myRefCount(obj)
inline int myRefCount(const OSObject* obj)
{
return obj ? obj->getRetainCount() : 0;
}
inline const char* myClassName(const OSObject* obj)
{
if (!obj) return "(null)";
return obj->getMetaClass()->getClassName();
}
#endif
我应该解释一下,taggedRetain()
和taggedRelease()
是retain()
和release()
的实际底层实现——如果覆盖后者,您将看不到任何来自OSCollections的保留和释放,因为它们使用标记的版本(带有非null标记)。
不幸的是,OSReportWithBacktrace()
生成的回溯只是一堆十六进制指针,但您可以使用gdb查找这些指针。
在任何情况下,通过记录对象的保留和释放,您都可以查看所有保留,并确保它们与正确位置的释放相匹配。当心自行车!