This commit is contained in:
yinkanglong_lab
2021-04-08 19:20:41 +08:00
parent 9e0739a93c
commit 4c48067395
9 changed files with 126 additions and 65 deletions

View File

@@ -19,7 +19,7 @@ foo_t *f = NULL;
```
### C++中的NULL
* C++中void* 不能进行前置类型转换成其他类型的NULL,所以int* 类型的空指针,不能使用void* 代替,则原来的宏定义无法使用。
* C++中void* 不能进行强制类型转换成其他类型的NULL,所以int* 类型的空指针,不能使用void* 代替,则原来的宏定义无法使用。
* C++中使用0,来表示空指针。
```
#ifdef __cplusplus ---简称:cpp c++ 文件

View File

@@ -76,8 +76,7 @@
### ~~补充项目——企业级开发JavaEE项目考试系统~~
> 因为是java项目,等日后学完java再写
### ~~补充项目———项目组织管理图书馆里系统开发~~
> 因为是java项目。
### ~~补充项目——windows通信编程。多人聊天室~~
* 项目概述:单人项目开发。
* 主要工作:MFC开发界面、windows网络通信。

View File

@@ -2,13 +2,16 @@
### **腾讯面试前复习**
- 基础知识复习
- [ ] 数据库
- [ ] 计算机网络
- [ ] 操作系统(最后一遍搞清楚同步通信、IO过程相关的问题提)
- [x] 数据库
- [x] 计算机网络
- [x] 操作系统(最后一遍搞清楚同步通信、IO过程相关的问题提)
- [x] C++
- [x] 项目经历
- [ ] 把所有项目上传到gitee和github并开源
- 腾讯要求复习
- [x] nosql/redis非关系型数据库
- [ ] Hadoop/spark 分布式数据处理(没时间了,等下一轮吧)
- 腾讯面经复习
- [ ] 针对腾讯的面试笔试问题进行复习
- [x] 针对腾讯的面试笔试问题进行复习
## 收获

View File

@@ -11,6 +11,7 @@
### 线程
* 线程是**独立调度的基本单位**。一个进程中可以有多个线程,它们共享进程资源。
* 线程栈是在进程的堆中分配栈空间,每个线程拥有独立的栈空间,为了避免线程之间的栈空间踩踏,线程栈之间还会有以小块guardsize用来隔离保护各自的栈空间,一旦另一个线程踏入到这个隔离区,就会引发段错误。
* QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。
![](image/2021-03-29-22-53-11.png)

View File

