主要内容:了解ChChor中的IPC机制。
前提说明:这里的 ipc 是进程间通信( Inter-Process Communication),不是核间通信(Inter-Processor Communication),更不是RPC(Remote Procedure Call)。所以它的主要目的还是把一个进程的数据,传递到目标进程。
ChChore进程间通信的特点
- 通信进程直接切换(有点像rpc,也是启发自LRPC和L4的直接切换技术)
- 同步的通信(client 调用 ipc_call 会阻塞等待 server 处理完然后 return)
- 通过共享内存传输大数据
- 基于Capability的权限控制
- 类似Unix文件描述符的权限机制,Capability表示一个线程/进程对于系统资源的具体权限
用法示例
场景:client需要把自己的一块内存数据传递给server,基于同步的ipc机制
client端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| int client() {
ret = ipc_register_client(new_thread_cap, &client_ipc_struct);
shared_page_pmo_cap = usys_create_pmo(PAGE_SIZE, PMO_DATA);
ret = usys_map_pmo(SELF_CAP, shared_page_pmo_cap, SHARED_PAGE_VADDR, VM_READ | VM_WRITE);
*(int *)SHARED_PAGE_VADDR = MAGIC_NUM; ipc_msg = ipc_create_msg(&client_ipc_struct, 0, 1); ipc_set_msg_cap(ipc_msg, 0, shared_page_pmo_cap); ipc_call(&client_ipc_struct, ipc_msg); ipc_destroy_msg(ipc_msg);
return 0; }
|
上面的第3,4,5,7步是为了演示共享内存数据用的,实际可以不需要。
server端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void ipc_dispatcher(ipc_msg_t * ipc_msg) { int ret = 0; int cap = ipc_get_msg_cap(ipc_msg, 0);
ret = usys_map_pmo(SELF_CAP, cap, SHARED_PAGE_VADDR + PAGE_SIZE, VM_READ | VM_WRITE);
printf("[Server] read %x\n", *(int *)(SHARED_PAGE_VADDR + PAGE_SIZE)); ret = 0;
ipc_return(ret); }
int main(int argc, char *argv[], char *envp[]) { int ret; info_page = (struct info_page *)info_page_addr; }
|
数据结构

