用 Namespace 手搓一个容器

容器技术由 Linux 下三项技术构成。这三项技术分别是 Namespace、Cgroups 和 Unionfs。他们分别实现了系统逻辑资源的隔离、物理资源的限制以及容器的文件系统。 在这之中最为关键的是 Namespace。因为 Namespace 实现了虚拟化中最重要的隔离的功能。在 Namespace 之外即使不使用 Cgroups,用其他文件系统替代 Unionfs,依然能够实现一个容器的许多功能。 所以本文我们尝试用 Namespace 构建一个简单的容器。让我们首先想想,一个容器中的环境究竟需要与 host 隔离哪些资源。(请把容器想象成 host 之外的另一台机器。) 文件系统:容器中的进程不能访问 host 的文件系统。这意味着挂载点的隔离 – Mount Namespace 进程空间:容器中的进程无法查看容器外的进程信息。这意味着进程号的隔离 – PID Namespace 网络接口:容器中的进程拥有自己的网络接口,不使用 host 上的网络接口。这意味着网络的隔离 – Network Namespace 用户:容器中的用户和容器外的用户无关,例如容器内的 root 和容器外的 root 并不相同。这意味着用户的隔离 – User Namespace 物理资源:容器能够看到和管理的物理资源和容器外的资源不同。这意味着 Cgroups 视图的隔离 – Cgroups Namespace 时间:容器中的时间系统和容器外的时间不一定相同 – Time Namespace 主机名:容器中的主机名和容器外的主机名不一定相同 – UTS Namespace IPC:IPC,例如 Posix 消息队列,使用类似文件名的标识符,但是又并不真正存在于文件系统中。容器中的这些标识符和容器外的相同标识无关 – IPC Namespace 虽然种类很多,但是想要形成虚拟化的错觉只需要用到其中的一部分即可。为了构建我们的容器,我们选择只使用 Mount、PID 和 User。 隔离文件系统 容器化最重要的是隔离文件系统。所谓的程序运行环境,本质上就是文件系统中的各类库和应用程序。同一主机上的不同发行版的容器都运行在相同的内核上,他们只是在库和应用程序上存在不同。...

十月 13, 2025 · 6 分钟 · 1074 字 · Wokron

Linux 下的用户和容器中的用户

多用户操作系统的时代早就结束了。现在的人们一般不会通过多个用户登陆到同一个操作系统的方式共享计算资源,我们有更好的虚拟化技术。 不过用户依然在发挥作用,其中最主要的作用就是权限隔离。这一点似乎有许多内容可以讲,不过这里我们只会列出最关键的内容。容器中的用户会出现更多特殊情况,我们也会进行讨论。 用户与权限 用户和用户组 操作系统通过用户控制权限,特定的用户才能执行特定的操作。 操作系统中有一组配置好的用户。其信息被保存在 /etc/passwd 文件中。每个用户都属于一个或多个用户组。用户和用户组分别有 uid 和 gid。使用 id 命令可以查看当前用户的对应 ID。 $ id uid=1000(wokron) gid=1000(wokron) groups=1000(wokron) 1001(xxxxx) 1002(yyyyy) 其中 gid 指向的表示该用户的主组。可以通过 newgrp 命令切换主组。这会启动一个新的 shell。 $ newgrp xxxxx $ id uid=1000(wokron) gid=1001(xxxxx) groups=1000(wokron) 1001(xxxxx) 1002(yyyyy) 下面的内容忽略了许多细节。不过忽略的内容相对很少遇到。 当选择一个用户登陆系统时,这次登陆所创建的 shell 会将用户和其主组的 ID 附加到进程中。这两个 ID 称为该进程的进程凭证。 子进程创建时会继承父进程的的进程凭证。进程凭证表明了该进程代表哪一用户进行操作。 超级用户 进程的用户和用户组决定了其是否有权限执行某一操作。经典的 Unix 权限系统只区分了两种用户:超级用户和普通用户。超级用户能够执行操作系统所提供的所有操作,而普通用户则受到了限制。普通用户的操作不能够影响操作系统的状态。 超级用户的 uid 为 0,通常名为 root。其用户组通常只有 root(gid=0)。除此以外的用户都是普通用户,他们之间没有权限上的差异。 利用 sudo 命令可以临时提升当前用户的权限,使其以 root 用户的身份运行进程。(即进程和其子进程的进程凭证为 root)。 $ sleep 1 & ps -o user,group,uid,gid -p $!...

十月 12, 2025 · 3 分钟 · 485 字 · Wokron

试试触发(几乎)所有信号

输入 kill -L,你可以看到 Linux 下所有可用的标准信号,总共有 31 个。 $ kill -L 1 HUP 2 INT 3 QUIT 4 ILL 5 TRAP 6 ABRT 7 BUS 8 FPE 9 KILL 10 USR1 11 SEGV 12 USR2 13 PIPE 14 ALRM 15 TERM 16 STKFLT 17 CHLD 18 CONT 19 STOP 20 TSTP 21 TTIN 22 TTOU 23 URG 24 XCPU 25 XFSZ 26 VTALRM 27 PROF 28 WINCH 29 POLL 30 PWR 31 SYS 这回试试在这些信号原本的应用场景下触发它们。...

