C++对象数据布局(一)——数据对齐的陷阱

C++ Class 对象的数据布局和 C Struct 数据布局遵循同样的原则,按顺序排布并考虑内存对齐的要求。
但是 C++ Class 对象相比于 C Struct 有其创新之处。C++ Class 添加了两个新的 access section,支持在类内声明函数,最重要的是,添加了“继承”的特性。
这其中有什么可怕的陷阱吗?直接公布答案有什么意思,你得自己一步一步趟过去才行。
小心,别中招了!

Read More

并发编程(二)—— 多线程

如果我们把进程比作一个工人的话,那线程就是工人的四肢。怎么理解这个比喻呢?

  • 工人拥有四肢,进程拥有线程
  • 工人干活要依靠手脚来做事,进程干活也是依靠线程来做事
  • 工人和工人之间只能通过工头调度或者相同的操作手册(大脑中的知识)来协作,进程和进程之间只能通过操作系统内核调度或者共享内存来协作
  • 工人的四肢用工人自己的大脑来协调,进程的线程用进程自己的虚拟内存空间来协作
  • 一个工人的四肢的协调效率比多个工人之间的协调效率高,一个进程的线程之间的协作效率比多个进程之间的协作效率要高

Read More

文件描述符表 —— 进程的磁盘资源

如果说基于虚拟页表的虚拟内存空间是进程物理内存资源的抽象,那基于文件描述符表的文件系统就是进程磁盘资源的抽象。通过持有文件描述符,进程可以方便地对磁盘上的特定位置(文件)进行读写。
打个不太准确的比方。如果我们把磁盘也看作一个像物理内存一样的字节序列,那磁盘上的文件就像内存中的分页,文件描述符就像 PTE,文件描述符中的 offset 就像 VPN(PPN)。
本文就来解释进程中的文件描述符表的机制。

Read More

Win 10 无法发现局域网电脑的解决办法

最近整了一台 Surface Go,想在主力 PC 间实现远程桌面和文件共享。
本以为都 2019 年了这些事情肯定“只需要点一下”就好了,实际上还是有很多坑。这篇文章就记录了 “Win 10 不能发现/连接局域网电脑” 的解决办法,基本上能解决所有网络上提到的 Win 10 联机问题(前提是在局域网)。

  • 确保在 “控制面板\网络和 Internet\网络和共享中心\高级共享设置” 勾选了 “启用网络发现” 和 “启用文件和打印机共享”,这是实现任何远程互动的前提
  • 如果要使用远程桌面,必须允许远程访问计算机,让 Cortana 找出来“必须允许远程访问计算机
  • 如果要使用文件共享,必须在 “启用或关闭Windows功能” 中启用 SMB,让 Cortana 找出来“启用或关闭Windows功能
  • 让 Cortana 找出“服务”,设置以下服务自动开启:
    • PNRP Machine Name Publication Service
    • TCP/IP NetBIOS Helper
    • Computer Browser(Browser)
    • Function Discovery host Provider(FDPHOST)
    • Function Discovery Resouce Publication(FDResPub)
    • Network Connections(NetMan)
    • Upnp Device Host(UpnpHost)
    • Peer Name Resolution Protocol (PNRPSvc)
    • Peer Networking Grouping (P2PSvc)
    • Peer Networking Identity Manager (P2PIMSvc)
  • 最后为了省心,干脆重启个电脑吧

Read More

基于异常控制流的进程协作

程序是编译出来保存在磁盘上的文件,进程是运行中的程序的实例。操作系统按照程序文件的描述为进程分配了独立的虚拟内存空间,并由 CPU 执行进程的指令。
在理想情况下,CPU 按顺序执行进程的指令。这些有序的指令流叫控制流。虽然控制流在实现分支或者进行调用函数时也会进行跳转,但这种跳转是程序自身有意控制的,属于正常范畴。
不过也有无意发生的跳转:当 CPU 执行完当前指令 I 后,接下来却执行和 I 毫无逻辑关系的 X。而 X 指令,可能是当前进程中的指令,也可能是其它进程中的指令。这种无意的跳转形成的控制流叫异常控制流(Exceptional Controll FLow)。
为什么会出现异常控制流呢?

Read More

虚拟内存系统(三)—— 分配和释放 HEAP 内存

.data 内存段、.text 内存段、共享对象内存段在程序加载和链接时就确认了一个不会变的地址范围,而 stack 内存段范围随着函数栈而自动伸缩,不在开发者的掌握之中。如果开发者想要自由地使用一点内存,就要通过 malloc 向 heap 内存申请一段空间来使用。
但 heap 内存空间也不是无限的,如何高效利用 heap 内存空间也就显得很重要了。
高效利用 heap 内存的第一关键点在于回收不再使用的空间,第二点在于分配时合理利用空闲空间

Read More

虚拟内存系统(一)—— 从物理到虚拟

在编译程序的汇编代码的时候,使用了很多内存位置。可是一台计算器只有一个物理内存,编译器怎么确保编译时使用的内存地址不会和其它程序冲突呢?
实际上,每个程序完全不用担心自己使用的内存地址和其它程序产生冲突,因为它们所使用的内存地址都是专属于自己的虚拟地址,在实际寻址的过程中再由操作系统翻译成真正的物理内存地址。
也就是说,虽然物理内存只有一个,但是被划分为多个彼此互不干扰的虚拟内存空间。
那这是如何实现的呢?虚拟内存空间寻址又是如何转换成物理内存空间寻址的呢?

Read More

通过链接编译复杂的软件(二)—— 静态链接库、动态链接库和打桩

在分离编译和链接技术的帮助下,修改源代码后的重新构建工作不再那么耗时耗力。更重要的是,分离编译和链接这一个过程为我们提供了一套合作协议。
在这套协议下,编译不仅可以是不同时的,甚至也可以是不同地的——我们可以编译修改后的源文件 A 再链接以前编译的模块 B,也可以编译自己电脑上的源文件 A 再链接其他开发者编译给我们的模块 B。这种特性非常适合使用第三库的场景,它使第三方库容易分发和使用(以及一定程度上的保密)。根据第三方库被链接的时机,发展出了静态链接库(编译时链接)和动态链接库(运行时链接)技术。
此外,符号解析这一步骤使得我们能够插手其中做一些干涉(interposition, 打桩),比如把对库函数 A 的引用解析到自己的函数 B ,在 B 里面做一些处理后再调用库函数 A,从而在无法/不需要修改库的情况下方便快速地实现定制功能。
这篇文章将分析静态链接、动态链接库和打桩技术的实现原理。

Read More