计算机知识体系2021_1009
内容
今天总结的内容是一个发散性和系统性都有的。
先要把整个操作系统的体系建立起来,存储体系、进程和线程体系。下去之后看操作系统哲学、程序员自我修养、深入理解计算机系统
从应用层穿到底层。
内存方面:通过new和malloc区别穿到:内存开辟底层的实现、内存的管理、页面的调换。
谷歌的tmalloc源码下载下来分析,不会问老师。
C语言的整个基础
- 结构体的使用–设计结构体–数据结构
- 函数指针
C++
的核心
学习C++
:先练(还是念?没听清),就是先理解面向对象的思想,然后用。C++
中最主要的是灵活使用STL。STL有几个重要的点:(Vector、List比较简单,刨除了)Map、HashMap、HashSet,不要剖析VS2019的源码,因为太难了,剖析VC6.0或版本比较低的。Hash类的STL中,HashMap是最重要的一个点,其底层是红黑树实现的,但红黑树问的不多,撑死问一个树的左旋右旋。主要是让你实现HashMap(无序)代码。
C++
更重要的是:
C++11
新特性很重要,可以看一本书C++11
新特性。最重要的是即智能指针。智能指针问的问题太多太多了。STL、智能指针会用,C++
就到头了。
C++
结束后,更重要的是修炼内功。
操作系统的两个点:进程、内存。I/O、多核可以了解一下,其他的不重要。
内存
页面置换算法:九个或八个,老师实现LRU算法,时钟算法自己实现,并且要讲清楚,才可以下达C++
代码。
任务1(一周完成):面试可能会问到:如果你定义了一个变量,你能否打印出其真实的物理地址?则我们必须对整个LINUX的内存管理原理非常熟悉才可以。
如果能分析出真实的物理地址,则报文原理、写时拷贝原理(fork的原理)就会立马明白。
任务2:父进程建立链表,子进程也要使用链表,子进程和父进程不改变数据值。
首先在父进程建立链表,创建子进程,打印每个地址和值,父进程也如是。当子进程或父进程改变某个节点数据值后,在子进程和父进程再次打印链表的地址。对照链表有何变化。
本质问题就是:原来我们讲过,局部变量和全局变量产生写时拷贝,那么我们在堆区如何产生写时拷贝?
进程
- 父进程构造了个对象5,fork后子进程会不会还要调用构造函数?当对象的局部发生改变后,打印对象地址。这问题会牵扯到后续的继承内容,有多态,存在虚表问题。
- 进程间的通信有哪几种?管道、共享内存、消息队列。如果内存知识不懂,则不会理解。一旦要共享内存,就会有互斥、同步问题。互斥量、互斥锁、信号量、原子操作、条件变量。任务3:原子操作的底层实现是?搞清原子操作,就可以搞清信号量、互斥量。
C/C++
面试
C和C++
的面试,重点不在语法上,而是透过问题C和C++
穿入到操作系统。
new和malloc区别
- new是关键字,malloc是函数,new调用malloc。
- new和malloc都是在堆区申请空间
- new有三个步骤
- 开辟空间
- 构造函数,初始化对象。
- 返回地址
如果考虑CPU的串行、并行,则2、3步可能会顺序不一致。有可能还没构建对象就拿到了地址,会导致错误。我们需要强制其按照2、3顺序来。
- new不会强转返回值类型,malloc需要程序员强转。
- new封装计算sizeof,而malloc需要程序员清楚要开辟的空间。
- 空间不足时,new会抛异常(
bad_alloc
),不会执行返回操作。malloc
会返回空指针。(int *p = new(nothrow) int(20);//我们可以加nothrow禁止抛出异常,则返回空指针
) - 释放时,如果对象是内置类型,delete和free可以混用。自己设计的类型,如果没有析构函数、继承关系,也没有虚函数,则也可以混用;如果你设计的new是一个完全的面向对象的写法,则不可以混用。
- new是个关键字,可以重载;malloc是函数,不可以重载。重载new,在类中重载的new和delete的最大特点是其具有静态特性,没有this指针。为什么类中重载的new没有this指针?this指向当前对象,new需要给当前对象开辟地址,调动new的时候,还不存在对象,那么无法传递this指针。
透过这个问题,我们可以联想到:
C++的核心思想:地址和空间分离。就比如,局部对象的定义时分配的空间是系统管理的,构造函数是无能力开辟空间的,只是在空间中构建出来。所以,有空间不一定有对象,有对象一定有空间。
针对这个思想,有一个例子:类中没有任何指令、成员的sizeof是1,对象占空间大小也是1字节。因为它至少要体现它存在着,起占位符作用。
new的三种调用形式
- 关键字
1 | int *p = new int(10); |
- 函数
1 | int *p = ::operator new(sizeof(int)); //malloc |
类似于调用malloc,但是还是有区别,这个形式的new内存不足时还是会抛出异常。
3. 定位new
定位new就是对已有的空间,在空间中拿定位new来构建你认为的对象。为什么呢?因为ar传递进去之后,传递进的是个无类型指针,无类型指针在地址范围内构建对象就可以了,不管原来是什么货,我现在认为:有地址即可构建对象。对系统内部来说,认为有空间我就能把地址构建到,而回到用户空间下做的话,整型数组里就不能有对象了。但对系统来说,不管类型,我就是可以构建。
1 | int ar[10]; |
malloc
- new和malloc都是调用malloc来分配空间
- malloc的底层实现是调用tmalloc和pmalloc,大块内存、小块内存组织方案不一样。这个问题就牵扯到了内存池。理解内存池后,malloc底层效率低,我们需要写自己的内存池来增加效率,比如谷歌的tmalloc,那么与STL中的内存池有何区别?
- 谷歌的tmalloc如果考虑在多线程环境下,有可能你调的是1号线程给你分配,有可能是2号,线程多了之后,可以加速分配速度。线程有自己的线程池,并不冲突。但是如果要的空间比较大时,要在底层对大空间进行一个统一的开辟,并且会产生竞争,则会出现同步问题。则会出现以下现象:原来是1号线程分配的空间,释放时用的2号线程。原来是2号线程分配的空间,释放时用的1号线程。那么空间如何有一个统一的方法还回去?线程池从某地方拿了一块给另一方,还回的时候是第二个线程还,那么就会把这一块放到线程空间里。所以有可能从1号的还给2号,有可能从2号来的,还给1号。
什么是内存泄漏?
- 内存泄露的本质原因是什么?
- 内存泄漏绝不是简单的malloc后没有free。进程结束后,malloc的内存都会释放。因为我们进程使用的是虚拟地址,我们会在进程执行完毕后,撤销虚拟地址在物理地址的映射关系。
- 内存不足时,我们采取的政策是什么?
进程同步经典问题
- 生产者-消费者:面试中经久不衰的问题,因为用到的场景太多了。与播放有关的都是这种问题。
- 短视频软件就是一个生产者-消费者模型。有环形的缓冲池,有倒水者(服务器)、接水者(用户)。
- 如果只有一个池子:使用过程中,队头和队尾需要一直迭代,我们可能需要计算队列中的个数,需要队尾、队头相减,则需要锁住队头和队尾指针,则不能灌水、取水。
- 三个池子来回倒:先把一个池子注满,注满后把池子拉给播放器。接着给另一个池子注,这样来说,两个池子是独立的。并行能力提升了。代价是要有两个池子的资源。
- 哲学家进餐,避免死锁
- 读者-写者问题:实际上是生产者-消费者的变种问题
- 读者不可改内容,写者可改。读者可以并发,读者和写者互斥,读者和读者并发,写者和写者互斥。代码比较小,但是要你设计框架。
- 实现信号量:给上三个线程,第一个线程打印A,第二个线程打印B,第三个线程打印C。总共打印10个,要求先A后B再C。不给信号量,能不能拿原子操作和条件变量完成?能不能拿条件变量和互斥量完成?能不能把原子操作、条件变量、互斥量三者合起来组成一个新的信号量?看你是不是学通了。这就面试到了很根本的难点了。年薪40-50W都会面到。
进程池、线程池
如何实现进程池、线程池?
学了TCP/IP就能体会到线程池了。
书
- 操作系统哲学
- 程序员的自我修养
- 看Linux就够了,Windows不看
两本看完后,可以弄一个4G文件,内存中取500M空间,玩地址映射。
- 看Linux就够了,Windows不看
- csapp
- 存储器
- 内存管理
- 页面置换算法
- 进程、线程
- effective C++
- 可当作对C++的扩充
- 最有价值的是第六章,应该反复去看,即继承关系。
- 陈硕-LINUX C++网络库
- 智能指针
- 深入理解C++11