pop指令的功能是什么?如何借助其他指令实现其功能_给Rust实现一个简单的stackful generator(中)上下文切换...

6c9df4c353e8bbcdc9ede2647f21a08f.png

本来我想一口气发完的,但感觉这次的文章写得实在太差,逻辑感不强,也比较长,所以本文尝试分为三部分:

  1. 原型与api https://zhuanlan.zhihu.com/p/91179318
  2. 上下文切换 https://zhuanlan.zhihu.com/p/91184528
  3. 完善功能 https://zhuanlan.zhihu.com/p/91186796

这部分主要讲上下文切换的实现。

ucontext

我们实现了上下文切换主要是使用了glibc提供的ucontext.h。它提供了四个api:

/* Get user context and store it in variable pointed to by UCP.  */
extern int getcontext (ucontext_t *__ucp) __THROWNL;

/* Set user context from information of variable pointed to by UCP.  */
extern int setcontext (const ucontext_t *__ucp) __THROWNL;

/* Save current context in context variable pointed to by OUCP and set
   context from variable pointed to by UCP.  */
extern int swapcontext (ucontext_t *__restrict __oucp,
			const ucontext_t *__restrict __ucp) __THROWNL;

/* Manipulate user context UCP to continue with calling functions FUNC
   and the ARGC-1 parameters following ARGC when the context is used
   the next time in `setcontext' or `swapcontext'.

   We cannot say anything about the parameters FUNC takes; `void'
   is as good as any other choice.  */
extern void makecontext (ucontext_t *__ucp, void (*__func) (void),
			 int __argc, ...) __THROW;

上一篇中实现C的原型的时候,就有展示过怎么用了。我们最主要的使用到了上下文切换的功能。而ucontext还支持了非局部转跳(setjmp/longjmp)等功能,多保存了些我们不需要的上下文信息。最主要的是glibc windows上也没有(但是有winapi提供的WinFiber)。

上下文切换

现在我们要去ucontext化,如何实现上下文切换功能呢?我们需要先要了解一下x86-64和posix abi的调用约定(先假定是非windows的x64平台)

寄存器

  1. rax作为返回值。不需要保护。
  2. rsp栈指针寄存器,指向栈顶。需要被保护。
  3. rdi, rsi, rdx, rcx, r8, r9依次对应函数的前六个参数,当参数超过6个,才会压栈。不需要保护。
  4. rbx, rbp, r12到r15,用于数据存储,“属于”调用者。需要被保护。

fe084be17117d575149ec7b59c272961.png
x86_64 posix abi各寄存器的说明

调用栈调用栈是从上往下增长的,一个函数的栈帧长这样:

1a02084293a5c58077695995b500e436.png
一个函数的栈帧

当前函数栈帧从高地址(栈底)到低地址(栈顶)分别有返回地址,局部变量等东西。然后前一个帧栈有函数参数。

函数调用

  1. call指令:先将下一条指令的地址(return address)push到栈顶,然后转跳到函数体
  2. ret指令:将return address(栈顶,rsp所指向的位置) pop到rip中。
  3. 在执行call之后,会将rsp挪到新的位置,创造一个新的帧栈。
  4. 在执行ret之前,会将rsp挪回去,销毁当前帧栈。

对于上下文切换我们只需要把需要被保护的寄存器存起来就好,同时也不需要处理浮点数的东西。

#[derive(Debug, Default, Clone, Copy)]
#[repr(C)]
struct Ctx {
    rsp: u64,
    r15: u64,
    r14: u64,
    r13: u64,
    r12: u64,
    rbx: u64,
    rbp: u64,
    gen_ptr: u64, // 当做参数指向协程generator
}

extern "C" {
    fn switch_ctx(old: *mut Ctx, new: *const Ctx);
}

上下文切换实现起来十分的简单,只要简单的保存上下文,恢复上下文就可以了。switch_ctx的汇编实现:

switch_ctx:
    ; 这里没像普通函数那样子开辟新的栈帧
    ; 保存当前上下文到old
    mov     %rsp, 0x00(%rdi)
    mov     %r15, 0x08(%rdi)
    mov     %r14, 0x10(%rdi)
    mov     %r13, 0x18(%rdi)
    mov     %r12, 0x20(%rdi)
    mov     %rbx, 0x28(%rdi)
    mov     %rbp, 0x30(%rdi)
    ; 从new恢复上下文
    mov     0x00(%rsi), %rsp ; 修改栈指针,使得调用栈从自定义的空间中开始
    mov     0x08(%rsi), %r15
    mov     0x10(%rsi), %r14
    mov     0x18(%rsi), %r13
    mov     0x20(%rsi), %r12
    mov     0x28(%rsi), %rbx
    mov     0x30(%rsi), %rbp
    ; 将gen_ptr当做第一个参数传给bootstrap
    mov     0x38(%rsi), %rdi
    ret

还有makecontext对应的过程:

