new和malloc有什么区别

  1. 属性

new为C++关键字,需要编译器支持,malloc为C/C++关键字,可支持C代码

  1. 参数

当使用new operator时,不需要传size参数,编译器会根据参数类型自动计算size的大小并传入operator new。
使用malloc必须手动传入size参数

  1. 返回值

new可直接返回对应参数类型的指针。
malloc返回的是void* 指针

  1. 功能差别

new不仅仅分配内存空间,针对自定义类型,还会构造对象。
malloc仅仅分配内存空间。

  1. 内存分配位置

new分配的内存空间位于自由存储区 。使用new分配的区域均为自由存储区,是C++上基于new的一个抽象概念。malloc分配的空间则位于堆,是操作系统的概念。
一般情况下,自由存储区均通过堆实现。但若是重载了operator new,则该区域可能为混合内存区

  1. 重载

可通过重载operator new来实现定制化的内存分配工作;

  1. placement new

可通过placement new来实现堆已有空闲区块的使用。

  1. //new 底层实现
  2. 1. void *memory=::operator new(sizeof(T));
  3. 2. memory上调用T();
  4. 3. T *ptr=static_cast<T*>(memory);

vector内存管理

由两级配置器完成vector的内存分配 对于已分配大内存的vector,仅可使用swap对其进行内存释放 vector.swap(old_vector); //临时对象在该行后析构

  • 第一级配置器__malloc_alloc_template

对malloc,realloc,free等操作的封装

  • 第二级配置器__default_alloc_template

由16个自由链表头地址组成的数组组成,该链表所代表的空闲区块大小分别为
8,16,24,….,128.为小内存需求服务
内存分配方法:
1. 申请内存大于128b,调用第一级配置器;否则转2

  1. 对所申请内存对齐至8*n的大小,并以索引n来寻找合适区块
  2. 若成功找到一个空闲区块,则返回;否则转4
  3. 尝试调用_s_chunk_alloc()分配20*n的内存,若申请成功转6,否则转5
  4. 若仅申请到8*n的空间,返回给用户
  5. 将申请到的的空间取出一块返回给用户,其余内存块链到自由链表中

内存释放过程:

函数接收两个参数,大小和位置

  1. 若size>128b,则交由第一级配置器处理。
  2. 否则,将该内存块加入对应的自由链表中

    Windows pagefile.sys (linux swap)

    pagefile.sys

    虚拟内存,属于系统文件,受保护的系统文件

作用:
内存不足时把系统需要的预读文件的数据先放到硬盘上保存,

swap文件

类似windows下的虚拟内存,当物理内存不足 时,拿出部分硬盘空间当swap分区(虚拟成内存)使用,从而解决内存容量不足的问题 主要目的:缓解物理内存用尽而选择直接粗暴OOM-killer(out of memory,内存溢出)进程的尴尬

  • 当进程向OS请求内存发现不足时,OS将内存中暂时不用的数据交换出去,SWAP OUT
  • 当某进程又需要这些数据且OS发现还有空闲物理内存时,把SWAP分区中的数据交换到物理内存中,SWAP IN

当然,swap大小是有上限的,一旦swap用完,OS会触发OOM-Killer机制,把消耗内存最多的进程kill掉以释放内存。

OOM-Killer: 当内存不足时,选择一个占用内存较大的进程并kill掉这个进程,以满足内存申请的需求。

Linux内存回收

回收原因:

  • 内核需要为任何时刻突发而来的内存申请提供足够的内存,以不至于让系统的剩余内存长期处于很少的状态
  • 当内存申请大于空闲内存时,会触发强制回收

两个场景触发:

  • 内存分配时发现没有足够空闲内存时会立刻触发内存回收
  • 开启了一个守护进程(swapd进程)周期性的对系统内存进行检查,在可用内存降低到特定阈值之后主动触发内存回收。

    虚拟内存(间接操作内存或硬盘)

    不让用户直接操作内存,而是让用户把内存想象为一个非常大的字节数组,即为虚拟内存,操作系统负责虚拟地址与**真实内存磁盘**地址之间的映射工作

虚拟内存结构

(WX支付)面经 - 图1(WX支付)面经 - 图2
在缓存原理中,换入/换出的数据以块为最小单位;在内存管理时,页是地址空间的最小单位
虚拟地址空间划分多个固定大小的虚拟页VP,物理空间地址则划分为多个固定大小的物理页PP,和VP大小相同,一般为4kb

