1. 前言 #
seL4 中最大的特色设计是 capability 机制。seL4 文档以及一些其他文章已经详细介绍了 capability 的设计概念,但是似乎没有基于实际例子的入门介绍文档。
因此本文计划深入内核态和用户态的相关源码,基于一些实际使用例子来介绍 capability 机制,作为我的学习笔记,对于初学者来说也更容易理解。
2. Capability 机制简介 #
对于 seL4 capability 机制的介绍已经很多,用一句话来解释该机制
将内核中所有实力抽象为能力,并且将这些能力存储到 能力空间 (cspace) 中,kernel 根据 index 寻找对应的能力,根据能力找到对应实例的地址
可以从一下文档中加深理解,本文就不再赘述 capability 的设计和相关概念
3. Capability 如何使用 #
相比于构建 cspace,使用 capability 可能更广泛和容易理解,所以我们首先介绍下 capability 是如何生效的。
- 所有内核对象都是 capability
 - 所有的 syscall 都是对一个 capability 进行某些操作
 - 每种 capability 可以看成一个内核服务,内核根据 syscall 中请求的 capability 使用对应的 handler 处理 syscall
 - cptr 和实际内核对象之间的关系,可以按照 C++ 中的智能指针来理解
 - 对于用户态来说,cslot 本质上是一个 index,cnode 理解成一个数组,cptr 时存储在其中元素。cptr 用户态是无法获取的,只能通过 index 告诉内核你想访问的 cptr
 
和 Linux, QNX 的 syscall 不同,seL4 的 syscall 应该理解为面对对象或者服务,而 Linux 的 syscall 则更像是一个个独立函数。
而 Capability 就是内核对象或服务,如 reL4 中实现的 syscall 处理函数
// 用户态 syscall
// static inline void riscv_sys_send_recv
    // a0 寄存器存放 dest cptr,就是上面提到的请求的内核服务
    register seL4_Word destptr asm("a0") = dest;
    // Message tag,具体可参考 seL4 IPCBuffer 概念
    register seL4_Word info asm("a1") = info_arg;
    /* Load beginning of the message into registers. */
    // 把尽量多的 msg 放到寄存器中,减少拷贝,msg 也就是需要传递的消息
    register seL4_Word msg0 asm("a2") = *in_out_mr0;
    register seL4_Word msg1 asm("a3") = *in_out_mr1;
    register seL4_Word msg2 asm("a4") = *in_out_mr2;
    register seL4_Word msg3 asm("a5") = *in_out_mr3;
    // syscall 类型,seL4 的 syscall 类型非常少,基本上都是根据动作来定义的,比如,call,send,recv 这种。而不是像 Linux 一样根据调用函数功能来,比如 write,poll 这种
    register seL4_Word scno asm("a7") = sys;
