在上一篇macOS 内核之 hw.epoch 是个什么东西?我们提到 XNU 内核包含了 BSD 和 Mach,其中 Mach Kernel 提供了 I/O Kit 给硬件厂商写驱动用的。这个部分在 NeXT 时期是用 Objective-C 提供的 API,叫做 Driver Kit,后来乔布斯回到苹果之后,升级了 BSD 和 Mach 的代码,于是在 OS X 中提供了 C++ 接口的 I/O Kit。
根据官方的这份文档,以下系统支持 I/O Kit:
- iOS 2.0+
- macOS 10.0+
- Mac Catalyst 13.0+
I/O Kit 里我们可以通过三种不同的方式获取电池信息,位于 IOKit/pwr_mgt
的 Power Mangement 接口,位于 IOKit/ps
的 Power Sources 接口,以及通过 IOServiceGetMatchingService
获取 AppleSmartBattery
Service 接口。
1. IOPM (Power Management) API
IOPM 接口需要使用 Mach Port 跟 IOKit 进行 IPC 通信,所以我们先来了解一点 Mach Port 的背景。
1.1 Mach Port
XNU 是一个混合内核,既有 BSD 又有 Mach Kernel,上层还有各种各样的技术,所以在 macOS 系统中,IPC (跨进程通信)的技术也多种多样。Mattt 在 NSHipster 上写过一篇 IPC 的文章: Inter-Process Communication - NSHipster 对此有过详解。
Mach Port 是在系统内核实现和维护的一种 IPC 消息队列,持有用于 IPC 通信的 mach messages。只有一个进程可以从对应的 port 里 dequeue 一条消息,这个进程被持有接收权利(receive-right)。可以有多个进程往某个 port 里 enqueue 消息,这些进程持有该 port 的发送权利(send-rights)。
如上图,PID 123 的进程往一个 port 里发送了一条消息,只有对应的接收端 PID 456 才能从 port 里取出这条消息。
我们可以简单把 mach port 看做是一个单向的数据发送渠道,构建一个消息结构体后通过mach_msg()
方法发出去。因为只能单向发送,所以当 B 进程收到了 A 进程发来的消息之后要自己创建一个新的 Port 然后又发回去 A 进程。
手动构建 mach message 发送是比较复杂的,大概长这个样子(代码来自 Mattt 的那篇文章):
natural_t data;
mach_port_t port;
struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_type_descriptor_t type;
} message;
message.header = (mach_msg_header_t) {
.msgh_remote_port = port,
.msgh_local_port = MACH_PORT_NULL,
.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0),
.msgh_size = sizeof(message)
};
message.body = (mach_msg_body_t) {
.msgh_descriptor_count = 1
};
message.type = (mach_msg_type_descriptor_t) {
.pad1 = data,
.pad2 = sizeof(data)
};
mach_msg_return_t error = mach_msg_send(&message.header);
if (error == MACH_MSG_SUCCESS) {
// ...
}
其中最关键的是 msgh_remote_port
和 msgh_local_port
。上述代码是发送消息,所以 msgh_remote_port
就是要接收这条消息的那个进程的 port
。我们得先知道这个 port
信息我们才能往里面发消息。另外例子中使用的是 mach_msg_send()
函数。
port name
留意到在上图中,PID 123 往一个名为 0xabc
的 port
发消息,PID 456 则从名为 0xdef
的 port
里取消息。这里 port name 只对当前进程有意义,并不需要全局一致,内核会自动根据进程 ID 和名字信息找到对应的进程。
Out-of-line memory
我们的代码在用户层调用,需要进出内核层,这是一进一出如果消息体里带上大量的信息就会非常慢。所以如果需要使用 mach message 来发送体积较大的信息,可以使用 “out-of-line memory” descriptor。
我们看到上面 Mattt 的代码使用 mach_msg_send()
函数来发送消息,message.body
带了一个 msgh_descriptor_count
为 1。这个 descriptor
是一个 natural_t
。我看到这里的时候并没有搞懂系统是怎么做 OOL 的 copy-on-write 的。于是照例翻一下 XNU 的源码,我发现 Mattt 的例子并没有使用 OOL descriptor,而是使用了 type descriptor。
typedef struct
{
natural_t pad1;
mach_msg_size_t pad2;
unsigned int pad3 : 24;
mach_msg_descriptor_type_t type : 8;
} mach_msg_type_descriptor_t;
ool descriptor 的结构如下:
typedef struct
{
uint64_t address;
boolean_t deallocate: 8;
mach_msg_copy_options_t copy: 8;
unsigned int pad1: 8;
mach_msg_descriptor_type_t type: 8;
mach_msg_size_t size;
} mach_msg_ool_descriptor64_t;
使用时我们需要把内存地址发过去,内核只负责传递地址指针,等到进程接受到了这条消息之后才会从内存里 copy buffer。
1.2 使用 Master Port 和 IOKit 通信
在 IOKit 里面,所有的通信都通过 IOKit Master Port
来进行,使用以下函数可以获取 master port。
kern_return_t
IOMasterPort( mach_port_t bootstrapPort,
mach_port_t * masterPort );
实际使用时如下:
mach_port_t masterPort;
IOMasterPort(MACH_PORT_NULL, &masterPort)
默认把 bootstrapPort
置空。如果返回值是 kIOReturnSuccess
就成功构建了一个 mach_port_t
用于跟 IOKit 通信。
bootstrapPort
不过在这个 API 里面,获取单一 master port 好理解,那 bootstrapPort 这个参数又是用来干啥的呢?
在上面的例子中 PID 123 和 PID 456 是在已经获知对方的 port name 的前提下才有办法互相通信的。但是如果你不知道对方的 port name 呢?于是 XNU 系统提供了 bootstrap port 这个东西,由系统提供查询服务,这样所有的进程都可以去广播自己的 mach port 接收端的名字,也可以查询其他人的名字。
查询接口大概是这样:
mach_port_t port;
kern_return_t kr = bootstrap_look_up(bootstrap_port, "me.justinyan.example", &port);
注册接口大概是这样:
bootstrap_register(bootstrap_port, "me.justinyan.example", port);
同时 bootstrap port 是一个特殊的 port。其他的 mach port 在父进程被 fork()
的时候,子进程是不会继承 port 的,只有 bootstrap port 可以被继承。
但是,自从 OS X 10.5 开始,苹果引入了 Launchd
这么一个服务,同时弃用了 bootstrap_register()
接口。关于这件事情当时 darwin 开发团队有个长长的邮件列表做了激烈的讨论: Apple - Lists.apple.com
新的接口可以参考 CFMessagePortCreateLocal()
和这篇文章: Damien DeVille | Interprocess communication on iOS with Mach messages
IOPM 获取电池信息接口
上面罗里吧嗦一大堆全是 mach port 的事情,现在终于到正题了。代码非常简单:
NSDictionary* get_iopm_battery_info() {
mach_port_t masterPort;
CFArrayRef batteryInfo;
if (kIOReturnSuccess == IOMasterPort(MACH_PORT_NULL, &masterPort) &&
kIOReturnSuccess == IOPMCopyBatteryInfo(masterPort, &batteryInfo) &&
CFArrayGetCount(batteryInfo))
{
CFDictionaryRef battery = CFDictionaryCreateCopy(NULL, CFArrayGetValueAtIndex(batteryInfo, 0));
CFRelease(batteryInfo);
return (__bridge_transfer NSDictionary*) battery;
}
return NULL;
}
NSDictionary *dict = get_iopm_battery_info();
NSLog(@"iopm dict: %@", dict);
输出:
iopm dict: {
Amperage = 0;
Capacity = 6360;
Current = 6360;
"Cycle Count" = 113;
Flags = 5;
Voltage = 12968;
}
可以看到电池循环次数、容量之类的信息,但是不多。IOPMLib.h
的注释说 不建议大家使用这个接口,可以考虑用 IOPowerSources API 代替。
2. IOPowerSources API
IOPowerSources 的接口比较简单,先用 IOPSCopyPowerSourcesInfo()
取到 info, 然后取 IOPSCopyPowerSourcesList()
,最后再 copy 一下就完事了。
NSDictionary* get_iops_battery_info() {
CFTypeRef info = IOPSCopyPowerSourcesInfo();
if (info == NULL)
return NULL;
CFArrayRef list = IOPSCopyPowerSourcesList(info);
// Nothing we care about here...
if (list == NULL || !CFArrayGetCount(list)) {
if (list)
CFRelease(list);
CFRelease(info);
return NULL;
}
CFDictionaryRef battery = CFDictionaryCreateCopy(NULL, IOPSGetPowerSourceDescription(info, CFArrayGetValueAtIndex(list, 0)));
// Battery is released by ARC transfer.
CFRelease(list);
CFRelease(info);
return (__bridge_transfer NSDictionary* ) battery;
}
NSDictionary *iopsDict = get_iops_battery_info();
NSLog(@"iops dict: %@", iopsDict);
输出:
iops dict: {
"Battery Provides Time Remaining" = 1;
BatteryHealth = Good;
Current = 0;
"Current Capacity" = 100;
DesignCycleCount = 1000;
"Hardware Serial Number" = D**********;
"Is Charged" = 1;
"Is Charging" = 0;
"Is Present" = 1;
"Max Capacity" = 100;
Name = "InternalBattery-0";
"Power Source ID" = 9764963;
"Power Source State" = "AC Power";
"Time to Empty" = 0;
"Time to Full Charge" = 0;
"Transport Type" = Internal;
Type = InternalBattery;
}
可以看到信息多了很多,还有 BatteryHealth
等信息,我们看到我的 MacBook 的电池设计循环次数是 DesignCycleCount = 1000
,然后我已经循环 113 次了。
但是,这批信息里面没有带电池的设计容量。
3. IOPMPS Apple Smart Battery API
IOKit 里提供了一套 IOService 相关的接口,你可以往里面注册 IOService 服务,带个名字,一样是通过 IOMasterPort()
来通信。IOKit 主要是面向硬件驱动开发者的,所以如果你的硬件依赖另外一个硬件,但是另外一个硬件还没有接入,这时候你可以往 IOService 注册一个通知。使用 IOServiceAddMatchingNotification
,等到你观察的硬件接入后调用了 registerService()
你就会收到对应的通知了。
这里我们直接用 IOServiceGetMatchingService()
来获取系统提供的 AppleSmartBattery
service。
NSDictionary* get_iopmps_battery_info() {
io_registry_entry_t entry = 0;
entry = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceNameMatching("AppleSmartBattery"));
if (entry == IO_OBJECT_NULL)
return nil;
CFMutableDictionaryRef battery;
IORegistryEntryCreateCFProperties(entry, &battery, NULL, 0);
return (__bridge_transfer NSDictionary *) battery;
}
NSDictionary *iopmsDict = get_iopmps_battery_info();
NSLog(@"iopmsDict: %@", iopmsDict);
输出:
iopmsDict: {
AdapterDetails = {
Current = 4300;
PMUConfiguration = 2092;
Voltage = 20000;
Watts = 86;
};
AdapterInfo = 0;
Amperage = 0;
AppleRawAdapterDetails = (
{
Current = 4300;
PMUConfiguration = 2092;
Voltage = 20000;
Watts = 86;
}
);
AppleRawCurrentCapacity = 6360;
AppleRawMaxCapacity = 6360;
AvgTimeToEmpty = 65535;
AvgTimeToFull = 65535;
BatteryData = {
AdapterPower = 1106486026;
CycleCount = 113;
DesignCapacity = 6669;
PMUConfigured = 0;
QmaxCell0 = 6812;
QmaxCell1 = 6859;
QmaxCell2 = 6784;
ResScale = 200;
StateOfCharge = 100;
SystemPower = 4625;
Voltage = 12968;
};
BatteryFCCData = {
DOD0 = 128;
DOD1 = 144;
DOD2 = 128;
PassedCharge = 0;
ResScale = 200;
};
BatteryInstalled = 1;
BatteryInvalidWakeSeconds = 30;
BatterySerialNumber = D**********;
BestAdapterIndex = 3;
BootPathUpdated = 1571194014;
CellVoltage = (
4323,
4322,
4323,
0
);
ChargerData = {
ChargingCurrent = 0;
ChargingVoltage = 13020;
NotChargingReason = 4;
};
CurrentCapacity = 6360;
CycleCount = 113;
DesignCapacity = 6669;
DesignCycleCount70 = 0;
DesignCycleCount9C = 1000;
DeviceName = bq20z451;
ExternalChargeCapable = 1;
ExternalConnected = 1;
FirmwareSerialNumber = 1;
FullPathUpdated = 1571290629;
FullyCharged = 1;
IOGeneralInterest = "IOCommand is not serializable";
IOReportLegend = (
{
IOReportChannelInfo = {
IOReportChannelUnit = 0;
};
IOReportChannels = (
(
7167869599145487988,
6460407809,
BatteryCycleCount
)
);
IOReportGroupName = Battery;
}
);
IOReportLegendPublic = 1;
InstantAmperage = 0;
InstantTimeToEmpty = 65535;
IsCharging = 0;
LegacyBatteryInfo = {
Amperage = 0;
Capacity = 6360;
Current = 6360;
"Cycle Count" = 113;
Flags = 5;
Voltage = 12968;
};
Location = 0;
ManufactureDate = 19722;
Manufacturer = SMP;
ManufacturerData = {length = 27, bytes = 0x00000000 *** };
MaxCapacity = 6360;
MaxErr = 1;
OperationStatus = 58433;
PackReserve = 200;
PermanentFailureStatus = 0;
PostChargeWaitSeconds = 120;
PostDischargeWaitSeconds = 120;
Temperature = 3067;
TimeRemaining = 0;
UserVisiblePathUpdated = 1571291169;
Voltage = 12968;
}
可以看到比前面的两次输出多了很多。
CurrentCapacity = 6360;
DesignCapacity = 6669;
有了当前电池容量和设计容量,就可以得到我的电池还剩 95%
的容量。
4. 列出所有 IOService
以上三种方法我都是从 Hammerspoon 的源码中习得。通过阅读这部分接口学习了相关的一些内核层 API 的概念,很有意思。那么在 #3 中 Hammerspoon 的作者是怎么知道系统有一个 IOService 叫做 "AppleSmartBattery" 的呢?我们不妨把系统所有的 IOService 打印出来,然后 grep 看看里面有没有带 battery
或者 energy
关键字的。
IOKitLib.h
里有一个接口 IORegistryCreateIterator()
可以创建一个迭代器,把所有已注册的 IOService 取出来。
核心代码如下:
const char *plane = "IOService";
io_iterator_t it = MACH_PORT_NULL;
IORegistryCreateIterator(kIOMasterPortDefault, plane, kIORegistryIterateRecursively, &it)
有一个开源库实现了这个功能,有兴趣的读者朋友可以看看这里: Siguza/iokit-utils: Dev tools for probing IOKit
➜ iokit-utils ./ioprint| grep -i battery
AppleSmartBatteryManager(AppleSmartBatteryManager)
AppleSmartBattery(AppleSmartBattery)
结果出来两个 battery
相关的,AppleSmartBattery
就是上述例子用到的,AppleSmartBatteryManager
则打印出如下结果:
iopmsDict: {
CFBundleIdentifier = "com.apple.driver.AppleSmartBatteryManager";
CFBundleIdentifierKernel = "com.apple.driver.AppleSmartBatteryManager";
IOClass = AppleSmartBatteryManager;
IOMatchCategory = IODefaultMatchCategory;
IOPowerManagement = {
CapabilityFlags = 2;
CurrentPowerState = 1;
MaxPowerState = 1;
};
IOProbeScore = 0;
IOPropertyMatch = {
IOSMBusSmartBatteryManager = 1;
};
IOProviderClass = IOSMBusController;
IOUserClientClass = AppleSmartBatteryManagerUserClient;
}
只是一堆苹果自家驱动的信息而已。
5. 用于 iOS 系统
我在运行了 iOS 13.1.2 的 iPhone Xs Max 机器上进行了测试。iOS 工程引入 IOKit 会比较麻烦,因为这个 Framework 是不公开的,所以你得把所有的头文件导出来,并且把 #import <IOKit/xxx.h>
的地方都改掉。可以参考此文: [Tutorial] Import IOKit framework into Xcode project | Gary's ...Lasamia
实测 IOPMCopyBatteryInfo
在 iOS 上无效,估计是 iOS 直接不给 mach port 权限到上层。 IOPSCopyPowerSourcesList
和 IOServiceNameMatching
能用。
iops dict: {
"Battery Provides Time Remaining" = 1;
"Current Capacity" = 100;
"Is Charged" = 1;
"Is Charging" = 0;
"Is Present" = 1;
"Max Capacity" = 100;
Name = "InternalBattery-0";
"Play Charging Chime" = 1;
"Power Source ID" = 2490467;
"Power Source State" = "AC Power";
"Raw External Connected" = 1;
"Show Charging UI" = 1;
"Time to Empty" = 0;
"Time to Full Charge" = 0;
"Transport Type" = Internal;
Type = InternalBattery;
}
iopmsDict: {
BatteryInstalled = 1;
ExternalConnected = 1;
}
可以看到信息比 macOS 的少了很多,并且没有包含 cycleCount
这个信息。
5.1 奇技淫巧 hack 之
但是毕竟 iOS 是有 IOKit 框架的,那么有没有什么奇技淫巧可以拿到 IOKit 的信息呢?eldade/UIDeviceListener: Obtain power information (battery health, charger details) for iOS without any private APIs.这个库可以在 iOS 7 - iOS 9.3 上捕获这部分信息。
所使用之操作也是非常有趣。从 iOS 3.0 开始,UIDevice 增加了 batteryState
和 batteryLevel
这两个参数,并且允许开启电池监控 batteryMonitoringEnabled
。通过上文我们已经知道,这些操作最终都是通过 IOKit 来进行的。
IOKit 会从 IORegistry
获取一份最新的电池信息,就像我们的 get_iopmps_battery_info()
方法一样。留意到从 IORegistry
取数据的接口长这样:
IORegistryEntryCreateCFProperties(
io_registry_entry_t entry,
CFMutableDictionaryRef * properties,
CFAllocatorRef allocator,
IOOptionBits options );
重点在第三个参数 CFAllocatorRef
,通常情况下系统会用默认的 CFAllocatorGetDefault()
。我们看看这个 allocator 长啥样CoreFoundation/CFBase.c:
typedef const struct CF_BRIDGED_TYPE(id) __CFAllocator * CFAllocatorRef;
// CFAllocator structure must match struct _malloc_zone_t!
// The first two reserved fields in struct _malloc_zone_t are for us with CFRuntimeBase
struct __CFAllocator {
CFRuntimeBase _base;
CFAllocatorRef _allocator;
CFAllocatorContext _context;
};
以及 CoreFoundation 提供了不少操作:
CFAllocatorGetDefault();
CFAllocatorGetContext();
CFAllocatorCreate();
CFAllocatorSetDefault();
如果能把系统的默认 allocator 替换成自己的实现,那么当我们打开 batteryMonitoringEnabled
然后电池发生变更的时候,系统就回去用 IORegistry
取一份电池信息,就会掉进我们替换掉的 allocator。这时候就能截取 allocator 刚刚 allocate 的内存信息了。真的佩服作者的脑洞。详细的实现大家可以看原来的库: eldade/UIDeviceListener,我们只看关键代码:
// 获取默认 allocator
_defaultAllocator = CFAllocatorGetDefault();
CFAllocatorContext context;
// 获取默认 allocator 的 context
CFAllocatorGetContext(_defaultAllocator, &context);
// 全部改成自己的实现, myAlloc/myRealloc/myFree 都是 C 函数
context.allocate = myAlloc;
context.reallocate = myRealloc;
context.deallocate = myFree;
// 用修改后的 context 创建新的 allocator
_myAllocator = CFAllocatorCreate(NULL, &context);
// 把自己创建的 allocator 替换掉系统的默认 allocator
CFAllocatorSetDefault(_myAllocator);
接下来看看 myAlloc
的实现:
void * myAlloc (CFIndex allocSize, CFOptionFlags hint, void *info)
{
// 做一下线程检查
VERIFY_LISTENER_THREAD();
// 实现一个新的 allocation
void *newAllocation = CFAllocatorAllocate([UIDeviceListener sharedUIDeviceListener].defaultAllocator, allocSize, hint);
// 失败就放过
if (newAllocation == NULL)
return newAllocation;
// 有东西了,赶紧把新的内容塞进准备好的 allocations 变量里,这是个 C++ 的 std::set<void *>
if (hint & __kCFAllocatorGCObjectMemory)
{
[UIDeviceListener sharedUIDeviceListener].allocations->insert(newAllocation);
}
return newAllocation;
}
与此同时,通过 KVO 观察 UIDevice
公开的 batteryLevel
属性,接收 KVO 回调:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if ([change objectForKey: NSKeyValueChangeNewKey] != nil)
{
std::set<void *>::iterator it;
for (it=_allocations->begin(); it!=_allocations->end(); ++it)
{
CFAllocatorRef *ptr = (CFAllocatorRef *) (NSUInteger)*it;
void * ptrToObject = (void *) ((NSUInteger)*it + sizeof(CFAllocatorRef));
if (*ptr == _myAllocator && // Just a sanity check to make sure the first field is a pointer to our allocator
[self isValidCFDictionary: ptrToObject]) // Check for valid CFDictionary
{
CFDictionaryRef dict = (CFDictionaryRef) ptrToObject;
if ([self isChargerDictionary: dict]) // Check if this is the charger dictionary
{
// Found our dictionary. Let's clear the allocations array:
_allocations->clear();
// We make a deep copy of the dictionary using the default allocator so we don't
// get callbacks when this object and any of its descendents get freed from the
// wrong thread:
CFDictionaryRef latestDictionary = (CFDictionaryRef) CFPropertyListCreateDeepCopy(_defaultAllocator, dict, kCFPropertyListImmutable);
if (latestDictionary != nil)
{
// Notify that new data is available, but that has to happen on the main thread.
// Because of the CFAllocator replacement, we generally shouldn't
// do ANYTHING on this thread other than stealing this dictionary from UIDevice...
dispatch_sync(dispatch_get_main_queue(), ^{
// Pass ownership of the CFDictionary to the main thread (using ARC):
NSDictionary *newPowerDataDictionary = CFBridgingRelease(latestDictionary);
[[NSNotificationCenter defaultCenter] postNotificationName:kUIDeviceListenerNewDataNotification object:self userInfo:newPowerDataDictionary];
});
}
return;
}
}
}
}
}
上面一堆嵌套代码判断了一层又一层,最后做了一个 CFPropertyListCreateDeepCopy
然后通过通知转发出去。
CFDictionaryRef latestDictionary = (CFDictionaryRef) CFPropertyListCreateDeepCopy(_defaultAllocator, dict, kCFPropertyListImmutable);
严格来说这种写法并没有用到私有 API,但是非常取巧。如果内核实现代码不用 default allocator 来取 IORegistry 的信息这里就失效了。事实上从 iOS 10 开始这个做法确实也失效了。但是整个思路非常有趣,值得观摩。
5.2 遍历所有的 IOService
上面我们在 macOS 上通过取 AppleSmartBattery
这个 IOService 可以获得更多电池信息,但是在 iOS 上没有。那么我们还能不能寻找其他的 IOService 看看是否有携带了电池信息的呢?
此文iOS IOKit Browser - Christopher Lyon Anderson 使用私有 API 遍历了 iOS 上所有的 IOService,并且在他的截屏中是包含了电池信息的。我 clone 下来发现已经没有 cycleCount
信息了,但是这个项目有个地方挺有意思:
NSString *bundlePath = [[NSBundle bundleWithPath:@"/System/Library/Frameworks/IOKit.framework"] bundlePath];
NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath];
CFBundleRef cfBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)bundleURL);
self.IORegistryGetRootEntryShim = CFBundleGetFunctionPointerForName(cfBundle, CFSTR("IORegistryGetRootEntry"));
先取系统的 IOKit.framework
,然后用 CoreFoundation 的接口来取函数指针,然后就可以使用这批 IOKit 的私有函数了。可惜此方法亦已无效。
6. 小结
iOS 方面暂时还未找到能展示 cycleCount
信息的方法,想必 Battery Health App 应该用了更加厉害的黑科技。可能只有越狱逆向一下才知道它是怎么做到的了。
之前因为 sysctl()
的缘故看了一下 XNU 的源码,结果发现内核层还是有不少有意思的东西。IOKit 作为驱动层的 API,除了获取电池信息之外还能干很多事情。
本文通过 IOKit 的简单接口,扩展学习了 XNU 的 IPC 通信机制 mach port。希望后续能通过这些工具做出点有意思的东西来。
内核系列文章
- macOS 内核之一个 App 如何运行起来
- macOS 内核之网络信息抓包(三)
- macOS 内核之网络信息抓包(二)
- macOS 内核之网络信息抓包(一)
- macOS 内核之系统如何启动?
- macOS 内核之内存占用信息
- macOS 内核之 CPU 占用率信息
- macOS 内核之 hw.epoch 是个什么东西?
- macOS 内核之从 I/O Kit 电量管理开始