五月 23, 2025 · 10 分钟 · 2058 字 · Wokron

Linux 进程的内存管理

虽然我们都学过一个进程的内存由堆和栈组成。但是这样的模型还是太抽象了,其中掩盖了许多操作系统的细节。所以这里简单梳理一下进程的内存管理有关知识。 堆的增长 libc 中提供 malloc 函数申请堆上内存。底层由 brk 系统调用负责申请堆上内存。 在内核的视角下,堆空间是一个简单的结构。它由一个固定的堆底(符号 end)和可变的堆顶(称为 program break)组成。内核所要做的就是根据用户设定的 program break 将堆底和堆顶之间的内存标为有效。而 brk 系统调用的作用便是将某一地址设置为堆顶。 在 brk 系统调用之上,glibc 提供了两个不同的函数 int brk(void *addr) 和 void *sbrk(intptr_t increment)。前者直接设置 program break 地址,后者则根据 increment 取值调整 program break 位置。 下面使用 sbrk() 函数进行内存分配。sbrk() 会返回调用之前原本的 program break 地址,因此还需额外进行加减以获取当前地址。这一语义比较合理,因为这样 brk(N) 和 malloc(N) 的返回值都代表新分配的内存的起始地址。 #include <stdio.h> #include <unistd.h> extern char end; int main() { printf("Address of end symbol: %p\n", (void *)&end); printf("Current program break: %p\n", sbrk(0)); printf("New program break: %p\n", sbrk(1024) + 1024); printf("After deallocation: %p\n", sbrk(-512) - 512); } 运行结果如下...

五月 17, 2025 · 7 分钟 · 1484 字 · Wokron

浅谈字体

之前使用 Matplotlib 画图的时候发现不能正常显示中文。在解决问题的过程中了解了一下字体显示的一些知识,在这里记录一下。 一、编码与字体 我们知道,为了让字符能够存储在计算机中,我们为每个字符分配对应的数码。这种人为约定称为字符编码。常见的编码包括 ASCII、GBK、Unicode 等等。字符编码是字符的数据表示。 然而,仅有编码依然不能在计算机中显示字符。因为符号是一种图形,若我们不能确定字符所对应的图形的样式,那么我们就无法在屏幕中看到字符。同样的,这种字符到对应图形的约定称为字形,字形的集合即字体。字体是字符的图像表示。 于是,这里我们就有了两种不同的映射关系。一种是字符到字符编码的映射关系,这一关系确定了字符的存储方式;另一种是字符到字体的映射关系,这一关系确定了字符的显示方式。 因此,简单来说,计算机显示字符的过程就是将编码数据转换为字形图像的过程。 二、编码的分裂和统一 然而这一过程并不真的那么简单。原因之一便是从计算机技术早期遗留下来的一系列互不兼容的编码方式。 在互联网还未诞生的时候,各个国家和地区为了在计算机中显示自己的文字,各自开发出自己的字符编码方式,例如简体中文的 GB2312、繁体中文的 Big5、日文的 Shift_JIS 等等。然而在互联网的国际化场景下,不同的编码方式带来了交流上的困难。 所以这时候 Unicode 标准应运而生。Unicode 由统一码联盟推动,旨在使用 Unicode 取代现存的字符编码。该标准整理并编码了世界上大部分的文字系统,使得电脑能以通用划一的字符集来处理和显示文字。 然而 windows 下的默认编码方式还是 gbk。 Unicode 编码空间区间为 $[0, 17 \times 2^{16})$,但目前只使用了其中很少一部分。该编码空间被划分为了 17 个平面,其中 4-13 号均未使用。其他平面的主要内容为: 0 号平面(0x0000-0x​FFFF)称为基本多文种平面,包含了几乎所有现代语言的字符。 1 号平面(0x10000-0x1FFFF)称为多文种补充平面,包括了绝大多数古代文字,现时已不再使用或很少使用的符号等。 2、3 号平面(0x20000-0x​2FFFF、0x30000-0x​3FFFF)称为补充表意文字平面,用于中日韩统一表意文字中未被包含在早期编码标准中的文字。 14 号平面(0xE0000-0xEFFFF)为特别用途补充平面。 15、16 号平面(0xF0000-0x​10FFFF)为私人使用平面。(例如苹果公司的图标字符) Unicode 虽然制定了统一的字符编码方式,但是直接使用 Unicode 编码进行存储的效率并不高。因此需要对 Unicode 进行进一步编码,这就带来了 UTF(Unicode Transformation Format),包括 UTF-32、UTF-16 和 UTF-8。 UTF-32 使用定长的 32 位对 Unicode 进行编码。根据 Unicode 的编码空间我们知道,最多只需要 21 位即可表示所有编码。因此 UTF-32 中的 11 位始终为 0。这使得 UTF-32 的空间效率很低。所以这种编码方式主要用在系统的内部 API 中。...

