iOS老司机的<<程序员的自我修养:链接、装载与库>>读书分享

语言: CN / TW / HK

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

前言

  • 当我们在开发iOS App时, cmd + R的背后发生了什么?
  • 这是个值得思考的问题. iPhone是一部手机, 也是一台移动计算机.
  • <<程序员的自我修养>>这本书就站在计算机操作系统底层的角度, 帮我们分析了程序运行背后的故事.
  • 近年来很多iOS同仁都在感叹大前端要学习的东西实在太多, 感觉学不过来.
  • 但是此书里就有着这样的视角: ``` CPU体系结构、汇编、C语言(包括C++)和操作系统, 永远都是编程 大师们的护身法宝,就如同少林寺的《易筋经》,是最为上乘的武功; 学会了《易筋经》,你将无所不能,任你创造武功; 学会了编程“易筋经”,大师们可以任意开发操作系统、编译器,甚至是开发一种新的程序设计语言!

——佚名

- 也提出了以下几点大家习以为常, 而又陌生的问题: 1. 为什么程序是从main开始执行?

  1. “malloc分配的空间是连续的吗?”

  2. “目标文件是什么?链接又是什么?” “为什么这段程序链接时报错?” ```

  3. 其实, 正如本书的核心观点程序入口、运行 库初始化、全局/静态对象构造析构、静态和动态链接时程序的初始化 和装载等。我们把这些问题归结起来,发现主要是三个很大的而且连贯 的主题,那就是“链接、装载与库”。
  4. 本书的内容的确不是介绍一门新的编程语言或展示一些实用的编程技术, 而是介绍程序运行背后的机制 和由来, 可以看作是程序员的一种修养.
  5. ps: 学习笔记纯手打, 阅读中如若发现错别字, 还望评论区指正, 先行谢过了:)

image.png

第一部分 简介

第1章 温故而知新

  • 1.1 从Hello World说起
  • 1.2 万变不离其宗

  • 1.3 站得高, 望得远

  • 1.4 操作系统做什么

    • 1.4.1 不要让CPU打盹
    • 1.4.2 设备驱动
  • 1.5 内存不够怎么办

    • 1.5.1 关于隔离
    • 1.5.2 分段(Segmentation)
    • 1.5.3 分页(Paging)
  • 1.6 众人拾柴火焰高

    • 1.6.1 线程基础
    • 1.6.2 线程安全
    • 1.6.3 多线程内部情况
  • 1.7 本章小结

    • 一个字节
      • 存储8位无符号数,储存的数值范围为0-255。
      • 如同字元一样,字节型态的变数
      • 只需要用一个位元组(8位元)的内存空间储存。
      • 1字节(Byte)=8位(bit) 
      • 1KB( Kilobyte,千字节)=1024B (2^10)B
      • 1MB( Megabyte,兆字节)=1024KB (2^10)KB
      • 1GB( Gigabyte,吉字节,千兆)=1024MB (2^10)MB
      • 4GB = 4*(2^30)B = (2^32)B

第二部分 静态链接

第2章 编译和链接

  • 2.1 被隐藏了的过程

    • hello.c文件
    • 2.1.1 预编译 生成hello.i文件
    • 2.1.2 编译 生成hello.s 目标文件
      • array[index] = (index + 4) * (2 + 6)
      • 扫描
      • 语法分析
      • 语义分析
      • 源代码优化

      • 代码生成
      • 目标代码优化
    • 2.1.3 汇编 生成hello.o文件
    • 2.1.4 链接 生成a.out 可执行文件
  • 2.2 编译器做了什么

    • 2.2.1 词法分析
    • 2.2.2 语法分析
    • 2.2.3 语义分析
    • 2.2.4 中间语言生成
    • 2.2.5 目标代码生成与优化
  • 2.3 链接器年龄比编译器长

  • 2.4 模块拼装--静态链接

  • 2.5 本章小结