// 内核态处理函数
pub fn handle_invocation(isCall: bool, isBlocking: bool) -> exception_t {
    ......
    // capability cptr 放在寄存器 a0,根据 cptr 从 cspace 中回去对应的 cslot
    let cptr = thread.tcbArch.get_register(ArchReg::Cap);
    let lu_ret = thread.lookup_slot(cptr);
    // 从 cslot 中获取 cptr
    let capability = unsafe { (*(lu_ret.slot)).capability.clone() };
    // 调用 decode_invocation,在其中会根据 capability 分别调用不同的处理函数
    let status = decode_invocation(
        info.get_message_label(),
        length,
        unsafe { &mut *lu_ret.slot },
        &capability,
        cptr,
        isBlocking,
        isCall,
        buffer.unwrap(),
    );
    ......
}
我们以一个最简单的 syscall TCBSuspend 为例
// TCBSuspend 用户态函数
LIBSEL4_INLINE seL4_Error
seL4_TCB_Suspend(seL4_TCB _service)
{
	seL4_Error result;
	// 设置 msg tag 的 label 为 TCBSuspend
    // msg info 的解释  
    // The tag consists of four fields: the label, message length, number of
    // capabilities (the extraCaps field) and the capsUnwrapped field.
    seL4_MessageInfo_t tag = seL4_MessageInfo_new(TCBSuspend, 0, 0, 0);
    ...
    // 调用 syscall,获取 result
	output_tag = seL4_CallWithMRs(_service, tag,
		&mr0, &mr1, &mr2, &mr3);
	result = (seL4_Error) seL4_MessageInfo_get_label(output_tag);
    ...
}
// 内核态处理,上述我们已经提到了 decode_invocation,因此从这个函数开始解释
pub fn decode_invocation(...) {
    // 根据目标 cptr 的类型,找到对应的处理函数
    match capability.clone().splay() {
        ...
        // 发现能力是 tcb,调用 decode_tcb_invocation
        cap_Splayed::thread_cap(data) => {
            decode_tcb_invocation(label, length, &data, slot, call, buffer)
        }
    }
}
// decode_tcb_invocation
pub fn decode_tcb_invocation(
    // 从 tag 中解析出了 label,对应的就是 syscall 中的 TCBSuspend
    invLabel: MessageLabel,
    length: usize,
    capability: &cap_thread_cap,
    slot: &mut cte_t,
    call: bool,
    buffer: &seL4_IPCBuffer,
) -> exception_t {
    ...
    match invLabel {
        // 根据 label 进行 TCBSuspend 相关处理,因为是 suspend,所以将 task 状态改为 ThreadStateRestart
        MessageLabel::TCBSuspend => {
            set_thread_state(get_currenct_thread(), ThreadState::ThreadStateRestart);
            invoke_tcb_suspend(convert_to_mut_type_ref::<tcb_t>(
                capability.get_capTCBPtr() as usize
            ))
        }
    }
}
// invoke_tcb_suspend,真正进行操作的地方,通过 cptr 找到了 tcb,并对 tcb 进行一些操作
#[inline]
pub fn invoke_tcb_suspend(thread: &mut tcb_t) -> exception_t {
    // cancel_ipc(thread);
    thread.cancel_ipc();
    thread.suspend();
    exception_t::EXCEPTION_NONE
}
// 值得注意的是,在 handle_invocation 中,如果 decode_invocation 出现错误,会回复 error
pub fn handle_invocation(isCall: bool, isBlocking: bool) -> exception_t {
    ...
    if status == exception_t::EXCEPTION_SYSCALL_ERROR {
        if isCall {
            reply_error_from_kernel(thread);
        }
        return exception_t::EXCEPTION_NONE;
    }
}
// 在 reply_error_from_kernel 中主要执行的是,在 msginfo 寄存器 (a1) 中填入错误码
pub fn reply_error_from_kernel(thread: &mut tcb_t) {
    thread.tcbArch.set_register(ArchReg::Badge, 0);
    unsafe {
        let len = set_mrs_for_syscall_error(thread);
        thread.tcbArch.set_register(
            ArchReg::MsgInfo,
            seL4_MessageInfo::new(current_syscall_error._type as u64, 0, 0, len as u64).to_word(),
        );
    }
}
上述就是一个 syscall 完整过程,这个 syscall 虽然简单,但是也大致显示了 seL4 中 capability 是如何生效的。
但是这个过程中,没有涉及到 capability 的传递,下一章详细介绍该部分内容。
4. Capability 的传递 #
除了 root-task 掌握了所有资源外,其他任务的资源都依赖 root-task 或者父进程分配,因此 cptr 传递是一个很重要的设计。
我理解,seL4 中有显示和隐式两种 cptr 传递方式。
4.1 Capability 的显示传递 #
显示传递比较容易理解,就是通过如 seL4_CNode_Mint, seL4_CNode_Copy 等函数将一个 cslot 从一个 cnode 拷贝或者传递到另一个 cnode。在这个过程中,可以简单将 cslot 中存储的 cptr 当作是一个 C++ 智能指针,指向的实例始终是同一个,但是智能指针可以有多个。
我们以 seL4_CNode_Copy 为例,mint 相比于 copy,多出一些增加权限,badge 的功能。
seL4_CNode_Copy 一个很常用的场景是,root0-task 创造一个 capability,并将它传递给某一个子任务。
// 用户态 syscall
LIBSEL4_INLINE seL4_Error
seL4_CNode_Copy(seL4_CNode _service, seL4_Word dest_index, seL4_Uint8 dest_depth, seL4_CNode src_root, seL4_Word src_index, seL4_Uint8 src_depth, seL4_CapRights_t rights)
{
	seL4_Error result;
    // 参考 seL4 message info 的定义,0 代表 unwrapped cptr num, 1 代表 有一个 extra cap, 5 代表 msg 长度
	seL4_MessageInfo_t tag = seL4_MessageInfo_new(CNodeCopy, 0, 1, 5);
	seL4_MessageInfo_t output_tag;
	seL4_Word mr0;
	seL4_Word mr1;
	seL4_Word mr2;
	seL4_Word mr3;
	/* Setup input capabilities. */
    // 将 src cptr root cnode 传递过来,从这可以看出,seL4_CNode_Copy 的调用者必须同时具有 src 和 dst cnode 的权限
	seL4_SetCap(0, src_root);
	/* Marshal and initialise parameters. */
    // 填入 src 和 dest cslot index,Copy 的时候不是直接将 cptr 实例拷贝,而是必须从一个 cslot 拷贝到另一个 cslot,cslot 本质上也就是一个 index
	mr0 = dest_index;
	mr1 = (dest_depth & 0xffull);
	mr2 = src_index;
	mr3 = (src_depth & 0xffull);
	seL4_SetMR(4, rights.words[0]);
	/* Perform the call, passing in-register arguments directly. */
	output_tag = seL4_CallWithMRs(_service, tag,
		&mr0, &mr1, &mr2, &mr3);
	result = (seL4_Error) seL4_MessageInfo_get_label(output_tag);
    ...
}
// 内核态处理
// 值得注意的是,如果需要使用多个 capbility 的时候,多余的 cptr 是放在 IPC Buffer 中的 extraCap 字段的,通过如下函数获取
let src_root = &get_extra_cap_by_index(0).unwrap().capability;
// 跳过之前介绍的 syscall 如何根据 cptr 类型进行处理的分析,直接来到 invoke_cnode_copy
// 主要分析 derive_cap 和 cte_insert 这两个函数
pub fn invoke_cnode_copy(
    src_slot: &mut cte_t,
    dest_slot: &mut cte_t,
    cap_right: seL4_CapRights,
) -> exception_t {
    ...
    // 按照前面的解释,cap 理解为一个智能指针,因此需要先 派生(derive) 出一个指向同一个实例的 cap,然后将派生出的这个 cptr 放到 dest cslot 中,完成复制过程
    let dc_ret = src_slot.derive_cap(&src_cap);
    ...
    cte_insert(&dc_ret.capability, src_slot, dest_slot);
}
pub fn derive_cap(&self, capability: &cptr) -> deriveCap_ret {
    if capability.is_arch_cap() {
        return self.arch_derive_cap(capability);
    }
    // 创建一个空的 deriveCap_ret, 用来存放派生出的 cptr
    let mut ret = deriveCap_ret {
        status: exception_t::EXCEPTION_NONE,
        capability: cap_null_cap::new().unsplay(),
    };
    match capability.get_tag() {
        // 这地方主要是进行一些检查,有一些 cptr 类型是不能派生的
        cap_tag::cap_zombie_cap => {
            ret.capability = cap_null_cap::new().unsplay();
        }
        cap_tag::cap_untyped_cap => {
            // untyped 类型派生时必须是一个全新的,至于为啥这么设计,没有研究
            // untyped 类型派生通常发生在,父进程从自己的 untyped cptr 中分出一部分内存,创建一个新的 untyped cap, 赋予给子进程
            ret.status = self.ensure_no_children();
            if ret.status != exception_t::EXCEPTION_NONE {
                ret.capability = cap_null_cap::new().unsplay();
            } else {
                ret.capability = capability.clone();
            }
        }
        #[cfg(not(feature = "kernel_mcs"))]
        cap_tag::cap_reply_cap => {
            ret.capability = cap_null_cap::new().unsplay();
        }
        cap_tag::cap_irq_control_cap => {
            // 很典型的例子,irq control 的能力只有 root-task 有,无法派生
            ret.capability = cap_null_cap::new().unsplay();
        }
        _ => {
            ret.capability = capability.clone();
        }
    }
    ret
}
// 完成派生后,就是将生成的 cptr 插入到指定 cslot 中
pub fn cte_insert(new_cap: &cap, src_slot: &mut cte_t, dest_slot: &mut cte_t) {
    ...
    // 插入的核心是这段,本质上就是将 src_slot 中的 cap 和其派生节点都移到 dest_slot
    dest_slot.capability = new_cap.clone();
    dest_slot.cteMDBNode = newMDB.clone();
    // 设置 src_slot 的派生节点为 dest slot,基本就是一个链表
    // 由于内核对象同处于一个地址空间,所以 cslot 中的派生 cptr 可以指向另一个 cnode 中的 cslot 
    src_slot
        .cteMDBNode
        .set_mdbNext(dest_slot as *const cte_t as u64);
    if newMDB.get_mdbNext() != 0 {
        let cte_ref = convert_to_mut_type_ref::<cte_t>(newMDB.get_mdbNext() as usize);
        cte_ref
            .cteMDBNode
            .set_mdbPrev(dest_slot as *const cte_t as u64);
    }
}
上述详细介绍了 seL4_CNode_Copy 在内核中的处理流程,其他 CNode 操作大致流程类似,感兴趣可以阅读 reL4 源码
4.2 Capability 的隐式传递 #
隐式传递其实就是通过 IPC 传递的机制,在通过 Endpoint 通信时,可以捎带上 cptr,传递给接收端。通常只能传递一个 cptr,多个也许也可以,没有仔细看文档了。传递的 cptr 放在 IPC Buffer 的 extra_cap 字段,接收方则需要在 IPC Buffer 中指定 receiveCNode receiveIndex receiveDepth 才可以接收,这三个字段都是定义在 IPC Buffer 中。
用户态我们以 rel4-linux-kit 中 RegisterIRQ 服务为例,client 请求发送功能代码如下
pub fn register_irq(irq: usize, target_slot: LeafSlot) {
    let msg = &mut MessageInfo::new(RootEvent::RegisterIRQ.into(), 0, 0, 1);
    // construct the IPC message
    let origin_slot = with_ipc_buffer_mut(|ipc_buffer| {
        // 在这设置了 receiveCNode receiveIndex receiveDepth
        ipc_buffer.set_recv_slot(&target_slot.abs_cptr());
        ipc_buffer.msg_regs_mut()[0] = irq as _;
        ...
    });
    // 调用了 seL4_call,call 可以理解为 send 和 recv 的组合,上面 recv_slot 在其中的 recv 阶段生效
    let recv_msg = call_ep!(msg.clone());
    ...
}
服务端做了以下处理
RootEvent::RegisterIRQ => {
    let irq = read_types!(ib, u64);
    let dst_slot = LeafSlot::new(0);
    // 调用自己的 IRQ_CONTROL 能力,创建一个 irq handler,内核收到对应 irq 中断时,会通知任务,有点像信号量
    slot::IRQ_CONTROL
        .cap()
        .irq_control_get(irq, &dst_slot.abs_cptr())
        .unwrap();
    // 将获取的 irq_handler cptr 所在的 cslot 放到 extra_cap 中
    ib.caps_or_badges_mut()[0] = 0;
    // 使用 reply syscall, reply 是一个专门用来恢复 call 的 syscall,可以简单理解为 send
    sel4::reply(ib, rev_msg.extra_caps(1).build());
    dst_slot.delete().unwrap();
}
上述时用户态的用法,看起来还是比较简洁的,接收方填入 recv_slot, 发送方将 cptr 所在的 cslot,kernel 起到的作用就是将发送方的 cslot move 到 recv_slot,我们看一下 kernel 代码
上面提到,reply 是一个专门回复 call 的 syscall,在这里我们不介绍其设计,而是直接进入 reply syscall
#[cfg(not(feature = "kernel_mcs"))]
fn handle_reply() {
    let current_thread = get_currenct_thread();
    // caller 就是调用 call 的进程,该进程在 call 的 send 阶段,会把自己填到接收方的 TCB_CALLER slot 里,存一个 reply cap 类型
    let caller_slot = current_thread.get_cspace_mut_ref(TCB_CALLER);
    if caller_slot.capability.clone().get_tag() == cap_tag::cap_reply_cap {
        if cap::cap_reply_cap(&caller_slot.capability).get_capReplyMaster() != 0 {
            return;
        }
        let caller = convert_to_mut_type_ref::<tcb_t>(
            cap::cap_reply_cap(&caller_slot.capability).get_capTCBPtr() as usize,
        );
        // 找到 caller 后
        current_thread.do_reply(
            caller,
            caller_slot,
            cap::cap_reply_cap(&caller_slot.capability).get_capReplyCanGrant() != 0,
        );
    }
}
#[cfg(not(feature = "kernel_mcs"))]
fn do_reply(&mut self, receiver: &mut tcb_t, slot: &mut cte_t, grant: bool) {
    // 检查下 caller 是否处于等待 reply 的状态
    assert_eq!(receiver.get_state(), ThreadState::ThreadStateBlockedOnReply);
    let fault_type = receiver.tcbFault.get_tag();
    if likely(fault_type == seL4_Fault_tag::seL4_Fault_NullFault) {
        // 进行 cptr 的传递,这里的 receiver 就是调用 call 的任务,作为接收者
        self.do_ipc_transfer(receiver, None, 0, grant);
        // 删除这个 reply cap,每个 call 会产生一个临时的 reply,用完就删掉
        slot.delete_one();
        // receiver 之前一直在阻塞等待 reply,reply 后,将其状态改成可运行,并切换到该任务
        set_thread_state(receiver, ThreadState::ThreadStateRunning);
        possible_switch_to(receiver);
    } else {
        ......
    }
}
// 所以传递 cptr 的核心函数是 do_ipc_transfer
fn do_ipc_transfer(
    &mut self,
    receiver: &mut tcb_t,
    ep: Option<&endpoint>,
    badge: usize,
    grant: bool,
) {
    if likely(self.tcbFault.get_tag() == seL4_Fault_tag::seL4_Fault_NullFault) {
        self.do_normal_transfer(receiver, ep, badge, grant)
    } else {
        self.do_fault_transfer(receiver, badge)
    }
}
// 我们暂时不考虑 Fault 的情况,调用 do_normal_transfer
fn do_normal_transfer(
    // self 是 tcb_t,这是一个 tcb_t 的成员函数
    &mut self,
    receiver: &mut tcb_t,
    ep: Option<&endpoint>,
    badge: usize,
    can_grant: bool,
) {
    let mut tag =
        seL4_MessageInfo::from_word_security(self.tcbArch.get_register(ArchReg::MsgInfo));
    let mut current_extra_caps = [0; SEL4_MSG_MAX_EXTRA_CAPS];
    if can_grant {
        // 具备 grant 权限时,才会获取当前 tcb IPC buffer 中的 extra_caps 信息
        // lookup_extra_caps 获取 extra_cap 字段并且填到 current_extra_caps 中
        let status = self.lookup_extra_caps(&mut current_extra_caps);
        if unlikely(status != exception_t::EXCEPTION_NONE) {
            current_extra_caps[0] = 0;
        }
    } 
    ...
    // 在这一步执行了传递
    receiver.set_transfer_caps(ep, &mut tag, ¤t_extra_caps);
    ...
}
// 在 set_transfer_caps 中进行传递
fn set_transfer_caps(
    &mut self,
    ep: Option<&endpoint>,
    info: &mut seL4_MessageInfo,
    current_extra_caps: &[pptr_t; SEL4_MSG_MAX_EXTRA_CAPS],
) {
    info.set_extraCaps(0);
    info.set_capsUnwrapped(0);
    let ipc_buffer = self.lookup_mut_ipc_buffer(true);
    if current_extra_caps[0] as usize == 0 || ipc_buffer.is_none() {
        return;
    }
    let buffer = ipc_buffer.unwrap();
    // 从 receiver 的 ipc buffer 中,获取 receive slot
    let mut dest_slot = self.get_receive_slot();
    let mut i = 0;
    while i < SEL4_MSG_MAX_EXTRA_CAPS && current_extra_caps[i] as usize != 0 {
        let slot = convert_to_mut_type_ref::<cte_t>(current_extra_caps[i]);
        let capability_cpy = &slot.capability.clone();
        // 这部分是一些 unwrapped 功能的实现,没细看
        ...
        else {
            if dest_slot.is_none() {
                // 只接受第一个传递的 cptr
                break;
            } else {
                // 和上述 copy 里面介绍的一样,生成一个派生,然后插入目标 slot
                let dest = dest_slot.take();
                let dc_ret = slot.derive_cap(&capability_cpy);
                if dc_ret.status != exception_t::EXCEPTION_NONE
                    || dc_ret.capability.get_tag() == cap_tag::cap_null_cap
                {
                    break;
                }
                cte_insert(&dc_ret.capability, slot, dest.unwrap());
                dest_slot = None;
            }
        }
        i += 1;
    }
    info.set_extraCaps(i as u64);
}
5 capability 创建 #
5.1 untyped 能力 #
所有内核对象占用的内存地址都由 untyped 能力分配而来。elf-loader 初始化的时候,会把所有可用的物理内存空间都映射到虚拟地址空间,也就是说,内核始终都可以访问所有的物理内存空间。
kernel 使用的内存区域使用 fixed offset map,虚实地址之间是一个固定的 offset,因此转换非常方便。基本可以理解为,内核始终可以访问所有的物理内存。
在初始化完成后,kernel 会把所有的还未用到的内存区域打包为 untyped 能力,其实就是存储了起始地址和可用长度。是不是感觉有点熟悉了,和我们常用的 physical memory allocator 很像,基本可以按照 allocator 来理解。
seL4 文档中还提到,untyped 是一种能力,分配给用户态。这里有一点值得强调,kernel 只是告诉用户态任务,你有一段可以分配的 物理内存 ,你可以从其中 new 新的对象。但是
- 这段内存不映射到用户地址空间,而是在 kernel 地址空间,只有 kernel 可以访问
 - 用户态不知道这这段空间有多大,起始地址是什么。它只能告诉内核,我要从这段内存中生成一个新的对象,仅此而已
 