@@ -1,5 +1,7 @@
# 4.2 分页存储管理方式
> 参考文献
> * [https://www.cnblogs.com/fkissx/p/4712959.html](https://www.cnblogs.com/fkissx/p/4712959.html)
## 1 页面与页表
### 页面和物理块
@@ -79,14 +81,9 @@
1. 总是淘汰最先调入主存的那一页,或者说主存驻留时间最长的那一页(常驻的除外)
2. 选择换出的页面是最先进入的页面。该算法会将那些经常被访问的页面换出,导致缺页率升高。
3. 模拟的是程序执行的顺序性,有一定合理性
### 4.3 第二次机会算法
FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改:
当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。
![](image/2021-04-06-00-21-08.png)
### 4.4 最近最少用LRU,Least Recently Used
### 4.3 最近最少用LRU,Least Recently Used
1. 为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。
2. 因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。
@@ -97,16 +94,7 @@ FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问
![](image/2021-04-06-00-17-39.png)
### 4.5 最不常用LFU
1. 淘汰最近一段时间内访问次数较少的页面,对OPT的模拟性比LRU更好
2. 基于时间间隔中断,并给每一页设置一个计数器
3. 时间间隔中断发生后,所有计数器清0
4. 每访问页1次就给计数器加1
5. 选择计数值最小的页面淘汰
### 4.6 最近未使用NRU, Not Recently Used
### 4.4 时钟CLOCK==最近未使用NRU, Not Recently Used
每个页面都有两个状态位:R 与 M,当页面被访问时设置页面的 R=1,当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类:
@@ -119,7 +107,12 @@ FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问
NRU 优先换出已经被修改的脏页面(R=0,M=1),而不是被频繁使用的干净页面(R=1,M=0)。
### 4.7 时钟CLOCK
> 算法执行如下操作步骤:
从指针的当前位置开始,扫描帧缓冲区。在这次扫描过程中,对使用位不做任何修改。选择遇到的第一个帧(u=0, m=0)用于替换。
如果第1)步失败,则重新扫描,查找(u=0, m=1)的帧。选择遇到的第一个这样的帧用于替换。在这个扫描过程中,对每个跳过的帧,把它的使用位设置成0。
如果第2)步失败,指针将回到它的最初位置,并且集合中所有帧的使用位均为0。重复第1步,并且如果有必要,重复第2步。这样将可以找到供替换的帧
> 第二个解析
1. 第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面连接起来,再使用一个指针指向最老的页面。
2. 采用循环队列机制构造页面队列,形成了一个类似于钟表面的环形表
@@ -134,6 +127,21 @@ NRU 优先换出已经被修改的脏页面(R=0,M=1),而不是被频繁
![](image/2021-04-06-00-22-23.png)
### 4.3 第二次机会算法
FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改:
当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。
![](image/2021-04-06-00-21-08.png)
### 4.5 最不常用LFU
1. 淘汰最近一段时间内访问次数较少的页面,对OPT的模拟性比LRU更好
2. 基于时间间隔中断,并给每一页设置一个计数器
3. 时间间隔中断发生后,所有计数器清0
4. 每访问页1次就给计数器加1
5. 选择计数值最小的页面淘汰
## 5 反置页表
### 提出:
1. 页面及相关硬件机制在地址转换、存储保护、虚拟地址访问中发挥了关键作用

View File

