一些构想


  • 内存的虚拟化,cpu的虚拟化,程序的分段,加载,动态链接,状态机的执行,从我们在文本编辑器中按照一定的语义敲下一些ascii字符,到执行时的转化为状态机变化。(环境变量,path,应用程序是什么)
  • 剥离一些外围知识后,纯粹的编程语言是什么(cs61a),在那种抽象模型下解释java,纯粹的java是什么,再补充java的细节。最后简短的介绍python。
  • 静态调度,动态调度,多线程的三个问题,解决方案,锁是什么,如何实现,锁-条件变量-信号量-生产者消费者(互斥与同步),最后从并发到IO多路复用,如何理解java的多线程中相关概念。
  • 介绍MYsql的相关概念
  • 前面处理了c代码从文本到程序执行的全过程,进一步梳理java代码的从文本到执行的全过程

    other


    tmux : https://www.ruanyifeng.com/blog/2019/10/tmux.html

  • Ctrl+b %:划分左右两个窗格。
  • Ctrl+b ":划分上下两个窗格。

    Project N


    M1 pstree


    常用系统命令:https://www.cyberciti.biz/tips/top-linux-monitoring-tools.html
    语言的问题:csapp中,大量的时间被花费来阅读理解我们需要干什么上了。
    目的:
    实现pstree命令、附带 -v -p -n 三个选项及之间的组合、
    20241104:
    是在不想干,那就先实现-v 参数的内容,从简单的做起。too easy, 好过在家打游戏到11点。不过忘了之前的多窗口管理与linux的可视化。
    不去主动安排时间,就会被时间安排!
    今晚至少没有打游戏,great,明天呢?
    1.完善参数解析。标记位:v,p,n; done.
    2.git 管理代码 stupid conten tracer… haha
    3.c语言,实现一个树状结构 done
    4.获取进程id,及parent,塞进树里。done
    其他:对树状结构读取并输出。
    20241105:
    明日:
    1.进一步明确树的结构,是否真的需要link 不需要
    2.树结构的排序
    3.进程填满树 done
    4.makefile 文件
    5.h 头文件 done
    20241106:
    明日: Critical 将进程信息正确的放到树中,我需要了解下c语言版本的数据结构。这是一个纯oj题。
    //一些奇怪的待查明的东西:

    while((entry = readdir(dir))!= NULL) {
      if (entry -> d_type == DT_DIR) {
          if (entry -> d_name[0] >= '0' && entry -> d_name[0] <='9') {
            //获取内部结构相关内容
            FILE *fp;
            char line[256];
            char ppid_s[8];
            char name[128];
            char path[128];
            char *pid_s = entry ->d_name;
    
            for (int i = 0; i < strlen(path); i++) {
              path[i]= '\0';
            }
            strcat(path, "/proc/");
            strcat(path, pid_s);
            strcat(path, "/status");
    
            fp = fopen(path, "r");
            while (fgets(line, sizeof(line), fp) != NULL)
            {
              if (strncmp(line, "Name:", 5) == 0) {
                sscanf(line, "Name:   %s", name);
              }
              if (strncmp(line, "PPid:", 5) == 0) {
                sscanf(line, "PPid:   %s", ppid_s);
                break;
              }
            }
            pid_t pid = atoi(pid_s);
            pid_t ppid = atoi(ppid_s);
            char *pname = (char *)malloc(sizeof(char) *128);
            strcpy(pname, name);
            process *cur = create_process(pid, pname);
            insert_tree(root, cur, ppid);
    
            fclose(fp);
    
          }
      }
    }
    


    path变量 stack 中? 不会在每次循环时重新建立?
    c语言中声明数组时需要额外初始化吗?还是声明的时候就已经初始化了?
    Ouch, 24-11-10 19:18,终于初步完成pstree,淦! 完全不想再去弄第二个实验了,第二周的课程也不想去完成了。

    L0


    什么是abstructmachine? 今晚需要完成到什么程度?现在没什么状态。回家洗个澡再战(即使房间里几乎没有效率 哭)
    –很明显,没有再战。
    首先需要我们理解AbstractMachine,并在其环境中实现一些库函数。然后在这个环境中开始我们的小game。
    20241122:12.18 great 总算跑起来了,能看到一个图形界面。
    20241124:16.30 总算实现了画面的移动。不过完全没理解absmachine的图形输出。puts 倒是可以输出字符串。

    M2


    start 创建并开始执行、wait 类似join、yield切换。main函数的执行也是一个协程。
    状态机:指令+状态
    1.如何在程序中实现状态的保存(有哪些状态,如何保存,如何恢复)
    2.如何在程序中实现指令的追踪
    哪些状态:stack + 寄存器(被调用者保存的寄存器)
    指令追踪:%rip
    直接保存寄存器!
    20241203 :1.setjmp/longjmp & GDB 调试;2.abstractmachine 的stack_switch_call (x86.h);3.GCC-Inline-Assembly-HOWTO;4.c inline;5.ABI 对对齐的要求;
    20241214,上述除了第五点都做完了,可是实验进度为0…
    Step1:以当前的背景知识,尽可能的细化分解。
    1.已文档中示例作结构
    2.co_start
    ​ 1.分配空间,初始化结构体
    ​ 2.需要一个容器,保存所有的结构体
    ​ 3.新创建的协程不会立即执行,而是调用 co_start 的协程继续执行,调用start的协程如何表示?何时被初始化?它的状态机模型是什么?
    3.co_wait
    ​ 1.判断目标协程的状态:
    ​ CO_DEAD:free。什么情况下状态会流转到 dead?
    ​ OTHER:什么都不做,yield
    ​ 2.main main函数天然的属于一个协程? 如何实现
    4.co_yield
    5.如何将main函数与协程体系结合起来?
    ​ 1.main中调用了wait后,如何确保main函数还是有可能继续被执行? – main = 协程,wait中yield后,main依旧保留在协程数组中,后续还是可能继续被执行。
    ​ 2.因为mian是协程,那么main的co结构体何时被创建呢? –main可以被默认初始化,或第一次调用start时初始化。
    6.状态流转:
    1.创建后:CO_NEW
    2.调用stack_switch_call第一次执行后:CO_RUNNING
    3.调用wait后:CO_WAITING
    4.程序执行结束后:CO_DEAD – 如何判断程序执行结束?maybe debug
    Step2:just do it!
    1.init main协程,放在第一次调用create的时候。
    2.完成start 操作, 仅仅初始化结构体
    3.实现wait操作
    4.全文最重要的操作了:yield的实现! 12.05 吃个饭
    20241214:迈出了重要的一步:神秘的Segmentation Fault !还好实验手册有提及(指引的重要性!)
    image-20241214155956425
    image-20241214160022756
    16.01 今天先溜了。ABI对齐 与 查看编译出的的汇编代码。gcc -I.. -m64 -Werror -g newtest.c ../co.c -DLOCAL_MACHINE
    fuck,15日15.19,终于简单的调试正确了!ABI关于对齐的相关要求没有弄出个所以然,大概理解为调用内核的相关方法时要求16字节对齐。一路不停的调试源码,最终如提示所言,segment fault 时 rsp指针没有对齐,也如提示所言,调用call前是对齐的,call之后就不对齐了!更如提示所言需要深入理解stack_switch_call!报错时rsp指针多了8个字节,故传参时-8字节,而原因则非常简单:call调用方法时需要8字节的空间存储返回时地址!!!故调用stack_switch_call 时的rsp 指针不能16字节对齐,需要多出来8字节!fuck!debug了 快3小时。
    虽然没有报错了,可测试程序只输出了a,没有输出b,即我们的切换失败了…
    一番鼓捣,成功实现了切换,现在的麻烦在于,如何在方法运行结束时将状态更新为dead?
    18.54 现在的问题是返回值。
    todo:
    1. 返回值与stack frame。 2. 在我们的整个协程体系,画出stack frame图!哪些是骚操作在一个frame里面用我们自己的stack调来调去?哪些是老老实实开了一个新的stack frame?
    16日,终于可以返回了,但返回后执行就报错了。也没有理解返回后的汇编代码。去保存寄存器的思路可能有问题,难道需要手动汇编,保存所有的call save寄存器?状态机,interesting!
    17日:fuck!fuck! 终于初步调试通了!hard,worth!
    image-20241217212915487
    image-20241217213031704
    1218:试了下测试样例,手动编译可以正常,make test 就会段错误…

    1219:still fuck!没想到最后如此简单…image-20241219224132560
    之前陷入到callee saved 寄存器的坑里去了。两个薄弱点:寄存器 & 寄存器地址指向的值,stack frame结构!最后竟然如此简单!!!陷了十几个小时的坑!
    1223:以为结束了吗?NO,直至今日还在处理问题。现在一个非常麻烦的点在于:main yield执行第一个协程后,在第一个协程的方法中yield执行第二个协程时,此时switch方法的rsp指针如何设置?
    image-20241224092007300
    co_wait 有一点挑战。不考虑资源回收,两个测试用例已经可以正常调通了。这里的资源回收应该要参考常见的垃圾回收机制。需要维持一个图。
    1224:64位的通了后,还需要额外了解32位。32位的堆栈结构。
    1225:总是以为懂了,事实确是并不懂,以为64位没问题了,结果打开debug就报错…,32位也始终调不通。卡了很久,每次静下来画下stack 以执行流,总能有新的收获。需要一个指针,表明是那个coroutine调用了stack switch,这样方法返回时新的co_yield就能setjmp 保存到正确的协程里。这样资源回收也简单了。花了太多时间了,许多都是低效的垃圾时间,即使上班期间的几个小时完整时间段。后续需要参考其他的协程实现!
    0101:核心在调度,64位的通了,32位的一直有问题,需要进一步了解32位的结构。tmd去掉debug 32位还是64位都没问题…不能再卡了,毕竟不是所有值得的事都必须要做好…给自己一个理由。
    image-20250101200248136
    todo一个实验报告(总结)
    补一下之前画的理解多个携程之间co_yield调用时的stack状态图。

    L1 pmm


    physical memory management
    Absmachine 中在给出了heap.start~heap.end 一段可用的内存区域,我们需要实现kallooc & kfree函数
    1. 对于大小为s的内存请求,实际分配 2^i^ 其中 2^i^ >= s。easy java参考hashmap中的实现
    2. 没有足够的空间分配内存时,返回NULL 3. 直接拒绝超过16MB的请求 4. 无需强制初始化申请的空间,也可以初始化为0(how to do?) 5. 满足并发的请求 6. 性能优化-区间树? 正确性与伸缩性 7. 实现klib
    20240105 todo:
    1.实验导读-框架&makefile 稍微详细的过了一遍,没太明白makefile的执行顺序。
    2.框架执行 -need 实现printf
    3.测试框架的搭建 -简单copy
    next todo:
    0.测试框架的实验 & printf的实现 & abs 再次导读
    1.阅读书中关于内存管理的部分 0106-done
    2.阅读csapp中关于内存管理的部分,尤其是伙伴系统
    3.尝试设计本次实验中的内存管理数据结构

    实验阅读材料


    No.17
    空闲列表= 任何堆上管理空闲空间的数据结构
    1.首先介绍了在一块完整的内存空间上,进行内存分配与空闲列表在其上的具像。
    2.管理空闲空间的策略:

  • 最优、最差、首次、下次
  • 分离空闲链表(https://www.grayxu.cn/2020/02/21/Slab/)
  • 伙伴系统
  • other
    glibc 的 malloc:https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/

    测试框架


    如2025-02-04 所记录:
    1.测试框架是否需要AM & Klib ?
    2.测试框架的代码是否需要运行在qemu上?(是否需要生成镜像)
    我们测试的对象是什么:pmm.c中的 kalloc 与 kfree,都是对从操作系统申请过来的一大批内存空间进行管理
    好像并不能完全的抛开am与 klib 因为pmm中的初始化会用到heap,os中的代码会用到putch。
    1.直接在我们test中的am.h 中添加上述依赖?
    2.我们的项目也接入am?
    在生成test可执行文件时也链接上am,会报错

    /usr/bin/ld: /learn/os/os-workbench-2022/LAB/kernel/../abstract-machine/am/build/am-x86_64-qemu.a(trap64.o): relocation R_X86_64_32S against symbol `__am_panic_on_return' can not be used when making a PIE object; recompile with -fPIE
    /usr/bin/ld: failed to set dynamic section sizes: bad value
    


    最终解决方案

    test: git
      gcc -no-pie $(shell find src/ -name "*.c") \
            $(shell find test/ -name "*.c") \
            -Iframework -Itest -DTEST -lpthread \
            -I$(AM_HOME)/am/include -DARCH_H=\"$(AM_HOME)/am/include/arch/$(ARCH).h\" \
            $(AM_HOME)/am/build/am-$(ARCH).a\
            -o build/test 
      @build/test
    


    链接了AM,同时添加 -no-pie, 管他呢,本地能运行…
    image-20250204154135751
    todo:
    还需要对框架进一步理解:为什么ARCH用的默认的,也可以运行,没有使用native,klib 是在哪里控制的呢?
    1.因为原本的项目包含了klib,不包含stdio,test 则包含了stdio,没有klib
    2.native 使用的glibc 而非klib 是在哪里控制?只看到了没有定义native才会生成相关替代方法。如何做到不生成替代就使用glibc?答案:似乎是包含了额外的platform.h文件。

    实现


    做了一堆的东西可就是没有正式的开始实验😂
    在csapp中通过宏直接操作内存地址,构建空闲链表,实现分配,合并等操作,此次计划采取伙伴系统,同时简单一点,使用结构体而非直接的内存地址操作。
    分配的时候我们可以不断的向小划分直至达到一个合适的块。并将划分的空闲块放入空闲列表。
    难点在于释放时的合并:
    1.如何确定当前空闲块的伙伴块?
    有点fancy的算法:当前块的地址^大小 = 伙伴块地址。
    2.合并时,需要将当前块&所有的伙伴块从空闲队列中移除。
    伙伴系统需要内存对齐….
    需要在test中对malloc分配的内存进行对齐…

    20250210 22.20:这会儿可太高兴了,被一个问题卡住了,终于解决了,都准备今晚摆烂放弃了
    image-20250210222116578
    在处理一下并发,完成任务近在咫尺!!!
    在前面jyy,提到,数据结构没有问题的情况下,影响效率的最大问题一般在于锁的颗粒度。
    一般建议,给每个cpu分配一段地址空间,但此时还没有理解thread-os,没有理解qemu及abs中关于cpu的部分。故现在还是所有cpu处理同一段地址空间。
    1.free要不要加锁?
    2.颗粒度控制在伙伴系统的层级还是伙伴系统的节点?如何实现?
    首先需要分析,不加锁,kalloc与kfree有哪些问题:
    空闲列表:
    分配:
    1.去空闲列表找:找到了就试着加锁,加锁成功才能用,失败就继续找?
    2.不合适会涉及拆分,将会在不同层级空闲列表,新增:需要在层级加锁?不然节点丢失了。
    释放:
    1.找伙伴:这一步不必担心并发,因为伙伴都是唯一的
    2.如果伙伴是空闲的,需要去对应层级空闲列表找伙伴,找到就移除:需要对前后节点加锁?
    3.将合并后的节点放入对应层级空闲列表:也是需要对涉及的节点加锁。
    对链表最细的操作就是在节点加锁,凡是涉及到节点的操作都加锁,锁太多影响效率?
    还是每一层一把锁,读取&操作 都需要先获取锁,颗粒度比方法层面细,也没那么麻烦。

    M3:spref


    strace --absolute-timestamps=format:unix,precision:ns ./a.out 
    strace --relative-timestamps=us ./a.out 
    strace -T ./a.out
    strace --syscall-times=ns ./a.out
    


    fork 与 管道:
    管道作为物理资源,创建后即使fork,也只存在一个,但文件描述符子进程会继承父进程的。
    正则表达式的补充:
    操作数+操作符
    操作符:连接+或+闭包
    闭包>连接>或
    操作数:字符集
    1.字符集的描述符:
    · 表示任意字符
    [] 表示括号内字符的任意一个
    ^ 匹配开头,后面跟 [] 则表示非
    $ 匹配结尾
    2.闭包的简写
    +表示重复 1/n
    ?表示重复 0/1
    {} 表示重复的 具体次数/范围
    3.转义
    \
    \s 任意空白字符
    \t tab

    转行

    L2 kmt


    概念:
    idle线程,操作系统最低优先级线程,没有任务可调度时执行。
    cte_init(os->trap) 指定了os->trap 是唯一的中断处理程序,所有处理器的中断都会调用该方法
    简短的过了一遍,因为甚至都没有尝试去理解,还瞌睡了几次。
    1.输出,大致的执行过程与相关函数的作用。
    开启中断,注册中断处理程序,运行初始化,每个处理器运行run,然后被中断,此时系统变成中断处理程序。
    2.调试os-thread,并理解absmachine下,的这一系列流程。
    3.调试xv-6,理解xv6多线程的部分。
    os->init 单处理器
    os->run 每个处理器都会执行
    带着疑问去调试thread-os,毕竟我们做的是加强版的thread-os:
    1.cte_init干了什么?mpe_init有干了什么?
    2.中断时absmachine干了什么?1.将寄存器保存到堆栈,2.执行os->trap,3.将os->trap返回的context加在进入寄存器
    开始调试thread-os:
    太久没有碰abs了,先-nb输出,这里又涉及到美化 Make file 导读

    #!/bin/bash
    make ARCH=x86_64-qemu
    qemu-system-x86_64 -drive format=raw,file=build/threados-x86_64-qemu -s -S -nographic
    


    target remote 127.0.0.1:1234
    symbol-file build/threados-x86_64-qemu.elf
    


    成功进行GDB调试
    0.5成功了vscode调试,无法step进入absm的方法…
    只能看汇编…
    thread-os:
    1.注册中断处理程序:跳到当前cpu的下一个task
    2.初始化task
    3.
    boot 到 _start_c 到 main
    最开始只有一个处理器,mpe_init 启动了其他处理器,其他cpu 都跳转到0x7c00,最终所有cpu都走到_start_c,主cpu已经将其他标记为ap cpu,其他cpu会跳转到各自的入口,然后xchg(&ap_ready, 1) 最终主cpu与ap cpu 都运行到user-entry。
    然后cpu各自中断,调用同一个 中断处理程序。不停的与task交互。线程级别的并发。自始至终系统只有一个地址空间。
    更完整的流程:(这一部分更应该是属于对absm的理解)
    1.固件将512字节加载到内存
    2.0x7c00 开始执行boot程序(boot/start.S)
    3.boot 会跳转到 load_kernel(boot的main.c将elf文件从磁盘加载进入内存,并设置entry(qemu的_start在qemu/strat.S))
    4._strat会跳转到 _start_c, _start_c 初始化boot cpu 然后 stack_switch_call,切换stack,执行main函数
    5.mpe_init:启动剩余cpu,均从0x7c00 ->_start -> _start_c ->__am_othercpu_entry -> 切换stack,交换ap_ready=1,调用user_entry (开启中断,然后中断)
    6.所有cpu定时中断,调用__am_irq_handle,调用 user_handler
    7.需要设置中断处理表IDT,中断发生后,cpu会依据中断号去查找对应的中断处理程序
    现在我们已经投入了不少时间去理解abstruct machine,接下来开始理解并细化需求,同时找到并参考xv6的实现
    注意:截止到目前,我们没有启动虚拟内存的相关部分,故此时所有cpu均是访问相同的地址空间。
    1.整理中断与spinlock的关系? 如何管理当前cpu的所有spinlock?
    2.semaphore与中断的关系?sem需要有一个task的队列来管理哪些应该被唤醒
    och,xv6没有线程!
    task 应该有哪些内容?
    20250418:我说注册怎么一个debug循环就报错:
    image-20250418172917112
    image-20250418173019528
    申请了8个字节,调用next就跑到了0x5 哪儿去了…然后诡异的无限中断…
    image-20250418173443482
    旧的问题刚去,新的问题又来? debug是一种修行🫠
    20250422:运行一段时间后就诡异重启,我放弃了… 似乎与context的保存有关系,rsp指针会运行一段时间后某次trap发生变化,然后某一次中断运行后就会重启… 还是先试着调试一下设备相关的代码

flag

todo: 2025年必须要完成剩下的L3与M4&M5