第3章 目标文件里有什么

  • 3.1 目标文件的格式 COFF(Common File Format)

    • 可重定位文件
    • 可执行文件
    • 共享目标文件
    • 核心转储文件
  • 3.2 目标文件是什么样的

  • 3.3 挖掘SimpleSection.o

    • 3.3.1 代码段
    • 3.3.2 数据段和只读数据段
    • 3.3.3 BSS段
    • 3.3.4 其他段
  • 3.4 ELF文件结构描述

    • 3.4.1 头文件
    • 3.4.2 段表
    • 3.4.3 重定位表
    • 3.4.4 字符串表
  • 3.5 链接的接口--符号

    • 3.5.1 ELF符号表结构
    • 3.5.2 特殊符号
    • 3.5.3 符号修饰与函数签名
      • 由于不同的编译器采用不同的名字修饰方法, 
      • 这就导致由不同编译期编译产生的目标文件无法正常相互链接,
      • 这是导致不同编译器之间不能互操作的主要原因之一.
    • 3.5.4 extern"C"
      • C++编译器会将在extern "C"的大括号内部的代码,
      • 当做C语言代码处理. C++的名称修饰机制将不会起作用.
    • 3.5.5 弱符号(Weak Symbol)与强符号
  • 3.6 调试信息

  • 3.7 本章小结

第4章 静态链接

  • 4.1 空间与地址分配

    • 问题
      • 对于多个输入目标文件,
      • 链接器如何将它们的各个段合并到输出文件?
    • 4.1.1 按序叠加
    • 4.1.2 相似段合并
    • 符号地址的确定
  • 4.2 符号解析与重定位

    • 4.2.1 重定位
      • objdump -h a.o
      • 把ELF文件的各个段的基本信息打印出来
      • objdump -d a.o
      • 查看"a.o"的代码段反汇编结果
    • 4.1.3 4.2.2 重定位表
      • objdump -r a.o
      • 查看"a.o"里面要重定位的地方
      • 即"a.o"所有引用到外部符号的地址
    • 4.2.3 符号解析
    • 4.2.4 指令修正方式
  • 4.3 COMMON块

  • 4.4 C++相关问题

    • 4.4.1 重复代码消除
    • 4.4.2 全局构造与析构
    • 4.4.3 C++与ABI
      • ABI(Application Binary Interface)
        • 符号修饰标准
        • 变量内存布局
        • 函数调用方式等
        • 跟可执行代码二进制兼容性相关的内容
        • 更关注二进制层面
      • API(Application Programming Interface)
        • 更关注源代码层面
  • 4.5 静态库链接

  • 4.6 链接过程控制

    • 4.6.1 链接控制脚本
    • 4.6.2 最"小"的程序
    • 4.6.3 使用ld链接脚本
    • 4.6.4 ld链接脚本语法简介
  • 4.7 BFD库(Binary File Descriptor library)

  • 4.8 本章小结

    • 静态链接中的第一个步骤
      • 目标文件在被链接成最终可执行文件时,
      • 输入目标文件中的各个段是如何被合并到输出文件中的,
      • 链接器如何为它们分配在输出文件中的空间和地址.
  • 一旦输入段的最终地址被确定, 

  • 接下来就可以进行符号的解析与重定位,

    • 链接器会把各个输入目标文件中对于外部符号的引用进行解析,
    • 把每个段中须重定位的指令和数据进行修补,
    • 使它们都指向正确的位置.

第5章 Windows PE/COFF

  • 5.1 Windows的二进制文件格式PE/COFF

  • 5.2 PE的前身--COFF

5.3 链接指示信息

".drectve段"(指令Directive的缩写), 

这个段保存的是编译期传递给链接器的命令行参数,

可以通过这个段实现指定运行库等功能.

  • 5.4 调试信息

  • 5.5 大家都有符号表

  • 5.6 Windows下的ELF--PE

    • 5.6.1 PE数据目录
  • 5.7 本章小结

    • Windows下的可执行文件、动态链接库等都使用PE文件格式,
    • PE文件格式是COFF文件格式的改进版本, 
    • 增加了PE头文件、数据目录等一些结构,
    • 使得能够满足程序执行时的需求.

第三部分 装载与动态链接