虚拟页和物理页的关系

虚拟页和磁盘文件映射,然后缓存到物理页。 根据是否映射,是否缓存,有如下三种状态:

  • 未映射
  • 未缓存

虚拟页映射了磁盘文件,但未缓存到内存上

  • 已缓存

(WX支付)面经 - 图3
虚拟地址工作原理

进程使用的均为虚拟地址。每个进程维护一个单独的页表;

  • 页表为一种数据结构,存储虚拟页的状态
  • 数组的索引号:虚拟页号
  • 数组的值:
    • null——未映射
    • 非null——第一位表示有效位,为1,缓存;为0,未缓存;其余位表示物理页号

(WX支付)面经 - 图4
进程访问虚拟地址步骤:

  1. CPU根据虚拟地址找到虚拟页,并查找虚拟页上对应条目的信息,有效位1转2,否则转3
  2. DRAM命中,根据物理页号,找到物理页中的内容,返回
  3. 未命中,产生缺页异常,调用内核缺页异常处理程序
    1. 判断是否存在空闲的物理页,若有,则将磁盘中的映射文件缓存到该物理页,否则选取一牺牲页(根据页置换算法),将该页内容映射至磁盘
    2. 修改虚拟页表中的表项
    3. 缺页异常处理完毕,中断返回,重新执行,此时缓存命中,执行1
  4. 将找到的内容映射到告诉缓存,CPU从高速缓存中取出该值

虚拟内存使用问题

  1. 磁盘和主存传送页的活动为页面调度。若程序局部性不好,会频繁进行页面调度,造成”缓存颠簸“;
  2. 一般采用多级页表,即一级页表指向二级页表,来压缩页表的大小

地址翻译

DRAM缓存命中后,由虚拟地址找到物理地址的过程

  1. 页表基地址寄存器:指向当前页表的基地址,便于快速定位当前进程的页表
  2. 根据虚拟页号,找到虚拟地址在页表中的值。
  3. 取出物理页号,找到物理地址

    优点

  • 将逻辑内存和物理内存分离
  • 虚拟内存允许文件和内存通过共享页被多个进程共享

    通过将共享对象映射到虚拟地址空间,系统库可为多个进程所共享。 虽然每个进程都认为共享库为其虚拟空间的一部分,而共享库所使用的物理页是所有进程共享的

    • 类似的,虚拟内存允许进程通过共享内存来通信

(WX支付)面经 - 图5

按需调页

并非将整个程序直接加载入内存,二十将进程驻留在次级存储器上,当需要执行进程时,将其换入内存【懒惰交换:仅在需要页时,才将其调入内存】

  • 页表
  • 次级存储器(交换空间,通常为快速磁盘)

    页面置换算法

  • FIFO(先进先出)

  • 最优页置换opt:置换最长时间不会使用的页(预知)
  • LRU(Least Recently used)

    系统颠簸:频繁的页调度操作

    当程序的局部性不好时,可能会发生系统颠簸

进程间通信的方式

管道

  • 半双工,数据单向流动
  • 仅能有血缘关系的进程间通信
  • 管道对于两端通信的进程而言,只是一种文件,不属于文件系统仅存于内存中的”伪文件
  • 没有名称
  • 管道缓冲区大小有限
  • 先入先出

实质: 利用环形队列的数据结构在内核缓冲区中的一个实现,故一个数据仅能被读取一次

  • 读管道
    • 管道中有数据,read返回实际字节数
    • 管道中无数据:
      • 管道写端被全部关闭,管道中数据读完后,read返回0(好像读到文件结尾)
      • 写端没有全部关闭,read阻塞等待
  • 写管道
    • 管道读端被全部关闭,进程异常终止(SIGPIPE)
    • 管道读端没有被全部关闭
      • 管道已满,write阻塞
      • 管道未满,write将数据写入,并返回实际写入的字节数。

        命名管道

        类似与管道,可用于无血缘关系,在文件系统中有对应的文件名。

        • 可用于无血缘关系进程间
        • 与管道不同之处:提供了一个路径名与之关联,以命名管道的文件形式存在于文件系统(以磁盘文件存在)中
        • 不同进程访问该同一路径,实现进程间通信
        • 先入先出
        • FIFO打开时必须确实对方存在,即以读打开FIFO,必须存在一进程写打开FIFO,否则阻塞
          • 管道创建时即打开,在读写时需要确定对方存在。

