原理:内核申请一片内存,并且对其进行管理和分配,共享给其他进程,已达到进程间通信的目的。
一、共享内存的管理方法
将共享内存进行格式化,按照一定字节对其,本案例用128字节对齐。并且将共享内存分为五大功能块,每CPU块、节点位图块、数据位图块、业务节点块、数据块。
每cpu块:进程于内核沟通的内存区域,每个cpu分配一块,减少锁的使用
节点位图块:映射业务节点内存区域,为节点数据是否被占用提供依据,方便节点的分配。
数据位图块:映射数据块,为数据块是否被占用提供依据,方便数据块的分配。
业务节点块:用来描述进程间通信的业务节点,包括进程通信的客户端、服务端、服务状态、输入输出数据等等。
数据块:用来存储业务的输入输出数据。
1.1 共享内存管理描述符
为了管理这部分内存,需要一个管理结构体,代码如下:
struct binder_buffer_info{
void *buffer_address;
uint buffer_size;
uint block_num;
uint pfn;
};
struct bind_super{
struct binder_buffer_info *binder_buffer_info;
spinlock_t lock_node_map; /* 自旋锁,操作节点位图 */
spinlock_t lock_data_map; /* 自旋锁,操作数据位图 */
uint cpu_num; //记录cpu数量
void* per_cpu_start; //每cpu在共享内存中的起始位置
uint per_cpu_size; //记录单个每cpu占用的内存空间
uint per_cpu_size_total; //记录所有每cpu占用的内存空间
int per_cpu_id;
void* node_map_start; //记录业务节点位图起点地址
uint node_map_size; //记录节点位图占用的空间大小
int node_map_id; //记录业务节点位图起点的块编号
void* node_start; //记录业务节点起点
int node_id; //记录业务节点起始块编号
uint node_num_total; //记录业务节点总数量
uint node_num_left; //记录业务节点剩余数量
uint per_node_size; //记录每个业务节点大小
uint node_size_total; //记录业务节点总大小
void* data_map_start; //记录数据位图起点
int data_map_id; //记录数据位图起始块编号
uint data_map_size; //记录数据总块大小
void* data_start; //记录数据块起点地址
int data_id;
uint data_num_total; //记录数据块总数据量
uint data_num_left; //记录数据块剩余数量
uint data_size_total; //记录数据块总大小
};
具体的图示描述如下:
1.2 每cpu结构体
每cpu用来组织用户态和内核态的通信,可以有效的减少锁的使用,提高进程效率,结构体如下:
struct client_server {
uint rpc_id; //需要调用的rpc id
uint input_size; //输入参数的长度
uint output_size; //输出参数的长度
};
struct bind_percpu{
uint cpuid; //cpuid
pid_t pid; //当前使用进程的pid
uint role; //当前进程的角色 server、client
uint server_start_id;// 当前进程为server时,第一个需要服务的node节点id
uint client_server_max; //一个客户端可以同时申请服务的最大数量
uint client_need_server_num;//当前进程为client,发起的服务数量
struct client_server server_need_start[CLIENT_SERVER_MAX]; //客户端需求描述,具体描述如上
};
1.3业务节点描述符
业务节点描述符如下:
struct rpc_node {
char rpc_status; //记录当前节点处理状态,客户端请求中、服务端处理中、服务端处理完、客户端已确认
uint node_id; //节点编号,释放节点内存是需要用到
uint rpc_id; //该节点处理的业务编号
pid_t client_pid; //客户端pid
pid_t service_pid; //服务端pid
uint input_data; //客户端输入数据
uint input_data_size;//输入长度
uint output_data; //服务端返回值和数据
uint output_data_size; //输出长度
uint next_client_req; //客户端的下一个请求
uint next_service_req; //服务端下一个需要处理的请求
};
二 业务流程
2.1 业务流程图
具体流程图如下:
三、业务的具体组织
3.1 组织结构体
业务通过一个总的结构体进行组织,具体结构体如下:
struct binder_dev {
struct binder_buffer_info binder_buffer_info; //共享内存简要信息
struct bind_super binder_super; //共享内存描述符
struct bind_percpu binder_percpu; //每cpu描述符
struct rb_root rpc_root; //rpc红黑树根节点
struct list_head client_hash[BINDER_HASH_SIZE]; //客户端hash表
struct list_head server_hash[BINDER_HASH_SIZE]; //服务进程hash表
wait_queue_head_t r_client_rq; //客户端等待队列头
wait_queue_head_t r_server_rq; //服务端等待队列头
};
3.2 注册的rpc用红黑树和哈希表组织。
数据结构如下:
struct server_item{
struct list_head hash_list; //进程的哈希节点
struct list_head reg_list; //注册rpc的哈希节点
spinlock_t item_lock; //同步自旋锁
uint start_node_id; //该服务进程需要处理的起始业务节点,业务会根据id组成一个单向链表
uint process_node_id; //当前正在处理的业务节点链表
pid_t pid; //该节点所属的进程pid
};
struct rpc_register {
struct rb_node rb_node_rpc; //红黑树节点
struct list_head reg_list; //哈希表节点
uint rpc_id; //rpc的id
pid_t pid; //rpc所属的进程pid
};
3.3 服务端数据组织结构
服务进程数据结构示意图:
3.4 客户端数据组织结构
结构体如下:
struct client_req_hash {
struct list_head list_head; //hash节点
pid_t pid; //节点所属进程pid
uint req_cnt; //进程业务需求数量
atomic_t req_done_num; //改进程需求完成数量,当需求数量和已完成数量相等,唤醒该进程的等待队列
uint start_req_node; //该进程第一个需要处理的业务,业务单向链表第一个节点编号
};
客户端的进程结构示意图可以参考服务进程示意图。
四、总结
通过对共享内存的管理,对服务进程和客户端进程的数据结构和流程的描述,大致讲解了该工具的实现,具体的代码实现下一章分析。
ps:该工具是原创,欢迎转载。