第6章 可执行文件的装载与进程

  • 6.1 进程虚拟地址空间

    • 虚拟地址空间的大小由CPU的位数决定
    • 硬件决定了地址空间的最大理论上限,
    • 即硬件的寻址空间大小,
    • 比如32位的硬件平台决定了虚拟地址空间的地址为2^32 - 1,
    • 即0x00000000~0xFFFFFFFF,
    • 也就是我们常说的4GB虚拟空间大小.
    • 64位的硬件平台具有64位寻址能力,
    • 它的虚拟地址空间达到了264字节,
    • 即0x0000000000000000~0xFFFFFFFFFFFFFFFF,
    • 总共17 179 869 184 GB.
  • 6.2 装载的方式

    • 6.2.1 覆盖装入
    • 6.2.2 页映射
      • FIFO(先进先出算法)
      • LUR(最少使用算法)
      • 目前硬件规定的页的大小有4096字节、8192字节、2MB、4MB等
      • 最常见的IntelA32处理器一般都使用4096字节的页,
      • 那么512MB的物理内存就有 51210241024 / 4096 = 131 072个页
  • 6.3 从操作系统角度看可执行文件的装载

    • 6.3.1 进程的建立
        1. 创建一个独立的虚拟地址空间.
        1. 读取可执行头文件, 并且建立虚拟空间与可执行文件的映射关系.
        1. 将CPU的指令寄存器设置成可执行文件的入口地址, 启动运行.
      • 虚拟内存区域VMA(Virtual Memory Area)
    • 6.3.2 页错误
  • 6.4 进程虚存空间分布

    • 6.4.1 ELF文件链接视图和执行视图
    • 6.4.2 堆和栈
      • 小结进程虚拟地址空间的概念:
      • 操作系统通过给进程空间划分出一个个VMA来管理进程的虚拟空间;
      • 基本原则是将相同权限属性的、有相同映像文件的映射成一个VMA;
      • 一个进程基本上可以分为如下几种VMA区域:
      • 代码VMA, 权限只读、可执行; 有映像文件.
      • 数据VMA, 权限可读写、可执行; 有映像文件.
      • 堆VMA, 权限可读写、可执行; 无映像文件, 匿名, 可向上扩展.
      • 栈VMA, 权限可读写、不可执行; 无映像文件, 匿名, 可向下扩展.
    • 6.4.3 堆的最大申请数量
    • 6.4.4 段地址对齐
    • 6.4.5 进程栈初始化
  • 6.5 Linux内核装载ELF过程简介

  • 6.6 Windows PE的装载

  • 6.7 本章小结

    • 程序运行时如何使用内存空间的问题,
    • 即进程虚拟地址空间问题.
    • 覆盖装入 => 页映射.
    • 在这一章中, 我们假设程序都是静态链接的,
    • 那么它们都只有一个单独的可执行文件模块.

第7章 动态链接

  • 7.1 为什么要动态链接

    • 静态链接
      • 使得不同的程序开发者和部门能够相对独立地开发和测试自己的程序模块,
      • 从某种意义上来讲大大促进了程序开发的效率, 
      • 原先限制程序的规模也随之扩大.
      • 但浪费内存和磁盘空间,模块更新困难.
      • 整个进程只有一个文件可执行文件本身要被映射.
    • 动态链接
      • 简单地讲, 就是不对那些组成程序的目标文件进行链接,
      • 等到程序要运行时才进行链接.
      • 把链接这个过程推迟到运行时再进行.
      • 除了映射可执行文件本身之外, 还有它所依赖的共享目标文件.
  • 7.2 简单的动态链接例子

  • 7.3 地址无关代码

    • 7.3.1 固定装载地址的困扰
    • 7.3.2 装载时重定位
    • 7.3.3 地址无关代码
      • -fPIC 参数产生地址无关代码
      • 模块内部
        • 指令跳转、调用: 相对跳转和调用
        • 数据访问: 相对地址访问
      • 模块外部
        • 指令跳转、调用: 间接跳转和调用(GOT)
        • 数据访问: 间接访问(GOT)
    • 7.3.4 共享模块的全局变量问题
    • 7.3.5 数据段地址无关性
  • 7.4 延迟绑定(PLT Procedure Linkage Table)

  • 7.5 动态链接相关结构

    • 7.5.1 ".interp"段(interpreter解释器)
    • 7.5.2 ".dynamic"段
    • 7.5.3 动态符号表
    • 7.5.4 动态链接重定位表
    • 7.5.5 动态链接时进程堆栈初始化信息
  • 7.6 动态链接的步骤和实现

    • 动态链接的步骤
        1. 启动动态链接器本身
        1. 装载所有需要的共享对象
        1. 重定位和初始化
    • 7.6.1 动态链接器自举(Bootstrap)
    • 7.6.2 装载共享对象
    • 7.6.3 重定位和初始化
    • 7.6.4 Linux动态链接器实现
        1. 动态链接器本身是动态链接的还是静态链接的
        1. 动态链接器本身必须是PIC的吗?
        1. 动态链接器可以被当做可执行文件运行, 
      • 那么装在地址应该是多少? 0x00000000
  • 7.7 显式 运行时 链接(Explicit Run-time Linking)

    • 7.7.1 dlopen() (用来打开一个动态库)
    • 7.7.2 dlsym() 
    • 7.7.3 dlerror()
    • 7.7.4 dlclose()
    • 7.7.5 运行时
  • 7.8 本章小结

    • 使用动态链接技术的原因?
    • 更加有效地利用内存和磁盘资源装载的演示程序
    • 更加方便地维护升级程序
    • 让程序的重用变得更加可行和有效
    • 动态链接中装载地址不确定时如何解决地址引用问题?
    • 装载时重定位
    • 缺点是无法共享代码段, 
    • 但是它的运行速度较快
    • 地址无关代码
    • 缺点是运行速度稍慢, 
    • 但它可以实现代码段在各个进程之间的共享

