资源
- 2025 南京大学操作系统原理 哔哩哔哩_bilibili
- 操作系统:三个简单的部分(OSTEP) | Remzi H.Arpaci-Dusseau,Andrea C.Arpaci-Dusseau | download on Z-Library
- Yanyan’s Wiki
正文
科研劝退信
Note
该信以批判视角揭示了当前学术圈的功利化生态:在政策指挥棒(如国家战略需求导向)与资源紧缩背景下,学术创新常被简化为“包装故事”的生存技能,导师群体陷入科研理想、考核指标与团队管理的多重博弈,学生则面临个人发展诉求与“学术流水线”工具属性的根本矛盾。导师劝退的本质是提醒学生清醒认知学术体系的运行规则——若无法在现实妥协中坚守研究初心,或缺乏自主突破困境的驱动力,需审慎评估读研的代价与收益。此信并非否定学术价值,而是以坦诚姿态呼吁理性选择,折射出学术界内部对系统性困境的自省与责任感。
我们作为科技工作者,哪怕科研甚至专业训练不及格,都需要学会的保命的技能是编故事:publish or perish (甚至有一个桌游)。学术界对 “创新” 是有包容性的:改变世界的发现总是领先于时代。此话不假,但也成了我们为 “低价值学术” 辩护的挡箭牌:哪怕内心认为这是绝无可能有用的东西,在利益面前,就总是能编出点 novelty,说它未来大约的确可能是有那么点用的。对付大同行,反正只要偷换概念,说得天花乱坠弄不明白就行。虽然有时候一本正经胡说八道是不得已为之,但久而久之,我们就习惯了缝缝补补、拉帮结派、维持体面的舒服日子。反正我已经实现闭环,管我研究品味有多差,钱和权力总是越滚越大的。
就这样,在 “国家科技要进步” 的大指挥棒下,学术圈经历了 SCI、论文分区分级、人才帽子、破五唯等一系列政策引导,最近的新闻是教育部 2022 年印发的《关于加强高校有组织科研 推动高水平自立自强的若干意见》,明确要求我们 “以国家战略需求为导向”。曾经资源管够、经费管饱的时候,风险投资就风险投资,大家其乐融融。现在地主家也没有余粮了,只能整出这么一招,意思是和国家战略需求没关系的,先靠边站,收缩投资的范围。当然了,学校里聪明人可不少,有招就能拆招,比如有话语权的人可以玩 “定义国家需求” 的游戏,或者在囚徒困境中向国家机器靠拢……
这就和各位天命人 (研究生) 扯上关系了。
学校本质上还是培养人的,我们国家的希望永远是寄托在下一代人身上的。我们当然希望保住底线的同时,能奖励其中最出色的那些。然而,培养人的过程一旦牵扯到学科评估的 KPI,就难免倒果为因,考核什么就做什么来得最方便。嘴上说招生工作优中选优、宁缺毋滥,但你少招一个人,学科评估就难看一点。嘴上说支持 “十年磨一剑”,但也万万不会放过你每年的产出——万一你躺平了最后就磨出个绣花针呢?
因此,天命人们要意识到国家和学校在博弈,学校和院系在博弈,院系和教师在博弈,教师和金字塔底层的吗喽们也在博弈。比如经济上行周期时的指挥棒是 SCI 论文,成就了今天的生化环材。我眼睁睁看着化院的精神小伙被导师催残得人样都快没了,博士毕业进了工业界立马满血复活。2010 年 CCF 发布了论文分级列表,计算机系立马行动制定了 “毕业套餐”。2011 年马所长发表了南大软件所第一篇软工 A 类会议论文 (FSE)。我在 PhD 期间发了 ICSE’14, ASE’15 和两篇 FSE’16,那时候 Program Committee 都没啥华人,甚至还有素不相识的 PC 在 pdf 上从头到尾批注论文,我这个产量在同龄人中也算是很可以了。再看看今天,软工四大会都快成 Chinasoft 了,论文迅速贬值 (没有像样的论文基本就直接出局了),你还得去卷别的东西:系统工程实现、影响力、研究工作的企业转化,最终变成人才头衔……指挥棒的初衷都是好的,但是嘛,有人的地方就有江湖,至于怎么包装自己的成果,那就是八仙过海、见仁见智了。
所以本质的矛盾是,大部分天命人希望的是获得指导、提升自己、求得职位。但无论是哪种导师,想做大工作的、想做项目的、想躺平的,都很有可能和你的利益发生冲突。你最想要一秒立即毕业,不想浪费时间做没用的项目,但先富起来的人拉高了标准,老板还想叫你出活。吗喽们刚刚经历了 “上课耽误学习” 的本科四年,就被赶鸭子上架,只顾着焦虑地刷 GPA 和各种履历想让自己在竞争中能取得一些优势,结果反而导致了基础不牢,写代码都还是个半吊子,于是产生出许多因果的报应,看看这个有趣的知乎问题吧。
当然,不管这个体系怎样,国家的终极宏观策略是相信我们这片土地上“小概率事件重复必然发生”,因此只要维持体系正常运转,就已经足够了。我们在互联网还是法外之地的时候见过世面的人,就总有已经先一步 “觉醒” 了创新的基因,有能力敢做一些大事了的人——对国家来说,有些水土能养起这样的人,其他人自会有着落。从黑猴子到 DeepSeek,总会有人出头的,他们什么时候成长起来,国运就来了。
所以啊,我有时候就想有一个志同道合的天命人,我们能提供一个相对宽松不压榨的环境、提供自己对问题的见解,也算认识一些人,有解决不了的问题还可以再找人问,就把一段宽松自由的求学经历当成是一个 startup,能自己驱动自己,遇到问题自己搞定,在我还能帮点忙的小领域里做点什么毁天灭地的东西。
我知道这么写很容易让我招不到学生,但其实我觉得 “创业” 并不是一件那么难的事情,只要你是真心喜欢折腾计算机做各种好玩的事情,而不是总问换什么好处就行:如果你能看穿学校教的之外的东西,并且能自己自发地补上短板,那就足够了。但这句话分量很重,恐怕已经把金字塔底都凿穿了吧。
我们每个人都是时代的因和果。
1. 操作系统概述
1.1 课程简介
1.2 为什么学操作系统?
现状
再没有人类可以阻止中国人了
- 人才的积累已经突破了临界点
- 没有行业能挡得住中国人了
人类已经创造出了全新的物种(LLM)(GPT-4 级别的模型是人类命运的转折点)
- 繁荣之后,我们也许要考虑生死存亡的问题……?
- 人与人之间的生产力将会呈现断崖式的差距
- 面向考试的 “功利” 学习收益急剧减少
- AI 技术使顶级的 “人类劳动力” 逐渐失去价值
互联网和开源改变了一切
- MIT OCW; Geometric Folding Algorithms (Erik Demaine):
为什么学
那么,为什么学习操作系统?
学习 X 的目的
- 人类的文明
- 基本动机、基本方法、里程碑、走过的弯路
- 走向应用、创新、革命
- 操作系统的历史就是计算机软硬件发展的历史
- 基本动机:更快更好地服务更多应用
- 基本方法:“Building Abstractions”
- 里程碑:UNIX, Linux, …
- 历程中藏着一个问题的答案
- 什么是今天计算机世界万丈高楼工程奇迹的地基?
学习操作系统能得到什么?
觉醒体内的 “编程力量”
-
能够知道程序能做什么、为什么能做
-
为什么能创建窗口?
因为操作系统提供了窗口系统(Windowing System)和图形界面 API(如 WinAPI、X11、Wayland、Quartz)来支持窗口的创建和管理。
-
为什么 Ctrl-C 有时不能退出程序?
Ctrl-C
通常用于向正在终端中运行的程序发送中断信号(SIGINT
),让程序有机会终止。但是在某些情况下,Ctrl-C
可能无法使程序退出,常见原因包括:- 程序捕获并忽略了
SIGINT
- 程序卡死在系统调用中
- 线程或子进程处理不当
- 前台控制权被转移
- 运行在图形界面或伪终端中
- StackOverflow (2017.5): Helping one million developers exit Vim (今天:不会再发生了)
- 程序捕获并忽略了
-
为什么有的程序能把组里服务器的 128 个 CPU 用满?
-
-
每天都在用的东西,能实现出来了
- 浏览器、编译器、IDE、游戏/外挂、杀毒软件、病毒……
-
于是,你可以去寻找你的梦想!
1.3 什么是操作系统?
操作系统定义
操作系统是来帮我们的,不是来折磨我们的
- 我们不需要 “精准” 的教科书定义
- 操作系统是帮我们更好地开发程序的
- 很多事办起来很复杂,所以需要操作系统 (所以知道 “能做什么” 很有必要)
理解操作系统:理解它发展的历史
- 操作系统如何从一开始变成现在这样的?
- 三个重要的线索
- 硬件 (计算机)、软件 (程序,本课程视角)、操作系统 (管理硬件和软件的软件)
会编程,你就拥有全世界!
Logisim 是本次课程的第一个彩蛋
- 同样的方式可以模拟任何数字系统 (包括计算机系统)
- 同时还体验了 UNIX 哲学
- Make each program do one thing well(让每个程序做好一件事)
- Expect the output of every program to become the input to another(期望每个程序的输出成为另一个程序的输入)
命令行是一个非常有趣的设计
- 在自然语言和编程语言之间达到了平衡
计算机系统基础
- 高级语言代码 → 指令序列 → 二进制文件 → 处理器执行
- Everything is a state machine(一切都是状态机)
本课程讨论狭义的操作系统
- 操作系统:硬件和软件的中间层
- 对单机 (多处理器) 作出抽象
- 支撑多个程序执行
- 这个概念可以推广到 “Systems”
- 对多台计算机抽象 (分布式系统)、对存储设备的抽象 (存储系统)、……
理解操作系统
- 理解硬件 (计算机) 和软件 (程序) 的发展历史
- 夹在中间的就是操作系统
操作系统历史
1940s
图灵机” 的数字电路实现 EAIAC(1946.2.14)
- 执行完一条指令后,可以根据结果跳转到任意一条指令
- 用物理线路 “hard-wire”
- 重编程需要重新接线:Programming the ENIAC
1940s 的计算机硬件:电子计算机的实现
- 逻辑门:真空电子管
- 存储器:延迟线 (delay lines)
- 输入/输出:打孔纸带/指示灯
1940s 的计算机软件
打印平方数、素数表、计算弹道……
- 解释了《程序设计》教课书上经典习题的来源
- (是时候改一改了)
- 大家还在和真正的 “bugs” 战斗
1940s 的操作系统
没有操作系统
连编程语言都没有
- 大家还在画流程图、写机制代码、戳纸带
能把程序跑起来就很了不起了
- 程序直接用指令操作硬件
- 不需要画蛇添足的程序来管理它
1950s-1960s
1950s-1960s 的计算机硬件
硬件改进了,逻辑门-存储-I/O 的基本格局没有变
- 晶体管、磁芯内存、丰富的 I/O 设备
- I/O 设备的速度严重低于处理器的速度,中断机制出现 (1953)
1950s-1960s 的计算机软件
更复杂的通用的数值计算
- 高级语言和 API 诞生 (Fortran, 1957):一行代码,一张卡片
- 80 行的规范沿用至今
Fortran 已经“足够好用”
- 迎来了自然科学、工程机械、军事……对计算机的需求暴涨
读取输入卡片中的三个整数,计算它们的和并输出结果,直到读到三个全是 0 的整数为止。
1 |
|
1950s-1960s 的操作系统
库函数 + 管理程序排队运行的调度代码
- 写程序 (戳纸带)、跑程序都是非常费事的
- 计算机非常贵
- −
- 通常一个学校只有一台
算力成为服务,操作系统概念形成
- 多用户轮流共享计算机,operator 负责操作程序切换
- Operating systems (操作系统/作业系統)
- (今天算力又成为服务了)
CTSS (Compatible Time-Sharing System,兼容分时系统)
-
操作系统中出现了各类对象:设备、文件、任务……
-
CTSS Subroutines(子程序):
-
控制和输入输出
-
RDFLXA: Read an input line from console
从控制台读取一行输入,类似于现代编程语言中的
readline()
。 -
WRFLX: Write an output line to console
将一行输出写入控制台,类似于
print()
。
-
-
用户状态管理
-
DEAD: Put the user into dead status, with no program in memory
将用户置于“死亡”状态,意味着程序已终止且从内存中移除。用户 session 结束,内存已释放。
-
DORMNT: Put the user into dormant status, with program in memory
将用户置于“休眠”状态,程序仍然保留在内存中,但当前不活跃或未运行。
-
-
内存管理
-
GETMEM: Get the size of the memory allocation
获取当前分配给程序的内存大小。
-
SETMEM: Set the size of the memory allocation
设置程序使用的内存大小。可能用于请求更多内存或释放多余的内存。
-
-
文件系统访问
-
TSSFIL: Get access to the CTSS system files on the disk
获取对系统级文件的访问权限(如
/sys
或共享库等)。 -
USRFIL: Change back to user’s own directory
返回用户自己的文件空间或工作目录。用于从系统目录切换回个人环境。
-
-
调试或程序状态
-
GETBRK: Get the instruction location counter at quit
获取程序结束时的指令计数器(instruction location counter),即“程序中断”或“退出”时的地址。用于调试或记录程序状态。
-
-
1960s-1970s
1960s-1970s 的计算机硬件
集成电路、总线出现
- 更快的处理器
- 更快、更大的内存;虚拟存储出现
- 可以同时载入多个程序而不用 “换卡” 了
- 更丰富的 I/O 设备;完善的中断/异常机制
更多的高级语言和编译器出现
- COBOL (1960), APL (1962), BASIC (1965), PASCAL (1970), C (1972)
- 计算机科学家们在今天难以想象的计算力下开发惊奇的程序
个人电脑登上历史舞台
1960s-1970s 的操作系统
能载入多个程序到内存且调度它们的管理程序
- 为防止程序之间形成干扰,操作系统自然地将共享资源 (如设备) 以 API 形式管理起来
- 有了进程 (process) 的概念
- 进程在执行 I/O 时,可以将 CPU 让给另一个进程
- 在多个地址空间隔离的程序之间切换
- 虚拟存储使一个程序出 bug 不会 crash 整个系统
操作系统中自然地增加进程管理 API
- 既然可以在程序之间切换,为什么不让它们定时切换呢?
- Multics (MIT, 1965):现代分时操作系统诞生
1970s+ 的操作系统
UNIX 奠定了今天计算机世界的基础
- 1973: 信号 API、管道 (对象)、grep (应用程序)
- 1983: BSD socket (对象)
- 1984: procfs (对象)……
- UNIX 衍生出的大家族
- 1BSD (1977), GNU (1983), MacOS (1984), AIX (1986), Minix (1987), Windows (1985), Linux 0.01 (1991), Windows NT (1993), Debian (1996), Windows XP (2002), Ubuntu (2004), iOS (2007), Android (2008), Windows 10 (2015), ……
1.4 怎样学操作系统?
试着去成为一个有梦想的 CS 人
- 同学总结:“考试完全在掌控中”,绝对不会考犄角旮旯的细节 (不是没有细节,但如果掌握了 big picture,你可以根据自己的理解补充细节)
知道 “编码” 是你手中的利剑
- 能够在大语言模型的帮助下自己动手解决问题
- “什么都不怕”: vim, tmux, grep, binutils, 前端, …
- 享受创造的乐趣
- 在出 bug 时默念 “机器永远是对的、我肯定能调出来的”
- 随时多问一句:“我这么做显得专业吗?还能更好吗?”
鉴定学术诚信(Academic integrity)
你的梦想可能不在计算机行业
- 但你的编程知识(短期内)一定能帮到你
成为 Power User
漫画内容概述:
第一格:
一群人站在一颗定时炸弹前,情急之下叫来了 Rob,说:“Rob!你会用 UNIX!快来!”第二格:
屏幕显示解除炸弹的条件:“要解除炸弹,请在第一次尝试时输入一个有效的 tar 命令,禁止查谷歌,你有 10 秒钟。”
第三格:
众人沉默,一脸无助地盯着炸弹。第四格:
一人轻声问:“……Rob?”
Rob 低头,说:“对不起。”笑点解析:
tar
是一个 UNIX/Linux 系统中用于打包文件的命令,功能强大但语法复杂且不直观。- 很多有经验的 UNIX 用户也经常记不住正确的 tar 语法(比如
tar -czvf
、tar -xvzf
等各种组合),每次用都要查手册或搜索引擎。- 因此,这幅漫画用“拯救世界靠你一次性打对一个 tar 命令”的夸张设定,幽默地讽刺了
tar
命令难以记忆的现实。
感到 Linux/PowerShell/… 很难用?
- 只要知道什么能做,然后学会提问就行了
开始写代码
命令行 + 浏览器就是全世界!
- 不需要讲语言特性、设计模式……编程中自然而然会有体会
开始你的《操作系统》课旅程!
2. 应用视角的操作系统
**操作系统是连接软件和硬件的桥梁。**因此想要理解操作系统,我们首先需要对操作系统的服务对象 (应用程序) 有更精确和深刻的理解——实际上这是这门课程最主要的内容。
2.1 操作系统上的程序
Operating System: A body of software, in fact, that is responsible for making it easy to run programs (even allowing you to seemingly run many at the same time), allowing programs to share memory, enabling programs to interact with devices, and other fun stuff like that. (OSTEP)
操作系统:实际上,它是一个软件体,负责简化程序的运行(甚至允许你同时运行多个程序),允许程序共享内存,使程序能够与设备交互,以及实现其他类似的功能。(OSTEP)
要想理解 “操作系统”,就要理解什么是 “程序”
- (这门课也一直从应用的视角讲操作系统)
1 |
|
计算机:无情的执行指令的机器
- 机器永远是对的
- 如果编译器没有优化,“我们写什么,机器就执行什么”
- (编译器会对这个代码做什么优化呢?)
理解计算机程序
Everything is a state machine.
- 任何程序都在计算机上运行
- 计算机是状态机
- 程序的执行就是状态的变化
C 语言的状态机模型
- PicoC: a very small C interpreter for scripting; 和你的 NEMU 一样 “单步执行”:
PicoC 是一个非常小的 C 语言解释器,用于编写脚本。它最初是写的 作为无人机机载飞行系统的脚本语言。它也非常 适用于其他机器人、嵌入式和非嵌入式应用。
核心 C 源代码大约有 3500 行代码。它不是故意的 ISO C 的完整实现,但它具有所有必需品。什么时候 编译后,它只占用几 k 的代码空间,而且非常节省 数据空间。这意味着它可以在小型嵌入式设备中正常工作。它也是 一个有趣的示例,说明如何创建一个非常小的语言实现,同时 仍然保持代码可读。
它已在 x86-32、x86-64、powerpc、arm、ultrasparc、HP-PA 和 blackfin 上进行了测试 处理器,并且易于移植到新目标。
1 |
|
能不能把刚才的想法真的实现出来?
-
AI 时代:
只要你想到,就能做到
- 曾经为 gdb(GNU Debugger)编程是非常繁琐的
- 但现在繁琐的事已经不需要人类来干了
- 未来任何事都不需要人类了
给我一个 Python 脚本,使得它能单步执行 gdb,并且输出一个 plot.md,嵌入 main 执行的状态迁移图,状态是局部变量 (只保留我程序中定义的变量),每一次 step 是一次迁移,执行的语句去掉行首空格画在 step 上。
先创建一个 sample.c
:
1 |
|
编译:
1 |
|
获得 Chat 老师提供的 trace_gdb_plot.py
:
# -*- coding: utf-8 -*-
import pexpect
import re
target = "./sample.out"
output_md = "plot.md"
transitions = []
state_counter = 0
state_map = []
def parse_variables(output):
variables = {}
for line in output.splitlines():
line = line.strip()
if not line:
continue
match = re.match(r'^(\w+)\s*=\s*(.+)$', line)
if match:
var, val = match.groups()
variables[var] = val
return variables
def format_state(vars_dict):
if not vars_dict:
return ""
return ", ".join("{}={}".format(k, v) for k, v in sorted(vars_dict.items()))
def get_clean_code(line):
return line.lstrip()
child = pexpect.spawn(f"gdb {target}", encoding='utf-8', timeout=10)
child.expect_exact("(gdb)")
child.sendline("set pagination off")
child.expect_exact("(gdb)")
child.sendline("start")
child.expect_exact("(gdb)")
prev_vars = {}
prev_state = f"S{state_counter}"
state_map.append(prev_state)
while True:
child.sendline("info locals")
child.expect_exact("(gdb)")
vars_output = child.before
# 调试打印
print("info locals 输出:")
print(vars_output)
# 如果info locals显示无符号表或者无栈帧,说明程序执行结束或已脱离调试上下文
if ("No symbol table info available." in vars_output) or ("No frame selected." in vars_output):
print("检测到程序已结束或无调试信息,退出循环")
break
current_vars = parse_variables(vars_output)
current_state = f"S{state_counter + 1}"
state_map.append(current_state)
child.sendline("list .")
child.expect_exact("(gdb)")
code_lines = child.before.splitlines()
code_line = ""
for line in code_lines:
if "=>" in line:
code_line = re.sub(r'.*=>\s*', '', line)
break
code_line = get_clean_code(code_line)
transition = f"{prev_state} -->|{code_line}| {current_state}"
label = format_state(current_vars)
if label:
transition += f"\n note right of {current_state}: {label}"
transitions.append(transition)
child.sendline("step")
idx = child.expect_exact(["(gdb)", "Program exited", "exited normally", pexpect.EOF], timeout=10)
if idx != 0:
print("程序结束或异常退出")
break
prev_vars = current_vars
prev_state = current_state
state_counter += 1
with open(output_md, "w", encoding="utf-8") as f:
f.write("# 执行状态迁移图\n\n")
f.write("<pre class="mermaid">\nstateDiagram-v2\n")
for t in transitions:
f.write(f"{t}\n")
f.write("```\n")
print(f"状态图写入 {output_md}")</pre>