参考资料:小林coding:https://www.xiaolincoding.com/
虚拟内存
内存分段
程序被分成不同的段,访问地址时用段地址+段内偏移量来表示。类似下面这样:
上面那个表就是段表。分段管理内存会引入外部内存碎片,不会引入内部碎片。分页管理会引入内部碎片,但是不会引入外部碎片。外部碎片存在于段和段之间,比如有3个段,两个碎片的内存之和有 3MB,但是是按照 1MB 和 2MB 分布的,这个时候就无法装入 2.5 MB 的程序。分页管理因为页的大小是固定的,内存碎片只存在于页内部。
解决外部内存碎片的办法就是通过 swap 机制,将一部分内存读到磁盘上,让内存重排布。
内存分页

简单的分页会导致页表占用空间太大。每个程序都有一个自己独立的逻辑地址空间。由此引出了多级页表。简单分页的问题就在于要对所有物理页都建立页表,实际上程序根本不会用到这么多页,采用多级页表时用不到的页就不用建立页表,可以节省空间。

快表(TLB)用来存放常用的页表项。
段页式内存管理

Linux 内核空间
Linux 中虚拟地址空间分成两部分,一部分是用户空间,另一部分是内核空间.所有的内核空间都会映射到相同的物理地址.


用户空间结构如下,分成 bss 段,代码段,数据段,栈,堆等。

malloc 如何分配内存
malloc 分配的是虚拟内存,在虚拟内存地址被真正访问之前,它是不会建立到物理内存的联系的。malloc 是 C 语言中的函数,底层是调用了 brk 和 mmap 两个系统调用. brk 是在堆区分配内存,mmap则是在文件映射区分配.


决定使用 brk 还是 mmap,小内存用 brk,大内存用 mmap。

内存分配过程
内存分配过程

哪些内存可以被回收

回收内存带来的性能影响

例题:在一个物理内存只有 4 G 的电脑上申请 8 G 的内存会发生什么情况
1.多少位的操作系统
32 位的操作系统虚拟地址空间都没有 4G,肯定不可以。64 位的话可以要看下面的情况。
2.是否开启 swap 机制
swap 机制就是将内存中的一部分写入到磁盘中,然后清空这部分内存。等到磁盘中的那个部分要用的时候再写入到内存中。如果开启 swap 的话可以,如果不开的话会因为内存不够而被杀死。

进程管理
线程是进程的一个部分。一个进程中的线程共享上下文,如地址空间。
进程切换

线程切换


进程之间通信
管道
消息队列

共享内存

信号量

信号

死锁的避免方法
死锁问题的出现条件

互斥条件

持有并等待条件

不可剥夺条件

环路等待条件

死锁问题的避免方法

锁

互斥锁

自旋锁

读写锁
普通互斥锁(Mutex)是「独占锁」------ 不管是读还是写,只要一个线程持有锁,其他线程都得等。但实际场景中: 读操作通常是并发安全的(多个线程同时读数据,不会改数据,没必要互斥); 写操作是独占的(写的时候不能读、也不能有其他写)。


乐观锁和悲观锁

线程崩溃之后所有线程都会退出吗
如果是 C 的话会,如果是 Java 的话不会。


Socket 连接
多进程模型
多进程模型是最基础的并发方案之一:主进程监听端口,每来一个客户端连接,就 fork 一个子进程专门处理这个客户端的所有请求。
- 主进程
accept()成功后,会得到一个新的 Socket 描述符(与客户端通信的句柄); fork()后,子进程会继承这个新 Socket 描述符,主进程则关闭这个描述符(避免句柄泄漏),继续等待下一个连接;- 每个子进程只处理一个客户端,直到客户端断开连接,子进程退出。
多线程模型
核心逻辑:主线程监听端口,每来一个客户端连接,就创建一个子线程专门处理该客户端的所有请求,主线程继续等待新连接。
- 主线程
accept()得到客户端 Socket 描述符后,将其传递给子线程; - 子线程独占这个 Socket,负责和客户端的所有交互,直到连接断开;
- 线程共享进程的文件描述符表、内存空间,因此无需复杂的 IPC 通信。

I/O 多路复用
IO 多路复用的核心是:一个线程 / 进程可以同时监听多个文件描述符(Socket),只有当描述符就绪(可读 / 可写 / 异常)时,才进行读写操作。FD:文件描述符。

select/poll

epoll