@@ -86,20 +86,24 @@ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *
> select/poll/epoll 都是 I/O 多路复用的具体实现,select 出现的最早,之后是 poll,再是 epoll。
### select
## 2.1 select
* select 允许应用程序监视一组文件描述符,等待一个或者多个描述符成为就绪状态,从而完成 I/O 操作。
### 函数说明
```c
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
```
* select 允许应用程序监视一组文件描述符,等待一个或者多个描述符成为就绪状态,从而完成 I/O 操作。
* 参数
- fd_set 使用数组实现,数组大小使用 FD_SETSIZE 定义,所以只能监听少于 FD_SETSIZE 数量的描述符。有三种类型的描述符类型:readset、writeset、exceptset,分别对应读、写、异常条件的描述符集合。
- timeout 为超时参数,调用 select 会一直阻塞直到有描述符的事件到达或者等待的时间超过 timeout。
* 返回值
- 成功调用返回结果大于 0,出错返回结果为 -1,超时返回结果为 0。
### 代码实现
```c
fd_set fd_in, fd_out;
struct timeval tv;
@@ -138,26 +142,38 @@ else
// output event on sock2
}
```
### 运行机制
### poll
* select()的机制中提供一种fd_set的数据结构,实际上是一个long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一Socket或文件可读。
* 从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
### select机制的问题
* 每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大
* 同时每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大
* 为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024)
## 2.2 poll
* poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll 的功能与 select 类似,也是等待一组描述符中的一个成为就绪状态。也就是说,poll只解决了上面的问题3,并没有解决问题1,2的性能开销问题。
### 函数说明
```c
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
```
* poll 的功能与 select 类似,也是等待一组描述符中的一个成为就绪状态。
* poll 中的描述符是 pollfd 类型的数组,pollfd 的定义如下:
```c
// poll 中的描述符是 pollfd 类型的数组,pollfd 的定义如下:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
```
* 参数
* struct pollfd *fds fds是一个struct pollfd类型的数组,用于存放需要检测其状态的socket描述符,并且调用poll函数之后fds数组不会被清空;一个pollfd结构体表示一个被监视的文件描述符,通过传递fds指示 poll() 监视多个文件描述符。其中,结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域,结构体的revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域
* nfds_t nfds 记录数组fds中描述符的总数量
* 返回值
* int 函数返回fds集合中就绪的读、写,或出错的描述符数量,返回0表示超时,返回-1表示出错;
### 代码实现
```c
// The structure for two events
struct pollfd fds[2];
@@ -193,34 +209,57 @@ else
### 比较
1. 功能。select 和 poll 的功能基本相同,不过在一些实现细节上有所不同。
- select 会修改描述符,而 poll 不会;
- select 的描述符类型使用数组实现,FD_SETSIZE 大小默认为 1024,因此默认只能监听少于 1024 个描述符。如果要监听更多描述符的话,需要修改 FD_SETSIZE 之后重新编译;而 poll 没有描述符数量的限制;
- poll 提供了更多的事件类型,并且对描述符的重复利用上比 select 高。
- 如果一个线程对某个描述符调用了 select 或者 poll,另一个线程关闭了该描述符,会导致调用结果不确定。
- select 会修改描述符,而 poll 不会;
- select 的描述符类型使用数组实现,FD_SETSIZE 大小默认为 1024,因此默认只能监听少于 1024 个描述符。如果要监听更多描述符的话,需要修改 FD_SETSIZE 之后重新编译;而 poll 没有描述符数量的限制;
- poll 提供了更多的事件类型,并且对描述符的重复利用上比 select 高。
- 如果一个线程对某个描述符调用了 select 或者 poll,另一个线程关闭了该描述符,会导致调用结果不确定。
2. 速度。select 和 poll 速度都比较慢,每次调用都需要将全部描述符从应用进程缓冲区复制到内核缓冲区。
3. 可移植性。几乎所有的系统都支持 select,但是只有比较新的系统支持 poll。
### epoll
## 2.3 epoll
* epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
### 函数说明
```c
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
```
* epoll_ctl() 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上,通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符。
* 从上面的描述可以看出,epoll 只需要将描述符从进程缓冲区向内核缓冲区拷贝一次,并且进程不需要通过轮询来获得事件完成的描述符。
* epoll 仅适用于 Linux OS。
* epoll 比 select 和 poll 更加灵活而且没有描述符数量限制。
1. epoll_create 函数创建一个epoll句柄,参数size表明内核要监听的描述符数量。调用成功时返回一个epoll句柄描述符,失败时返回-1。
2. epoll_ctl 函数注册要监听的事件类型。
* epfd 表示epoll句柄
* op 表示fd操作类型,有如下3种:
* EPOLL_CTL_ADD 注册新的fd到epfd中
* EPOLL_CTL_MOD 修改已注册的fd的监听事件
* EPOLL_CTL_DEL 从epfd中删除一个fd
* fd 是要监听的描述符
* event 表示要监听的事件
3. epoll_wait 函数等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0。
* epfd 是epoll句柄
* events 表示从内核得到的就绪事件集合
* maxevents 告诉内核events的大小
* timeout 表示等待的超时事件
### 原理
* epoll_ctl() 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵**红黑树**上,通过**回调函数**内核会将 I/O 准备好的描述符加入到一个**链表**中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符。
* 从上面的描述可以看出,epoll 只需要将描述符从**进程缓冲区向内核缓冲区拷贝一次**,并且进程不需要通过轮询来获得事件完成的描述符。
* epoll 仅适用于 Linux OS。epoll 比 select 和 poll 更加灵活而且没有描述符数量限制。
* epoll 对多线程编程更有友好,一个线程调用了 epoll_wait() 另一个线程关闭了同一个描述符也不会产生像 select 和 poll 的不确定情况。
### 代码实现
```c
// Create the epoll descriptor. Only one is needed per app, and is used to monitor all sockets.
// The function argument is ignored (it was not before, but now it is), so put your favorite number here
@@ -276,6 +315,8 @@ else
2. ET 模式。和 LT 模式不同的是,通知之后进程必须立即处理事件,下次再调用 epoll_wait() 时不会再得到事件到达的通知。
* 很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
* LT和ET原本应该是用于脉冲信号的,可能用它来解释更加形象。Level和Edge指的就是触发点,Level为只要处于水平,那么就一直触发,而Edge则为上升沿和下降沿的时候触发。比如:0->1 就是Edge,1->1 就是Level。
### 应用场景
@@ -284,4 +325,16 @@ else
2. poll 应用场景。poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。
3. epoll 应用场景。只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。
3. epoll 应用场景。只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。
## 3 对比
| | select | poll | epoll |
|---|---|---|---|
| 操作方式 | 遍历 | 遍历 | 回调 |
| 底层实现 | 数组 | 链表 | 红黑树 |
| IO效率 | 每次调用都进行线性遍历,时间复杂度为O(n) | 每次调用都进行线性遍历,时间复杂度为O(n) | 事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1) |
| 最大连接数 | 1024(x86)或2048(x64) | 无上限 | 无上限 |
| fd拷贝 | 每次调用select,都需要把fd集合从用户态拷贝到内核态 | 每次调用poll,都需要把fd集合从用户态拷贝到内核态 | 调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝 |
* epoll是Linux目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超select和poll。目前流行的高性能web服务器Nginx正式依赖于epoll提供的高效网络套接字轮询服务。但是,在并发连接不高的情况下,多线程+阻塞I/O方式可能性能更好。

