1、
借鉴Embassy宏,通过函数签名的检查,判断传入的函数是一个普通的函数指针(或闭包)还是一个用Rust编写的异步函数
通过trait bound,使函数接口既能接收Fn、FnOnce等普通函数闭包实现的trait,又能接收Future这一异步函数实现的trait。接下来通过trait实现函数类型的判断(判断是异步的还是同步的):ps,应该是实现不了的,这些检查操作只能在编译时进行,那就必须依赖于rust语言本身
fix (liam): 可以事先为函数预先的分配一些async task,这些task都是一样的,接收一个固定的pc参数(函数指针),然后会跳转到对应函数(pc指向的)执行,最后在函数执行完成后,调用返回,然后主动调用await进行控制权转移(或者这里应该考虑drop?,就是在某个drop里面让executor重调度,可以去看看embassy的实现)。但是仍然存在问题,怎么统一接口?直接传入一个函数指针,无法区分到底是异步函数还是普通函数。那我们可以对所有的函数,都包裹一层async任务,该任务就是前面所说的,接收固定的pc参数(函数指针),其poll就是执行pc对应的函数/异步函数。这里就需要实现给这样的任务写好,参考embassy,我们只需要写一个这样的异步函数,设置其pool足够多即可。但是,这里有个地方我认为是需要区别于embassy进行改进的:
这里看embassy的操作,在函数
dequeue_all
中应该是一次poll一个task,这个task被dequeu出run-queue
(这里embassy的实现细节需要注意,它选择直接将head
置为空,也就是让这个run-queue
先被清空,然后再调度任务执行),直到所有task被poll完。通过dequeue_all
使得每个在run-queue
中的任务都会在上一个任务poll完成后,被调度执行(所以可以在这个位置加入优先级的判断,选择最高优先级的任务进行优先调度)。我们应该取消掉taskpool的设计,对于每次相同async函数的spawn时候再进行一次alloc的分配,这是为了空间的利用,如果按照embassy的taskpool的设计,那么对于taskpool设置的很大的async情况,那么会在这个async 函数的第一个task被spawn的时候就分配整个taskpool的空间,这在我们的设计里会很浪费,因为我们会预先分配未来所可能产生的所有协程/线程,都对其分配一个异步task来接收,这就导致后续没有使用到的协程也会占用内存空间(原来embassy这样考虑是为了保证在编译时能检查到所有任务的空间是足够的,不会发生溢出的情况),我们这里就打算将其改为每个任务都是运行时分配空间,上层调用接口创建一个任务的时候,我们拿出底层创建好的异步块对它进行包裹(也就是接受它作为一个pc指针的东西传入,底层创建好的异步块任务只是执行pc的内容)。
给所有的任务(不过是否异步,不管是c还是rust)套上一个固定的异步任务来执行它们,这些异步任务接收一个指针做参数,指针是一个函数,固定的任务就会跳到指针指向的函数去执行,而这个函数是由用户提供的,如果用户是c/rust的普通函数,它这个函数就是task函数,而如果是rust的异步函数,那么用户传入的这个就是poll_function 然后这个实现过程是需要预先有足够多的固定异步任务的,然而如果一开始就开这么多固定的任务就有问题,所以就需要改变原有taskpool设计,利用arena的实现,动态为它们分配栈空间
2、关于中断调度
会有刚好await点让权了但是刚好被中断的情况,中断和await都会触发一次任务调度,所以这里需要考虑一下这个细节:
我感觉最简单的思路是一个全局变量标记,然后进入调度的时候都先判断一下这个标记(这里需要进入临界区),这里详细说一下:
Q: 如果所有的中断发生的时候都给一个栈的话不是就跟线程没什么区别了吗,只不过一个线程的栈空间不是固定的,但是他们还是会一直持有一个栈。我想的是,如果在协程任务执行的时候被中断就说明是非await点,这个时候才需要给协程分配栈。而如果是在执行器代码执行的时候被中断,那么就不需要给协程一个栈,因为这个时候当前协程已经主动让权了。所以如果把整个调度都套上cs的话确实没有问题,这个时候就能来一个中断就给一个栈,并且做到非await点才会分配栈。但是如果都套上临界区的话我又怕临界区太大了,实时性不好。 A: 临界区如果是在执行器那里应该很快,因为协程切换操作开销小,不需要现场保护,而且找最高优先级的操作是位图实现的,我记得ucosii这里其实也是把这里加的临界区:
这样想起来效率还挺高,因为await的存在,只要协程部分能保证到切换到高优先级的任务运行,那实际上中断来的时候,检测调度找最高优先级的时候,就完全不需要切换,因为当前任务就是最高优先级任务,基本上很少会分配栈,而且分配了栈很容易在后续就会又回归到协程的模式,栈很快就能被回收回去
还有一个思路是借鉴ucosii的操作:把他们都搞到pendsv就能不嵌套,这样在出现await的时候也是pendsv,然后中断会等他调度完成,然后还是会调度一次,不过会很快发现当前任务就是最高优先级任务,然后就不调度了。
3、关于实时性
在我们这个系统里面,中断来的时候如果是非await点并且新协程的优先级更高的话的话还是要sw,而在uc里面也是这样。但是如果新的协程优先级更低的话就不会进行切换,原来的uc也是这样。那么跟原来的唯一的区别感觉就是在await点被中断了,但是我们现在的设想是在跑执行器代码的时候不会被中断,那就在实时性上应该跟没有await没有区别)
但是我认为高的地方在于sw操作本身的速度,以及我们在协程部分没有使用pendsv的软中断方式切换任务,只要协程调度的次数多,应该大部分时候纯协程那部分能让当前跑的都是最高优先级任务
不过如果考虑平均值,考虑上非await点的时候,动态分配栈的开销,可能也不好说,那就做出来的时候再测试,到时候针对实时性可能还得设计一些好的测试用例