unsafe fn init_ctx<Send, Recv>(
    ctx: &mut Ctx,
    dual_gen: Box<Gen<Recv, Send>>, // caller的generator
    stack_ptr: *mut u8,
    stack_size: usize,
) {
    // bootstrap的第一个参数,并用Box::into_raw释放其所有权。
    // 进入bootstrap前给rdi寄存器
    ctx.gen_ptr = Box::into_raw(dual_gen) as u64;
    // 设置协程入口点为bootstrap
    ptr::write(
        // 这里要给栈底设置足够多的空闲位置
        stack_ptr.add(stack_size - 32) as *mut u64,
        bootstrap::<Send, Recv> as u64,
    );
    // 让栈顶指针指向返回地址(bootstrap)
    // 当switch_ctx返回时,就会将所指向的地址(bootstrap)弹到rip中,然后执行bootstrap
    ctx.rsp = stack_ptr.add(stack_size - 32) as u64;
}

现在只需要将swapcontext换成我们写的switch_ctx,然后将makecontext换成init_ctx,最后在bootstrap最后手动调用switch_ctx就好。

其中bootstrap的签名为:

unsafe fn bootstrap<Send, Recv>(dual_gen_raw: *mut Gen<Recv, Send>) -> ! 

伪小结

本篇的实现参考了文章

Introduction​cfsamson.gitbook.io
89bb3cd6e06541f916ade96f0c0d9c03.png

将ucontext替换为了自己的实现的上下文切换的函数。这里的汇编代码可以先编译成静态库然后给rust使用,也可以编写build.rs执行编译打包的脚本,直接在cargo工程中编写汇编。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个类似于CGroup的进程组机制的结构体,附带相关成员的注释。 ```rust pub struct ProcessGroup { name: String, // 进程组的名称 pid_list: Vec<i32>, // 存储进程组内所有进程的 pid cpu_quota: Option<u64>, // CPU 配额,单位为 1/1000 CPU 时间 cpu_period: Option<u64>, // CPU 周期,单位为微秒 cpu_shares: Option<u64>, // CPU 资源共享权重 memory_limit_in_bytes: Option<u64>, // 内存限制,单位为字节 oom_score_adj: Option<i32>, // OOM(Out Of Memory)分数调整值 } impl ProcessGroup { // 创建一个新的进程组 pub fn new(name: String) -> Self { ProcessGroup { name, pid_list: Vec::new(), cpu_quota: None, cpu_period: None, cpu_shares: None, memory_limit_in_bytes: None, oom_score_adj: None, } } // 添加一个进程到进程组 pub fn add_pid(&mut self, pid: i32) { self.pid_list.push(pid); } // 获取进程组所有进程的 pid pub fn get_pids(&self) -> &[i32] { &self.pid_list } // 设置 CPU 配额 pub fn set_cpu_quota(&mut self, quota: u64) { self.cpu_quota = Some(quota); } // 获取 CPU 配额 pub fn get_cpu_quota(&self) -> Option<u64> { self.cpu_quota } // 设置 CPU 周期 pub fn set_cpu_period(&mut self, period: u64) { self.cpu_period = Some(period); } // 获取 CPU 周期 pub fn get_cpu_period(&self) -> Option<u64> { self.cpu_period } // 设置 CPU 资源共享权重 pub fn set_cpu_shares(&mut self, shares: u64) { self.cpu_shares = Some(shares); } // 获取 CPU 资源共享权重 pub fn get_cpu_shares(&self) -> Option<u64> { self.cpu_shares } // 设置内存限制 pub fn set_memory_limit_in_bytes(&mut self, limit: u64) { self.memory_limit_in_bytes = Some(limit); } // 获取内存限制 pub fn get_memory_limit_in_bytes(&self) -> Option<u64> { self.memory_limit_in_bytes } // 设置 OOM 分数调整值 pub fn set_oom_score_adj(&mut self, adj: i32) { self.oom_score_adj = Some(adj); } // 获取 OOM 分数调整值 pub fn get_oom_score_adj(&self) -> Option<i32> { self.oom_score_adj } } ``` 这里的 `ProcessGroup` 结构体具有以下几个成员: - `name`: 进程组的名称,可以用来标识进程组。 - `pid_list`: 存储进程组内所有进程的 pid。 - `cpu_quota`: CPU 配额,单位为 1/1000 CPU 时间。如果未设置,则为 `None`。 - `cpu_period`: CPU 周期,单位为微秒。如果未设置,则为 `None`。 - `cpu_shares`: CPU 资源共享权重。如果未设置,则为 `None`。 - `memory_limit_in_bytes`: 内存限制,单位为字节。如果未设置,则为 `None`。 - `oom_score_adj`: OOM(Out Of Memory)分数调整值。如果未设置,则为 `None`。 以上成员可以通过 `set_xxx()` 和 `get_xxx()` 方法来设置和获取。其 `add_pid()` 方法用于将进程添加到进程组,`get_pids()` 方法用于获取进程组所有进程的 pid。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值