NOTE
- 源码版本:Android 7.1.2。
- 内核版本:android-goldfish-3.4
- 内核下载:
git clone https://aosp.tuna.tsinghua.edu.cn/kernel/goldfish.git(清华镜像站) - 以下分析思路均来自老罗的《Android 系统源代码情景分析(修订版)》。
Binder 通信实践
- 基于之前介绍过的
Binder通信库,我们可以写一个简单的应用实例来熟悉它的使用方法。 - 在参考书中,这个关于
Binder的例子里实现了一个Service组件,这个组件负责管理一个虚拟的硬件设备freg,这个虚拟的设备是作者在介绍HAL层时实现的。这里我们只需要用到这个设备的内核驱动部分,所以首先我们要实现这个虚拟设备。 - 完成设备的驱动程序后,就要开始写我们的
Binder实例了。这个实例分为三个模块:common:- 实现硬件访问服务接口
IFregService。 - 实现
Binder本地对象类BnFregService与Binder代理对象类BpFregService。
- 实现硬件访问服务接口
server:- 实现了
Server进程,其中包含了一个Service组件FregService。
- 实现了
client:- 实现了一个
Client进程,它通过一个BpFregService代理对象去访问运行在Server进程中的Service组件FregService所提供的服务。
- 实现了一个
1. 为虚拟字符设备 Freg 编写驱动
- 在
/kernel/goldfish/drivers下新建一个文件夹freg:mkdir freg
1.1 freg.h
- 定义四个字符串常量,分别描述
freg在设备文件系统中的名称。 - 定义结构体
fake_reg_dev描述虚拟设备freg:val:描述一个虚拟寄存器。sem:信号量,用于同步访问寄存器。dev:标准 Linux 字符设备结构体变量,用于标志freg为字符设备类型。
#ifndef _FAKE_REG_H_
#define _FAKE_REG_H_
#include <linux/cdev.h>
#include <linux/semaphore.h>
#define FREG_DEVICE_NODE_NAME "freg"
#define FREG_DEVICE_FILE_NAME "freg"
#define FREG_DEVICE_PROC_NAME "freg"
#define FREG_DEVICE_CLASS_NAME "freg"
struct fake_reg_dev {
int val;
struct semaphore sem;
struct cdev dev;
};
#endif1.2 freg.c
- 向用户空间提供三个访问设备
freg的寄存器val的接口:proc文件系统接口。- 传统设备文件系统接口。
devfs文件系统接口。
- 首先定义一些相关变量以及函数原型:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include "freg.h"
/* the major/minor device number */
static int freg_major = 0;
static int freg_minor = 0;
/* types and struct of the device */
static struct class* freg_class = NULL;
static struct fake_reg_dev* freg_dev = NULL;
/* traditional operations */
static int freg_open(struct inode* inode,
struct file* filp);
static int freg_release(struct inode* inode,
struct file* filp);
static ssize_t freg_read(struct file* filp,
char __user *buf,
size_t count,
loff_t* f_pos);
static ssize_t freg_write(struct file* filp,
const char __user *buf,
size_t count,
loff_t* f_pos);
/* traditional operations list */
static struct file_operations freg_fops = {
.owner = THIS_MODULE,
.open = freg_open,
.release = freg_release,
.read = freg_read,
.write = freg_write,
};
/* device properties operation of filesystem 'devfs' */
static ssize_t freg_val_show(struct device* dev,
struct device_attribute* attr,
char* buf);
static ssize_t freg_val_store(struct device* dev,
struct device_attribute* attr,
const char* buf,
size_t count);
/* devfs filesystem's device properties */
static DEVICE_ATTR(val,
S_IRUGO | S_IWUSR,
freg_val_show,
freg_val_store);- 实现传统设备文件操作:
freg_open/freg_release:打开 / 关闭设备。freg_read/freg_write:读取 / 写入val。
/* open this device */
static int freg_open(struct inode* inode,
struct file* filp)
{
struct fake_reg_dev* dev;
/* save our struct to the private_data of file pointer */
dev = container_of(inode->i_cdev, struct fake_reg_dev, dev);
filp->private_data = dev;
return 0;
}
/* release this device */
static int freg_release(struct inode* inode,
struct file* filp)
{
return 0;
}
/* read the value from the device */
static ssize_t freg_read(struct file* filp,
char __user *buf,
size_t count,
loff_t* f_pos)
{
ssize_t err = 0;
struct fake_reg_dev* dev = filp->private_data;
/* synchronize */
if (down_interruptible(&(dev->sem)))
{
return -ERESTARTSYS;
}
if (count < sizeof(dev->val))
{
goto out;
}
/* copy value to the buffer */
if (copy_to_user(buf, &(dev->val), sizeof(dev->val)))
{
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}
/* write value to the device */
static ssize_t freg_write(struct file* filp,
const char __user *buf,
size_t count,
loff_t* f_pos)
{
ssize_t err = 0;
struct fake_reg_dev* dev = filp->private_data;
/* synchronize */
if (down_interruptible(&(dev->sem)))
{
return -ERESTARTSYS;
}
if (count != sizeof(dev->val))
{
goto out;
}
/* copy buffer to the value */
if (copy_from_user(&(dev->val), buf, count))
{
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}- 定义用于访问设备的
devfs文件系统接口:- 将寄存器
val当做设备的一个属性,通过读写熟悉达到访问目的。 freg_val_show:读取val。freg_val_store:写入val。- 为方便编写
proc文件系统接口,将一些共通的操作提取出来,写成两个内部函数__freg_get_val与__freg_set_val。
- 将寄存器
/* read dev->val to buffer */
static ssize_t __freg_get_val(struct fake_reg_dev* dev,
const char* buf)
{
int val = 0;
if (down_interruptible(&(dev->sem)))
{
return -ERESTARTSYS;
}
val = dev->val;
up(&(dev->sem));
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
/* write buffer value to dev->val */
static ssize_t __freg_set_val(struct fake_reg_dev* dev,
const char* buf,
size_t count)
{
int val = 0;
/* str to int */
val = simple_strtol(buf, NULL, 10);
if (down_interruptible(&(dev->sem)))
{
return -ERESTARTSYS;
}
dev->val = val;
up(&(dev->sem));
return count;
}
/* read device properties value */
static ssize_t freg_val_show(struct device* dev,
struct device_attribute* attr,
char* buf)
{
struct fake_reg_dev* hdev = (struct fake_reg_dev*) dev_get_drvdata(dev);
return __freg_get_val(hdev, buf);
}
/* write value to device properties */
static ssize_t freg_val_store(struct device* dev,
struct device_attribute* attr,
const char* buf,
size_t count)
{
struct fake_reg_dev* hdev = (struct fake_reg_dev*) dev_get_drvdata(dev);
return __freg_set_val(hdev, buf, count);
}- 定义
proc文件系统接口:freg_proc_read/freg_proc_write:寄存器读写操作。freg_create_proc/freg_remove_proc:创建 / 删除/proc/freg文件。- 注意在
creat的时候,原本有一句entry->owner = THIS_MODULE的,但是在这个版本里不需要。
/* read dev->val to page buffer */
static ssize_t freg_proc_read(char* page,
char** start,
off_t off,
int count,
int* eof,
void* data)
{
if (off > 0)
{
*eof = 1;
return 0;
}
return __freg_get_val(freg_dev, page);
}
/* save buffer value to dev->val */
static ssize_t freg_proc_write(struct file* filp,
const char __user *buff,
unsigned long len,
void* data)
{
int err = 0;
char* page = NULL;
if (len > PAGE_SIZE)
{
printk(KERN_ALERT"The buff is too large: %lu.\n", len);
return -EFAULT;
}
page = (char*) __get_free_page(GFP_KERNEL);
if (!page)
{
printk(KERN_ALERT"Failed to alloc page.\n");
return -ENOMEM;
}
/* copy user buffer to kernel buffer */
if (copy_from_user(page, buff, len))
{
printk(KERN_ALERT"Failed to copy buff from user.\n");
err = -EFAULT;
goto out;
}
err = __freg_set_val(freg_dev, page, len);
out:
free_page((unsigned long) page);
return err;
}
/* create file 'proc/freg' */
static void freg_create_proc(void)
{
struct proc_dir_entry* entry;
entry = create_proc_entry(FREG_DEVICE_PROC_NAME, 0, NULL);
if (entry)
{
entry->read_proc = freg_proc_read;
entry->write_proc = freg_proc_write;
}
}
/* remove file 'proc/freg' */
static void freg_remove_proc(void)
{
remove_proc_entry(FREG_DEVICE_PROC_NAME, NULL);
}- 定义驱动的模块加载与卸载函数:
freg_init:注册与初始化freg。freg_exit:反注册与释放freg。- 注意在
setup中,采用了sema_init(&(dev->sem), 1)来初始化信号量,而在源码分析的原文中,这里用的是init_MUTEX(&(dev->sem)),但目前这个操作已经被弃用了。
/* init device */
static int __freg_setup_dev(struct fake_reg_dev* dev)
{
int err;
dev_t devno = MKDEV(freg_major, freg_minor);
memset(dev, 0, sizeof(struct fake_reg_dev));
/* init cdev */
cdev_init(&(dev->dev), &freg_fops);
dev->dev.owner = THIS_MODULE;
dev->dev.ops = &freg_fops;
/* register cdev */
err = cdev_add(&(dev->dev), devno, 1);
if (err)
{
return err;
}
/* init freg_dev */
sema_init(&(dev->sem), 1);
dev->val = 0;
return 0;
}
/* load module */
static int __init freg_init(void)
{
int err = -1;
dev_t dev = 0;
struct device* temp = NULL;
printk(KERN_ALERT"Initializing freg device.\n");
/* dynamic allocation of the major and minor number */
err = alloc_chrdev_region(&dev, 0, 1, FREG_DEVICE_NODE_NAME);
if (err < 0)
{
printk(KERN_ALERT"Failed to alloc char dev region.\n");
goto fail;
}
freg_major = MAJOR(dev);
freg_minor = MINOR(dev);
/* alloc struct freg */
freg_dev = kmalloc(sizeof(struct fake_reg_dev), GFP_KERNEL);
if (!freg_dev)
{
err = -ENOMEM;
printk(KERN_ALERT"Failed to alloc freg device.\n");
goto unregister;
}
/* init device */
err = __freg_setup_dev(freg_dev);
if (err)
{
printk(KERN_ALERT"Failed to setup freg device: %d.\n", err);
goto cleanup;
}
/* create freg_class dir 'freg' in '/sys/class' */
freg_class = class_create(THIS_MODULE, FREG_DEVICE_CLASS_NAME);
if (IS_ERR(freg_class))
{
err = PTR_ERR(freg_class);
printk(KERN_ALERT"Failed to create freg device class.\n");
goto destroy_cdev;
}
/* create dir 'freg' in '/dev/' and 'sys/class/freg' */
temp = device_create(freg_class, NULL, dev, NULL, "%s", FREG_DEVICE_FILE_NAME);
if (IS_ERR(freg_class))
{
err = PTR_ERR(temp);
printk(KERN_ALERT"Falied to create freg device.\n");
goto destroy_class;
}
/* create properties dir 'val' in 'sys/class/freg/freg' */
err = device_create_file(temp, &dev_attr_val);
if (err < 0)
{
printk(KERN_ALERT"Failed to create attribute val of freg device.\n");
goto destroy_device;
}
dev_set_drvdata(temp, freg_dev);
/* create 'proc/freg' */
freg_create_proc();
printk(KERN_ALERT"Succeeded to initialize freg device.\n");
return 0;
destroy_device:
device_destroy(freg_class, dev);
destroy_class:
class_destroy(freg_class);
destroy_cdev:
cdev_del(&(freg_dev->dev));
cleanup:
kfree(freg_dev);
unregister:
unregister_chrdev_region(MKDEV(freg_major, freg_minor), 1);
fail:
return err;
}
/* unload module */
static void __exit freg_exit(void)
{
dev_t devno = MKDEV(freg_major, freg_minor);
printk(KERN_ALERT"Destroy freg device.\n");
/* delete '/proc/freg' */
freg_remove_proc();
/* destroy device and its class */
if (freg_class)
{
device_destroy(freg_class, MKDEV(freg_major, freg_minor));
class_destroy(freg_class);
}
/* delete cdev and release mem */
if (freg_dev)
{
cdev_del(&(freg_dev->dev));
kfree(freg_dev);
}
/* release devno */
unregister_chrdev_region(devno, 1);
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Fake Register Driver");
module_init(freg_init);
module_exit(freg_exit);1.3 Kconfig
- 驱动程序写完后,需要继续编写一些编译配置文件。
- 这个文件定义了驱动的编译选项。
config FREG
tristate "Fake Register Driver"
default n
help
This is the freg driver for android system.- 在
freg文件夹下写好这个文件后,还要去修改内核的Kconfig文件,否则无法编译我们加入的新内核驱动。 kernel/goldfish/drivers/Kconfig中,加入:source "drivers/freg/Kconfig"
1.4 Makefile
- 这个驱动的编译脚本很简单,只有一行:
obj-$(CONFIG_FREG) += freg.o
- 相应地,我们也需要修改内核的 Makefile 文件:
drivers/Makefile- 添加语句:
obj-$(CONFIG_FREG) += freg/
1.5 编译内核驱动模块
- 指令
make menuconfig配置编译方式:- 用上下键选择
Device Drivers项,按下Enter。 - 选择
Fake Register Driver项,按下Y。 - 保存配置,退出。
- 用上下键选择
- 执行
make,成功后会有提示:Kernel: arch/arm/boot/zImage is ready
- 之后启动模拟器
emulator的时候,加上-kernel kernel/goldfish/arch/arm/boot/zImage,就能使用编译好的内核了。
2. Binder 实例 —— Common 模块
- 在
./external/binder文件夹下创建common文件夹:mkdir common- 进入
cd common
2.1 IFregService.h
- 宏
FREG_SERVICE描述了该Service组件注册到Service Manager的名称。 - 定义硬件访问服务接口
IFregService:getVal:读取硬件设备freg中寄存器val的值。setVal:写入val值。
- 定义
Binder本地对象类BnFregService:- 实现成员函数
onTransact,用于处理收到的信息。
- 实现成员函数
- 宏
DECLARE_META_INTERFACE:- 作用是声明
IFregService类的元接口。 - 定义一个静态成员变量
descriptor,用于描述接口名。 - 定义成员函数
getInterfaceDescriptor,用于获取接口名。 - 定义静态成员函数
asInterface,用于将一个IBinder对象转换为IFregService接口。 - 定义了
IFregService的构造与析构函数。
- 作用是声明
#ifndef IFREGSERVICE_H_
#define IFREGSERVICE_H_
#include <utils/RefBase.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>
#define FREG_SERVICE "stone.FregService"
using namespace android;
class IFregService : public IInterface
{
public :
DECLARE_META_INTERFACE(FregService);
virtual int32_t getVal() = 0;
virtual void setVal(int32_t val) = 0;
};
class BnFregService : public BnInterface<IFregService>
{
public :
virtual status_t onTransact(uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};
#endif2.2 IFregService.cpp
- 定义枚举变量,表示进程间通信代码:
GET_VAL:对应成员函数getVal。SET_VAL:对应成员函数setVal。
- 定义
Binder代理对象类BpFregService,它实现了IFregService接口:getVal:- 将需要传输的数据封装到
Parcel对象中。 - 通过
remote函数获得一个BpBinder代理对象。 - 通过代理对象的成员函数
transact来请求运行在Server进程中的一个Binder本地对象执行一个GET_VAL操作。 - 操作的返回结果是一个整数,封装在另一个
Parcel对象中返回。
- 将需要传输的数据封装到
setVal:- 将传递的数据封装在一个
Parcel对象中。 - 同样获取
BpBinder代理对象。 - 通过代理的成员函数
transact请求SET_VAL操作。 - 通过
SET_VAL操作将一个整数写入到设备freg的寄存器中。
- 将传递的数据封装在一个
- 宏
IMPLEMENT_META_INTERFACE:- 与头文件中的宏是对应的,它实现了
IFregService类的元接口。 - 将接口名设置为
"stone.IFregService"。 - 实现
IFregService类的构造,析构函数(空实现)。 - 实现成员函数
getInterfaceDescriptor。 - 实现成员函数
asInterface:- 其参数
obj应指向一个类型为BnFregService的Binder本地对象,或一个类型为BpBinder的Binder代理对象,否则其返回值为NULL。 - 若指向本地对象,则调用成员函数
queryLocalInterface直接返回一个IFregService接口。 - 若指向代理对象,则成员函数
queryLocalInterface返回NULL,随后将其封装成一个BpFregService对象,并将它的IFregService接口返回。
- 其参数
- 与头文件中的宏是对应的,它实现了
- 实现了
BnFregService类成员函数onTransact:- 负责将
GET_VAL与SET_VAL请求分发给其子类的成员函数getVal与setVal处理。 BnFregService的子类为FregService,其具体实现了getVal与setVal。
- 负责将
#define LOG_TAG "IFregService"
#include <utils/Log.h>
#include "IFregService.h"
using namespace android;
enum
{
GET_VAL = IBinder::FIRST_CALL_TRANSACTION,
SET_VAL
};
class BpFregService : public BpInterface<IFregService>
{
public :
BpFregService(const sp<IBinder>& impl)
: BpInterface<IFregService>(impl)
{
}
int32_t getVal()
{
Parcel data;
data.writeInterfaceToken(IFregService::getInterfaceDescriptor());
Parcel reply;
remote()->transact(GET_VAL, data, &reply);
int32_t val = reply.readInt32();
return val;
}
void setVal(int32_t val)
{
Parcel data;
data.writeInterfaceToken(IFregService::getInterfaceDescriptor());
data.writeInt32(val);
Parcel reply;
remote()->transact(SET_VAL, data, &reply);
}
};
IMPLEMENT_META_INTERFACE(FregService, "stone.IFregService");
status_t BnFregService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch(code)
{
case GET_VAL:
{
CHECK_INTERFACE(IFregService, data, reply);
int32_t val = getVal();
reply->writeInt32(val);
return NO_ERROR;
}
case SET_VAL:
{
CHECK_INTERFACE(IFregService, data, reply);
int32_t val = data.readInt32();
setVal(val);
return NO_ERROR;
}
default:
{
return BBinder::onTransact(code, data, reply, flags);
}
}
}3. Binder 实例 —— Server 模块
- 在
./external/binder文件夹下创建common文件夹:mkdir server- 进入
cd server
3.1 FregServer.cpp
- 首先实现了一个
Service组件类FregService:- 继承了
BnFregService,并具体实现了IFregService接口。 - 构造函数中,调用
open函数来打开设备文件/dev/freg,并将得到的文件描述符保存在成员变量fd中。 - 相应地,析构函数中则会关闭设备文件。
getVal与setVal都会通过打开的设备文件对其寄存器进行读写操作。- 静态成员函数
instantiate负责将FregService组件注册到Service Manager中,并将注册名设置为"stone.FregService",如此一来,Client进程就能通过该名称获取这个FregService组件的一个代理对象了。
- 继承了
main函数即是该Server进程的主函数:- 调用
instantiate注册组件。 - 调用
ProcessState对象成员函数startThreadPool启动Binder线程池。 - 调用主线程
IPCThreadState对象的成员joinThreadPool将主线程添加到进程Binder线程池中,用于处理来自Client的通信请求。
- 调用
#define LOG_TAG "FregServer"
#include <stdlib.h>
#include <fcntl.h>
#include <utils/Log.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include "../common/IFregService.h"
#define FREG_DEVICE_NAME "/dev/freg"
class FregService : public BnFregService
{
public :
FregService()
{
fd = open(FREG_DEVICE_NAME, O_RDWR);
if (-1 == fd)
{
ALOGE("Failed to open device %s.\n", FREG_DEVICE_NAME);
}
}
virtual ~FregService()
{
if (fd != -1)
{
close(fd);
}
}
public :
static void instantiate()
{
defaultServiceManager()->addService(String16(FREG_SERVICE), new FregService());
}
int32_t getVal()
{
int32_t val = 0;
if (fd != -1)
{
read(fd, &val, sizeof(val));
}
return val;
}
void setVal(int32_t val)
{
if (fd != -1)
{
write(fd, &val, sizeof(val));
}
}
private :
int fd;
};
int main(int argc, char** argv)
{
FregService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
return 0;
}3.2 Android.mk
server模块的编译脚本。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := ../common/IFregService.cpp \
FregServer.cpp
LOCAL_SHARED_LIBRARIES := libcutils libutils libbinder
LOCAL_MODULE := FregServer
include $(BUILD_EXECUTABLE)4. Binder 实例 —— Client 模块
- 在
./external/binder文件夹下创建common文件夹:mkdir server- 进入
cd server
4.1 FregClient.cpp
- 首先调用
defaultServiceManager()获取Service Manager的代理对象。 - 调用代理对象成员函数
getService获取名为"stone.FregService"的Service组件的一个类型为BpBinder的代理对象。 - 将
BpBinder代理对象封装成BpFregService代理对象,获取其IFregService接口,保存在变量service中。 - 通过
service->getVal()获取寄存器的当前值,并打印。 - 通过
service->setVal()设置寄存器的值,这个值为当前值加一。 - 再次通过
service->getVal()获取寄存器值并打印。 - 可以预见到的结果是,运行
FregClient后屏幕上应有的输出应该是:n与n + 1。 - 由于寄存器值初始化为
0,所以第一次运行FregClient时输出应为0和1。 - 可以预见到的是,第二次运行
FregClient时输出为1和2。
#define LOG_TAG "FregClient"
#include <utils/Log.h>
#include <binder/IServiceManager.h>
#include "../common/IFregService.h"
int main()
{
sp<IBinder> binder = defaultServiceManager()->getService(String16(FREG_SERVICE));
if (binder == NULL)
{
ALOGE("Failed to get freg service: %s.\n", FREG_SERVICE);
return -1;
}
sp<IFregService> service = IFregService::asInterface(binder);
if (service == NULL)
{
ALOGE("Failed to get freg service interface.\n");
return -2;
}
printf("Read original value from FregService:\n");
int32_t val = service->getVal();
printf(" %d.\n", val);
printf("Add value 1 to FregService.\n");
val += 1;
service->setVal(val);
printf("Read the value from FregService again:\n");
val = service->getVal();
printf(" %d.\n", val);
return 0;
}4.2 Android.mk
client模块的编译脚本。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := ../common/IFregService.cpp \
FregClient.cpp
LOCAL_SHARED_LIBRARIES := libcutils libutils libbinder
LOCAL_MODULE := FregClient
include $(BUILD_EXECUTABLE)5. 测试结果
- 回到
Android根目录下:mmm ./external/binder/server/mmm ./external/binder/client/make snod
- 编译成功后:
emulator -kernel kernel/goldfish/arch/arm/boot/zImage &adb shell
- 接下来的操作与输出如下:
- 启动
FregServer模块。 - 启用一次
FregClient观察到两个值:0和1。 - 启用第二次
FregClient观察到两个值:1和2。
- 启动
- 实测结果与 4.1 中的分析一致,说明这个实例已经成功运作了。
generic:/ # FregServer &
[1] 1139
generic:/ # FregClient
Read original value from FregService:
0.
Add value 1 to FregService.
Read the value from FregService again:
1.
generic:/ # FregClient
Read original value from FregService:
1.
Add value 1 to FregService.
Read the value from FregService again:
2.
版权声明:本文为qq_16775897原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。