3. Binder内存映射
任务目标
编写一个用户空间C程序,通过open()打开Binder设备节点/dev/binder,使用mmap()建立内核缓冲区映射,并打印映射前后的虚拟地址布局与映射属性,以验证Binder IPC中内存映射机制的基本行为。
核心实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/ioctl.h>
#include <errno.h>
#define BINDER_DEV_PATH "/dev/binder"
#define MAP_SIZE (128 * 1024) // 128KB映射区
// Binder驱动版本信息结构(与内核include/uapi/linux/android/binder.h对应)
struct binder_version {
long protocol_version;
};
#define BINDER_VERSION _IOWR('b', 9, struct binder_version)
int main(void) {
int fd;
void *map_addr;
struct binder_version ver;
// 1. 打开Binder设备
fd = open(BINDER_DEV_PATH, O_RDWR | O_CLOEXEC);
if (fd < 0) {
perror("open /dev/binder");
return EXIT_FAILURE;
}
printf("[1] 打开 %s 成功, fd = %d\n", BINDER_DEV_PATH, fd);
// 2. 查询Binder驱动协议版本(验证设备可用性)
memset(&ver, 0, sizeof(ver));
if (ioctl(fd, BINDER_VERSION, &ver) == 0) {
printf("[2] Binder驱动协议版本: %ld\n", ver.protocol_version);
} else {
perror("ioctl BINDER_VERSION");
close(fd);
return EXIT_FAILURE;
}
// 3. 映射前进程内存布局关键信息
printf("\n[3] mmap前:\n");
printf(" 当前程序堆起始(brk): %p\n", sbrk(0));
printf(" 准备映射大小: %zu 字节 (%zu KB)\n", MAP_SIZE, MAP_SIZE / 1024);
// 4. 执行mmap映射Binder内核缓冲区
// MAP_PRIVATE: 写时复制,不修改内核缓冲区内容
// PROT_READ: 只读映射(Binder驱动要求)
map_addr = mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
if (map_addr == MAP_FAILED) {
perror("mmap /dev/binder");
close(fd);
return EXIT_FAILURE;
}
printf("\n[4] mmap成功:\n");
printf(" 映射起始地址: %p\n", map_addr);
printf(" 映射结束地址: %p\n", (char *)map_addr + MAP_SIZE);
printf(" 映射长度: %zu 字节\n", MAP_SIZE);
// 5. 读取映射区首个字节(验证可访问性)
volatile char first_byte = *((volatile char *)map_addr);
printf("\n[5] 读取映射区首字节: 0x%02x (可正常访问)\n", (unsigned char)first_byte);
// 6. 映射后进程内存布局变化
printf("\n[6] mmap后:\n");
printf(" 当前程序堆起始(brk): %p (未变化)\n", sbrk(0));
printf(" 映射区域: %p - %p\n", map_addr, (char *)map_addr + MAP_SIZE);
// 7. 清理
if (munmap(map_addr, MAP_SIZE) == 0) {
printf("\n[7] munmap解除映射成功\n");
} else {
perror("munmap");
}
close(fd);
printf(" 关闭fd, 程序退出\n");
return EXIT_SUCCESS;
}
编译与运行
# 编译(需Linux内核头文件支持Binder uapi)
gcc -o binder_mmap binder_mmap.c
# 运行(需要root权限或所属binder组)
sudo ./binder_mmap
验证方法与判定标准
程序执行后应依次观察到以下现象,全部满足即验证通过:
- 步骤[1]:成功打开
/dev/binder,fd值大于等于3;若提示Permission denied,说明当前用户无权限,需sudo或加入binder组。 - 步骤[2]:打印出Binder驱动协议版本号(通常为1),证明设备节点可正常通信。
- 步骤[4]:mmap返回非
MAP_FAILED地址,且映射起始地址按页对齐(低12位为0)。 - 步骤[5]:能成功读取映射区首字节而不触发段错误,说明内核已建立物理页映射。
- 步骤[6]:
brk堆地址在mmap前后保持不变,而映射区域位于进程虚拟地址空间的独立区间(通常在堆与栈之间),验证mmap不会干扰堆内存布局。 - 步骤[7]:munmap返回0,程序正常退出,退出码为0。
关键说明
- Binder驱动要求映射使用
PROT_READ(只读),不可使用PROT_WRITE,否则mmap返回EACCES错误。 - 映射大小
MAP_SIZE取128KB仅为演示值,实际Binder驱动会按需扩展映射区域,但用户空间一次mmap的映射大小即为进程可访问的Binder缓冲区上限。 - 若系统未加载Binder驱动(如纯容器环境),
open()会返回ENOENT或ENODEV,此时需在宿主机或具有Binder驱动的环境中运行。 - 映射区首字节读取值通常为0,这是Binder内核缓冲区的初始状态,不表示数据传输内容。