第8章 LINUX共享库的组织

  • 8.1 共享库版本(Shared Library)

    • 8.1.1 共享库兼容性
    • 8.1.2 共享库版本命名
      • libname.so.x.y.z
    • 8.1.3 SO-NAME
  • 8.2 符号版本

    • 8.2.1 历史回顾
    • 8.2.2 Solaris中的符号版本机制
    • 8.2.3 Linux中的符号版本
    • 8.3 共享库系统路径
      • FHS(File Hierarchy Standard)
        • 这个标准规定了一个系统中的系统文件,
        • 应该如何存放, 包括各个目录的结构、组织和作用.
        • FHS规定, 一个系统中主要有两个存放共享库的位置,
        • /lib, 存放系统最关键和基础的共享库
        • /usr/lib, 一些非系统运行时所需要的关键性的共享库
        • /usr/local/lib, 一些跟操作系统本身并不十分相关的库,三方程序的库
      • /lib和/usr/lib是一些很常用的、成熟的, 一般是系统本身所需要的库
      • /usr/local/lib是非系统所需的第三方程序的共享库
  • 8.4 共享库查找过程

    • ldconfig程序
  • 8.5 环境变量

  • 8.6 共享库的创建和安装

    • 8.6.1 共享库的创建
    • 8.6.2 清除符号信息
    • 8.6.3 共享库的安装
    • 8.6.4 共享库构造和析构函数
    • 8.6.5 共享库脚本
  • 8.7 本章小结

    • 由于系统中存在大量的共享库, 并且每个共享库都会随着更新和升级
    • 形成不同的相互兼容或不兼容的版本.
    • 如何管理和维护这些共享库, 让它们的不同版本之间不会相互冲突,
    • 是使用共享库的一个重要问题.