View File

@@ -107,12 +107,11 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex);
* 返回值: 成功则返回0, 出错则返回错误编号.
### 互斥锁、信号量、条件变量对比
1. 互斥锁要么被锁住,要么被解开,和二值信号量类似
2. 互斥锁必须是谁上锁就由谁来解锁,而信号量的wait和post操作不必由同一个线程执行。
1. 互斥锁要么被锁住,要么被解开,和二值信号量类似。互斥锁必须是谁上锁就由谁来解锁,而信号量的wait和post操作不必由同一个线程执行。
2. 信号量有计数值,每次信号量post操作都会被记录,而条件变量在发送信号时,如果没有线程在等待该条件变量,那么信号将丢失。
3. sem_post是各种同步技巧中,唯一一个能在信号处理程序中安全调用的函数
4. 互斥锁是为上锁而优化的;条件变量是为等待而优化的;信号量既可用于上锁,也可用于等待,因此会有更多的开销和更高的复杂性
5. 互斥锁,条件变量都只用于同一个进程的各线程间,而信号量(有名信号量)可用于不同进程间的同步。当信号量用于进程间同步时,要求信号量建立在共享内存区。
6. 信号量有计数值,每次信号量post操作都会被记录,而条件变量在发送信号时,如果没有线程在等待该条件变量,那么信号将丢失。
## 3 读写锁

View File

@@ -58,7 +58,7 @@
### 同步非阻塞
> 在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。
* poll机制。轮训IO信号,虽然没有阻塞,但是在循环中一直确认IO是否完成。效率很低。
* 用户进程轮训IO信号,虽然没有阻塞,但是在循环中一直确认IO是否完成。效率很低。
### 异步非阻塞通信

View File

@@ -6,12 +6,10 @@
> 停止等待、回退N步、选择重传。
1. 校验和
2. 序号机制
3. 确认机制和重传机制
4. 超时重传机制。
5. 滑动窗口和流水线技术
6. 累计确认机制
7. 选择重传机制
8. 快速重传机制
3. 确认机制和超时重传。
4. 滑动窗口和流水线技术
5. 累计确认机制
6. 选择重传机制
## 3. TCP的三次握手四次挥手
> 报文段类型SYN、FIN。状态迁移。序号变化。