操作系统—微内核通信
在微内核架构下,一个应用程序获取系统服务通常需要通过进程间通信的方式。
Mach:早期的微内核进程间通信设计
mach通过两种基本的抽象—端口和消息,设计和实现一种间接通信ipc。
端口设计:
mach将端口分为发送者端口和接受者端口,mach的通信不是指定的,而是发送者从发送端口发送消息,然后接收者可以从接受端口去拿消息,发送端口可以有多个,但接受端口只能有一个。
消息设计:
mach传递消息支持传递端口,比如进程a与进程b通信,进程a也与进程c通信,那么进程a可以发送给进程b,进程c的端口,这样就建立了b与c的连接。
mach有三个借口,msg_send,msg_receive,msg_rpc,单向通信一般用msg_send,双向通信一般用msg_rpc。
L4:围绕进程间通信优化而设计的微内核系统
L4消息传递:前面介绍的mach消息传递端口的方式,在sel4中以消息传递capability的方式传递下来。L4将消息分为长消息和短消息,后期还有中等消息,这样设计的目的是减少数据拷贝。
短消息:短消息通过寄存器来传递,这样实现了零拷贝。
具体流程:
1.调用者将消息写在全局寄存器中
2.控制流转移,陷入内核,从调用者上下文切换到被调用者的上下文。
3.被调用者从寄存器取数据
长消息:长消息通常需要两次拷贝:从发送者用户态拷贝到内核缓冲区,以及从内核缓冲区拷贝到接收者用户态。有一种优化可以让拷贝次数只有一次,就是将发送者临时缓冲区的虚拟地址映射到接收者物理地址。
L4控制流转移:
惰性调度:就是在线程被阻塞时,我们还把线程放在就绪队列中,只改变线程TCB中的状态,这样避免大量的队列操作,当他不阻塞时我们还是改变TCB中的状态即可。
L4通信连接:以前L4是直接通信,知道一个线程号就可以与他一对一通信,由于一些安全问题,转变为像mach一样的间接通信了。
L4权限检查:capability机制,capability简单看来是对内核对象的一个引用,以及对该内核对象的一个权限。以ipc为例,微内核会在每个通信连接维护一个IPC内核对象,这个内核对象包含接受者,发送者,缓冲区等通信相关信息,当发送者要发起通信时,需要告知内核一个特定的capability来发起通信,内核检查capability,然后找到capability对应的内核对象以及与之对应的接受者,从而开启通信。
所以在开启通信前,两个进程要先通过命名服务等方式获取到对应的capability。
LRPC:迁移线程模型
迁移线程就是把服务端的代码拉取到客户端来处理数据。这样就避免了控制流切换和数据传输。
迁移线程的基本原则:
- 简化控制流切换,让客户端线程执行“服务端的代码”。
- 简化数据传输,共享参数栈和寄存器
- 简化接口,减少序列化等开销
- 优化并发,避免共享的全局数据结构。
迁移线程中,服务端更像一个代码提供者,此外内核不会进行完整的上下文切换,而是只切换地址空间等和请求处理相关的系统状态。其中不会涉及线程和优先级的切换,也不会调用调度器。
LRPC设计:
共享参数栈和寄存器:内核为每个rpc都设立一个参数栈。然后将参数栈映射到客户端和服务端的地址空间,这样他们就可以通过这个栈来传递数据。
通信连接的建立:当客户端申请和一个服务端建立连接时,内核会分配参数栈和连接记录(用来返回的),并返回给客户端一个绑定对象,之后客户端可以通过绑定对象发起通信。
通信过程:
- 内核验证绑定对象的正确性,并找到正确的服务描述符
- 内核验证参数栈和连接记录的正确性
- 内核检查是否有并发调用(可能导致参数栈异常)
- 内核将调用者的返回地址和栈指针放到连接记录中
- 内核将连接记录放到线程控制结构体中的栈上
- 内核切换到被调用者进程地址空间
- 内核找到被调用者进程的运行栈(执行代码所用的栈)
- 内核将调用者线程的栈指针设置为被调用者线程的运行栈地址
- 内核将代码指针指向被调用者地址空间中的处理函数