第9章 Windows下的动态链接

  • 9.1 DLL(Dynamic-Link Library动态链接库)简介

    • 9.1.1 进程地址空间和内存管理
    • 9.1.2 基地址和RVA(Relative Virtual Address相对地址)
    • 9.1.3 DLL共享数据段
    • 9.1.4 DLL的简单例子
    • 9.1.5 创建DLL
    • 9.1.6 使用DLL
    • 9.1.7 使用模块定义文件
    • 9.1.8 DLL显式运行时链接
  • 9.2 符号导出导入表

    • 9.2.1 导出表
    • 9.2.2 EXP文件
    • 9.2.3 导出重定向
    • 9.2.4 导入表
      • 如果我们在某个程序中使用到了来自DLL的函数或者变量,
      • 那么我们就把这种行为叫做符号导入(Symbol Importing)。
      • FirstThunk指向一个导入地址数组(Import Address Table),
      • IAT是导入表中最重要的结构。
      • 对于一个只读的段,动态连接器是怎么改写它呢?
      • 对于Windows来说,由于它的动态连接器其实是Windows内核的一部分,
      • 所以他可以随心所欲地修改PE装载以后的任意一部分内容,
      • 包括内容和它的页面属性。装载时,将导入表所在的页面改成刻度写的,
      • 一旦导入表的IAT被改写完毕,再将这些页面设回至只读属性。
      • ELF运行程序随意修改.got,而PE则不允许,PE的做法更安全。
    • 9.2.5 导入函数的调用
  • 9.3 DLL优化

    • 二分查找法
    • 9.3.1 重定基地址(Rebasing)
      • 空间换时间
    • 9.3.2 序号(Ordinal Number)
    • 9.3.3 导入函数绑定
  • 9.4 C++与动态链接

    • 共享库可以单独更新是它的一大优势,
    • 但是如果这是一个C++编写的共享库,那又是另外一回事了,
    • 它有可能是一场噩梦。这一切噩梦的根源还是由于:
    • C++的标准只规定了语言层面的规则,而对二进制级别却没有任何规定。
    • 《COM(Component Object Model组件对象模型)本质论》
      • 如果StringFind.DLL所使用的CRT版本与用户主程序或者其他DLL所使用的CRT版一样,
      • 程序就会发生内存释放错误。
      • 可以吧COM的一些精神提取去来,用于指导我们使用C++编写动态链接库。
      • 在Windows平台下,要尽量遵循以下几个寄到意见:
          1. 所有的接口函数都应该是抽象的。所有的方法都应该是纯虚的。
          1. 所有的全局函数都应该使用extern“C”来防止名字修饰的不兼容。
          1. 不要使用C++标准库STL。
          1. 不要使用异常。
          1. 不要使用虚析构函数。
          1. 不要在DLL里面申请内存,而且在DLL外释放(或者相反)。
          1. 不要在接口中使用重载方法(Overloaded Mothods)。
        • 因为不同的编译器对于vtable的安排可能不同。
      • 在DLL中使用C++的其他特性
        • 数函数、多继承、异常、重载、模版
  • 9.5 DLL HELL(DLL噩梦)

    • 版本更新时发生不兼容的问题。
    • Manifest机制
  • 9.6 本章小结 ``` 动态链接机制对于Windows操作系统来说极其重要,

整个Windows系统本身即基于动态链接机制,

Windows的API也以DLL的形式提供给程序开发者,

而不像Linux等系统是以系统调用作为操作系统的最终入口。

DLL比Linux下的ELF共享库更加复杂,提供的功能也更为完善。

在这一章中介绍了DLL在进程地址空间中的分布、基地址和RVA、共享数据段、

如何创建和使用DLL、如何使用模块文件控制DLL的产生。

接着我们还详细分析了DLL的符号导入导出机制以及DLL的

重定基地址、序号和导入函数绑定、DLL与C++等问题。

最后我们探讨了DLL HELL问题,并且介绍了

解决DLL HELL问题的方法、manifest及相关问题。 ```

第四部分 库与运行时

  • 提问 ```
  • malloc是如何分配出内存的?

  • 局部变量存放在哪里?

  • 为什么一个编译好的简单的HelloWorld程序也需要占据好几KB的空间?

  • 为什么程序一启动就有堆、I/O或异常系统可用? ```

  • 程序的环境由以下三个部分组成:内存、运行库、系统调用。

  • 内存是承载程序运行的介质,也是程序进行各种运算和表达的场所。

第10章 内存

  • 10.1 程序的内存布局

    • 栈(Stack):栈用于维护函数调用的上下文,离开了栈函数调用就没法实现。
      • 栈通常在用户空间的最高地址处分配,通常有数兆字节的大小。
    • 堆:堆是用来容纳应用程序动态分配的内存区域,
      • 当程序使用malloc或new分配内存时,得到的内存来自堆里。
      • 堆通常存在于栈的下方(低地址方向),在某些时候,
      • 堆也可能没有固定统一的存储区域。
      • 堆一般比栈大很多,可以有几十至是百兆字节的容量。
  • 10.2 栈与调用惯例

    • 10.2.1 什么是栈 ``` 在经典的操作系统里,栈总是向下增长的。

在i386下,栈顶由称为esp的寄存器进行定位。

压栈(push)的操作使栈顶的地址减小,

弹出(pop)的操作使栈顶地址增大。

栈保存了一个函数调用需要的维护信息,

这常常被称为堆栈帧(Stack Frame)或活动记录(Activate Record)

在i386中,一个函数的活动记录用ebp和esp这两个寄存器划定范围。

esp寄存器始终指向栈的顶部,同时也就指向了当前函数的活动记录的顶部。

而相对的,ebp寄存器指向了函数活动记录的一个固定位置,

ebp寄存器又被称为帧指针(Frame Pointer)。

ebp之前首先是这个函数的返回地址,ebp-4。 ```

    • 10.2.2 调用惯例
      • 函数参数的传递顺序和方式
      • 栈的维护方式
      • 名字修饰的策略(Name-mangling)
    • 10.2.3 函数返回值传递
      • C++ 返回值优化 TVO (Return Value Optimization)
  • 10.3 堆与内存管理

    • 10.3.1 什么是堆
    • 10.3.2 Linux进程堆管理
    • 10.3.3 Windows进程堆管理
    • 10.3.4 堆分配算法
      • 空闲链表
      • 位图
      • 对象池
  • 10.4 本章小结 ``` 首先回顾了i386体系结构下程序的基本内存布局, 

并且对程序内存结构中非常重要的两部分栈与堆进行了详细的介绍.

在介绍栈的过程中, 学习了栈在函数调用中发挥的重要作用,

以及与之伴生的调用惯例的各方面的知识.

最后, 还了解了函数传递返回值的各种技术细节.

在介绍堆的过程中, 首先了解了构造堆的主要算法: 空闲链表和位图.

此外, 还介绍了Windows和Linux的系统堆的管理内幕. ```