代码分析
为了简化分析,先只考虑一个 server 对应一个 client 的场景,因此先忽略多 cliient 的相关内容。
另外,主要分析 内核 相关的 ipc 代码,对用户空间的先不做分析。
注册server
我们要在用户空间的 ipc server thread 执行函数,就得要指定一些信息:比如 callback(具体执行的函数),stack 的起始地址、size;server thread 需要访问共享内存,因此也需要指定 用户空间的 buffer 地址和 size。
这也是 为什么要 这样设置 server_ipc_config 的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| u64 sys_register_server(u64 callback, u64 max_client, u64 vm_config_ptr) { return register_server(current_thread, callback, max_client, vm_config_ptr); }
static int register_server(struct thread *server, u64 callback, u64 max_client, u64 vm_config_ptr) {
server_ipc_config = kmalloc(sizeof(struct server_ipc_config)); server->server_ipc_config = server_ipc_config;
server_ipc_config->callback = callback;
server_ipc_config->max_client = max_client; server_ipc_config->conn_bmp = kzalloc(BITS_TO_LONGS(max_client) * sizeof(long)); vm_config = &server_ipc_config->vm_config; r = copy_from_user((char *)vm_config, (char *)vm_config_ptr, sizeof(*vm_config)); }
|
可以看到,注册 ipc server,主要是 创建了 server_ipc_config 并挂到 server thread 上。
注册client
注册一个client,它需要和 server 关联起来;另外一个 client 对应一个connection,因此也需要创建 conn cap 内核对象。另外,client 也有自己的 config 参数,用来保存用户栈空间和共享内存信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| u32 sys_register_client(u32 server_cap, u64 vm_config_ptr) { r = copy_from_user((char *)&vm_config, (char *)vm_config_ptr, sizeof(vm_config)); server = obj_get(current_thread->process, server_cap, TYPE_THREAD);
client_buf_size = vm_config.buf_size;
conn_cap = create_connection(client, server, &vm_config); conn = obj_get(current_process, conn_cap, TYPE_CONNECTION);
if (client_buf_size != vm_config.buf_size) { r = copy_to_user((char *)vm_config_ptr, (char *)&vm_config, sizeof(vm_config)); if (r < 0) goto out_obj_put_conn; }
r = conn_cap;
return r; }
|
这里面最重要是create_connection函数,它创建了一个connection内核对象,完成了相关的信息绑定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| static int create_connection(struct thread *source, struct thread *target, struct ipc_vm_config *client_vm_config) {
conn = obj_alloc(TYPE_CONNECTION, sizeof(*conn)); conn->target = create_server_thread(target);
server_ipc_config = target->server_ipc_config; vm_config = &server_ipc_config->vm_config; conn_idx = find_next_zero_bit(server_ipc_config->conn_bmp, server_ipc_config->max_client, 0); set_bit(conn_idx, server_ipc_config->conn_bmp);
server_stack_base = vm_config->stack_base_addr + conn_idx * vm_config->stack_size; stack_size = vm_config->stack_size; kdebug("server stack base:%lx size:%lx\n", server_stack_base, stack_size); stack_pmo = kmalloc(sizeof(struct pmobject)); if (!stack_pmo) { ret = -ENOMEM; goto out_free_obj; } pmo_init(stack_pmo, PMO_DATA, stack_size, 0); vmspace_map_range(target->vmspace, server_stack_base, stack_size, VMR_READ | VMR_WRITE, stack_pmo);
conn->server_stack_top = server_stack_base + stack_size;
server_buf_base = vm_config->buf_base_addr + conn_idx * vm_config->buf_size; client_buf_base = client_vm_config->buf_base_addr; buf_size = MIN(vm_config->buf_size, client_vm_config->buf_size); client_vm_config->buf_size = buf_size;
buf_pmo = kmalloc(sizeof(struct pmobject));
pmo_init(buf_pmo, PMO_DATA, buf_size, 0);
vmspace_map_range(current_thread->vmspace, client_buf_base, buf_size, VMR_READ | VMR_WRITE, buf_pmo); vmspace_map_range(target->vmspace, server_buf_base, buf_size, VMR_READ | VMR_WRITE, buf_pmo);
conn->buf.client_user_addr = client_buf_base; conn->buf.server_user_addr = server_buf_base;
conn_cap = cap_alloc(current_process, conn, 0);
server_conn_cap = cap_copy(current_process, target->process, conn_cap, 0, 0); if (server_conn_cap < 0) { ret = server_conn_cap; goto out_free_obj; } conn->server_conn_cap = server_conn_cap;
return conn_cap;
}
|
这个函数有点复杂,主要是完成 conn 的创建,总结一下:
申请一个 conn obj
一个connection需要在server的上下文执行函数,因此需要创建一个 server thread,这是一个 shadow thread
创建 server 线程
获取 server 的 config
根据 config 创建 server 线程的栈
创建 共享内存,完成 内存映射
完成 conn 的配置
client 的 conn_cap 返回给 client,server 的 conn_cap 挂在 conn 数据结构中
这样操作之后,就完成了 server 线程的创建,申请了共享内存,具备了共享数据和在 server thread 执行 callback 的条件。
create_server_thread() 主要是完成真正的 server thread 创建,并配置 server_ipc_config 信息。这里就贴代码了。
ipc_call
client 拿到了 conn_cap,这时候就可以开始进行 ipc_call 了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| u64 sys_ipc_call(u32 conn_cap, ipc_msg_t *ipc_msg) { struct ipc_connection *conn = NULL; u64 arg; int r;
conn = obj_get(current_thread->process, conn_cap, TYPE_CONNECTION);
r = copy_to_user((char *)&ipc_msg->server_conn_cap, (char *)&conn->server_conn_cap, sizeof(u64));
r = ipc_send_cap(conn, ipc_msg);
arg = conn->buf.server_user_addr; thread_migrate_to_server(conn, arg);
return r; }
|
主要是两件事情:
- cap拷贝给server
- 转移到server线程执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| int ipc_send_cap(struct ipc_connection *conn, ipc_msg_t *ipc_msg) { int i, r; u64 cap_slot_number; u64 cap_slots_offset; u64 *cap_buf;
r = copy_from_user((char *)&cap_slot_number, (char *)&ipc_msg->cap_slot_number, sizeof(cap_slot_number));
r = copy_from_user((char *)&cap_slots_offset, (char *)&ipc_msg->cap_slots_offset, sizeof(cap_slots_offset));
cap_buf = kmalloc(cap_slot_number * sizeof(*cap_buf));
r = copy_from_user((char *)cap_buf, (char *)ipc_msg + cap_slots_offset, sizeof(*cap_buf) * cap_slot_number);
for (i = 0; i < cap_slot_number; i++) { u64 dest_cap;
kdebug("[IPC] send cap:%d\n", cap_buf[i]); dest_cap = cap_copy(current_process, conn->target->process, cap_buf[i], false, 0); if (dest_cap < 0) goto out_free_cap; cap_buf[i] = dest_cap; }
r = copy_to_user((char *)ipc_msg + cap_slots_offset, (char *)cap_buf, sizeof(*cap_buf) * cap_slot_number);
return r; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| static u64 thread_migrate_to_server(struct ipc_connection *conn, u64 arg) { struct thread *target = conn->target;
conn->source = current_thread; target->active_conn = conn; current_thread->thread_ctx->state = TS_WAITING; obj_put(conn);
arch_set_thread_stack(target, conn->server_stack_top); arch_set_thread_next_ip(target, conn->target->server_ipc_config->callback); arch_set_thread_arg(target, arg);
target->thread_ctx->sc = current_thread->thread_ctx->sc;
switch_to_thread(target); eret_to_thread(switch_context());
BUG_ON(1); return 0; }
|
ipc_return
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| static int thread_migrate_to_client(struct ipc_connection *conn, u64 ret_value) { struct thread *source = conn->source; current_thread->active_conn = NULL;
arch_set_thread_return(source, ret_value); switch_to_thread(source); eret_to_thread(switch_context());
BUG_ON(1); return 0; }
void sys_ipc_return(u64 ret) { struct ipc_connection *conn = current_thread->active_conn;
thread_migrate_to_client(conn, ret);
BUG("This function should never\n"); return; }
|