信号

  • Linux系统中进程间相互通信或操作的一种机制,信号可在任何时候发给某一进程,而无需知道该进程状态
    • 若该进程处于未执行状态,则该信号由内核保存,直到该进程恢复执行
    • 若该进程将该信号设置为阻塞,则该信号的传递被延迟直到该进程取消阻塞
  • 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消是才被传递给进程。

    Linux系统中常用信号:

    1. SIGHUP:用户从终端注销,所有已启动进程都将收到该进程。系统缺省状态下对该信号的处理是终止进程。
    2. SIGINT:程序终止信号。程序运行过程中,按Ctrl+C键将产生该信号。
    3. SIGQUIT:程序退出信号。程序运行过程中,按Ctrl+\键将产生该信号。
    4. SIGBUS和SIGSEGV:进程访问非法地址。
    5. SIGFPE:运算中出现致命错误,如除零操作、数据溢出等。
    6. SIGKILL:用户终止进程执行信号。shell下执行kill -9发送该信号。
    7. SIGTERM:结束进程信号。shell下执行kill 进程pid发送该信号。
    8. SIGALRM:定时器信号。
    9. SIGCLD:子进程退出信号。如果其父进程没有忽略该信号也没有处理该信号,则子进程退出后将形成僵尸进程。
      可以使用 kill -l 查看当前系统可用信号有哪些

处理步骤

  1. 信号被某个进程产生,并设置此信号传递的对象(一般为对应进程的pid),然后传递给操作系统;
  2. 操作系统根据接收进程的设置(是否阻塞)而选择性的发送给接收者,如果接收者阻塞该信号(且该信号是可以阻塞的),操作系统将暂时保留该信号,而不传递,直到该进程解除了对此信号的阻塞(如果对应进程已经退出,则丢弃此信号),如果对应进程没有阻塞,操作系统将传递此信号。
  3. 目的进程接收到此信号后,将根据当前进程对此信号设置的预处理方式,暂时终止当前代码的执行,保护上下文(主要包括临时寄存器数据,当前程序位置以及当前CPU的状态)、转而执行中断服务程序,执行完成后在回复到中断的位置。当然,对于抢占式内核,在中断返回时还将引发新的调度。

(WX支付)面经 - 图6

消息队列

  • 消息队列是存放内核的消息链表,每个消息队列由消息标识符表示
  • 与管道不同的是,消息队列存放再内核中,只有内核重启或显示地删除一个消息队列时,其才被删除
    • 某一进程在往消息队列写数据时,不需要另一个进程在该队列上等待消息的到达。
  • 另外与管道不同的是,消息队列在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达

    消息队列特点总结:

    1. 消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识.
    2. 消息队列允许一个或多个进程向它写入与读取消息
    3. 管道和消息队列的通信数据都是先进先出的原则。
    4. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势。
    5. 消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。
    6. 目前主要有两种类型的消息队列:POSIX消息队列以及System V消息队列,System V消息队列目前被大量使用。System V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。
  1. 共享内存 多个进程访问同一块内存空间,最快!往往和信号量结合使用,进程同步和互斥
  2. 信号量 用于进程间或线程间的同步手段
  3. 套接字 程间通信机制,可用于不同机器之间的进程间通信。

    套接字有几种

  • SOCK_STREAM(流套接字)
    • 不会丢失
    • 按序传输
    • 发送接收端不同步
  • SOCK_DGRAM(数据包套接字)

    • 强调快速传输而非传输顺序
    • 传输的数据包可能丢失或损毁
    • 限制每次传输数据的大小
    • 发送接收端同步

      TCP套接字和UDP套接字使用有什么不同

  • TCP网络程序

    • 服务端
      • socket()创建套接字
      • setsockopt()设置端口复用,多个客户端可同时连接同一个端口
      • bind()绑定端口号
      • listen()监听端口队列是否有连接请求
      • accept()接受请求
      • close()关闭套接字
    • 客户端
      • socket()创建套接字
      • bind()绑定端口号
      • connect()请求连接服务器
        • 发送数据write
        • 请求数据read
      • close()关闭套接字

(WX支付)面经 - 图7

  • UDP网络程序
    • 服务端
      • 少了listen()和connect()
    • 客户端
      • 少了connect()

(WX支付)面经 - 图8

TCP三次握手