第11章 运行库

  • 11.1 入口函数和程序初始化

    • 11.1.1 程序从main开始执行吗
    • 11.1.2 入口函数如何实现
    • 11.1.3 运行库与I/O
    • 11.1.4 MSVC CRT的入口函数初始化 ``` MSVC的I/O初始化主要进行了如下几个工作:
  • 建立打开文件表.

  • 如果能够集成自父进程, 那么从父进程获取继承的句柄.

  • 初始化标准输入输出. ```

  • 11.2 C/C++运行库

    • 11.2.1 C语言运行库
    • 11.2.2 C语言标准库
    • 11.2.3 glibc与MSVC CRT
  • 11.3 运行库与多线程

    • 11.3.1 CRT的多线程困扰 ``` 为了解决C标准库在多线程环境下的窘迫处境,

许多编译器附带了多线程版本的运行库.

在MSVC中, 可以用/MT或/MTd等参数指定使用多线程运行库. ``` - - 11.3.2 CRT改进

    • 11.3.3 线程局部存储实现TLS(Thresd Local Storage)
  • 11.4 C++全局构造与析构

    • 11.4.1 glibc全局构造与析构
    • 11.4.2 MSVC CRT的全局构造和析构
  • 11.5 fread实现

    • 11.5.1 缓冲
    • 11.5.2 fread_s
    • 11.5.4 _read
    • 11.5.5 文本换行
    • 11.5.6 fread回顾 ``` 当用户调用CRT的fread时, 它到ReadFile的调用诡计如下:

fread ->

fread_s(增加缓冲溢出保护, 加锁) ->

_fread_nolock_s(循环读取、缓冲) ->

_read(换行符转换) ->

ReadFile(Windows文件读取API) - 11.6 本章小结 介绍了程序运行库的各个方面, 

首先详细了解了Glibc和MSVC CRT的程序入口点的实现,

并在此基础上着重分析了MSVC CRT的初始化过程,

尤其是MSVC的IO初始化.

还介绍了C/C++运行库的其他方方面面, 

包括库函数的实现、运行库的构造、运行库与并发的关系,

以及最后C++运行库实现全局构造的方法.

在介绍这些内容的过程中, 我们一改以往以Glibc的代码为主要示例的方法,

着重以MSVC提供的运行库源代码为例子介绍了fread在CRT中的实现.

犹豫Glibc为了支持多平台, 它的IO部分源代码显得十分复杂且难懂,

不便于在本书中讲解, 于是改为介绍MSVC的fread实现. ```

第12章 系统调用与API

  • 12.1 系统调用介绍

    • 12.1.1 什么是系统调用
    • 12.1.2 Linux系统调用
    • 12.1.3 系统调用的弊端 ``` 解决这个问题, "万能法则"又可以发挥作用了:

"解决计算机的问题可以通过增加层来实现",

于是运行库挺身而出, 它作为系统调用与程序之间的

一个抽象层可以保持着这样的特点:

使用便捷、形式统一. ``` - 12.2 系统调用原理

    • 12.2.1 特权级与中断
      • User Mode 用户态
      • Kernel Mode 内核态
    • 12.2.2 基于int的Linux的经典系统调用实现 ```
  • 触发中断

  • 切换堆栈

在Linux中, 用户态和内核态使用的是不同的栈

两者各自负责各自的函数调用, 互不干扰.

  1. 中断处理程序 ```
    • 12.2.3 Linux的新型系统调用机制 ``` ldd /bin/ls 获取一个可执行文件的共享库的依赖情况

虚拟动态共享库 Virtual Dynamic Shared Library

cat /proc/self/maps 查看cat自己的内存布局

_kernel_vsyscall函数 负责进行新型的系统调用. ``` - 12.3 Windows API(Application Programming Interface 程序编程接口)

    • 12.3.1 Windows API概览
      • MSDN是学习Win32 API极佳的工具.
    • 12.3.2 为什么要使用Windows API
      • Windows API层就是这样的一个"银弹".
    • 12.3.3 API与子系统 ``` 子系统实际上有事Windows架设在API和应用程序之间的另一个中间层.

Windows子系统在实际上已经被抛弃了. - 12.4 本章小结 回顾了进程与操作系统打交道的途径: 系统调用和API.

介绍操作系统调用的部分中, 主要介绍了

特权级、中断等系统调用的实现原理, 

然后还详细介绍了Linux的系统调用的内容和实现细节.

介绍API的过程中, 回顾了API的历史与成因、API的组织形式、实现原理.

同时还提到了与API伴生的子系统, 

介绍了子系统的存在意义、组织形式等. ```