五月 5, 2024 · 2 分钟 · 255 字 · Wokron

QEMU 模拟器介绍

本文是北航《操作系统》课程预习教程的一部分。此版本由本人编写。 2024 年课程实验环境由 GXemul 更换为 QEMU,为了方便同学适应新的实验环境,在预习教程中特地新增《GDB:程序的解剖术》和《QEMU 模拟器介绍》两篇文章。 操作系统是直接运行在计算机硬件之上,向下管理硬件资源,对上为软件提供统一服务的一类程序。在本课程的实验中,为了开发和运行我们的 MOS 操作系统,我们必须具备一套支持操作系统运行的硬件系统,其中包括处理器、内存、外部设备(如磁盘)等多个组成部分。 然而,为每位同学都准备一套硬件设备是不切实际的。相较之下,使用模拟器则是一个更好的选择。模拟器能够模拟计算机硬件的行为和特性,使开发者可以在模拟的环境中运行和测试软件,而无需实际的物理硬件设备。 本实验所采用的模拟器为 QEMU,接下来我们就会对这一模拟器进行介绍。 什么是 QEMU QEMU(Quick Emulator)是一个通用的开源的机器仿真和虚拟化工具,由传奇程序员法布里斯·贝拉(Fabrice Bellard)编写。QEMU 能够提供跨体系结构的硬件模拟,支持 x86、ARM、MIPS、RISC-V 等多种架构。 法布里斯·贝拉 是 QEMU、FFmpeg 等著名项目的创始人。他的工作涉足操作系统(QEMU)、编译器(Tiny C Compiler)、图形学(TinyGL)、通信技术(Amarisoft)、数学(Bellard’s formula)、音视频(FFmpeg)、人工智能(NNCP)等众多领域,并都做出过许多突出的贡献。是一位近乎全才的人物。 QEMU 拥有多种不同的使用方式,而在实验中我们所使用的主要是 QEMU 的系统仿真模式。在此模式中,QEMU 能够模拟处理器的执行过程以及各种硬件设备的行为,从而提供包括处理器、内存和外部设备在内的整机虚拟模型。在此模型之上,我们能够运行一个完整的操作系统,而不需要任何额外硬件的支持。 QEMU 提供了高度定制化的硬件模拟能力,使得搭建指定硬件平台的运行环境十分容易。并且 QEMU 也提供了使用 GDB 进行调试的原生支持,使程序的开发更加便捷。正因如此,QEMU 成为了底层开发领域十分重要的工具。 QEMU 的工作原理 这部分并不是本课程要求掌握的内容。各位可以按兴趣阅读。 在正式谈论 QEMU 的工作原理前,我们需要先了解一下虚拟化(Virtualization)技术。这里的虚拟化特指硬件虚拟化,是指隐藏真实的物理硬件,而由软件模拟出特定的硬件环境,在此环境中运行的操作系统就好像运行在实际的物理机器上一样。在此过程中,通过模拟产生的硬件环境称为虚拟机(Virtual Machine),实现虚拟化的程序称为虚拟机管理程序(Hypervisor)。本质上,虚拟机管理程序是一种中间件。 通过虚拟化技术,我们可以屏蔽底层硬件的差别,从而在单台物理设备上运行许多不同的操作系统环境,充分利用硬件资源。虚拟化产生的硬件环境也很容易在不同设备间迁移,这也利于系统的管理和维护。 根据虚拟化实现方式的不同,虚拟机管理程序分为第一类虚拟机管理程序(Type 1 Hypervisor)和第二类虚拟机管理程序(Type 2 Hypervisor)。 第一类虚拟机管理程序直接运行在硬件之上,如下图 (a) 所示。此时虚拟机管理程序实际上占据了类似操作系统的位置,整个物理机被其分割为多个虚拟机。 而第二类虚拟机管理程序则运行在操作系统之上,是操作系统中的应用程序,如下图 (b) 所示。其中称运行该虚拟机管理程序的操作系统所处的机器为宿主机(Host),而管理程序中的虚拟机则为客户机(Guest)。由于第二类虚拟机管理程序采取了软件模拟处理器、解释执行机器码的方式,所以也被称为模拟器(Simulator)。 第一类虚拟机管理程序主要在企业数据中心或服务器中使用。常见的产品包括 KVM、VMWare ESXi 等等。而第二类虚拟机管理程序则通常在个人计算机上使用,以便能在运行虚拟机的同时执行其他进程。常见的产品包括 VMware Workstation、Oracle VirtualBox 等等,其中也包括 QEMU。 图片来自 Andrew S....

一月 21, 2024 · 4 分钟 · 707 字 · Wokron