终端和操作系统
终端和操作系统
终端和操作系统
程序是怎么和终端 “配对” 的?
用户登录的起点
系统启动 (内核 → init → getty)
远程登录 (sshd → fork → openpty)
stdin, stdout, stderr 都会指向分配的终端
vscode (fork → openpty)
login 程序继承分配的终端
(是的,login 是一个程序)
fork() 会继承文件描述符 (指针)
因此,子进程也会指向同一个终端
终端和操作系统
终于,我们有了足够的机制实现 “用户界面”
终端和操作系统
当然,不是图形的
UNIX Shell: “终端” 时代的经典设计
“Command-line interface” (CLI) 的巅峰
终端和操作系统
进程管理:要解决的问题
我们有那么大一棵进程树,都指向同一个终端,有的在前台,有的在后台,Ctrl-C 到底终止哪个进程?
答案:终端才不管呢
它只管传输
字符
Ctrl-C: End of Text (ETX),
\x03
Ctrl-D: End of Transmission (EOT),
\x04
stty -a: 你可以看到按键绑定 (奇怪的知识增加了)
但
操作系统
收到了这个字符
就可以对 “当前” 的进程采取行动
终端和操作系统
终端上的 “当前进程”
作为操作系统的设计者,需要在收到 Ctrl-C 的时候找到一个 “当前进程”
你会怎么做?
fork() 会产生树状结构
(还有托孤行为)
Ctrl-C 应该终止所有前台的 “进程们”
但不能误伤后台的 “进程们”
终端和操作系统
会话 (Session) 和进程组 (Process Group)
终端和操作系统
会话和进程组:机制
给进程引入一个额外编号 (Session ID,大分组)
子进程会继承父进程的 Session ID
一个 Session 关联一个控制终端 (controlling terminal)
Leader 退出时,全体进程收到 Hang Up (SIGHUP)
再引入另一个编号 (Process Group ID,小分组)
只能有一个前台进程组
操作系统收到 Ctrl-C,向前台进程组
所有进程
发送 SIGINT
(真累……但你也想不到更好的设计了)
终端和操作系统
会话和进程组:API
太不优雅了
setsid/getsid
setsid 会脱离 controlling terminal
setpgid/getpgid
tcsetpgrp/tcgetpgrp
迷惑 API
以及……uid, effective uid (?), saved uid (???)
Setuid Demystified
任何软件都很难逃脱千疮百孔的设计
终端和操作系统
终于能实现 Job Control 了
窗口和多任务:终端可以有 “一个前台进程组”
“最小化” = Ctrl-Z (SIGTSTP)
SIGTSTP 默认行为暂停进程,收到 SIGCONT 后恢复
“切换” = fg/bg (tcsetpgrp)
为了实现 “窗口栏上的按钮”,还很是大费周章
还不如 tmux 管理多个 pty 呢 (选择性 “绘制” 在终端上)
那是因为发明 session/pg 的时候还没有 pty 呢……
终端和操作系统
是的,历史的糟粕
但是,这是 POSIX 的一部分……
几乎任何人都无法预知 “软件” 的未来
回头看这个问题
我们不需要 “绑定进程到设备”
管理程序 (tmux, gnome, ...) 去模拟就行
Window Manager: 只需要 “进程组” 就行了
关窗口,全部
一个不留
Android: 每个 app 都是不同的用户
强行终止 = 杀掉属于这个用户的所有进程
Snap: 程序在隔离的沙箱运行
AppArmor + seccomp + namespaces (真狠)
终端和操作系统
在未来回头看现在
人机交互的方式根本不应该是这样的
我们很少能清醒地认识到
我要做
X
X
X
应该分解成
Y
→
(
Z
,
W
)
→
T
Y \to (Z, W) \to T
Y
→
(
Z
,
W
)
→
T
因此,
坐在电脑前的大部分时间都浪费了
终端和操作系统
最后:Ctrl-C 到底做了什么?
signal
注册一个信号的 “处理程序”
f
f
f
操作系统会记下这个
f
f
f
kill
在程序从操作系统返回时,强制加一个向
f
f
f
的跳转
程序 = 状态机
只要 “模拟” 调用
f
f
f
的行为即可
今天有更可靠的版本 (sigaction)
让 AI 帮你解释吧