(WX支付)面经 - 图9

  1. 客户端向服务器发送一段TCP报文:
    1. 置SYN标志位,表示”请求建立连接”
    2. 序号为x
    3. 客户端进入SYN-SENT状态
  2. 处于监听状态的服务器接收到客户端发来的报文,结束LISTEN阶段,并返回一段TCP报文:
    1. 置SYN与ACK,表示”确认客户端的报文seq有效,服务器可正常接收客户端发送的数据,同意建立连接”
    2. 序号设置y;
    3. 服务器进入SYN-RECV阶段
  3. 客户端接收到服务器发来的报文,明确了客户端到服务器之间的连接正常,结束SYN-SENT阶段,进入ESTABLISHED阶段
    1. 置ACK,表示”确认服务端报文seq有效,客户端可正常接收服务端数据”
    2. 序号设置为x+1,表示”正确收到服务端ack”
    3. ack置为y+1,表示”收到服务端seq”

为什么需要三次握手?

防止失效的连接请求报文段被服务器接受,从而产生错误。

两次握手时,服务端在收到客户端的请求连接报文时,就进入ESTABLISHED阶段(无需经历SYN-RECV),这回导致失效的请求连接报文激活服务器,导致服务器一直等待的问题。 即当客户端发送的报文被服务器接收后,服务器回复一条TCP报文,若此时网络拥塞,客户端迟迟未收到服务器的应答,则客户端重发连接报文,服务端接收后建立连接、发送数据并断开连接。 断开连接后,服务器收到【客户端第一次发来的请求连接报文】,进入ESTABLISHED状态,等待接收数据(若是等待发送数据或发送数据(因为对端已经关闭,有写无读),会收到内核触发的reset导致连接关闭)。而此时客户端已处于closed状态,故服务器会一直等待下去,浪费服务器资源。

边沿触发(核心是变化!)与水平触发(有内容就频繁触发)的区别

ET模式下,仅在监视的描述符状态发生改变,才会传送事件

ET模式的建议方法:

  • 非阻塞的文件描述符
  • 仅在read和write返回EAGAIN时等待

    边沿触发下,假设缓冲区中有数据,读了一半不读了,这时候又来了数据,句柄处于触发态还是非触发态?

    以得出ET模式下被唤醒(返回就绪)的条件为:

    空->不空 内容变多 满->不满 内容变少

  • 对于读取操作:

(1) 当buffer由不可读状态变为可读的时候,即由**空变为不空**的时候。
(2) 当有新数据到达时,即buffer中的待读**内容变多**的时候。
另外补充一点:
(3) 当buffer中有**数据可读(即buffer不空)且用户对相应fd进行epoll_mod IN**事件时(具体见下节内容)。
对于情况(1)(2)分别对应图1(a),图1(b)。

  • 对于写操作:

(1) 当buffer由不可写变为可写的时候,即由**满状态变为不满状态**的时候。
(2) 当有旧数据被发送走时,即buffer中待写的**内容变少**得时候。
另外补充一点:
(3) 当buffer中有**可写空间(即buffer不满)且用户对相应fd进行epoll_mod OUT事件时**

LT

LT模式下进程被唤醒(描述符就绪)的条件就简单多了,它包含ET模式的所有条件,也就是上述列出的六中读写被唤醒的条件都是用于LT模式。此外,还有更普通的情况LT可以被唤醒,而ET则不理会,这也是我们需要注意的情况。
l 对于读操作
当buffer中有数据,且数据被读出一部分后buffer还不空的时候,即buffer中的内容减少的时候,LT模式返回读就绪。如下图所示。
(WX支付)面经 - 图10
l 对于写操作
当buffer不满,又写了一部分数据后扔然不满的的时候,即由于写操作的速度大于发送速度造成buffer中的内容增多的时候,LT模式会返回就绪。如下图所示。
(WX支付)面经 - 图11
注:poll和select都是LT模式

TCP如何实现流量控制和拥塞控制

流量控制

滑动窗口来实现流量控制。

  • 第一次发送数据的窗口大小由【链路带宽】决定
  • 数据重传:由于捎带确认,即使确认应答包丢失,也不用重传确认应答报
  • 三次重复ACK:通知发送端立即重发数据包
  • 发送窗口
    • 最左端的数据收到确认后,向右滑动
  • 接收窗口

    • 最左端的数据被取出后,右滑

      拥塞控制(慢开始(幂增长),拥塞避免(线性增长),快恢复(三个重复ACK,cwnd和ssthresh设置为当前cwnd一半))

  • cwnd: 拥塞窗口

  • ssthresh: 慢开始门限

