进程间通信(IPC)

在本部分,我们将实现ChCore的进程间通信,从而允许跨地址空间的两个进程可以使用IPC进行信息交换。

进程间通讯概览

ChCore的IPC接口不是传统的send/recv接口。其更像客户端/服务器模型,其中IPC请求接收者是服务器,而IPC请求发送者是客户端。 服务器进程中包含三类线程:

  • 主线程:该线程与普通的线程一样,类型为TYPE_USER。该线程会调用ipc_register_server将自己声明为一个IPC的服务器进程,调用的时候会提供两个参数:服务连接请求的函数client_register_handler和服务真正IPC请求的函数server_handler(即图中的ipc_dispatcher),调用该函数会创建一个注册回调线程;

  • 注册回调线程:该线程的入口函数为上文提到的client_register_handler,类型为TYPE_REGISTER。正常情况下该线程不会被调度执行,仅当有Client发起建立IPC连接的请求时,该线程运行并执行client_register_handler,为请求建立连接的Client创建一个服务线程(即图中的IPC handler thread)并在服务器进程的虚拟地址空间中分配一个可以用来映射共享内存的虚拟地址。

  • 服务线程:当Client发起建立IPC连接请求时由注册回调线程创建,入口函数为上文提到的server_handler,类型为TYPE_SHADOW。正常情况下该线程不会被调度执行,仅当有Client端线程使用ipc_call发起IPC请求时,该线程运行并执行server_handler(即图中的ipc_dispatcher),执行结束之后会调用ipc_return回到Client端发起IPC请求的线程。

注意

注册回调线程和服务线程都不再拥有调度上下文(Scheduling Context),也即不会主动被调度器调度到。其在客户端申请建立IPC连接或者发起IPC请求的时候才会被调度执行。为了实现该功能,这两种类型的线程会继承IPC客户端线程的调度上下文(即调度时间片budget),从而能被调度器正确地调度。

具体流程

为了实现ChCore IPC的功能,首先需要在Client与Server端创建起一个一对一的IPC Connection。该Connection保存了IPC Server的服务线程(即上图中IPC handler Thread)、Client与Server的共享内存(用于存放IPC通信的内容)。同一时刻,一个Connection只能有一个Client接入,并使用该Connection切换到Server的处理流程。ChCore提供了一系列机制,用于创建Connection以及创建每个Connection对应的服务线程。下面将以具体的IPC注册到调用的流程,详细介绍ChCore的IPC机制:

  1. IPC服务器的主线程调用: ipc_register_server (user/chcore-libc/musl-libc/src/chcore-port/ipc.c中)来声明自己为IPC的服务器端。

    • 参数包括server_handler和client_register_handler,其中server_handler为服务端用于提供服务的回调函数(比如上图中IPC handler Thread的入口函数ipc_dispatcher);client_register_handler为服务端提供的用于注册的回调函数,该函数会创建一个注册回调线程。

    • 随后调用ChCore提供的的系统调用:sys_register_server。该系统调用实现在kernel/ipc/connection.c当中,该系统调用会分配并初始化一个struct ipc_server_config和一个struct ipc_server_register_cb_config。之后将调用者线程(即主线程)的general_ipc_config字段设置为创建的struct ipc_server_config,其中记录了注册回调线程和IPC服务线程的入口函数(即图中的ipc_dispatcher)。将注册回调线程的general_ipc_config字段设置为创建的struct ipc_server_register_cb_config,其中记录了注册回调线程的入口函数和用户态栈地址等信息。

  2. IPC客户端线程调用ipc_register_client(定义在user/chcore-libc/musl-libc/src/chcore-port/ipc.c中)来申请建立IPC连接。

    • 该函数仅有一个参数,即IPC服务器的主线程在客户端进程cap_group中的capability。该函数会首先通过系统调用申请一块物理内存作为和服务器的共享内存(即图中的Shared Memory)。

    • 随后调用sys_register_client系统调用。该系统调用实现在kernel/ipc/connection.c当中,该系统调用会将刚才申请的物理内存映射到客户端的虚拟地址空间中,然后调用create_connection创建并初始化一个struct ipc_connection类型的内核对象,该内核对象中的shm字段会记录共享内存相关的信息(包括大小,分别在客户端进程和服务器进程当中的虚拟地址和capability)。

    • 之后会设置注册回调线程的栈地址、入口地址和第一个参数,并切换到注册回调线程运行。

  3. 注册回调线程运行的入口函数为主线程调用ipc_register_server是提供的client_register_handler参数,一般会使用默认的DEFAULT_CLIENT_REGISTER_HANDLER宏定义的入口函数,即定义在user/chcore-libc/musl-libc/src/chcore-port/ipc.c中的register_cb

    • 该函数首先分配一个用来映射共享内存的虚拟地址,随后创建一个服务线程。

    • 随后调用sys_ipc_register_cb_return系统调用进入内核,该系统调用将共享内存映射到刚才分配的虚拟地址上,补全struct ipc_connection内核对象中的一些元数据之后切换回客户端线程继续运行,客户端线程从ipc_register_client返回,完成IPC建立连接的过程。

  4. IPC客户端线程调用ipc_create_msgipc_set_msg_data向IPC共享内存中填充数据,然后调用ipc_calluser/chcore-libc/musl-libc/src/chcore-port/ipc.c中)发起IPC请求。

    • ipc_call中会发起sys_ipc_call系统调用(定义在kernel/ipc/connection.c中),该系统调用将设置服务器端的服务线程的栈地址、入口地址、各个参数,然后迁移到该服务器端服务线程继续运行。由于当前的客户端线程需要等待服务器端的服务线程处理完毕,因此需要更新其状态为TS_WAITING,且不要加入等待队列。
  5. IPC服务器端的服务线程在处理完IPC请求之后使用ipc_return返回。

    • ipc_return会发起sys_ipc_return系统调用,该系统调用会迁移回到IPC客户端线程继续运行,IPC客户端线程从ipc_call中返回。

练习题 7

user/chcore-libc/musl-libc/src/chcore-port/ipc.c与kernel/ipc/connection.c中实现了大多数IPC相关的代码,请根据注释补全kernel/ipc/connection.c中的代码。之后运行ChCore可以看到 “[TEST] Test IPC finished!” 输出,你可以通过 Test IPC 测试点。


success

以上为Lab4 Part3的所有内容

Last change: 2024-10-26, commit: 4635c78