南京大学操作系统课程学习4
计算机安全
在操作系统API上,我们可以构建命令行工具、编译器、数据库,浏览器等丰富应用。
在8086时代,每次的访问都是直接映射到地址空间中,任何程序可以访问任何硬件。百度可以复制自己,然后感染更多的计算机程序。
我们希望计算机有confidentiality,integrity,availability。
有一些软件硬件的协同设计。
- 分页机制和进程隔离
- 系统调用和访问控制
- 鉴权和授权
- 加密
- 审计与日志
- 机密计算(TEE)
理论上应用程序如果不信任操作系统,硬件也提供了特权来执行应用程序的代码。
访问控制
进程+虚拟内存已经实现了隔离。进程只能以ELF规定的权限访问自己的虚拟空间地址。系统是唯一访问操作系统对象的途径。(前提是内核没有被漏洞攻破)
操作系统还有一些虚拟化机制、容器。
访问控制:限制程序对操作系统对象的访问。拒绝越权访问和修改。
理论上可以直接用一张表来做访问控制。然而表可能特别大。unix用整数表示身份
uid=0就是root用户,gid是完全自由的,虽然一般0是root,mode规定了rwx的权限。
操作系统没有用户名的概念,操作系统也不负责验证用户名。有个程序叫做login负责进行密码的验证。
现在系统通常使用shadow文件存储密码的hash。chsh,passwd只是直接修改了文件。操作系统只管uid不管如何解读用户。
如今的操作系统有很多uid:
- real uid(ruid)
- saved uid(suid)
- effective uid(euid)
- filesystem uid(uid) 如今已经没人用了。
操作系统还有非常多的访问控制的方法
- access control list(ACL)
- SELinux、AppArmor
- capabilities
攻防
假设我们运行这样一段程序
会发生段异常。这个叫做undefined behavior。
容器、虚拟机、微服务
进程一直以来都是操作系统中的核心抽象。作为应用程序的主题,运行它的方式在多年的发展中经历了许多变化。
操作系统理论上是可以无限套娃的,比程序完成整个操作系统。取指令,编译代码,执行
但是它的性能,不及原生的10%。full system emulation
1997年 brings back an idea popular in the 1970s: virtual machine monitor
VMware在1998年把这个技术做成了产品。VMware会把应该在系统内部调用的程序放在操作系统直接运行。但是系统调用会被VMware程序劫持变成一个软件模拟的系统调用。
wsl在windows下模拟了linux地址空间,如果发现systemcall来自linux,就用linux子系统完成。
之后intel提供了许多虚拟化指令集。
VT-x 2005->VT-d 2006 ->EPT 2008
- 一些特权指令没法直接执行,比如关闭中断。可以让虚拟机来控制中断。
- VT-d提供了堪比物理机一样的高效的IO:IO虚拟化。
- EPT提供页表,8级页表其中4级给操作系统用,4级给虚拟机用。
Intel 的 EPT(Extended Page Tables,扩展页表) 是硬件辅助虚拟化技术的一部分,主要用于优化虚拟机(VM)中客户机物理地址(GPA)到宿主机物理地址(HPA)的转换。
在没有 EPT 的情况下,虚拟机监控器(VMM/Hypervisor)需要通过软件模拟页表,处理客户机的内存访问请求(GPA → HPA),导致大量性能开销(称为“影子页表”问题)。
EPT 在硬件层面提供第二层页表(EPT 页表),直接由 CPU 的 MMU(内存管理单元)完成 GPA 到 HPA 的转换,避免了 Hypervisor 的软件介入,大幅降低内存访问延迟。
最早的时候通过虚拟机进行超卖。
虚拟机导致更容易的管理状态了。
最早的系统的版本休眠是可以掉电的。掉电之后打开还是可以继续之前的状态的。但是在物理机上实现这个功能是很复杂的,需要把所有进程都停下来。遍历设备程序驱动。dump到磁盘里。但是虚拟机很容易实现这个功能,因为虚拟机就是一个进程,直接做一个core dump就可以了。
另一个技术叫做空间转移的技术:optimizing migration of virtual computers
还可以使用虚拟化技术做操作系统内核的热更新。
不同虚拟化技术的实现差异
全虚拟化(Full Virtualization):
使用二进制翻译(如早期VMware)动态替换特权指令,或依赖硬件虚拟化扩展。
Guest OS无需修改,但性能开销较大。
半虚拟化(Paravirtualization):
Guest OS被修改,主动调用Hypervisor提供的API(如Xen的
hypercall
)代替特权指令,避免陷入开销。容器(Container):
共享主机内核,通过命名空间(namespace)和cgroups隔离资源,直接调用主机系统调用,无虚拟化层。
容器
但理论上操作系统自己就能虚拟化自己。操作系统理论上可以假装自己在虚拟机中执行一个系统调用。
pid可以不再是整个系统唯一的,给每一个进程增加一个osid,增加系统调用vos(fs_root),这个系统调用会创建一个新的虚拟机。新的树的osid就是2。但是os2的getpid就可以由操作系统控制。然后准备俩个不一样的文件系统。
- 创建一个新的osid
- pid从1开始分配
- fork()继承父进程的osid
每一次systemcall都是在当前的osid下执行,对其他osid屏蔽。
但是由于增加了osid,要确认操作系统的哪些资源是可以被虚拟化的。
- pid
- user:用户和组
- mnt:文件系统和设备
- ipc:信号量、消息队列、共享内存
- net:网络设备、协议栈、端口(localhost:5000)
- time:系统时间和时区
- uts主机名和域名.
在操作系统中可以看到自己的namespace,linux: /proc/[pid]/ns
在这个基础上我们还想进一步实现资源的调度,圈一些进程,使用特定的资源策略
Control Groups (cgroups) 是 Linux 内核提供的一种机制,用于限制、记录和隔离进程组的资源使用情况(如 CPU、内存、磁盘 I/O、网络等)
Cgroup是于2.6内核由Google公司主导引入的,它是Linux内核实现资源虚拟化的技术基石,LXC(Linux Containers)和docker容器所用到的资源隔离技术,正是Cgroup。
那么你发明了cgroups,这是一个和namespaces正交的机制。
共同使用,你就得到了容器。例子:只有busybox的系统中的系统
到这里你就发明了docker(2013)。
在2008年就有了Linux containers,通过整合内核的 cgroups(控制资源)和 namespaces(隔离进程、网络等),首次在 Linux 上实现了完整的容器功能。但配置复杂,需手动管理。由IBM工程师开发,得到了linux社区的广泛贡献。
Google 的 Borg 系统(2004年内部使用,2015年发布):大规模使用容器技术(基于 cgroups)管理集群资源,但未开源,仅限于 Google 内部。
如果只需要linux,容器就和虚拟机一样。开销比虚拟机低很多,安全性略低。如果操作系统有资源没有很好的被namespace隔离开,就可能产生安全问题。比如机器的总端口可能是有限的。
Kubernetes:容器编排,跨主机、弹性自动伸缩。这是云厂商最爱看到的。
每个容器可能非常小,比如rust写的,几十mb就能跑起来了。
云原生,serverless
直接将函数最为服务,进行按量付费。serverless连容器保活都不需要了。function as a service。
嵌入式和移动操作系统
embedded计算机系统是嵌入到设备中的。人造卫星,工业控制、家用电器、医疗器械、穿戴设备。
体积小、计算第、可靠性高,操作系统通常更简单(领域固定);但linux也是可以的
例如PLC(programmable logic controller)和工控。
通常有实时保障。在严格的时间内对外部事件做出可预测响应。
比如我们希望某个程序一毫秒执行一次,但是linux就难以做到这样的事情(特别是当程序运行的越来越多的时候)
无论车机多花哨,最终实时控制仍然是MCU。
比如FreeRTOS。支持抢占式调度,按照优先级执行任务,确保高优先级的任务及时响应。内核仅需几KB ROM和RAM,适合资源受限的MCU。
高优先级的任务可以实现更高优先级的时间片分配。但是优先级也会带来麻烦,比如低优先级的任务拿到了锁,导致高优先级的任务被阻塞。高优先级任务只能让出执行时间片给低优先级的任务先执行。这叫做优先级反转(priority inversion)。应用程序的依赖可能形成等待链,导致系统变卡。
到系统出现不稳定时,重启系统总是能解决问题,是因为重启能恢复状态机的状态。
Android
最早的手机不能安装软件,只能收发信息和打电话。甚至只能显式三四行的文字。之后诺基亚的5310可以执行一些复杂的任务。支持彩色的显示,并且能够播放音乐,运行可安装的java程序。j2ME时代,甚至可以用java渲染网页。320x240的屏幕。
之后乔布斯发布了ios。谷歌2005年收购了Android inc。安卓是完全基于linux开发的,Linux+Full JVM+Framework API开放平台。
一开始用java发现程序非常卡。当时是32位的armv6@528mhz(1cpu,TSMC)
塞班系统当时暴毙于C++,windows phone 暴毙于C#。当时java有很大的市场。
现在看起来这是一个很高瞻远瞩的决定,赌摩尔定律生效(同年 i7865,4c8t)
存在一个推理,在未来存在一个临界点,能实现实时推理无处不在。
在安卓5.0的时候支持将所有程序从原来的.class编译到.out
安卓之间应用程序经常需要进行写作。安卓提供了remote.transact()。
这是在优化和易用之间的权衡。
安装也提供了丰富的开发者工具。比如android studio。DDMS。android debug bride(ADB)。还有android shell。
比如可以监听系统的所有事件。简单来说我们可以将硬件操作挂在到一个设备上,和之前提到的一样,读取硬件的操作,然后捕捉对应的事件。我们写一个程序定期拍照,然后将点积事件传回给手机,就有了电脑控制手机的功能(scrcpy)。
甚至可以任意改变APP的行为。
如何杀死一个android进程?android每个app都有独立的uid,遍历进程表,找到属于uid的进程。遍历进程表来杀死进程的时候,可能出现杀不干净。安卓默认间隔5ms,连续杀死40次,防止偶然杀不干净。