(WX支付)面经 - 图12

快恢复

触发条件:发送端接收到3个重复ACK,即丢失个别报文段。 结果:不启动慢开始,将cwnd和慢开始门限设置为当前窗口的一半,并进行拥塞避免算法;

  • 为什么有的快恢复会把cwnd设置为new_sshresh+3?

答:既然发送端接收到3个重复ACK,说明有3个ACK离开了网络,整个网络中减少了3个报文段,所以此处+3;

什么是快重传?

重传有2中方式:

  • 超时重传计时器
  • 三次重复ACK

    接收端连续发送三个重复ACK,以告知发送端尽快进行重传,而不是等待超时重传计时器超时再重传!

对接收端有什么要求?

  1. 避免等待捎带确认,而是立即发送确认
  2. 即使收到失序的报文段,也要立即发出对已收到报文段的重复确认

    SQL注入

    HTTP协议,GET和POST区别

    HTTP定义了与服务器交互的不同方法,最基本的四种,为GET,POST,PUT,DELETE,对应查改增删,
  • GET:获取/查询资源
  • POST:更新资源信息
  • PUT:让服务器用请求的主体部分创建一个由所请求URL命名的新文档,是幂等的

    PUT和POST的主要区别在于PUT是幂等的,POST重复请求具有多次创建相同资源的副作用

  • DELETE: 请求服务器删除URL所对应的资源【客户端无法保证删除操作一定会执行,因为HTTP规范允许服务器在不通知客户端的情况下撤销请求】

    GET

    通常用于请求服务器发送某个资源,安全和幂等的

  • 安全:指该操作仅用于获取信息而非修改信息

  • 幂等:对同一个URL请求返回相同的结果

特征:

  • 被缓存
  • 保留在历史记录
  • 收藏为书签
  • 不应包含敏感数据
  • 请求有长度限制
  • 应当只用于取回收据

    POST

    向服务器提交数据,比如完成表单数据的提交,将数据提交给服务器处理

特征:

  • 不会被缓存
  • 不会被记录
  • 不会被书签
  • 请求无长度限制

    GET和POST的区别

    GET产生一个TCP数据包;POST产生两个TCP数据包(header和request分开发送)。

答:GET请求的数据会附在URL之后,以?分隔URL和传输数据,参数之间以&分隔。例如login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD。
而POST

GET POST
后退按钮/刷新 无害 数据会被重新提交(浏览器应该告知用户数据会被重新提交)。
书签 不可
缓存 不能
编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。
(参数保存)浏览器历史 保留 不保留
对数据长度的限制 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)。 无限制。
对数据类型的限制 只允许 ASCII 字符。 没有限制。也允许二进制数据。
安全性 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。
在发送密码或其他敏感信息时绝不要使用 GET !
POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。
可见性 数据在 URL 中对所有人都是可见的。 数据不会显示在 URL 中。


  1. #include <atomic>
  2. #include <cstdio>
  3. #include <thread>
  4. #include <vector>
  5. using namespace std;
  6. atomic<int> cnt_;
  7. atomic<int> who; // who 用于判断轮到哪个线程输出
  8. void threadFun(int i, int end_, int N){
  9. while(cnt_ <= end_){
  10. if(who.load() == i){
  11. printf("Thread%d-%d\n",i, cnt_.load() % i);
  12. cnt_++;
  13. who++; // next thread to printf
  14. if(who == N) // reset , let thread0 to printf
  15. who = 0;
  16. }
  17. }
  18. }
  19. // 下面的for生成的thread要加入vector中,然后在main返回前join
  20. // 不能定义的时候给初始值
  21. // 在main里赋值为0!!!!!!!!!!!!!!!!!!!!!!!!!!
  22. // !!!!!!!!!!!!!!!
  23. int main(){
  24. cnt_ = 0;
  25. who = 0;
  26. int N_ =4, M_ = 7;
  27. vector<std::thread> mythreads;
  28. for(int i = 0; i < N_; i++){
  29. mythreads.push_back(std::thread(threadFun, i, M_, N_));
  30. }
  31. for(std::thread& t : mythreads)
  32. t.join();
  33. return 0;
  34. }

需要改进的地方:

  • 找项目里面更深入的点进行挖掘!