5.2 capability 创建过程 #
上述详细介绍了 capability 使用和流转的过程,现在最后介绍下 capability 创建的过程。前面说到,capability 可以理解成一个智能指针。那么创建的过程相当于
// T 是具体的 capability 类型,如 tcb, endpoint 
auto cptr = std::make_shared<T>();
当然在 seL4 中,由于内存管理更加严格,创建过程远比上述复杂。同样,我们先看下用户态如何创建一个能力。
seL4 中,所有空闲内存都由 untyped 能力管理,通过 seL4_Untyped_Retype API 创建一个内核对象,也就是 能力
LIBSEL4_INLINE seL4_Error
seL4_Untyped_Retype(seL4_Untyped _service, seL4_Word type, seL4_Word size_bits, seL4_CNode root, seL4_Word node_index, seL4_Word node_depth, seL4_Word node_offset, seL4_Word num_objects)
{
    ...
	/* Setup input capabilities. */
    // 设置一个接收 cnode,比如 root cnode,根据 node_index 和 depth 可以得到 slot
	seL4_SetCap(0, root);
	/* Marshal and initialise parameters. */
	// 填一些参数,比如对象类型,放置的 cslot 等等
    mr0 = type;
	mr1 = size_bits;
	mr2 = node_index;
	mr3 = node_depth;
    // 看起来是可以批量创建,我们暂且不分析
	seL4_SetMR(4, node_offset);
	seL4_SetMR(5, num_objects);
	/* Perform the call, passing in-register arguments directly. */
    // 调用 syscall 
	output_tag = seL4_CallWithMRs(_service, tag,
		&mr0, &mr1, &mr2, &mr3);
	result = (seL4_Error) seL4_MessageInfo_get_label(output_tag);
	/* Unmarshal registers into IPC buffer on error. */
    ...
}
我们再看下 rel4-linux-kit 中是如何创建内核对象的
// 使用 OBJ_ALLOCATOR 作为对象生成分配器,创建一个 endpoint
let fault_ep = OBJ_ALLOCATOR.lock().alloc_endpoint();
// OBJ_ALLOCATOR 中的创建新能力的方法
pub fn allocate_and_retype(
    &mut self,
    // 对象类型
    blueprint: sel4::ObjectBlueprint,
) -> sel4::cap::Unspecified {
    // 对象或者说能力需要一个 slot 承载
    let leaf_slot = self.allocate_slot();
    // 近似于调用 seL4_Untyped_Retype
    self.ut
        .untyped_retype(
            &blueprint,
            &leaf_slot.cnode_abs_cptr(),
            leaf_slot.offset_of_cnode(),
            // rel4-linux-kit 中只支持创建一个对象
            1,
        )
        .unwrap();
    // 返回创建完成的能力,其实能力在用户态也就是一个 index,实例存储在 kernel 中
    leaf_slot.cap()
}
内核态中有 invoke_untyped_retype 函数处理 seL4_Untyped_Retype. 该部分是 untyped 能力的重要实现。
seL4 内核中的 cap 类型定义在 structures.bf 文件中,所有的 cap 类型都是一个 2usize 长度的位图。基本上所有的 cap 类型,比如 cnode,endpoint 都会存储它指向的实例地址,像一个指针。但是 untyped 能力不同,所有的信息都存在这 2usize 的位图中,没有所谓的实例。
下面我们详细分析下 decode_untyed_invocation 的实现,主要的实现如下
pub fn decode_untyed_invocation(
    inv_label: MessageLabel,
    length: usize,
    slot: &mut cte_t,
    capability: &cap_untyped_cap,
    buffer: &seL4_IPCBuffer,
) -> exception_t {
    ...
    // 获取对象创建类型
    let op_new_type = ObjectType::from_usize(get_syscall_arg(0, buffer));
    // 获取 syscall 参数
    let new_type = op_new_type.unwrap();
    // 有的类型,比如 untyped 类型申请的内存大小是不固定的
    let user_obj_size = get_syscall_arg(1, buffer);
    let node_index = get_syscall_arg(2, buffer);
    let node_depth = get_syscall_arg(3, buffer);
    let node_offset = get_syscall_arg(4, buffer);
    let node_window = get_syscall_arg(5, buffer);
    // 返回对象大小,有一些类型是固定的,有一些则从 user_obj_size 获取
    let obj_size = new_type.get_object_size(user_obj_size);
    // 有一些检查操作,确认参数有效
    // 找到 dest cnode,通常可能是 root_cnode
    let node_cap = &mut cap_cnode_cap::new(0, 0, 0, 0);
    let status = get_target_cnode(node_index, node_depth, node_cap);
    if status != exception_t::EXCEPTION_NONE {
        return status;
    }
    ...
    let status = slot.ensure_no_children();
    let (free_index, reset) = if status != exception_t::EXCEPTION_NONE {
        // 原始 untype 有子节点,获取当前 untyped 中空闲位置的 index,也就是相对位置
        (capability.get_capFreeIndex() as usize, false)
    } else {
        (0, true)
    };
    // 获取空闲内存地址,untyped cap base_addr + free_index
    let free_ref = GET_FREE_REF(capability.get_capPtr() as usize, free_index);
    // 获取还剩下的空闲内存大小
    let untyped_free_bytes = BIT!(capability.get_capBlockSize()) - FREE_INDEX_TO_OFFSET(free_index);
    // 做一个对齐处理,获取对齐后的地址
    let aligned_free_ref = alignUp(free_ref, obj_size);
    // 执行 invoke_untyped_retype,在其中创建对象,创建相应能力,并且将该能力插到指定 cslot 中
    invoke_untyped_retype(
        slot,
        reset,
        aligned_free_ref,
        new_type,
        user_obj_size,
        convert_to_mut_type_ref::<cte_t>(node_cap.get_capCNodePtr() as usize),
        node_offset,
        node_window,
        device_mem as usize,
    )
}
pub fn invoke_untyped_retype(
    src_slot: &mut cte_t,
    reset: bool,
    retype_base: pptr_t,
    new_type: ObjectType,
    user_size: usize,
    dest_cnode: &mut cte_t,
    dest_offset: usize,
    dest_length: usize,
    device_mem: usize,
) -> exception_t {
    // 获取 untyped 区域的起始地址,untyped 区域就是一段目前还没有用到,没有对象占用的内存区域
    let region_base = cap::cap_untyped_cap(&src_slot.capability).get_capPtr() as usize;
    // 创建对象的大小,注意这里是指总大小,如果创建多个对象,那么是加起来的
    // get_object_size 会返回创建对象的大小,根据对象类型和用户态提供的大小产生,page_table 就是 4KiB
    let total_object_size = dest_length << new_type.get_object_size(user_size);
    let free_ref = retype_base + total_object_size;
    // 在 untyped 类型上做个标记,已经被占用了
    // 这个时候可以理解为,对象本身已经 new 完了,后面就是创建 cptr 的过程了
    cap::cap_untyped_cap(&src_slot.capability)
        .set_capFreeIndex(GET_FREE_INDEX(region_base, free_ref) as u64);
    // 本质是创建 cptr,这里是创建 cap_page_table, 并且加到 cspace 中,就不展开了
    create_new_objects(
        new_type,
        src_slot,
        dest_cnode,
        dest_offset,
        dest_length,
        retype_base,
        user_size,
        device_mem,
    );
    exception_t::EXCEPTION_NONE
}
pub fn arch_create_object() {
    ...
    // 这里创建 page_table cptr
    ObjectType::seL4_ARM_PageTableObject => {
        cap_page_table_cap::new(ASID_INVALID as u64, region_base as u64, 0, 0).unsplay()
    }
}
通过上述流程分析,可以看出,创建 capability 其实分两步
第一步,类似于 new,从 untyped 能力中获取一段内存空间,创建一个内核对象
第二步,创造一个 cptr 把对象包装起来,主要就是存对象地址和一些关键信息。然后把 cptr 插到某个 cslot 中,这样通过 capability 寻址就可以找到这个 cptr,相当于找到了对象实例指针,就可以用它了。