最近看了操作系统相关的知识，结合上《程序员的自我修养》这本书所讲的链接装载与库，感觉对于一个程序从编译，到汇编转换成机器码，再到多目标文件与库的链接，是一个非常复杂的过程。所以想要浅显易懂的总结一下，也刚好是找到了一个算是浅显易懂的总结，所以在自己的理解上再进行了一些润色。编译流程从源代码得到可执行文件的编译流程可被细化为多个阶段（虽然输入一条命令便可将它们全部完成）：编译器 (Compiler) 将每个源文件从某种高级编程语言转化为汇编语言，注意此时源文件仍然是一个 ASCII 或其他编码的文本文件；汇编器 (Assembler) 将上一步的每个源文件中的文本格式的汇编指令转化为机器码，得到一个二进制的 目标文件 (Object File)；链接器 (Linker) 将上一步得到的所有目标文件以及一些可能的外部目标文件链接在一起形成一个完整的可执行文件。汇编器输出的每个目标文件都有一个独立的程序内存布局，它描述了目标文件内各段所在的位置。而链接器所做的事情是将所有输入的目标文件整合成一个整体的内存布局。在此期间链接器主要完成两件事情：第一件事情是将来自不同目标文件的段在目标内存布局中重新排布。如下图所示，在链接过程中，分别来自于目标文件 1.o 和 2.o 段被按照段的功能进行分类，相同功能的段被排在一起放在拼装后的目标文件 output.o 中。注意到，目标文件 1.o 和 2.o 的内存布局是存在冲突的，同一个地址在不同的内存布局中存放不同的内容。而在合并后的内存布局中，这些冲突被消除。第二件事情是将符号替换为具体地址。这里的符号指什么呢？我们知道，在我们进行模块化编程的时候，每个模块都会提供一些向其他模块公开的全局变量、函数等供其他模块访问，也会访问其他模块向它公开的内容。要访问一个变量或者调用一个函数，在源代码级别我们只需知道它们的名字即可，这些名字被我们称为符号。取决于符号来自于模块内部还是其他模块，我们还可以进一步将符号分成内部符号和外部符号。然而，在机器码级别（也即在目标文件或可执行文件中）我们并不是通过符号来找到索引我们想要访问的变量或函数，而是直接通过变量或函数的地址。例如，如果想调用一个函数，那么在指令的机器码中我们可以找到函数入口的绝对地址或者相对于当前 PC 的相对地址。[scode type="blue" size=""]PC (Program Counter) 程序计数器，用来存放当前欲执行指令的地址。可自动形成指向下一条指令的地址。一般来讲，这其实是一种行为的代称而不是真实存在的寄存器，在 x86 体系，一般是由 CS:IP （段地址：偏移地址）的组合来构成这个逻辑，而在 ARM 体系中 R15 就是 PC，当然 ARM 和 IA-32、x64 都支持高级内存管理，所以 PC 的内容未必是当前指令在内存中的绝对位置。而且所谓的每次增加1，其实是逻辑上的增加1，并不代表内存地址的增量为1，这受到指令长度的影响。[/scode]那么，符号何时被替换为具体地址呢？因为符号对应的变量或函数都是放在某个段里面的固定位置（如 全局变量往往放在 .bss 或者 .data 段中，而函数则放在 .text 段中），所以我们需要等待符号所在的段确定了它们在内存布局中的位置之后才能知道它们确切的地址。当一个模块被转化为目标文件之后，它的内部符号就已经在目标文件中被转化为具体的地址了，因为目标文件给出了模块的内存布局，也就意味着模块内的各个段的位置已经被确定了。然而，此时模块所用到的外部符号的地址无法确定。我们需要将这些外部符号记录下来，放在目标文件一个名为符号表（Symbol table）的区域内(对于ELF文件一般在.symtab)。由于后续可能还需要重定位，内部符号也同样需要被记录在符号表中。外部符号需要等到链接的时候才能被转化为具体地址。假设模块 1 用到了模块 2 提供的内容，当两个模块的目标文件链接到一起的时候，它们的内存布局会被合并，也就意味着两个模块的各个段的位置均被确定下来。此时，模块 1 用到的来自模块 2 的外部符号可以被转化为具体地址。同时我们还需要注意：两个模块的段在合并后的内存布局中被重新排布，其最终的位置有可能和它们在模块自身的局部内存布局中的位置相比已经发生了变化。因此，每个模块的内部符号的地址也有可能会发生变化，我们也需要进行修正。上面的过程被称为重定位（Relocation），这个过程形象一些来说很像拼图：由于模块 1 用到了模块 2 的内容，因此二者分别相当于一块凹进和凸出一部分的拼图，正因如此我们可以将它们无缝地拼接到一起。
操作系统学习笔记（1）——什么是操作系统
前言最近找到一个好文档，关于如何用Rust编写一个操作系统，里面系统的讲述了操作系统的相关知识和源代码等等，所以就打算跟着这个文档开始学习操作系统相关的知识(之后就可以去读CSAPP了从认识来看一个操作系统（OS）是一个软件，它帮助用户和应用程序使用和管理计算机的资源。操作系统可能对最终用户不可见，但控制着嵌入式设备、更通用的系统（如智能手机、台式计算机和服务器）以及巨型机等各种计算机系统。从功能开始操作系统主要做的事情就只有两件：一是向下管理计算机硬件和各种外设，二是向上给应用软件提供各种服务帮助。进一步描述操作系统是一个可以管理CPU、内存和各种外设，并管理和服务应用软件的系统软件。[scode type="share"]系统软件是在一个计算机系统范围内使用的软件，可涉及到整个计算机系统。文本编辑器这样的软件只涉及到对文本文件的编辑，并不能被叫做系统软件。而在计算机中安装的 C 标准库 libc（类似的有 Rust 标准库 等）可以算是一个，因为它提供了与OS交互的系统调用接口，其功能覆盖了整个计算机系统，会被许多不同的软件访问。[/scode]为了完成这些工作，操作系统需要知道如何与硬件打交道，如何更好地给应用软件提供服务，这就有一系列操作系统相关的理论、抽象、设计等来支持如何做和做得好的需求。[scode type="blue"]在不同的应用场景下，操作系统的边界也是不同的，我们可以把运行时库、图形界面支持库等这些可支持不同应用的系统软件 (System Software) 也看成是操作系统的一部分。比如说嵌入式环境，一般操作系统以运行时库的形式与应用程序紧密结合在一起，形成一个可在嵌入式硬件上单一执行的嵌入式应用。而 Android 应用运行在 ARM 处理器上 Android 操作系统的执行环境中。[/scode]简化描述站在应用程序的角度来看，我们可以发现常见的应用程序其实是运行在由硬件、操作系统内核、运行时库、图形界面支持库等所包起来的一个 执行环境 (Execution Environment) 中，应用程序只需根据与系统软件约定好的应用程序二进制接口 (ABI, Application Binary Interface) 来请求执行环境提供的各种服务或功能，从而完成应用程序自己的功能。于是我们可以将操作系统的定义简化成：应用程序的软件执行环境。一直在变化的定义操作系统的内涵和外延随着历史的发展也一直在变化，并没有类似于“1+1=2”这样的明确定义。如今在二十一世纪初期的大众眼中，操作系统就是他们的手机/终端上的软件系统，包括各种应用程序集合，图形界面和网络浏览器是其中重要的组成部分。但无论操作系统的内在实现和具体目标如何变化，其管理计算机硬件，给应用提供服务的核心定位没有变化。
1.伪类一种选择器，可以帮我在我的标记文本中减少多余的类例如当我在写一篇文章时，我要给第一段加粗，此时我可能会写一个类，并把第一个p标签挂上这个类来实现加粗效果，但如果我在这段话之前再加上一段，此时要进行更改便比较困难，此时可以使用:first-child伪类选择器原本的写法.first { font-size: 120%; font-weight: bold; } ... ... 现在的写法article p:first-child{ font-size: 120%; font-weight: bold; } ... ... 这其中还包括用户行为伪类，可用于进行与用户交互例如 :hover——只会在用户将指针挪到元素上的时候才会激活，一般就是链接元素。 :focus——只会在用户使用键盘控制，选定元素的时候激活。a:hover{color:hotpink!important;} Hover over me2.伪元素伪元素以类似方式表现，不过表现得是像你往标记文本中加入全新的HTML元素一样，而不是向现有的元素上应用类。伪元素开头为双冒号::[scode type="yellow"]备注：一些早期的伪元素曾使用单冒号的语法，所以可能会在代码或者示例中看到。现代的浏览器为了保持后向兼容，支持早期的带有单双冒号语法的伪元素。[/scode]例如::first-line便可以做到仅改变某段落的第一段的风格article p::first-line { font-size: 120%; font-weight: bold; } ... 如果结合两种用法，则可以仅改变第一段第一行的内容样式，即article p:first-child::first-line参考文章：https://developer.mozilla.org/zh-CN/docs/Learn/CSS/Building_blocks/Selectors/Pseudo-classes_and_pseudo-elements