首页
题库
公司真题
专项练习
面试题库
在线编程
面试
面试经验
AI 模拟面试
简历
求职
学习
基础学习课
实战项目课
求职辅导课
专栏&文章
竞赛
搜索
我要招人
发布职位
发布职位、邀约牛人
更多企业解决方案
AI面试、笔试、校招、雇品
HR免费试用AI面试
最新面试提效必备
登录
/
注册
何人听我楚狂声
字节跳动_抖音_后端开发工程师
发布于浙江
关注
已关注
取消关注
到现在想起来还很气!!
@何人听我楚狂声:
一次失败的项目实践——春节七天乐(不起来)
缘起最初是在过年回家的高铁上,在知乎上看到了这篇文章:将Go程序跑在裸机上,大致想法是通过实现一遍系统接口,来接管 golang 程序的各种系统调用和中断之类的。感觉这个想法十分有趣。作者还用 golang 写了一个 x86 os:eggos,完成度相当之高。由于是从底层魔改了 golang 的运行时,用户程序完全无感知,所以各种 golang 的第三方库都可以直接使用。作者甚至实现了一个支持 TCP/IP 的协议栈,使得一些网络库可以直接使用。看的我心潮澎湃。搜了搜一些前人的工作,发现这个想法很早就被人提出来过。2018 年 OSDI 会议上就有一篇论文,讲述了使用高级语言实现操作系统的好处和代价,幻灯片在这儿。另外,相关的实现这几年也是有的,比如 gopher-os,一个验证性质的内核,只是为了证明使用 golang 实现操作系统是可行的。另外还有 MIT 的一个博士论文项目 Buscuit,思路是 hack 编译器使得能够编译到裸机,这个项目完成度更高,实现了部分 POSIX 接口,甚至可以在上面跑 redis 和 nginx。在研究资料过程中,发现了一个共同点:都是基于 x86 架构实现的。我之前用 c 写过一个小内核,是基于 RISC-V 架构,RISC-V 的汇编和各种机制都十分简单,写起来也很舒服。于是就有了这么个想法:用 go 实现一个 RISC-V 的操作系统。说干就干!到家第二天就开始搞起来了。就是干!做一个项目,很重要的一点,就是起名字(bushi但我确实首先想到了一个绝妙的名字:goose太妙了兄弟们!首先 go 是原生支持交叉编译到 RISC-V 64 位的可执行文件的,这是好事。只需要在 go build 命令之前加上 GOOS=linux GOARCH=riscv64 即可,非常方便。虚拟机照例使用的是 qemu,平台还是 virt。virt 平台的内存布局:0x80000000 以上是物理内存区域,0x80000000 以下是 mmio 区域(大概就是把设备的内存映射到了这片区域,操作这块内存等同于操作这个设备)。virt 启动时会把 pc 设置为 0x80000000然而正常编译的 go 可执行文件,由于运行在用户态虚拟地址上,entry 的地址都是低地址,大概 0x10000 左右。好在 go 提供了一个链接标志 -T 来指定 TEXT 段的起始地址,可以用这个标志把整个代码段放在高地址内存处,同时还可以通过 -E 来指定入口的标志,这样就可以写一个函数来接管 go 的启动过程(go 程序的入口不是 main 函数,而是 _entry 函数,这个函数用来做一些初始化工作)然而还有一个严重的问题:我们指定了入口函数,但是没有办法指定这个函数的起始地址,就没法把这个函数放到 0x80000000 处,virt 在启动的时候,0x80000000 就可能是一堆啥也不知道的代码。通常,如果是 c,我们可以通过编写链接脚本来解决这个问题,而且非常简单:直接指定好入口标记的地址,一行就完事。可是这是 go。在查了一些资料后,在 stackoverflow 上看到了这个提问,使用外部链接器而非 go 自己内置的链接器,这样就可以指定链接脚本了。但是尝试了下之后,不太可行。go 的可执行文件中除了一些已知的 text 段、bss 段、rodata 段和 data 段,还有一些自己的乱七八糟的段,这些都必须在链接脚本里显式指定,几乎不太可能。于是更换思路,入口可以写一段 c 代码,这段 c 代码动态获取 go 代码的入口然后跳转过去。由于 go 代码的入口只存在于 elf 文件中,在加载后的内存映像中是没有这个信息的。所以可以把这个 elf 文件直接以二进制的形式链接到 c 程序的 data 段,可以为这段保存二进制的内存开始和结尾指定一个名字,我是用的是 _binary_kernel_elf_start 和 _binary_kernel_elf_end。这样在 c 代码中就可以快速找到了。而 c 代码的作用,就是解析这段内存中保存的 elf 文件,把需要载入内存的段复制到内存对应的地址处,再跳转到 elf 指定的 entry 处即可。这里贴一下入口的汇编代码,大概就是设置好栈就跳转进 c 函数中,同时指定了 data 段中的两个符号间的一段内存是编译好的 go 可执行文件: .section .text.entry .globl _start # 仅仅是设置了 sp 就跳转到 main_start: la sp, bootstacktop call bootmain # 启动线程的内核栈 bootstack 放置在 bss 段的 stack 标记处 .section .bss.stack .align 12 .global bootstackbootstack: # 以下 16K 字节的空间作为 OS 的启动栈 .space 0x4000 .global bootstacktopbootstacktop: .section .data .globl _binary_kernel_elf_start .globl _binary_kernel_elf_end_binary_kernel_elf_start: .incbin "kernel.elf"_binary_kernel_elf_end:C 函数 bootmain 也十分简单,解析 elf 文件,读取程序头表,把各个段都加载到需要的物理内存处:voidbootmain(){ struct elfhdr *elf; struct proghdr *ph, *eph; void (*entry)(void); uchar *pa; elf = (struct elfhdr *)(_binary_kernel_elf_start); if (elf->magic != ELF_MAGIC) return; ph = (struct proghdr *)((uchar *)elf + elf->phoff); eph = ph + elf->phnum; for (; ph < eph; ph++) { pa = (uchar *)ph->paddr; readseg(pa, ph->filesz, ph->off); if (ph->memsz > ph->filesz) clearMem(pa + ph->filesz, ph->memsz - ph->filesz); } entry = (void (*)(void))(elf->entry); entry();}最后 entry 的位置就是从 elf 头中读出来的 go 入口函数地址,跳转过去即可。go 入口函数是 rt0 函数,是一个汇编函数。go 使用的汇编格式是 PLAN9 汇编,起源于一个上古操作系统 plan9。这个格式的汇编支持多个指令集架构,但是很神奇的是找不到任何官方的文档描述不同的指令集架构中这个格式的汇编支持哪些指令。x86 的还能找到点资料,因为 PLAN9 汇编的例子基本是 x86 的,RV64 则是一点痕迹都没有,完全靠猜(通过各种摸索,最后终于写出了入口:#include "textflag.h"TEXT ·rt0(SB),NOSPLIT|NOFRAME,$0 CALL ·kernelStackTop(SB) MOV 0(SP), A1 MOV A1, SP CALL ·kmain(SB) UNDEF RET这个格式也蛮阴间的……做的事情基本一致,调用 kernelStackTop 获得预先分配好的栈顶地址,并把 SP 指针指向那个地址,随后就调用 go 语言的入口了:kmain。唯一的 go 文件写得也很简单:type stack [16 * 4096]bytetype virtualAddress uintptrvar ( kstack stack)//go:nosplitfunc (s *stack) top() virtualAddress { stackTop := uintptr(unsafe.Pointer(&s[0])) + unsafe.Sizeof(*s) // Align to 16 bytes. stackTop = stackTop &^ 0xf return virtualAddress(stackTop)}//go:nosplitfunc kernelStackTop() uint64 { return uint64(kstack.top())}//go:nosplitfunc rt0()//go:nosplitfunc kmain() { for { }}预先分配了 stack 数组作为内核栈,kmain 啥也没干,就是无限循环。注意每个函数都有一个编译标识://go:nosplit,表示让编译器不要插入检查这个函数的是否会栈溢出的代码,同时还有一个隐式的用途:阻止编译器在函数中插入 gc 检查点。如果触发了 gc,以现在这个啥也没有的裸机,gc 是完全不支持的(当然 gc 也不应该在内核中跑,更多的处理用户空间的堆)这样 Makefile 就可以这样写了:Image: kernel.elf $(CC) $(CFLAGS) -fno-pic -O -nostdinc -I. -c boot/boot.c $(CC) $(CFLAGS) -fno-pic -nostdinc -I. -c boot/boot_header.S $(LD) $(LDFLAGS) -T image.ld -o Image boot.o boot_header.okernel.elf: GOOS=linux GOARCH=riscv64 go build -o kernel.elf -ldflags '-E goose/kernel.rt0 -T 0x80200000' -gcflags "-N -l" ./kmainkernel.elf 编译 go 的 elf 文件,指定入口函数为 goose/kernel.rt0,TEXT 段的起始地址为 0x80200000。而 Image 则是编译了上面说的加载内核的入口代码,image.ld 中指定了将入口函数放在 TEXT 段的入口,并把 TEXT 段放在 0x80000000 位置。/* 执行入口 */ENTRY(_start)/* 数据存放起始地址 */BASE_ADDRESS = 0x80000000;SECTIONS{ /* . 表示当前地址(location counter) */ . = BASE_ADDRESS; /* start 符号表示全部的开始位置 */ kernel_start = .; text_start = .; /* .text 字段 */ .text : { /* 把 entry 函数放在最前面 */ *(.text.entry) /* 要链接的文件的 .text 字段集中放在这里 */ *(.text .text.*) } ...}妥!因为兴趣很大,整个春节我亲戚都没有走好,整天就是憋在屋里收集资料,在外面也是发呆想思路,魔怔了一样。大失败噔噔咚!在把内核加载到 qemu 开始运行后,debug 看到卡死在把程序段加载到内存中。于是用 readelf 检查了一下 go build 出来的 elf 文件,发现了这个诡异的东西Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x00000000801ff040 0x00000000801ff040 0x0000000000000188 0x0000000000000188 R 0x10000 NOTE 0x0000000000000f9c 0x00000000801fff9c 0x00000000801fff9c 0x0000000000000064 0x0000000000000064 R 0x4 LOAD 0xffffffffffff1000 0x00000000801f0000 0x00000000801f0000 0x0000000000063300 0x0000000000063300 R E 0x10000 LOAD 0x0000000000060000 0x0000000080260000 0x0000000080260000 0x000000000006adb8 0x000000000006adb8 R 0x10000 ...注意第三段的 Offset 是 0xffffffffffff1000 这个大的吓人的数。Offset 是这个段的内容在文件中存放的位置相对于文件开头的偏移。这个 elf 文件才几十 KB,哪来这么大的偏移?即使加载到内存中,virt 计算机的默认物理内存大小也只有 128 MB,直接炸裂百思不得其解,于是开始试验起来,最后发现,只要加上 -T 这个链接参数,就会出现这种情况。但是不加又不行,这些内存段不能被加载到低地址上,因为那是 mmio 的位置。于是我去 go 的 github 仓库里发了个 issue:cmd/link: wrong program header offset when cross-compile to riscv64 when setting -T text alignment。描述了一下后,得到的回答是:看来是 RV64 对 -T 的支持不太完善……于是这个项目就被搁置到了现在,可惜了我想的好名字/(ㄒoㄒ)/~~ 只能期待后续 go 官方能修复这个问题,但是感觉 go 对 RV64 不是很上心,原生支持交叉编译到 RV64 也是近几年才合进主线的……很气,转投 Rust 去了!
点赞 20
评论 3
全部评论
推荐
最新
楼层
暂无评论,快来抢首评~
相关推荐
02-21 15:56
上海交通大学 算法工程师
美团推荐算法一面
岗位名称:推荐算法面试时长:1h+自评分:7/10是否下一轮:待通知一、检索与排序在项目中使用的 BM25 原理是什么?相比 TF-IDF 改进在哪里?公式中每一项的含义是什么?(追问)k1 和 b 参数分别控制什么?如果 b=0 会发生什么?为什么在实际系统中要做混合检索?Sparse 检索和 Dense 检索的基本原理分别是什么?(追问)两种方式在长尾 Query 上的表现差异如何?Dense 检索中常见的相似度度量方式有哪些?为什么很多系统选择 Cosine 或 Inner Product?(追问)如果向量没有归一化,Inner Product 和 Cosine 的结果会有什么差异?在你...
技术必备题库
点赞
评论
收藏
分享
02-24 16:22
杭州电子科技大学 大数据开发工程师
说真的,面试时听到这些问题,我CPU都快烧了
在牛客看了这么多面经,大家都在卷八股、卷算法。但有些面试官问的问题,真的让人一秒钟想点点离会(或者直接起身走人)。作为开发,你最讨厌面试被问到什么?1. “你最大的缺点是什么?”这是最典型的“废话文学”。说实话吧,面试官觉得你没自我认知;说个“我太追求完美”这种陈年老梗吧,面试官觉得你太油腻。真实内心: 我最大的缺点就是现在还没拿到心仪的Offer,而且我还得坐在这儿想出一个听起来像优点的缺点来骗你。2. “你现在的薪资是多少?期望薪资凭什么涨这么多?”这种“查户口式”逼问真的让人PTSD。技术面还没过呢,就开始商业谈判了?而且涨幅是根据市场行情和我现在的技术水平定的,不是根据我上一份合同定的...
查看5道真题和解析
点赞
评论
收藏
分享
01-23 17:59
已编辑
赤峰学院 Java
苍穹外卖都没学完 字节直接oc?
对不起各位,标题党了。最近几天都在找实习,今天下午面试了一个后端开发岗,一上来就叫我写力扣。有一个不会换了一道题写出来了,但是整体给面试官的感觉还是不太行,也深深知道了自己在这方面有很多欠缺。不出意外的话,今天下午面试应该是挂掉了。我现在有一个问题,想了想问大佬们。 我现在手上有一个51word的offer,它的方向是 自动驾驶仿真场景和AIGC ScenarioCopilot。但是我主要现在还是想找一个JAVA后端开发的日常实习,因为一直在学java。不知道该怎么选了,求大佬们支招。另外下面是我的简历,大佬们看看有没有需要修改的地方。学生管理系统就是苍穹外卖改版,当时都没学到redis,学完增删改查就开始投了。二更:麻烦大佬们看看我最新的帖子,看看我该选哪个
李橙子:
你这如果想找java方向的,那你的项目就不要写python啊,不然会以为你是主要找python的
点赞
评论
收藏
分享
02-11 00:11
江西电力职业技术学院 硬件开发
佬们帮我看看简历
考试下岸25应届目标嵌软和硬件工程师不行的话投助理可以吗
投了多少份简历才上岸
点赞
评论
收藏
分享
昨天 17:19
卓驭科技_HR(准入职员工)
卓驭(大疆车载)内推
卓驭 嵌入式中间件实习 面经写一写面经,回馈一下社区。⌚️timeline:五月底👋part1:自我介绍 && 项目介绍1. 项目里的内存占用,资源使用的性能评估?性能优化的思考?2. 端侧大模型的选型?3. 机器人比赛中最难的一个问题?技术方案的选择用了多长的时间?4. 之前实习的主要工作?方案是如何确定的?5. 对车载中间件的了解?6. 。。。忘了🤏part2:八股拷打1. 设计模式?平时开发有用到过哪一些设计模式吗?2. 对多态的了解?静态and动态?3. 虚函数里面父类和子类的交互?4. C++容器中vector和list的差异?5. vector的底层实现原理?扩...
点赞
评论
收藏
分享
评论
点赞成功,聊一聊 >
1
收藏
分享
评论
提到的真题
返回内容
全站热榜
更多
1
...
你们开工红包发了多少?评论抽2人送外卖券
3050
2
...
总结下秋招被问到的关于AI的面试题
2714
3
...
没有不拿offer的义务!
1889
4
...
双非大厂实习
1703
5
...
字节后端Agent一面凉经
1241
6
...
开水加点🍬
1016
7
...
实在智能Java二面面经 (仍需沉淀)
799
8
...
没想到我因为一件“小事”彻底破防了
794
9
...
相求问下我的简历该怎么优化?很多hr拿到简历之后就不回复了很难受
774
10
...
双非秋招0offer,去实习转正还是allin春招呢?
769
创作者周榜
更多
正在热议
更多
#
开工第一帖
#
12665次浏览
271人参与
#
携程求职进展汇总
#
882427次浏览
5795人参与
#
xx岗简历求拷打
#
4101次浏览
48人参与
#
工作不开心辞职是唯一出路吗
#
7995次浏览
30人参与
#
有转正机会的小厂实习值得去吗?
#
5954次浏览
73人参与
#
掌握什么AI技能,会为你的求职大大加分
#
4328次浏览
198人参与
#
实习期间如何提升留用概率?
#
241451次浏览
1824人参与
#
为什么国企只招应届生
#
238735次浏览
1301人参与
#
参加完秋招的机械人,还参加春招吗?
#
111090次浏览
709人参与
#
哪些公司开春招了?
#
32725次浏览
204人参与
#
秋招你经历过哪些无语的事
#
101326次浏览
597人参与
#
金三银四,你有感觉到吗
#
691558次浏览
6088人参与
#
毕业季等于分手季吗
#
54880次浏览
654人参与
#
牛客租房专区
#
160191次浏览
1921人参与
#
联想求职进展汇总
#
335013次浏览
2220人参与
#
牛友投递互助,不漏校招机会
#
439075次浏览
5243人参与
#
正在春招的你,也参与了去年秋招吗?
#
353067次浏览
2597人参与
#
你最讨厌面试被问什么
#
6207次浏览
81人参与
#
非技术er求职现状
#
139145次浏览
821人参与
#
你觉得今年春招回暖了吗
#
931216次浏览
7233人参与
牛客网
牛客网在线编程
牛客网题解
牛客企业服务