@@ -651,194 +651,6 @@ Rust语法已有异步机制所需要的async和await;
651
651
652
652
TODO
653
653
654
-
655
- ---
656
-
657
-
658
- # 日志
659
-
660
- ## ** 20210227-RISC-V进程地址空间切换交流**
661
-
662
- ### ** 吴一凡介绍目前的代码状态**
663
-
664
- 内核与用户进程各一个地址空间,共同有一个Trampoline(alltrap的代码)和TrapContext(alltrap的数据);Trampoline用于在用户地址空间和内核态时访问。
665
-
666
- ### ** 讨论**
667
-
668
- #### ** trampoline区域**
669
-
670
- trampoline区域的初始化在哪?
671
-
672
- 1 . trap_return, 链接时,把相关代码放到内核中;
673
- 2 . map_trampoline建立映射
674
- 1 . 用户态是不能访问的;
675
- 2 . 中断时,中断进入时硬件保存现场并不直接访问这个区域的内存,而是放在寄存器中;
676
- 3 . 这个区域只在内核态且是用户地址空间时访问;
677
- 4 . 这两个页表的设置是一样的,可以保证它只在内核态可以访问;
678
- 5 . 中断服务例程的地址在链接时得到;
679
-
680
- 这个思路的原始出处是哪?
681
-
682
- XV6;MIT也没有维护X86的版本了。
683
-
684
- #### ** 内核进程中的用户进程页表**
685
-
686
- 1 . 每个进程在内核中有一个页表数据结构;
687
- 2 . 用户页表项:在内核中是线性映射;
688
- 3 . 每个用户进程的页表逻辑地址是不同的;
689
- #### ** 用户进程的内核栈**
690
-
691
- 1 . 目前每个用户进程在内核进程中有一个自己的内核栈;
692
- 2 . 陈老师认为,每个用户进程必须有自己的内核栈,向勇认为可以没有;有兴趣时,同学可以来讨论。
693
- #### ** 系统调用时用户进程与内核进程间的切换过程**
694
-
695
- 有可能基于Trampoline完成整个切换过程吗?把内核中处理系统调用和调度的功能独立出来,形成两个或一个进程;这是有可能的;这个工作应该是hypervisor的功能;
696
-
697
- #### ** 用户进程切换**
698
-
699
- 有可能进行进程间的切换吗?有可能;有时间时,再进入进一步的讨论;
700
-
701
- #### ** 内核态的中断处理**
702
-
703
- 在内核态的中断的处理与用户态的有什么不同?没有地址空间切换了,服务一定是不一样的,可能还有其他区别;
704
-
705
- ### ** 建议**
706
-
707
- 1 . 其他同学在看明白后,可以给出自己的理解和想法描述出来;
708
- 2 . 吴一凡:改[ 异步方案文档] ( https://shimo.im/docs/473QyY5re6tvXb3w?fileGuid=473QyY5re6tvXb3w ) ;
709
-
710
- # 20201111-交流
711
-
712
- ## 内核页表隔离
713
-
714
- 每个进程两个地址空间:用户和内核。用户地址空间只保留少部分内核空间数据,每次系统调用都从用户地址空间切到内核地址空间。防止熔断漏洞。
715
-
716
- 结论:目前暂时不这样实现
717
-
718
- ## 与 IPC 的统一
719
-
720
- IO 核的运行可看做另一个进程,IO 核处理用户进程的 IO 请求相当于 IPC。这种设计可与传统的 IPC 统一,即使用 ring buffer,在两个用户进程之间进行 IPC。
721
-
722
- # 20201101-交流
723
-
724
- 目前进展:基本完成普通核部分的内核功能,有简单的 C 用户程序和库。(step 3)
725
-
726
- ## 分工
727
-
728
- jyk:完成 IO 核部分的内核功能:创建共享内存、调度 IO 协程、执行 IO。
729
-
730
- zyr:制定系统调用接口和共享内存数据格式,用 C 编写用户态 IO 测试程序。对应上文提到的三种实现方式:3 异步 read、4b 同步写法的异步 read、4c 同步写法的异步 read。
731
-
732
- lfy:编写 rust 用户库,支持运行 rust 用户程序,编写利用 rust async 创建多个协程的用户程序。然后利用 rust async 将 C 的 IO 测试程序改成 rust。对应上文提到的实现方式:4a 同步写法的异步 read。
733
-
734
- # 20201014-讨论的实现方案
735
-
736
- 利用 io_uring/virtio 的思想,基于共享内存实现的高效异步系统调用接口。
737
-
738
- 假设系统有两个核,分别为:
739
-
740
- 1 . 普通核:类似传统的 OS,有用户态和内核态,负责运行用户进程、调度用户进程、处理普通系统调用。
741
- 2 . IO 核:只有内核态,负责运行内核协程、调度内核协程、处理异步调用。实现了一套类似 io_uring 的机制,内核协程会不断 poll 共享内存中的请求队列,处理完毕后放入完成队列。使用 Rust async 实现。
742
- ## 前期简化
743
-
744
- 有且仅有两个核;没有用户线程;单用户进程;没有抢占;
745
-
746
- ## 用户程序的实现
747
-
748
- 1 . 通过系统调用,创建用户进程
749
- 2 . 通过系统调用,在 IO 核同时创建 io_uring 共享内存,和对应的内核协程(需要 IPI)。
750
- 3 . 异步 read:向请求队列写入 read 操作和所需的参数,立即返回。直到完成队列显示已完成,并得到返回结果。(可能需要通知机制?)
751
- 4 . 同步写法的异步 read(这部分是用户库的内容吗?是):
752
- 1 . 使用 Rust async 创建用户协程,poll 函数里检查对应的 IO 操作是否在完成队列中
753
- 2 . read 后轮询是否完成,用 yield 去别的进程执行一会儿
754
- 3 . read 后 wait 进入睡眠,直到完成时才被唤醒(通知机制?)
755
- ## 请求队列与完成队列的设计
756
-
757
- TODO
758
-
759
- 使用共享内存在用户进程与内核协程间共享。
760
-
761
- 是否要和 io_uring 完全一样?
762
-
763
- 是否还是两队列形式?更好的设计?
764
-
765
- ## 最小原型系统
766
-
767
- 预计实现的普通系统调用:(有用户/内核切换)
768
-
769
- 1 . spawn:创建用户进程
770
- 2 . setup_io:创建共享内存和内核协程,返回共享内存地址
771
- 3 . yield:主动放弃当前进程的执行,进行进程调度
772
- 4 . * wait:主动放弃当前进程的执行,进行进程调度,直到 IO 操作完成时返回*
773
-
774
- 预计实现的异步调用:(只需写共享内存,无用户/内核切换)
775
-
776
- 1 . open
777
- 2 . read
778
- 3 . write
779
- 4 . close
780
-
781
- 测试环境:
782
-
783
- * RISCV64,QEMU/K210
784
- * memory fs/SD 卡
785
- ## 实现步骤
786
-
787
- 方案一:从头开始造
788
-
789
- 1 . RISCV64 QEMU 多核启动
790
- 2 . 普通核实现内核的基本功能(内存管理、进程管理、memory FS)
791
- 3 . 支持运行一个 hello_world 用户进程
792
- 4 . IO 核实现共享内存创建
793
- 5 . 内核协程创建与调度
794
- 6 . 实现 open/close/read/write
795
- 7 . 完善系统调用
796
- 8 . 编写性能测试程序,真机测试
797
-
798
- 方案二:将 rCore_Tutorial 改造成这种架构
799
-
800
- ## 其他问题
801
-
802
- IO 核如何访问不同用户进程的IO数据?好像还是要切页表?
803
-
804
- (第一版无需考虑)
805
-
806
- 完成时如何通知用户程序?
807
-
808
- 是否可在普通核开中断?
809
-
810
- 是否可以支持乱序完成?(似乎 io_uring 就是乱序的,可以通过参数强制顺序执行)
811
-
812
-
813
- ---
814
- #
815
-
816
- ## 用户线程与地址空间相关的问题
817
-
818
- #### 普通核
819
-
820
- 内核态有一个全局调度器,负责调度和执行用户线程(Thread)。每个线程都有独立的地址空间(可以多个线程共享同一地址空间),通过系统调用进入内核态时不发生地址空间切换,只有调度器进行线程切换时才进行地址空间切换。因此线程地址空间需要同时映射内核与用户的代码与数据。
821
-
822
- * 注:目前的实现线程==进程,是最基本的调度单位。以后可通过将共享地址空间的线程组成一个“线程组”,来表示进程的概念。在具体实现中,用户线程也被封装为了一个 rust future,通过 executor 进行调度。*
823
-
824
- #### IO 核
825
-
826
- 只有一个地址空间,即内核地址空间,只映射内核的代码和数据,且不会在之后进行切换。也运行一个全局调度器,复制调度和执行用于 IO 的协程。当用户程序创建 IO 共享内存时,会同时在 IO 核的内核地址空间,和普通核对应的用户线程地址空间的用户数据段增加映射。
827
-
828
- #### 关键问题
829
-
830
- IO 核如何访问不同用户进程的IO buffer而不用频繁切页表?
831
-
832
- 目前想到的解决方法:(这几种方法可以一起用:共享内存放在特定区域,以优化地址转换;加一个描述缓冲区的数据结构,提高灵活性)
833
-
834
- 1 . 创建专门的共享内存,存放 IO buffer。缺点:IO buffer 需要放在特定的地方,降低了灵活性,增加编程复杂性。
835
- 2 . 内核要访问用户数据时,手动进行用户虚拟地址到物理地址的转换,然后直接访问物理地址获取数据。缺点:降低安全性?对于不连续的页面每隔 4K 需要一次地址转换,开销大。适合 IO buffer 小到几个页面范围内的情况。
836
- 3 . 对方案 2 的优化:将 IO buffer 映射连续的物理地址,只需进行一次地址转换;使用大页,每隔 2M 进行一次地址转换;将用户虚拟地址到物理地址的映射结果缓存到专门的区域,以减少每次地址转换的开销(相当于4级页表的4次访存变为1次)。
837
-
838
- ---
839
-
840
-
841
-
842
654
---
843
655
844
656
0 commit comments