第13章 运行库实现

  • 13.1 C语言运行库

    • 13.1.1 开始
    • 13.1.2 堆的实现
    • 13.1.3 IO与文件操作
    • 13.1.4 字符串相关操作
    • 13.1.5 格式化字符串
  • 13.2 如何使用Mini CRT

  • 13.3 C++运行库实现

    • 13.3.1 new与delete
    • 13.3.2 C++全局构造与析构
      • 全局new/deete操作符重载(Global new/delete operator overloading)
    • 13.3.3 atexit实现
      • 先构造的全局对象应该后析构
    • 13.3.4 入口函数修改
    • 13.3.5 stream与string
  • 13.4 如何使用Mini CRT++

  • 13.5 本章小结 ``` 在这一章, 我们首先尝试实现了一个支持C运行的简易CRT: Mini CRT.

接着又为它加上了一些C++语言特性的支持, 并且将它称为Mini CRT++.

在实现C语言运行库的时候, 介绍了入口函数entry、堆分配算法malloc/free、IO

和文件操作fopen/fread/fwrite/fclose、

字符串函数strlen/strcmp/atoi和格式化字符串printf/fprintf.

在实现C++运行库时, 着眼于实现C++的几个特性:

new/delete、全局构造和析构、stream和string类.

因此在实现Mini CRT++的过程中, 我们得以详细了解并亲自动手

实现运行库的各个细节, 得到一个可编译运行的瘦身运行库版本.

当然, Mini CRT++所包含的仅仅是真正的运行库的一个很小子集,

它并不追求完整, 也不在运行性能上做优化,

它仅仅是一个CRT的雏形, 虽说很小, 但能够通过

Mini CRT++窥视真正的CRT和C++运行库的全貌,

抛砖引玉、举一反三正式Mini CRT++的目的. ``` - 附录A

    • A.1 字节序 ``` 通信双方交流的信息单元应该以什么样的顺序进行传送.

目前在各种体系的计算机中通常采用的字节存储机制主要有两种:

大端(Gig-endian)

小端(Little-endian)

MSB(Most Significant Bit/Byte)最重要的位/字节

LSB(Least Significant Bit/Byte)最不重要的位/字节

一个十六进制的整数: 0x12345678, 

0x12就是MSB, 0x78就是LSB.

对于0x78这个字节而言, 它的二进制是 01111000,

那么最左边的那个0就是MSB, 最右边的那个0就是LSB.

Big-endian和Little-endian的区别就是

Big-endian规定MSB在存储时放在低地址, 在传输时MSB放在流的开始;

LSB存储时放在高地址, 在传输时放在流的末尾.

Little_Endian主要用于我们现在的PC的CPU中,

即Intel的x86系列兼容机;

Big-Endian则主要应用在目前的Mac机器中, 一般指PowerPC系列处理器.

另外值得一提的是, 目前的TCP/IP网络及Java虚拟机的字节序都是Big-Endian的.

这意味着如果通过网络传输0x12345678这个整型变量,

首先被发送的应该是0x12, 最后是0x78.

所以我们的程序在处理网络流的时候, 必须注意字节序的问题.

Little-endian则相反. ```

阅读导图

IMG_1676.PNG

发文不易, 喜欢点赞的人更有好运气👍 :), 定期更新+关注不迷路~

ps:欢迎加入笔者18年建立的研究iOS审核及前沿技术的三千人扣群:662339934,坑位有限,备注“掘金网友”可被群管通过~