目录
1 简述 C++ 中智能指针的特点,简述 new 与 malloc 的区别
3 STL 中 vector 与 list 具体是怎么实现的?常见操作的时间复杂度是多少?
17 编译时链接有几种方式?静态链接和动态链接的区别是什么?
29 C++ 的 vector 和 list 中,如果删除末尾的元素,其指针和迭代器如何变化?若删除的是中间的元素呢?
3 简述 select, poll, epoll 的使用场景以及区别,epoll 中水平触发以及边缘触发有什么不同?
17 简述 Linux 系统态与用户态,什么时候会进入系统态?
23 Linux 中虚拟内存和物理内存有什么区别?有什么优点?
24 BIO、NIO 有什么区别?怎么判断写文件时 Buffer 已经写满?简述 Linux 的 IO模型
28 Linux 下如何查看 CPU 荷载,正在运行的进程,某个端口对应的进程?
32 系统调用的过程是怎样的?操作系统是通过什么机制触发系统调用的?
36 简述 CPU L1, L2, L3 多级缓存的基本作用
1 简述 TCP 三次握手以及四次挥手的流程。为什么需要三次握手以及四次挥手?
4 TCP 与 UDP 在网络协议中的哪一层,他们之间有什么区别?
8 DNS 查询服务器的基本流程是什么?DNS 劫持是什么?
11 RestFul 是什么?RestFul 请求的 URL 有什么特点?
13 RestFul 与 RPC 的区别是什么?RestFul 的优点在哪里?
14 TCP 挥手时出现大量 CLOSE_WAIT 或 TIME_WAIT 怎么解决?
19 TCP 的 keepalive 了解吗?说一说它和 HTTP 的 keepalive 的区别?
20 简述常见的 HTTP 状态码的含义(301,304,401,403)
21 简述 TCP 的 TIME_WAIT 和 CLOSE_WAIT
33 TCP的拥塞控制具体是怎么实现的?UDP有拥塞控制吗?
39 HTTP 是无状态的吗?需要保持状态的场景应该怎么做?
2 C++的new和delete,什么时候用new[ ]申请,可以用delete释放?
3 C++的static关键字的作用(我从elf结构,链接过程来回答)?
5 C++的继承多态,空间配置器,vector和llst的区别,map,多重map?
11 STL、map底层、deque底层、vector里的empty()和size()的区别、的数对象?
21 什么是纯虚函数;为什么要有纯虚函数:虚函数表放在哪里的?
22 说一下C++中的const,const与static的区别?
32 讲一下map的底层实现,avl和rbtree有什么区别?
博主自己对相关的内容做了一些整理,有不完善或者不对的地方请指出,尽量及时对错误内容做出修改
C++
1 简述 C++ 中智能指针的特点,简述 new 与 malloc 的区别
【C++】C++ new和malloc到底哪里不一样 - 李春港 - 博客园 (cnblogs.com)
new/delete:这两个是C++中的关键字,若要使用,需要编译器支持;
malloc/free:这两个是库函数,若要使用则需要引入Stdlib.h 头文件才可以正常使用。
new顺序
1 operator new 2 申请足够空间 3 调用构造函数,初始化成员变量(初始化后映射,拥有物理内存)
delete顺序
1 调用析构函数 2 operator delete 3 释放空间
malloc在申请内存空间外还申请了一个16byte的空间,负责存放申请的空间大小,分配的是虚拟内存,malloc只是申请内存没有初始化,所以是虚拟内存
free释放的空间回到了内存池,并不会消失,free调用时就是根据这个16byte的大小来释放空间
2 C++ 中虚函数与纯虚函数的区别
定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
在一些情况中基类本身生成对象是不合情理的。为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数。在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象,只能创建派生类的对象实例。
3 STL 中 vector 与 list 具体是怎么实现的?常见操作的时间复杂度是多少?
vector
- 使用动态数组的方式实现的,里面有一个指针指向一片连续的内存空间。
- 如果动态内存的数组空间不够,就要动态地重新分配内存,一般是当前大小的两倍,然后把原数组的内容拷贝过去,接着释放原来的空间。
- 插入(push_back)、删除(pop_back)、访问 的时间复杂度为O(1)
- 插入(insert)、删除(earse)的时间复杂度为O(n)
list
- 用双向链表来实现的,是一种物理存储单元上非连续、非顺序的存储结构。
- 以结点为单位存放数据,结点的地址在内存中不一定连续,每次插入或删除一个元素,就配置或释放一个元素空间。
- 插入、删除 的时间复杂度为O(1)
- 访问的时间复杂度为O(n)
vector 和 list 的区别
- vector底层实现是数组;list是双向链表。
- vector支持随机访问,list不支持。
- vector是顺序存储,list不是。
- vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。
- vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。
4 简述 vector 的实现原理
答案同3的vector
- vector的size()表示数组中元素个数有多少,capacity()表示数组有多大容量。
5 C++ 11 有什么新特性
一、关键字和语法
auto关键字:自动推导右边的数据类型
nullptr:指针专用,与整数区别
for_each:泛型算法
右值引用:move右值引用和forward完美转发
模版的新特性:typename... T 表示可变参
二、绑定器和函数对象
function 函数对象
bind 绑定器 bind1st bind2nd +二元函数对象 ——》一元函数对象
lambda表达式
三、智能指针
shared_ptr weak_ptr
四、容器
unordered_set、unordered_map、array
五、C++语言级别支持的多线程
createThread
pthread_create
clone
6 C++ 中智能指针和指针的区别是什么?
- 普通指针不会自动释放内存,需要手动调用
delete
或delete[]
来释放。而智能指针会自动管理所指向的对象的内存,当智能指针超出作用域或被显式释放时,它会自动调用delete
或delete[]
来释放内存。 - 普通指针不提供多线程安全的保证,如果多个线程同时访问同一个指针,可能会导致竞态条件。而智能指针可以通过引用计数或其他机制来保证多线程安全。
- 普通指针可以随意拷贝和赋值,这可能会导致多个指针指向同一个内存地址,造成内存泄漏或悬空指针。而智能指针可以通过禁止拷贝和赋值(unique_ptr)或使用引用计数(shared_ptr)等机制来避免这种问题。
7 简述 C++ 右值引用与转移语义
右值引用解决了临时对象的问题,有以下好处
- 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
- 能够更简洁明确地定义泛型函数
转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。
通过转移语义,临时对象中的资源能够转移其它的对象里。(注意是临时对象中的“资源”而不是临时对象本身!这里所谓的使用资源是指指针的指向问题,通过改变指针的指向可以直接使用临时对象中的资源。所以如果,临时对象中并不涉及动态分配内存的问题时,转移语义并不能起到作用,也不必起作用。)
8 C++ 中多态是怎么实现的
多态就是多种形态,C++的多态分为静态多态与动态多态。
静态多态就是函数重载,因为在编译期决议确定,所以称为静态多态。在编译时就可以确定函数地址。
动态多态就是通过继承重写基类的虚函数实现的多态,因为实在运行时决议确定,所以称为动态多态。运行时在虚函数表中寻找调用函数的地址。
在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。
9 const, static 关键字有什么区别
const
被const修饰表示不可以改变,当const修饰成员变量时,需要在类外使用构造函数初始化列表实现
class base
{
public:
base():i(100){}
private:
const int i=100;//error!!!
};
//通过这样的方式来进行初始化
base::base():i(100){}
static
- 对于普通变量和函数,变量前加static关键字将成为全局变量,作用域限制为当前文件中可见,函数也同样的道理。其他的文件不能修改,访问;在其他的文件中定义相同的变量或者函数也不会冲突
- 对于成员变量和函数,将成为类的全局对象,需要在类外初始化,不能通过构造函数初始化,类的静态成员函数,其他的类共享此函数,不包含this指针,与普通函数类似
10 简述 C++ 中内存对齐的使用场景
在三种数据类型中:struct/class/union 存在内存对齐
有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。
结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
11 指针和引用的区别是什么?
指针和引用在汇编代码层次看是一样的
- 指针是一个实体,而引用仅是个别名;
- 引用使用时无需解引用(*),指针需要解引用;
- 引用只能在定义时被初始化一次,之后不可变;指针可变;
- 引用没有 const,指针有 const,const 的指针不可变;
- 引用不能为空,指针可以为空;
- “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
- 指针和引用的自增(++)运算意义不一样;
12 C++ 的多态是如何实现的?
C++的多态必须满足两个条件:
1 必须通过基类的指针或者引用调用虚函数
2 被调用的函数是虚函数,且必须完成对基类虚函数的重写
13 C++ 中解释类模板和模板类的区别
类模板和模板类主要关注点是后一个单词。
类模板:主要描述的是模板,这个模板是类的模板。可以理解为一个通用的类,这个类中的数据成员,成员函数的形参类型以及成员函数的返回值类型不用具体的指定,这些类型都是虚拟的。在使用类模板进行对象定义的时候,才会根据对象的实际参数类型来替代类模板中的虚拟类型。通俗一点来说,可以看作是做蛋糕的模具。
模板类:主要描述的是类,这个类使用类模板进行声明。将类模板中的虚拟类型参数指定成一个具体的数据类型参数。通俗一点来说可以看作是通过蛋糕模具做出来的蛋糕。
14 简述 C++ 的内联函数
内联函数(inline)在编译时是将该函数的目标代码插入每个调用该函数的地方。类内定义的函数都是内联函数,不管是否有inline 修饰符;函数声明在类内,但定义在类外的看是否有 inline 修饰符,如果有就是内联函数,否则不是。
递归调用的函数、复杂语句的函数(例如for、Switch、while等)、包含静态变量的函数不适合作为内联函数。(如果内联函数中有静态变量,那么每次生成新的函数代码时,也会生成新的静态变量。这样就会导致多个静态变量共存于程序中,并且互相独立,无法保持一致性)
15 C++ 中哪些函数不能被声明为虚函数?
1)不能被继承的函数。2)不能被重写的函数。
- 普通函数不属于成员函数,是不能被继承的。
- 友元函数不属于类的成员函数,不能被继承。
- 构造函数不允许继承。
构造函数是用来初始化对象的。假如子类可以继承基类构造函数,那么子类对象的构造将使用基类的构造函数,而基类构造函数并不知道子类的有什么成员,显然是不符合语义的。从另外一个角度来讲,多态是通过基类指针指向子类对象来实现多态的,在对象构造之前并没有对象产生,因此无法使用多态特性,这是矛盾的。
- 内联成员函数 内联函数是在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。因此显然内联函数和多态的特性相违背。
- 静态成员函数是编译时确定的,无法动态绑定,不支持多态,因此不能被重写,也就不能被声明为虚函数。
16 简述 C++ 编译的过程
C++的编译过程通常包括以下四个主要步骤:
- 预处理:处理源代码中的预编译指令,例如宏定义、头文件包含和条件编译等(#展开)
- 编译:将源代码转化为汇编语言,在这个过程中同时会进行语法检查、类型匹配等操作
- 汇编:将汇编语言转化为机器语言
- 链接,将多个目标文件和库文件连接成可执行文件
17 编译时链接有几种方式?静态链接和动态链接的区别是什么?
静态链接和动态链接
- 静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时
- 静态链接中每个可执行程序中对所有需要的目标文件都要有一份副本,库更新之后需要重新编译,执行速度较快
- 动态链接不会生成副本,更新比较方便,运行时间比静态链接更长
18 C++ 是如何进行内存管理的?
new 和 delete 操作符
malloc与free库函数
智能指针
19 C++ 的重载和重写是如何实现的?
重载
1.函数名相同
2.函数形参个数、顺序、类型不同(三者中至少有一个不同),没有返回值
重写
是指在派生类中重新对基类中的虚函数重新实现。即函数名和参数都一样,只是函数的实现体不一样
20 内存中堆与栈的区别是什么?
栈区(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等。
堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。
- 申请方式
- 栈:由系统自动分配。例如在声明函数的一个局部变量int b,系统自动在栈中为b开辟空间。
- 堆:需要程序员自己申请,并指明大小,在C中用malloc函数;在C++中用new运算符。
申请后系统的响应
- 栈:只要栈的剩余空间大于所申请的空间,系统将为程序提供内存,否则将报异常提示栈溢出。
- 堆:操作系统有一个记录空间内存地址的链表,当系统收到程序的申请时,会遍历链表,寻找第一个空间大于所申请空间的堆节点,然后将节点从内存空闲节点链表中删除,并将该节点的空间分配给程序。对于大多数操作系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的对节点的大小不一定正好等于申请的大小,系统会自动地将多余的那部分重新放入到链表中。
- 申请大小的限制
- 栈:在Windows下,栈是向低地址拓展的数据结构,是一块连续的内存的区域。站定地址和栈的大小是系统预先规定好的,如果申请的内存空间超过栈的剩余空间,将提示栈溢出。
- 堆:堆是向高地址拓展的内存结构,是不连续的内存区域。是系统用链表存储空闲内存地址的,不连续。
申请效率的比较
- 栈:由系统自动分配,速度较快。但程序员无法控制。
- 堆:由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来方便。 拓展:在Windows操作系统中,最好的方式使用VirtualAlloc分配内存。不是在堆,不是在栈,而是在内存空间中保留一块内存,虽然用起来不方便,但是速度快,也很灵活。
堆和栈的存储内容
- 栈:在函数调用时,第一个进栈的是主函数的中的下一条指令(函数调用的下一个可执行语句)的地址,然后是函数的各个参数。在C编译器中,参数是由右往左入栈的,然后是函数的局部变量。静态变量不入栈。
- 堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。 数据结构方面的堆和栈与上边叙述不同。这里的堆是指优先队列的一种数据结构,第一个元素有最高的优先权;栈实际就是满足先进后出的性质的数学或数据结构。
21 简述 STL 中的内存分配器allocator原理
这个allocator是一个由两级分配器构成的内存管理器,当申请的内存大小大于128byte时,就启动第一级分配器通过malloc直接向系统的堆空间分配,如果申请的内存大小小于128byte时,就启动第二级分配器,从一个预先分配好的内存池中取一块内存交付给用户,这个内存池由16个不同大小(8的倍数,8~128byte)的空闲列表组成,allocator会根据申请内存的大小(将这个大小round up成8的倍数)从对应的空闲块列表取表头块给用户。
allocator的功能:开辟内存(malloc实现)、构造对象、析构对象、释放内存(free实现)
allocator分配内存的算法如下:
算法:allocate
输入:申请内存的大小size
输出:若分配成功,则返回一个内存的地址,否则返回NULL
22 构造函数和析构函数可以被声明为虚函数吗?
两类函数不能被声明为虚函数:不能被继承的函数、不能被重写的函数
构造函数不可以被声明为虚函数。多态是通过基类指针指向子类对象来实现多态的,在对象构造之前并没有对象产生,因此无法使用多态特性,这是矛盾的。
析构函数常常为虚函数
- 若析构函数是虚函数,delete 时,基类和子类都会被释放;
- 若析构函数不是虚函数,delete 时,只有基类会被释放,而子类没有释放,存在内存泄漏的隐患。
23 类默认的构造函数是什么?
默认构造函数是可以不用实参进行调用的构造函数,它包括了以下两种情况:
- 没有带明显形参的构造函数。
- 提供了默认实参的构造函数。
24 lambda 函数的特点,和普通函数相比有什么优点?
lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的匿名函数。
- lambda函数比较轻便,即用即扔,很适合需要完成某一项简单功能
- lambda是匿名函数
- 作为回调函数,可以传递给某些应用,比如消息处理等。
25 父类和子类是不是在同一个虚函数表
子类 与 父类拥有不同的虚函数表。
如果要对父类中的虚函数进行重写时或添加虚函数,顺序是:
①先将父类的虚函数列表复制过来
②重写虚函数时是把从父类继承过来的虚函数表中对应的虚函数进行相应的替换。
③如果子类自己要添加自己的虚函数,则是把添加的虚函数加到从父类继承过来虚函数表的尾部。
26 简述 STL 中的 map 的实现原理
map是一类关联式容器。底层是用红黑树实现,根据key值有序排列,查找效率很高。它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。插入的元素健位不允许重复
一个map是一个键值对序列,即(key ,value)对。它提供基于key的快速检索能力,在一个map中key值是唯一的。map提供双向迭代器,即有从前往后的(iterator),也有从后往前的(reverse_iterator)。
27 简述使用协程的优点
协程 :协程是微线程,纤程,本质是一个单线程
协程能在单线程处理高并发,因为遇到 I/O 自动切换,线程遇到 I/O 操作会等待、阻塞。
协程的优缺点:
缺点 : 缺点是无法利用多核资源,本质是单核的,它不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上。
优点: 不仅是处理高并发(单线程下处理高并发),还特别节省资源(协程的本质是一个单线程,当然节省空间)。
28 简述 C++ 的内存分区
操作系统的内存分区(用户空间3G,内核空间1G)
操作系统将一整块内存划分了几个区域,每个区域用来做不同的事情:
- text段:存储程序的二进制指令,即程序源码编译后的二进制代码
- data段:存储已被初始化的全局变量、常量
- bss段:存储未被初始化的全局变量,和data段一样都属于静态分配,在编译阶段就确定了大小,不释放
- stack段(栈空间):主要用于函数调用时存储临时变量的,这部分的内存是自动分配,自动释放的
- heap段(堆空间):主要用于动态分配,C语言中malloc和free操作堆内存。
C++内存分区
C++程序执行时,将内存大致划分为四个区域
代码区:存放函数的二进制代码,由操作系统 进行管理
全局区:存放全局变量和静态变量以及常量
栈区:由编译器自动分配释放,存放函数的参数值、局部变量等
堆区:由程序员分配和释放,若不释放,程序结束时由操作系统回收
29 C++ 的 vector 和 list 中,如果删除末尾的元素,其指针和迭代器如何变化?若删除的是中间的元素呢?
迭代器不是指针,是类模板,表现的像指针。
- vector特性 动态数组。元素在内存连续存放。随机存取任何元素都在常数时间完成。在尾端增删元素具有较大的性能(大部分情况下是常数时间)。
- list特性 双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。
- 对于vector而言,删除某个元素以后,该元素后边的每个元素的迭代器都会失效,后边每个元素都往前移动一位,erase返回下一个有效的迭代器。
- 对于list而言,删除某个元素,只有“指向被删除元素”的那个迭代器失效,其它迭代器不受任何影响。
30 什么是字节对齐,为什么要采用这种机制?
在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
未对齐的内存访问(unaligned memory access),其影响因体系结构而异。字节对齐的根本原因在于CPU访问数据的效率问题。
31 C++ 中什么是菱形继承问题?
- 二义性是指无法直接通过变量名进行读取,需要通过域(::)成员运算符进行区分。
- 浪费内存空间
为了解决上述的问题,使用虚基类解决,作用是 在间接继承共同基类时只保留一份基类成员
32 什么是内存泄漏,怎么确定内存泄漏?
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间
内存泄漏的常见情况
- 指针重新赋值
- 错误的内存释放
- 返回值的不正确处理
判断内存泄漏的方法
- 代码审查
- 运行时工具
- 性能监控工具,查看内存使用情况
- 日志和追踪,使用代码添加日志
33 只定义析构函数,会自动生成哪些构造函数?
只定义了析构函数,编译器将自动为我们生成拷贝构造函数和默认构造函数。
默认构造函数和初始化构造函数。 在定义类的对象的时候,完成对象的初始化工作。有了有参的构造了,编译器就不提供默认的构造函数。赋值构造函数默认实现的是值拷贝(浅拷贝)。
34 变量的声明和定义有什么区别?
-
变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。
-
变量声明:用于向程序表明变量的类型和名字。
变量在使用前就要被定义或者声明。
在一个程序中,变量只能定义一次,却可以声明多次。
定义分配存储空间,而声明不会。
35 C/C++内存存储区有哪几种类型?
代码段.text 数据段(.data 已经初始化 .bss未初始化) 堆区heap 栈区stack
36 简述 C++ 从代码到可执行二进制文件的过程
c(c++)程序的生成过程: 编写源代码->预编译->编译->汇编->链接 。
编译
源程序:是指用源语言写的;目标程序:源程序通过翻译程序加工以后生成的机器语言程序;把源程序转化为目标程序的操作就叫做编译。
链接
C语言代码经过编译以后,会变成了二进制形式的目标文件。但此时的代码还不能运行起来。因为它还需要和系统提供的组件(比如标准库)结合起来,这些组件都是程序运行所必须的。经过链接才会生成 可执行程序
- 编译就是将我们编写的源代码“翻译”成计算机可以识别的二进制格式,它们以目标文件的形式存在;
- 链接就是一个“打包”的过程,它将所有的目标文件以及系统组件组合成一个可执行文件。
操作系统
1 进程和线程之间有什么区别?
- 进程是资源分配的最小单位,结程是程序执行的最小单位
- 进程有独立的代码和数据空间,线程共享同一进程的资源
- 进程切换开销大,线程切换开销小(进程切换开销大的原因是快表)
- 一个程序至少有一个进程,一个进程至少有一个线程,一个线程只属于一个进程
- 线程不能独立执行,必须依存在进程中
程序执行时通过CPU执行代码段的指令执行,逻辑就体现在方法、函数(代码段、栈)
栈归属于进程和线程,线程就是一个容器,cpu访问的一个基本单位
线程有自己独有的栈空间,代码逻辑不同时推荐使用多线程
2 进程间有哪些通信方式?(7种)
- 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
- 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
- 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
3 简述 select, poll, epoll 的使用场景以及区别,epoll 中水平触发以及边缘触发有什么不同?
IO复用模型有三种:select,poll,epoll
select(时间复杂度O(n))
有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。select的可移植性好
poll(时间复杂度O(n))
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的
epoll(时间复杂度O(1))
空间换时间,内核中开辟了一个空间,外界链接进来后,放进去存储,之后等待
epoll会把哪个流发生了怎样的I/O事件通知我们。
条件触发方式中,只要输入缓冲有数据就会一直通知该事件。边缘触发中输入缓冲收到数据时仅注册1次该事件。即使输入缓冲中还留有数据,也不会再进行注册。
4 简述 Linux 进程调度的算法
- 在早期的批处理系统中包括,先到先服务调度算法、短作业优先调度算法、高响应比优先调度算法
先到先服务类似于排队买东西,从公平的角度出发;短作业优先选择到达队列运行时间最短的进程;高响应比优先算法计算所有进程的响应比,选择响应比最大的进程(响应比 = (等待时间+要求服务时间)/ 要求服务的时间)
- 在交互系统中包括时间片轮转调度算法、优先级调度算法、多级反馈队列调度算法
时间片轮转调度算法是每个进程运行一定长度的时间片,经过一段时间片没结束就放到队列末尾重新排队;优先级调度算法每次选择就绪队列中优先级最高的进程;多级反馈队列调度算法生成多个优先级队列,每个队列有自己的时间片,当程序运行对应的时间片后,进入下一个优先级队列,以此类推运行完全部进程。
5 简述几个常用的 Linux 命令以及他们的功能
目录操作、文件操作、进程操作、压缩解压缩
6 简述操作系统如何进行内存管理
操作系统内存管理的目的是将线性物理地址用抽象的逻辑地址空间,从而保护物理地址。此外,可以独立地址空间,共享内存以及虚拟化。
操作系统内存管理方式:
- 重定位 程序和数据装入内存时,需对目标程序中的地址进行修改。这种把逻辑地址转变为内存物理地址的过程称作重定位。
- 分段 程序是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段(Segmentation)的形式把这些段分离出来。
- 分页 将虚拟地址空间分为大小相等的页,线性地址通过页表转换成物理地址
- 虚拟存储 使得不同的进程可以共享同一个主存,或者说是不同的虚拟机可以安全的共享同一个内存。
7 线程有多少种状态,状态之间如何转换
除了上述的3中状态还有创建和终止,创建在就绪态前面,终止在运行态后面
8 简述操作系统中的缺页中断
内存管理时我们采用的是虚拟内存,虚拟内存并不能与实际内存建立完全的映射关系。缺页就是虚拟内存无法与实际内存建立映射的一种情况。我们通过页表的状态位判断是否产生缺页。缺页发生时,我们就需要将虚拟内存对应的外存中的那一页调入内存。
缺页中断处理的基本过程如下:
- 当程序访问一个虚拟地址时,操作系统会检查该地址是否被映射到物理内存中,如果没有,则触发缺页中断。
- 缺页中断会暂停程序执行,将控制权交给操作系统内核。内核会检查缺失的页面是否在磁盘上,并进行必要的页面置换。
- 如果缺失的页面在磁盘上,操作系统将会将磁盘上的页面加载到空闲的物理页面中,并更新页表信息。
- 如果缺失的页面不在磁盘上,则操作系统会将程序终止并报告错误。
- 当操作系统处理完缺页中断后,程序会恢复执行,并重新访问之前发生缺页中断的地址。
9 线程间有哪些通信方式?
三种:共享存储、消息传递、管道
共享存储
基于存储区的共享:操作系统在内存中划出一块共享存储区,数据的形式、存放位置都由通信进程控制,而不是操作系统。这种共享方式速度很快,是一种高级通信方式。
基于数据结构的共享:比如共享空间里只能放一个长度为10的数组。这种共享方式速度慢、限制多,是一种低级通信方式
为避免出错,各个进程对共享空间的访问应该是互斥的。
消息传递
进程间的数据交换以格式化的消息(Message)为单位。进程通过操作系统提供的“发送消息/接收消息”两个原语进行数据交换。
- 直接通信方式:点名道姓的消息传递(直接指明消息发给谁)
- 简介通信方式(信箱通信方式):以“信箱”为中间实体进行消息传递,可以多个进程往同一个信箱send消息,也可以多个进程从同一个信箱中receive消息
管道通信(循环队列)
数据流向单向,先进先出。“管道”是一个特殊的共享文件,又名pipe文件。其实就是在内存中开辟一个大小固定的内存缓冲区,以数据流形式传递。相比共享存储限制更多,共享存储读写都可以随意。
管道只能采用半双工通信,某一时间段内只能实现单向的传输。如果要实现双向同时通信,则需要设置两个管道。
各进程要互斥地访问管道(由操作系统实现)
当管道写满时,写进程将阻塞,直到读进程将管道中的数据取走,即可唤醒写进程
当管道读空时,读进程将阻塞,直到写进程往管道中写入数据,即可唤醒读进程。
10 什么时候会由用户态陷入内核态?
进行系统调用的时候、发生中断的时候
11 进程有多少种状态?
12 简述自旋锁与互斥锁的使用场景
互斥锁加锁失败后,线程会释放 CPU,给其他线程;自旋锁加锁失败后,线程会忙等待,直到它拿到锁;
- 如果我们明确知道被锁住的代码的执行时间很短,那我们应该选择开销比较小的自旋锁
- 如果临界区需要睡眠,应该选择互斥锁
- 中断里应该使用自旋锁,因为中断处理中不允许睡眠
13 Linux 下如何查看端口被哪个进程占用?
netstat -tunlp |grep 端口号
14 操作系统中,虚拟地址与物理地址之间如何映射?
让所有的程序都各自享有一个从0开始到最大地址的空间,这个地址空间是独立的,是该程序私有的,其它程序既看不到,也不能访问该地址空间,找个地地址空间和其他程序无关,和具体的计算机也无关,这个方案就是虚拟地址。
虚拟地址只是逻辑上存在的地址,无法作用于硬件电路上,程序装进内存中需要执行,就需要和内存打交道,从内存中取得指令和数据,而内存只认一种地址,那就是物理地址
虚拟地址必须转换成物理地址,这样程序才能正常执行。要转换必须要转换结构,它相当于一个函数:p=f(v),输入虚拟地址 v,输出物理地址 p。这种方式采用软硬件结合的方式实现,它就是 MMU(内存管理单元)。MMU 可以接受软件给出的地址对应关系数据,进行地址转换。
分页内存管理机制将虚拟内存和物理内存都分成大小一样大的部分,我们称为页,然后按页进行内存分配.
根据页表从虚拟地址映射到物理地址
15 进程通信中的管道实现原理是什么?
管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。(一个输入,一个读取,单向)一个缓冲区不需要很大,它被设计成为环形的数据结构(循环队列),以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
16 Linux 下如何排查 CPU 以及 内存占用过多?
top [选项] 查看系统资源
-d 指定屏幕信息刷新的时间间隔,缺省是3秒,如果希望10秒刷新一次,则使用:top -d 10
-u 只显示指定用户的进程,如果希望只显示wucz用户的进程,则使用:top -u wucz
-p 只显示指定的进程,例如:top -p 1038, 1038是进程编号。
-i 不显示闲置和僵尸的进程,例如:top -i
-c 显示产生进程的完整命令,例如:top -c
17 简述 Linux 系统态与用户态,什么时候会进入系统态?
用户空间:指的就是用户可以操作和访问的空间,这个空间通常存放我们用户自己写的数据等。
内核空间:是系统内核来操作的一块空间,这块空间里面存放系统内核的函数、接口等。
在用户空间下执行,我们把此时运行得程序的这种状态成为用户态,而当这段程序执行在内核的空间执行时,这种状态称为内核态。
用户态切换到内核态的3种方式:a.系统调用 b.异常 c.外围设备的中断
18 多线程和多进程的区别是什么?
19 简述 Linux 虚拟内存的页面置换算法
程序运行过程中,有时要访问的页面不在内存中,而需要将其调入内存。但是内存已经无空闲空间存储页面,为保证程序正常运行,系统必须从内存中调出一页程序或数据送到磁盘对换区,此时需要一定的算法来决定到底需要调出那个页面。通常将这种算法称为“页面置换算法”。
- 最佳置换算法
最佳置换算法(OPT,Optimal):每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率(缺页次数 / 操作次数)。
注意:缺页时未必发生页面置换。若还有可用的空闲内存块,就不用进行页面置换。
- 先进先出置换算法
先进先出置换算法(FIFO):每次选择淘汰的页面是最早进入内存的页面
实现方法:把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时选择队头页面即可队列的最大长度取决于系统为进程分配了多少个内存块。
Belady 异常--当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。
- 最近最久未使用置换算法(LRU)
最近最久未使用置换算法(LRU,leastrecentlyused):每次淘汰的页面是最近最久未使用的页面
实现方法:赋予每个页面对应的页表项中,用访问字段记录该页面自上次被访问以来所经历的时间t。当需要淘汰一个页面时,选择现有页面中t值最大的,即最近最久未使用的页面。
- 时钟置换算法
最佳置换算法性能最好,但无法实现;先进先出置换算法实现简单,但算法性能差;最近最久未使用置换算法性能好,是最接近OPT算法性能的,但是实现起来需要专门的硬件支持,算法开销大。时钟置换算法是一种性能和开销较均衡的算法,又称CLOCK算法,或最近未用算法(NRU,NotRecently Used)
20 创建线程有多少种方式?
继承 Thread
- 创建一个继承Thread的类ThreadDemo
- 重新run()方法
实现 Runnable
- 实现Runnable 接口创建线程类 RThread
- 重写run()方法
通过 ExecutorService 和 Callable\ 实现有返回值的线程
- 创建一个类实现Callable接口
- 重写 call() 方法
基于线程池的execute(),创建临时线程
- 创建线程池
- 调用线程池的execute()方法
- 采用匿名内部类的方法,创建Runnable对象,并重写run()方法
21 简述 Linux 零拷贝的原理
零拷贝技术主要依赖于操作系统的支持。它利用了操作系统提供的文件描述符重定向和内存映射机制,将待传输的数据直接从源地址映射到目标地址,从而避免了数据的复制操作。具体来说,零拷贝技术通过以下几个步骤实现高效的数据传输:
- 内存映射:通过内存映射技术,将待传输的数据文件或缓冲区映射到进程的地址空间中。这样,进程可以直接访问这些数据,而无需进行复制操作。
- 文件描述符重定向:将源文件描述符和目标文件描述符重定向到同一个物理地址。这样,当目标端从该文件描述符读取数据时,实际上是从源端数据的同一物理地址读取,从而避免了数据的复制。
- 直接网络传输:利用操作系统的网络传输层接口,将数据从源端直接发送到目标端,而无需经过CPU的复制操作。这样可以减少CPU的负载,提高数据传输的效率。
22 简述同步与异步的区别,阻塞与非阻塞的区别
同步与异步关注的是消息通信机制,
- 同步:执行一个操作之后,等待结果,然后才继续执行后续的操作。
- 异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作
同步阻塞的例子:
你上QQ问书店老板有没有《分布式系统》这本书,如果是同步阻塞,书店老板会说,你稍等,“我查一下”,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。(你一直等的,等老板给你回复)
同步非阻塞的例子:
而如果是同步非阻塞,不管老板有没有告诉你,你自己一边玩去了,当然你也会偶尔过几分钟问一下老板,书找到了吗。
异步机制的例子:
书店老板会直接告诉你我查一下啊,查好了,我直接回复你,然后直接那人就下线(不返回结果)。然后查好了,他会主动联系你。
- 阻塞:进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作
- 非阻塞:进程给CPU传达任务后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。
23 Linux 中虚拟内存和物理内存有什么区别?有什么优点?
- 物理内存:真实的内存条,CPU可以直接寻址的内存空间。
- 虚拟内存:利用磁盘空间虚拟出一块逻辑内存。
(1)虚拟内存系统在内存中自动缓存最近使用的存放在磁盘上的虚拟地址空间的内容(通过缺页实现); (2)虚拟内存系统简化了内存管理,进而又简化了链接、进程间共享数据、进程的内存分配和程序加载。 (3)虚拟内存系统通过在页表条目中加入保护位,从而简化了内存保护。
24 BIO、NIO 有什么区别?怎么判断写文件时 Buffer 已经写满?简述 Linux 的 IO模型
BIO(Blocking IO) 是最传统的IO模型,也称为同步阻塞IO。
优点:
- 简单易用: BIO模型的编程方式相对简单,易于理解和使用。
- 可靠性高: 由于阻塞特性,IO操作的结果是可靠的。
缺点:
- 阻塞等待: 当一个IO操作被阻塞时,线程会一直等待,无法执行其他任务,导致资源浪费。
- 并发能力有限: 每个连接都需要一个独立的线程,当连接数增加时,线程数量也会增加,造成资源消耗和性能下降。
- 由于I/O操作是同步的,客户端的连接需要等待服务器响应,会降低系统的整体性能。
NIO称为同步非阻塞IO
线程可以对某个IO事件进行监听,并继续执行其他任务,不需要阻塞等待。当IO事件就绪时,线程会得到通知,然后可以进行相应的操作,实现了非阻塞式的高伸缩性网络通信。在NIO模型中,数据总是从Channel读入Buffer,或者从Buffer写入Channel,这种模式提高了IO效率,并且可以充分利用系统资源。
Linux IO模型简述: Linux中有五种主要的I/O模型:
阻塞I/O (Blocking I/O):调用read或write时,如果没有数据可读或写缓冲区满,进程会被阻塞直到数据准备好或写入完成。
非阻塞I/O (Non-blocking I/O):调用read或write不会阻塞,但如果数据未准备好或缓冲区满,会立即返回错误信息(通常是EAGAIN或EWOULDBLOCK)。应用程序需不断轮询检查数据是否准备好。
I/O复用 (I/O Multiplexing):包括select、poll和epoll。它们可以让单个线程监视多个描述符(如文件描述符),当其中任何一个描述符准备好进行I/O操作时返回。典型应用是epoll,它提供了一种高效的事件通知机制。
信号驱动I/O (Signal-driven I/O):通过SIGIO信号告知进程I/O操作可以继续,主要用于异步通知。
异步I/O (Asynchronous I/O, AIO):发出read或write请求后,进程可以去做其他事情,等到数据真正读取完毕或数据完全写入后,由操作系统发送信号或调用回调函数通知进程。AIO提供了真正的非阻塞I/O操作,但相比其他模型较为复杂,不常用。在Linux中对应的系统调用是aio_read和aio_write。
不同IO模型是为了减少系统调用的触发,减少浪费 ,IO模型的不断迭代就是程序员修补BUG的过程,BIO是由于内核提供的阻塞导致的,NIO是在内核中将阻塞设为false,但是NIO也存在问题,当链接客户端很多,但是只有一个客户端发送消息的时候,会产生资源浪费,系统调用无意义。
无论是BIO、NIO、多路复用器都是同步线程模型,多路复用器不是读数据,仅分析内核返回有没有相应的IO事件,只拿到了事件状态,没有真正去读写
25 简述 mmap 的使用场景以及原理
mmap
也是零拷贝技术的一种实现
一般来说,修改一个文件的内容需要如下3个步骤:
- 把文件内容读入到内存中。
- 修改内存中的内容。
- 把内存的数据写入到文件中。
直接在用户空间读写 页缓存
,那么就可以免去将 页缓存
的数据复制到用户空间缓冲区的过程。上述的技术实现就是mmap
使用 mmap
系统调用可以将用户空间的虚拟内存地址与文件进行映射(绑定),对映射后的虚拟内存地址进行读写操作就如同对文件进行读写操作一样
26 两个线程交替打印一个共享变量
- 使用锁lock
- 使用 wait/notify
- 使用信号量
——27 简述操作系统中 malloc 的实现原理
28 Linux 下如何查看 CPU 荷载,正在运行的进程,某个端口对应的进程?
1. lsof -i:端口号
2.netstat -tunlp |grep 端口号
两个方式都可以查看端口号的进程使用情况
29 LVS 的 NAT、TUN、DR 原理及区别
LVS是一种负载均衡的方案,负载均衡,就是通过不同的调度机制将用户的请求分派到后端不同的服务器。
LVS由两部分组成,包括ipvs和ipvsadm:
ipvs:ipvs是工作在内核空间netfilter的input链上的框架,通过用户空间工具进行管理,是真正生效实现调度的代码。
ipvsadm:ipvsadm负责为ipvs内核框架编写规则,管理配置内核中ipvs程序的用户空间的管理工具
NAT:修改请求报文的目标IP,多目标IP的DNAT
就是把客户端发来的数据包的IP头的目的地址,在负载均衡器上换成其中一台真实服务器RS的IP地址,并发至此RS来处理,RS处理完成后把数据交给经过负载均衡器,负载均衡器再把数据包的原IP地址改为自己的IP,将目的地址改为客户端IP地址即可。
DR:操纵封装新的MAC地址
负载均衡器和RS都使用同一个IP对外服务,但只有DR对ARP请求进行响应,所有RS对本身这个IP的ARP请求保持静默,也就是说,网关会把对这个服务IP的请求全部定向给DR,而DR收到数据包后根据调度算法,找出对应的RS,把目的MAC地址改为RS的MAC(因为IP一致)并将请求分发给这台RS,这时RS收到这个数据包,处理完成之后,由于IP一致,可以直接将数据返给客户,则等于直接从客户端收到这个数据包无异,处理后直接返回给客户端。由于负载均衡器要对二层包头进行改换,所以负载均衡器和RS之间必须在一个广播域,也可以简单的理解为在同一台交换机上。
TUN:在原请求IP报文之外新加一个IP首部
把客户端发来的数据包,封装一个新的IP头标记(仅目的IP)发给RS,RS收到后,先把数据包的头解开,还原数据包,处理后,直接返回给客户端,不需要再经过负载均衡器。
30 共享内存是如何实现的?
共享内存(Shared Memory)允许多个进程(不要求进程之间有血缘关系)访问同一块内存空间,是多个进程之间共享和传递数据最高效的方式。进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也会改变。
共享内存没有提供锁机制,也就是说,在某一个进程对共享内存进行读/写的时候,不会阻止其它进程对它的读/写。如果要对共享内存的读/写加锁,可以使用信号量。
shmget函数
该函数用于创建/获取共享内存。(不存在就创建,存在就获取)
int shmget(key_t key, size_t size, int shmflg);
key 共享内存的键值,是一个整数(typedef unsigned int key_t),一般采用十六进制,例如0x5005,不同共享内存的key不能相同。
size 共享内存的大小,以字节为单位。
shmflg 共享内存的访问权限,与文件的权限一样,例如0666|IPC_CREAT,0666表示全部用户对它可读写,IPC_CREAT表示如果共享内存不存在,就创建它。
返回值:成功返回共享内存的id(一个非负的整数),失败返回-1(系统内存不足、没有权限)
shmat函数
该函数用于把共享内存连接到当前进程的地址空间。共享内存在使用时需要强制转化数据类型
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid 由shmget()函数返回的共享内存标识。
shmaddr 指定共享内存连接到当前进程中的地址位置,通常填0,表示让系统来选择共享内存的地址。
shmflg 标志位,通常填0。
调用成功时返回共享内存起始地址,失败返回(void*)-1。
shmdt函数
该函数用于将共享内存从当前进程中分离,相当于shmat()函数的反操作。
int shmdt(const void *shmaddr);
shmaddr shmat()函数返回的地址。
调用成功时返回0,失败时返回-1。
shmctl函数
该函数用于操作共享内存,最常用的操作是删除共享内存。
int shmctl(int shmid, int command, struct shmid_ds *buf);
shmid shmget()函数返回的共享内存id。
command 操作共享内存的指令,如果要删除共享内存,填IPC_RMID。
buf 操作共享内存的数据结构的地址,如果要删除共享内存,填0。
调用成功时返回0,失败时返回-1。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
using namespace std;
struct stgirl // 超女结构体。
{
int no; // 编号。
char name[51]; // 姓名,注意,不能用string。
};
int main(int argc,char *argv[])
{
if (argc!=3) { cout << "Using:./demo no name\n"; return -1; }
// 第1步:创建/获取共享内存,键值key为0x5005,也可以用其它的值。
int shmid=shmget(0x5005, sizeof(stgirl), 0640|IPC_CREAT);
if ( shmid ==-1 )
{
cout << "shmget(0x5005) failed.\n"; return -1;
}
cout << "shmid=" << shmid << endl;
// 第2步:把共享内存连接到当前进程的地址空间。
stgirl *ptr=(stgirl *)shmat(shmid,0,0);
if ( ptr==(void *)-1 )
{
cout << "shmat() failed\n"; return -1;
}
// 第3步:使用共享内存,对共享内存进行读/写。
cout << "原值:no=" << ptr->no << ",name=" << ptr->name << endl; // 显示共享内存中的原值。
ptr->no=atoi(argv[1]); // 对超女结构体的no成员赋值。
//strcpy(ptr->name,argv[2]); // 对超女结构体的name成员赋值。
ptr->name=argv[2];
cout << "新值:no=" << ptr->no << ",name=" << ptr->name << endl; // 显示共享内存中的当前值。
// 第4步:把共享内存从当前进程中分离。
shmdt(ptr);
//第5步:删除共享内存。
if (shmctl(shmid,IPC_RMID,0)==-1)
{
cout << "shmctl failed\n"; return -1;
}
}
用ipcs -m可以查看系统的共享内存,包括:键值(key),共享内存id(shmid),拥有者(owner),权限(perms),大小(bytes)。
用ipcrm -m 共享内存id可以手工删除共享内存
31 如何调试服务器内存占用过高的问题?
可以按照以下几个方面进行排查
- 查看内存占用情况
free -h
- 查看进程内存占用
top
- 查看系统日志
- 内存泄漏检测
- 查看共享内存
- 检查swap分区
32 系统调用的过程是怎样的?操作系统是通过什么机制触发系统调用的?
过程
- 用户进程通过库函数或者直接使用汇编指令(如svc)发起系统调用请求,将系统调用号和参数保存在寄存器中。
- 处理器切换到内核模式,根据中断向量表跳转到系统调用总入口程序,保存用户进程的现场信息。
- 系统调用总入口程序根据系统调用号查找对应的服务例程,执行服务例程的功能。
- 服务例程执行完毕后,恢复用户进程的现场信息,返回到用户空间继续执行。
机制
操作系统通过中断机制触发系统调用。中断机制是一种硬件和软件之间的通信方式,可以让处理器响应外部事件或者内部异常。中断可以分为硬中断和软中断。硬中断是由外设产生的信号,如键盘输入、鼠标移动等。软中断是由当前运行的进程产生的指令,如svc、int等。系统调用就是一种软中断,它模拟了硬中断的处理过程。
33 Linux 如何查看实时的滚动日志?
tail -f path/test.log 查看路径下的test文件
34 malloc 创建的对象在堆还是栈中?
堆区,申请的内存需要操作者释放,系统不会自动释放
35 为什么进程切换慢,线程切换快?
线程切换比进程块的主要原因就是进程切换涉及虚拟内存地址空间的切换而线程不会。因为每个进程都有自己的虚拟内存地址空间,而线程之间的虚拟地址空间是共享的,因此同一个进程之中的线程切换不涉及虚拟地址空间的转换,然而将虚拟地址转化为物理地址需要查找页表,查找页表是一个很慢的过程,所以线程切换自然就比进程快了
页表的切换实质上导致快表的缓存全部失效,这些寄存器里的内容需要全部重写。而线程切换无需经历此步骤。
36 简述 CPU L1, L2, L3 多级缓存的基本作用
L1(1级)高速缓存是计算机系统中存在的最快内存。在访问优先级方面,L1缓存具有CPU在完成特定任务时最可能需要的数据。
L2(级别2)缓存比L1缓存慢,但是大小更大。它的大小通常在256KB到8MB之间,尽管较新的,功能强大的CPU往往会超过它。L2缓存保存下一步可能由CPU访问的数据。
L3(Level 3)缓存是最大的缓存单元,也是最慢的缓存单元。它的范围在4MB到50MB之间。现代CPU在CPU裸片上有专用空间用于L3缓存,占用了大量空间。
CPU会先在最快的L1中寻找需要的数据,找不到再去找次快的L2,还找不到再去找L3,L3都没有那就只能去内存找了。
37 简述创建进程的流程
进程创建的具体操作步骤如下:
- 用户程序通过系统调用请求操作系统创建新进程。
- 操作系统检查用户程序的请求,确定新进程需要分配的内存空间、资源等。
- 操作系统为新进程分配内存空间,并初始化进程环境。
- 操作系统为新进程分配所需的系统资源。
- 操作系统为新进程创建进程控制块,并将其加入到就绪队列中。
- 操作系统根据进程的优先级和资源需求来决定进程的执行顺序,并将新进程加入到就绪队列中。
38 进程空间从高位到低位都有些什么?
39 什么情况下,进程会进行切换?
进程切换一定发生在 中断/异常/系统调用 处理过程中,常见的有以下情况:
- 时间片中断、IO中断后 更改优先级进程;(导致被中断进程进入就绪态);
- 阻塞式系统调用、虚拟地址异常;(导致被中断进程进入等待态)
- 终止用系统调用、不能继续执行的异常;(导致被中断进程进入终止态)
40 简述 Linux 的 I/O模型
一个输入操作通常包括两个阶段:
- 等待数据准备好
- 从内核向进程复制数据
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。
Unix 有五种 I/O 模型:
- 阻塞式 I/O
- 非阻塞式 I/O
- I/O 复用(select 和 poll)
- 信号驱动式 I/O(SIGIO)
- 异步 I/O(AIO)
41 简述 traceroute 命令的原理
traceroute,现代Linux系统称为tracepath,Windows系统称为tracert,是一种计算机网络工具。它可显示数据包在IP网络经过的路由器的IP地址。我们可以用这个命令来查看数据包途径的网络节点和ISP,从而排除一部分网络问题。
程序是利用增加存活时间(TTL)值来实现其功能的。每当数据包经过一个路由器,其存活时间就会减1。当其存活时间是0时,主机便取消数据包,并发送一个ICMP TTL数据包给原数据包的发出者。程序发出的首3个数据包TTL值是1,之后3个是2,如此类推,它便得到一连串数据包路径。注意IP不保证每个数据包走的路径都一样。
42 Linux 页大小是多少?
4KB
当系统中两个程序想要打开同一个文件的时候,系统提供一份页使其共享,因为内存判断之前已经出现过了
43 信号量是如何实现的?
信号量就是一个变量(可以是整数或复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量。
它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
网络协议
1 简述 TCP 三次握手以及四次挥手的流程(传输控制层)。为什么需要三次握手以及四次挥手?
三次握手 (建立连接)
举例:公安局长王哥 和 陈某打电话
- 公安局:你好!陈某,听得到吗?(一次会话)
- 陈某:听到了,王哥,你能听到吗 (二次会话)
- 公安局:听到了,你过来自首吧 (开始会话)(三次会话)
第三次握手的作用:防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误
- 第一次握手: 客户端向服务器端发送报文
证明客户端的发送能力正常- 第二次握手:服务器端接收到报文并向客户端发送报文
证明服务器端的接收能力、发送能力正常- 第三次握手:客户端向服务器发送报文
证明客户端的接收能力正常
客户端和服务端开辟对应的资源(socket,文件描述符)就是链接,如果链接过程中断掉(网线掉了/服务器炸了),连接仍然存在,所以TCP存在心跳机制(keepalive)
socket需要4部分组成:源IP地址、源端口号、目标IP地址、目标端口号
socket的rec-Q和send-Q来实现负载均衡,当APP提供的链接过大时,直接转移或者宣布失败。
资源更底层的是socket中的queue,网络IO中读写的对象也是针对内核中的队列
第一次握手:建立连接时,客户端发送SYN包(seq=x)到服务器,并进入SYN_SEND状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
四次挥手(断开连接)
张三:好的,那我先走了
李四:好的,那你走吧
李四:那我也走了?
张三:好的,你走吧
第一次挥手:客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,终止标志位FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
第二次挥手:服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
第三次挥手: 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
第四次挥手:客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态,至此,完成四次挥手。
为什么需要三次握手以及四次挥手?
TCP进行可靠传输的关键就在于维护一个序列号,三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值。
当服务端收到客户端的 FIN 数据包后,服务端可能还有数据没发完,不会立即close。所以服务端会先将 ACK 发过去告诉客户端我收到你的断开请求了,但请再给我一点时间,这段时间用来发送剩下的数据报文,发完之后再将 FIN 包发给客户端表示现在可以断了。之后客户端需要收到 FIN 包后发送 ACK 确认断开信息给服务端。
2 HTTP 与 HTTPS 有哪些区别?
- HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
- HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
3 从输入 URL 到展现页面的全过程
- 浏览器根据请求的 URL 交给 DNS 域名解析,找到真实 IP ,向服务器发起请求;
- 服务器交给后台处理完成后返回数据,浏览器接收⽂件( HTML、JS、CSS 、图象等);
- 浏览器对加载到的资源( HTML、JS、CSS 等)进⾏语法解析,建立相应的内部数据结构 (如 HTML 的 DOM);
- 载⼊解析到的资源⽂件,渲染页面,完成。
4 TCP 与 UDP 在网络协议中的哪一层,他们之间有什么区别?
属于传输层。
- tcp是面向连接的,udp是面向无连接的。tcp在通信之前必须通过三次握手机制与对方建立连接,而udp通信不必与对方建立连接,不管对方的状态就直接把数据发送给对方
- tcp连接过程耗时,udp不耗时
- tcp连接过程中出现的延时增加了被攻击的可能,安全性不高,而udp不需要连接,安全性较高
- tcp是可靠的,保证数据传输的正确性,不易丢包,udp是不可靠的,易丢包
5 TCP 怎么保证可靠传输?
- 建立连接:通过三次握手建立连接,保证连接实体真实存在
- 序号机制:保证数据是按序、完整到达
- 合理分片:tcp会按最大传输单元(MTU)合理分片,接收方会缓存未按序到达的数据,重新排序后交给应用层。
- 数据校验:TCP报文头有校验和,用于校验报文是否损坏
- 超时重传:如果发送一直收不到应答,可能是发送数据丢失,也可能是应答丢失,发送方再等待一段时间之后都会进行重传。
- 流量控制:当接收方来不及处理发送方的数据,能通过滑动窗口,提示发送方降低发送的速率,防止包丢失。
- 拥塞控制:网络层拥堵造成的拥塞,包括慢启动,拥塞避免,快速重传三种机制
6 简述 HTTP 1.0,1.1,2.0 的主要区别
HTTP1.0:
- 浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接
HTTP1.1:
- 引入了持久连接,即TCP连接默认不关闭,可以被多个请求复用
- 在同一个TCP连接里面,客户端可以同时发送多个请求
- 虽然允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的,服务器只有处理完一个请求,才会接着处理下一个请求。如果前面的处理特别慢,后面就会有许多请求排队等着
- 新增了一些请求方法
- 新增了一些请求头和响应头
HTTP2.0:
- 采用二进制格式而非文本格式
- 完全多路复用,而非有序并阻塞的、只需一个连接即可实现并行
- 使用报头压缩,降低开销
- 服务器推送
7 TCP 中常见的拥塞控制算法有哪些?
慢开始和拥塞控制
快重传和快恢复
8 DNS 查询服务器的基本流程是什么?DNS 劫持是什么?
DNS查询服务器的基本流程
- 本地缓存查询
在本地DNS缓存中查找是否已有对应的IP地址记录。若本地缓存中有最近获取的、尚未过期的记录,则直接返回IP地址,解析过程结束。
- 递归查询
若本地缓存中没有所需记录,则设备会向其配置的本地DNS服务器发起递归查询请求。本地DNS服务器负责代理客户端进行整个查询过程,直到找到答案为止。
- 迭代查询
- 本地DNS服务器首先在其自身的缓存中查找记录。如果没有找到,则向根域名服务器发起查询请求。
- 根域名服务器不直接提供域名的实际IP地址,而是告知本地DNS服务器下一步应去询问哪个顶级域名服务器(TLD服务器,例如.com、.net、.org等)。
- 本地DNS服务器接着向指定的顶级域名服务器发出查询请求。
- 顶级域名服务器根据其数据库指向权威DNS服务器(通常是该域名注册者的DNS服务器)。
- 这个过程沿着DNS层次结构不断向下查询,每一步都可能是迭代的过程,直至找到最终的权威DNS服务器。
- 权威服务器响应
当查询到达权威DNS服务器时,它会查询其区域文件,并返回域名对应的IP地址给最初发起查询的本地DNS服务器。
- 返回结果并缓存
本地DNS服务器收到权威服务器的响应后,将IP地址返回给客户端,并将这次查询结果保存到本地缓存中
- 客户端获得IP地址并连接
客户端设备在得到IP地址后,就可以使用这个IP地址与目标服务器建立连接
DNS劫持
DNS劫持就是通过劫持了DNS服务器,通过某些手段取得某域名的解析记录控制权,进而修改此域名的解析结果,导致对该域名的访问由原IP地址转入到修改后的指定IP,其结果就是对特定的网址不能访问或访问的是假网址,从而实现窃取资料或者破坏原有正常服务的目的。DNS劫持通过篡改DNS服务器上的数据返回给用户一个错误的查询结果来实现的。
9 Cookie 和 Session 的关系和区别是什么?
什么是Cookie?
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端会把Cookie保存起来。
当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
信息保存的时间可以根据需要设置
什么是Session?
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
每个用户访问服务器都会建立一个session,那服务器是怎么标识用户的唯一身份呢?事实上,用户与服务器建立连接的同时,服务器会自动为其分配一个SessionId。
Session和Cookie的区别?
- 数据存储位置:cookie数据存放在客户的浏览器上,session数据放在服务器上。
- 安全性:cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。
- 服务器性能:session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。
- 数据大小:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
- 信息重要程度:可以考虑将登陆信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中。
10 简述 HTTPS 的加密与认证过程
- 客户端请求服务器获取证书公钥
- 客户端(SSL/TLS)解析证书(无效会弹出警告)
- 生成随机值
- 用公钥加密随机值生成密钥
- 客户端将密钥发送给服务器
- 服务端用私钥解密密钥得到随机值
- 将信息和随机值混合在一起进行对称加密
- 将加密的内容发送给客户端
- 客户端用密钥解密信息
11 RestFul 是什么?RestFul 请求的 URL 有什么特点?
如果一个架构符合REST原则,就称为RESTful架构,是一种面向资源的软件架构风格
REST最大的几个特点为:资源、统一接口、URI和无状态。
用 URL 表示要操作的资源,用不同的 HTTP 请求(GET,POST,PUT,DELETE)描述对资源的操作
12 什么是 TCP 粘包和拆包?
粘包:
TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
拆包:
固定发送信息长度,或在两个信息之间加入分隔符。
13 RestFul 与 RPC 的区别是什么?RestFul 的优点在哪里?
区别
- 从本质区别上看,RPC是基于TCP实现的,RestFul是基于HTTP来实现的。
- 从传输速度上来看,因为HTTP封装的数据量更多所以数据传输量更大,所以RPC的传输速度是比RestFul更快的。
RestFul的优点:
简洁性与可读性:使用标准HTTP方法来表达对资源的操作意图,直观易懂
无状态性:不保留客户端会话状态,每次请求包含所有必要的信息
可扩展性:REST架构强调组件的独立性,服务可以很容易地扩展,新的资源可以添加而不会影响已有接口。
前后端分离
跨平台与跨语言兼容性:几乎所有的编程语言和平台都能轻松实现RESTful API的调用和开发,提高了系统的互操作性。
安全性:HTTP认证机制以及JSON格式的数据传输,可以有效降低注入攻击等安全风险
14 TCP 挥手时出现大量 CLOSE_WAIT 或 TIME_WAIT 怎么解决?
time_wait过多产生原因
正常的TCP客户端连接在关闭后,会进入一个TIME_WAIT的状态,持续的时间一般在1-4分钟,对于连接数不高的场景,1-4分钟其实并不长,对系统也不会有什么影响,
但如果短时间内(例如1s内)进行大量的短连接,则可能出现这样一种情况:客户端所在的操作系统的socket端口和文件描述符被用尽,系统无法再发起新的连接!
time_wait过多解决办法
- 改短连接为长连接
- 增大可用端口范围
CLOSE_WAIT过多产生原因
close_wait 按照正常操作的话应该很短暂的一个状态,接收到客户端的fin包并且回复客户端ack之后,会继续发送FIN包告知客户端关闭关闭连接,之后迁移到Last_ACK状态。但是close_wait过多只能说明没有迁移到Last_ACK,也就是服务端是否发送FIN包,只有发送FIN包才会发生迁移,所以问题定位在是否发送FIN包。FIN包的底层实现其实就是调用socket的close方法,这里的问题出在没有执行close方法。说明服务端socket忙于读写。
CLOSE_WAIT 过多解决办法
- 代码问题,代码层面忘记了 close 相应的 socket 连接,那么自然不会发出 FIN 包,从而导致 CLOSE_WAIT 累积
- 响应太慢或者超时设置过小:如果连接双方不和谐,一方不耐烦直接 timeout,另一方却还在忙于耗时逻辑,就会导致 close 被延后。响应太慢是首要问题,不过换个角度看,也可能是 timeout 设置过小。
15 什么是跨域,什么情况下会发生跨域请求?
跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。
浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。
16 HTTP 中 GET 和 POST 区别
- post更安全(不会作为url的一部分,不会被缓存、保存在服务器日志、以及浏览器浏览记录中)
- post发送的数据更大(get有url长度限制)
- post能发送更多的数据类型(get只能发送ASCII字符)
- post比get慢
- post用于修改和写入数据,get一般用于搜索排序和筛选之类的操作(淘宝,支付宝的搜索查询都是get提交),目的是资源的获取,读取数据
18 简述对称与非对称加密的概念
加密和解密过程:
- 加密过程:数据 + 密钥 = 密文
- 解密过程:密文 - 密钥 = 数据
加密:
- 对称加密:加密算法是公开的,靠的是秘钥来加密数据,使用一个秘钥加密,必须使用相同的秘钥才解密。
- 非对称加密:加密和解密使用不同的秘钥,一把公开的公钥,一把私有的私钥。公钥加密的信息只有私钥才能解密,私钥加密的信息只有公钥才能解密。
19 TCP 的 keepalive 了解吗?说一说它和 HTTP 的 keepalive 的区别?
HTTP Keep-Alive 也叫 HTTP 长连接,该函数由应用程序实现,允许使用相同的TCP连接发送和接收多个HTTP请求/响应,减少由HTTP短途连接产生的多个TCP连接的创建和释放成本。
TCP的存活者 也叫 TCP 保活机制,这个函数由内核实现,当客户和服务端在一定时间内与数据不交互时,为了确保连接仍然有效,就会发送探测报文,检查对方是否还在网上,然后决定是否关闭连接
20 简述常见的 HTTP 状态码的含义(301,304,401,403)
- 301(永久移动)请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。您应使用此代码告诉 Googlebot 某个网页或网站已永久移动到新位置。
- 304(未修改)自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。如果网页自请求者上次请求后再也没有更改过,您应将服务器配置为返回此响应(称为 If-Modified-Since HTTP 标头)。服务器可以告诉 Googlebot 自从上次抓取后网页没有变更,进而节省带宽和开销。
- 401(未授权)请求要求身份验证。对于登录后请求的网页,服务器可能返回此响应。
- 403(禁止)服务器拒绝请求。如果您在 Googlebot 尝试抓取您网站上的有效网页时看到此状态码(您可以在 Google 网站管理员工具诊断下的网络抓取页面上看到此信息),可能是您的服务器或主机拒绝了 Googlebot 访问。
21 简述 TCP 的 TIME_WAIT 和 CLOSE_WAIT
TCP连接是全双工的,因此每个方向都必须单独进行关闭。
假如终止命令由client端发起。
在Sever端收到Client的FIN消息之后,Sever端就会出现CLOSE_WAIT
当Client端收到Sever的FIN消息之后,Client端就会出现TIME_WAIT,TIME_WAIT一定发生在Client端,其时间长度是固定的2MSL(报文最大生存时间),到期自动转为CLOSED,不会导致系统资源耗尽的问题。
22 简述 TCP 滑动窗口以及重传机制
重传机制
TCP 实现可靠传输的方式之一,是通过序列号与确认应答。在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。TCP 针对数据包丢失的情况,会用重传机制解决。
- 超时重传
- 快速重传
- SACK
- D-SACK
超时重传:重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK
确认应答报文,就会重发该数据,也就是我们常说的超时重传。
快速重传:不以时间为驱动,而是以数据驱动重传。当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。但是面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。
SACK:在 TCP 头部「选项」字段里加一个 SACK
的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
D-SACK
使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。
ACK丢包的情况
网络延迟的情况
后两种重传机制在linux2.4后默认打开
滑动窗口
TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。
这样的传输方式有一个缺点:数据包的往返时间越长,通信的效率就越低。
为解决这个问题,TCP 引入了窗口这个概念。即使在往返时间较长的情况下,它也不会降低网络通信的效率。
那么有了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。
窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。
23 简述 DDOS 攻击原理,如何防范它?
定义
DDoS攻击通过大规模协调的恶意节点网络,同时向目标发送海量请求,瞬间淹没目标的带宽或资源。例如,Memcached反射放大攻击利用Memcached服务器的UDP协议特性,将小体积请求放大数百倍,形成巨大的带宽洪水。
常见的攻击方式
- 带宽消耗型攻击:如UDP洪水攻击、DNS放大攻击等,利用大流量冲击目标的网络带宽。
- 资源消耗型攻击:如CC攻击(Challenge Collapsar,即应用层攻击),集中攻击Web服务器资源,耗尽其处理能力。
- 协议层攻击:利用TCP/IP协议栈的弱点,如SYN洪水攻击、ACK洪水攻击等。
防范措施
- 网络带宽扩容与流量清洗:
- 提升网络带宽容量,抵抗大流量冲击。
- 使用专业的DDoS防护服务或硬件设备,如防火墙、抗D设备等,对流量进行实时监控与清洗,丢弃异常流量。 - 智能路由与负载均衡:
- 通过智能路由技术,将攻击流量导向黑洞或其他可以吸收攻击的地方,保护正常服务不受影响。
- 利用负载均衡技术,将流量分散到多个服务器节点,减轻单点压力。 - IP黑名单与白名单:
- 建立IP黑名单,阻断已知攻击源。
- 对合法用户进行白名单管理,优先保证白名单内的流量正常访问。 - 协议栈加固与漏洞修复:
- 对服务器操作系统和网络设备的协议栈进行加固,修复已知漏洞,减少协议层攻击的可能性。
- 定期更新和补丁管理,保持软件和系统的最新状态。 - 实时监测与应急响应:
- 部署入侵检测系统(IDS)和入侵防御系统(IPS),实时监测网络流量,识别异常行为并
迅速作出响应。
- 建立应急预案,发生DDoS攻击时能快速启动防御机制,并与ISP、云服务商等多方合作,
共同应对。
24 简述 OSI 七层模型,TCP,IP 属于哪一层?
OSI把网络通信的工作分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。每一层对于上一层来讲是透明的,上层只需要使用下层提供的接口,并不关心下层是如何实现的。
TCP,IP 属于传输层
25 从系统层面上,UDP 如何保证尽量可靠?
-
加上序号 每次发出的数据进行编号,同时保持顺序的正确。
-
确认机制,超时重传 每次接收方接收到数据,发出应答信号。同时发送方在规定的时间检测是否接收到应答,如果没有接收到应答,重发
-
校验机制,出错重传 发送数据时,发送方增加校验位。如果接收方校验出错,请求重发。
26 简述 JWT 的原理和校验机制
jwt认证机制实际上就是通过token密钥对于用户信息的认定
(1)客户端通过post请求 将用户名密码提交给服务器,服务器验证通过之后,将用户的重要信息从信息对象中剔除并加密生成一个token字符串
(2)服务器响应客户端的post请求,并将生成的token字符串返回给客户端
(3)客户端将token存储在本地localStorage或sessionStorage中
(4)客户端再次发起请求时,通过Authorization字段,将token作为请求头发送给服务器
(5)服务器对token进行解析,将其还原成用户的信息对象,并以此为依据,对用户在数据表中的数据组进行锁定,完成用户请求中的操作内容
(6)操作完成之后,服务器向客户端响应回相应的结果
27 HTTP 的方法有哪些?
根据 HTTP 标准,HTTP 请求可以使用多种请求方法。
HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD 方法。
HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。
1 | GET | 用于请求服务器发送某个资源。GET 请求不应该对服务器上的资源做出任何更改,并且应该是幂等的(即多次重复的请求应该产生相同的结果)。例如,当你在浏览器中输入 URL 地址时,浏览器会发送一个 GET 请求来获取该 URL 对应的网页。 |
2 | HEAD | 类类似于 GET 请求,但服务器不返回请求的资源主体,只返回响应头。HEAD 请求通常用于获取资源的元信息,如资源的大小、类型等,而不需要获取资源的实际内容。 |
3 | POST | 用于向服务器提交数据,通常用于提交表单或上传文件。POST 请求可能会导致服务器上的状态更改,并且不一定是幂等的。例如,在提交注册表单时,浏览器通常会发送一个 POST 请求,将用户提供的信息发送到服务器进行处理。 |
4 | PUT | 用于向服务器上传资源,通常用于更新已存在的资源或创建新的资源。PUT 请求应该是幂等的,即多次执行相同的 PUT 请求应该产生相同的结果。 |
5 | DELETE | 用于请求服务器删除指定的资源。DELETE 请求应该是幂等的,即多次执行相同的 DELETE 请求应该产生相同的结果。 |
6 | CONNECT | 用于建立到服务器上指定端口的隧道,通常用于代理服务器。 |
7 | OPTIONS | 用于请求服务器返回支持的 HTTP 方法和其他选项。例如,客户端可以发送 OPTIONS 请求来确定服务器支持哪些 CORS(跨域资源共享)策略。 |
8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
9 | PATCH | 用于在请求-响应链上的每个节点获取传输路径。TRACE 请求通常用于调试和测试,以查看请求在经过各种代理服务器和中间件时如何被修改。 |
28 TCP 长连接和短连接有什么么不同的使用场景?
长连接:建立连接 ——> 传输数据 ——> ... (保持连接) ... ——> 传输数据 ——> 关闭连接
短连接:建立连接 ——> 传输数据 ——> 关闭连接。
- 长连接一般多用于需要频繁进行读写操作,点对点通讯,而且连接数不太多的情况。(数据库的连接)
- 短连接一般用于不需要频繁进行读写操作,并且连接数很大的情况下。(web网站的http连接)
29 什么是 ARP 协议?简述其使用场景
什么是ARP
ARP是地址解析协议(Address Resolution Protocol)的英文缩写,它是一个链路层协议,工作在OSI 模型的第二层,在本层和硬件接口间进行联系,同时对上层(网络层)提供服务。我们知道二层的以太网交换设备并不能识别32位的IP地址,它们是以48位以太网地址(就是我们常说的MAC地址)传输以太网数据包的。也就是说IP数据包在局域网内部传输时并不是靠IP地址而是靠MAC地址来识别目标的,因此IP地址与MAC地址之间就必须存在一种对应关系,而ARP协议就是用来确定这种对应关系的协议。
ARP是地址解析协议,用于根据目的IP地址来解析MAC地址,进行二层通讯。
ARP的使用场景
- 如果目的IP和本机IP属于同一网段,则ARP请求查询的就是目的IP的MAC地址
- 如果目的IP和本机IP不属于同一网段,当本机存在到达目的IP的路由时,则ARP请求查询的就是该路由下一跳的MAC地址;如果没有明细路由,就请求查询缺省路由下一跳(也就是网关)的MAC地址。
30 简述 RPC 的调用过程
RPC(远程过程调用)是一种计算机通信协议,用于在不同的计算机或进程之间进行通信和调用远程过程。它允许一个计算机程序调用另一个计算机上的函数或过程,就像调用本地函数一样。RPC 隐藏了底层通信细节,使得远程调用的过程对于开发人员来说更加透明和简单。
RPC 的工作流程如下:
- 客户端发起调用:客户端程序调用远程过程时,会像调用本地过程一样,传递参数并指定要调用的远程过程的名称;
- 参数封装:客户端将调用的参数封装成一个消息,包括要调用的远程过程的名称和参数值;
- 消息传输:客户端通过网络将封装好的消息发送给服务器端;
- 服务器端接收消息:服务器端接收到消息后,解析消息,获取要调用的远程过程的名称和参数值;
- 远程过程调用:服务器端根据接收到的消息,调用对应的远程过程,并传递参数值;
- 执行过程:服务器端执行远程过程,并根据参数值进行相应的计算或操作;
- 结果封装:服务器端将计算结果封装成一个消息,包括结果值;
- 消息传输:服务器端通过网络将封装好的消息发送给客户端;
- 客户端接收结果:客户端接收到结果消息后,解析消息,获取结果值;
- 结果处理:客户端根据接收到的结果值进行相应的处理,完成远程过程调用。
31 TCP 中 SYN 攻击是什么?如何防止?
SYN 攻击方式最直接的表现就会把 TCP 半连接队列打满,这样当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。
避免 SYN 攻击方式,可以有以下四种方法:
- 调大 netdev_max_backlog;
- 增大 TCP 半连接队列;
- 开启 tcp_syncookies;
- 减少 SYN+ACK 重传次数
32 简述 TCP 的报文头部结构
- 32位确认号:用作另外一方发送来的TCP报文段的响应。其值就是收到的TCP报文段的序号值加1。
- 4位头部长度:标识该TCP头部有多少个32bit字(4字节)。4位最大能表示15,所以TCP头部最长是60字节。
- URG:表示紧急指针是否有效
- ACK: 确认号是否有效。
- PSH:提示接收端应用程序应该立即从TCP接受缓冲区读走数据,为之后的接受的数据腾出位置。
- RST:表示要求对方重写建立连接。复位报文端。
- SYN:表示建立一个连接。同步报文段。
- FIN:表示通知对方本端要关闭连接了。结束报文端
- 16位窗口大小:是TCP流量控制的一个手段。它告诉对方本段的TCP接受缓冲区的情况,控制对方的发送的速度。
- 16位校验和:由发送端填充,接收端对TCP报文端执行CRC算法以校验TCP报文段在传输过程是否损坏。(数据和头部全部校验的。)
- 16位紧急指针:发送端向接受端发送紧急数据使用的。
33 TCP的拥塞控制具体是怎么实现的?UDP有拥塞控制吗?
慢开始、拥塞控制、快重传、快恢复
慢开始和拥塞控制
快重传和快恢复
UDP没有拥塞控制
34 什么是中间人攻击?如何防止攻击?
中间人(MiTM) 攻击是一种网络攻击,恶意行为者会在双方不知情的情况下拦截并可能改变双方之间的通信。在这种攻击中,攻击者将自己置于发送者和接收者之间,有效地“窃听”通信或操纵正在交换的数据。
防止攻击的方法
- 避免不安全的公共 Wi-Fi 网络:公共 Wi-Fi 网络通常未加密,容易受到 MiTM 攻击。连接到此类网络时最好避免访问敏感信息或使用安全帐户。
- 使用虚拟专用网络 (VPN):VPN 会加密您的互联网流量,提供额外的安全和隐私层,尤其是在使用公共网络时。
- 使用安全和加密的通信渠道:确保您访问的网站使用 HTTPS 加密,尤其是在传输密码或财务详细信息等敏感信息时。
- 保持您的设备和软件最新:定期更新您的操作系统、网络浏览器和安全软件,以减少攻击者可能利用的漏洞。
- 验证数字证书:访问需要身份验证的网站或服务时,请确保所提供的数字证书有效且由受信任的证书颁发机构颁发。
- 警惕可疑链接和附件:避免点击来自未知或不受信任来源的链接或下载附件,因为它们可能会导致恶意网站或触发恶意软件的安装。
35 简述什么是 XSS 攻击以及 CSRF 攻击?
跨站脚本(XSS)
Cross Site Scripting,为了和层叠样式表(Cascading Style Sheet,CSS)有所区分
- 不需要你做任何的登录认证,它会通过合法的操作(比如在
url
中输入、在评论框中输入),向你的页面注入脚本(可能是js
、hmtl
代码块等)。
跨站请求伪造(CSRF)
CSRF (Cross-site request forgery,跨站请求伪造)
- 登录受信任网站
A
,并在本地生成Cookie
。(如果用户没有登录网站A
,那么网站B
在诱导的时候,请求网站A
的api
接口时,会提示你登录)。 - 在不登出
A
的情况下,访问危险网站B
(其实是利用了网站A
的漏洞)。
CSRF和XSS都是客户端攻击,它们滥用同源策略,利用web应用程序和受害用户之间的信任关系。XSS和跨站脚本攻击允许攻击者破坏合法用户与任何易受攻击的应用程序的交互。
36 如何防止传输内容被篡改?
使用HTTPS(超文本传输安全协议)进行通信,它在HTTP的基础上加入了SSL/TLS协议,可以提供加密和身份验证服务。这样可以确保数据在传输过程中不被窃听或篡改。
37 如何设计 API 接口使其实现幂等性?
幂等性是指无论对于一个系统操作执行一次还是多次,最终的结果都是一样的。在 REST API 设计中,一个幂等性的操作将始终产生相同的结果,无论它被执行多少次。
使用 HTTP 方法
HTTP 方法中的 GET、HEAD、OPTIONS 方法不会对服务器上的资源状态产生副作用,所以这些方法都是幂等的。
而 PUT、DELETE、POST 方法可能会对服务器上的资源状态产生副作用,所以必须谨慎使用。为了保证幂等性,可以在 API 设计中使用 PUT 方法来更新资源,DELETE 方法来删除资源,POST 方法用于创建资源。在使用 PUT 和 DELETE 方法时,需要考虑请求的 idempotent 性,以确保它们不会影响服务器上的资源状态。
建立唯一约束
幂等性的另一个重要方面是确保请求中包含唯一标识符,例如一个 UUID 或者其他全局唯一标识符。使用这些唯一标识符来建立约束,确保只有一个实例能够被创建或更新。
检查状态
在设计幂等性 REST API 时,需要考虑请求的状态,并确保它们不会产生副作用。这包括检查资源的当前状态,以确保它们未被更改,以及验证请求参数的正确性,以确保它们不会导致错误。
38 简述 BGP 协议和 OSPF 协议的区别
1、类型不同
OSPF是无类别链路状态路由协议,属于IGP,工作也在一个AS内;
BGP是无类别路径矢量路由协议,属于EGP,工作在AS间
2、作用不同
OSPF是用来发现、计算路由的;
BGP是用来传递、控制路由的
39 HTTP 是无状态的吗?需要保持状态的场景应该怎么做?
HTTP是一种无状态协议,即服务器不保留与客户交易时的任何状态。
也就是说,上一次的请求对这次的请求没有任何影响,服务端也不会对客户端上一次的请求进行任何记录处理。
两种用于保持HTTP状态的技术,一个是 Cookie,而另一个则是 Session。
40 从系统层面上,UDP如何保证尽量可靠?
做到以下4点,就可以保证UDP协议可靠:
-
①不要TCP的三次握手和四次挥手。
-
②发送方每次发出的数据进行编号,同时保持顺序的正确。
-
③每次接收方接收到数据,发出应答信号。同时发送方在规定的时间检测是否接收到应答,如果没有接收到应答,重发,三次后还未收到应答直接判断发送失败。
-
④发送数据时,发送方增加校验位。如果接收方校验出错,请求重发。
41 简述 TCP 协议的延迟 ACK 和累计应答
一、ACK定义
TCP协议中,接收方成功接收到数据后,会回复一个ACK数据包,表示已经确认接收到ACK确认号前面的所有数据。
ACK字段长度为32位,能表示0~2^32-1之间的值。二、ACK作用
发送方在一定时间内没有收到服务端的ACK确认包后,就会重新发送TCP数据包。发送方收到了ACK,表明接收方已经接收到数据,保证了数据的可靠达到。三、ACK机制
接收方在接收到数据后,不是立即会给发送方发送ACK的。这可能由以下原因导致:
- 收到数据包的序号前面还有需要接收的数据包。因为发送方发送数据时,并不是需要等上次发送数据被Ack就可以继续发送TCP包,而这些TCP数据包达到的顺序是不保证的,这样接收方可能先接收到后发送的TCP包(注意提交给应用层时是保证顺序的)。
- 为了降低网络流量,ACK有延迟确认机制。
- ACK的值到达最大值后,又会从0开始。
四、ACK延迟确认机制
接收方在收到数据后,并不会立即回复ACK,而是延迟一定时间。一般ACK延迟发送的时间为200ms,但这个200ms并非收到数据后需要延迟的时间。系统有一个固定的定时器每隔200ms会来检查是否需要发送ACK包。这样做有两个目的。
- 这样做的目的是ACK是可以合并的,也就是指如果连续收到两个TCP包,并不一定需要ACK两次,只要回复最终的ACK就可以了,可以降低网络流量。
- 如果接收方有数据要发送,那么就会在发送数据的TCP数据包里,带上ACK信息。这样做,可以避免大量的ACK以一个单独的TCP包发送,减少了网络流量。
42 SSL握手流程为什么要使用非对称秘钥?
解密密钥(私钥)是秘密的。这样,只有预期的接收者才能解密该消息。
43 简述 WebSocket 是如何进行传输的
WebSocket 连接允许客户端和服务器之间进行全双工通信,
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,通过TCP三次握手建立连接,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
44 traceroute 有什么作用?
当源主机A向目标主机B发送消息,发现消息无法送达。此时,可能是某个中间节点发生了问题,比如路由器02因负载过高产生了丢包。
此时,可以通过traceroute进行初步的检测,定位网络包在是在哪个节点丢失的,之后才可以进行针对性的处理。
45 如何解决 TCP 传输丢包问题?
首先TPC为什么会丢包?
TCP是基于不可靠的网路实现可靠传输,肯定会存在丢包问题。
如果在通信过程中,发现缺少数据或者丢包,那边么最大的可能性是程序发送过程或者接受过程中出现问题。
为了满足TCP协议不丢包。TCP协议有如下规定:
- 数据分片:发送端对数据进行分片,接受端要对数据进行重组,由TCP确定分片的大小并控制分片和重组
- 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认
- 超时重发:发送方在发送分片时设置超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片数据
- 滑动窗口:TCP连接的每一方的接受缓冲空间大小固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出
- 失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;
- 重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;
- 数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验或有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发
46 简述在四层和七层网络协议中负载均衡的原理
四层负载均衡工作在OSI模型中的第四层(传输层),主要基于IP地址和端口进行负载均衡。而七层负载均衡则工作在OSI模型中的第七层(应用层),主要基于应用层的信息,如URL、HTTP头部等进行负载均衡。
- 四层负载均衡,也就是主要通过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。
- 七层负载均衡,也称为“内容交换”,也就是主要通过报文中的真正有意义的应用层内容,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。
47 简述 TCP 半连接发生场景
- 半连接队列,也称 SYN 队列
服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来。
48 什么是 SYN flood,如何防止这类攻击?
SYN Flood是互联网上最原始、最经典的DDoS(Distributed Denial of Service)攻击之一。它利用了TCP协议的三次握手机制,攻击者通常利用工具或者控制僵尸主机向服务器发送海量的变源IP地址或变源端口的TCP SYN报文,服务器响应了这些报文后就会生成大量的半连接,当系统资源被耗尽后,服务器将无法提供正常的服务。
cookie源认证:带上特定的sequence number (记为cookie)。真实的客户端会返回一个ack 并且Acknowledgment number 为cookie+1。 而伪造的客户端,将不会作出响应。这样我们就可以知道那些IP对应的客户端是真实的,将真实客户端IP加入白名单。下次访问直接通过,而其他伪造的syn报文就被拦截。
reset认证:
防护设备收到syn后响应syn_ack,将Acknowledgement number (确认号)设为特定值(记为cookie)。当真实客户端收到这个报文时,发现确认号不正确,将发送reset报文,并且sequence number 为cookie + 1。 而伪造的源,将不会有任何回应。这样我们就可以将真实的客户端IP加入白名单。
49 简述 HTTP 报文头部的组成结构
HTTP 请求报文由请求行、请求头、空行和请求包体(body)组成。
50 为什么需要序列化?有什么序列化的方式?
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。
序列化的使用情况
- 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
- 当你想用套接字在网络上传送对象的时候;
- 当你想通过RMI传输对象的时候;
序列化的方式
JDK(不支持跨语言)、JSON、XML、Hessian、Kryo(不支持跨语言)、Thrift、Protostuff、FST(不支持跨语言)
51 TCP 如何实现数据有序性?
TCP协议将数据切分为多个小片段(数据被划分为合理长度),小片段由头部(header)和数据(payload)组成,为了确保抵达数据的顺序,TCP协议给每个片段的头部(header)都分配了 序列号 ,方便后期按照序列号排序。
52 简述 HTTP 短链接与长链接的区别
一、长连接与短连接:
长连接:客户端与服务端先建立连接,连接建立后不断开,然后再进行报文发送和接收。这种方式下由于通讯连接一直存在。
短连接:客户端与服务端每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。此方式常用于一点对多点通讯。
二、长连接与短连接的操作过程:
短连接的操作步骤是:建立连接——数据传输——关闭连接…建立连接——数据传输——关闭连接
长连接的操作步骤是:建立连接——数据传输…(保持连接)…数据传输——关闭连接
53 简述 iPv4 和 iPv6 的区别
- IPv4是32位IP地址,而IPv6是128位IP地址。
- IPv4是数字地址,用点分隔。IPv6是一个字母数字地址,用冒号分隔。
- IPv4公网地址数量大大小于2^32-1的个数,导致了IPv4公网地址稀缺的问题。IPv6则是128bit的地址,理论上IPv6地址的数据是IPv4的2^96倍。
- IPv6可以通过入网设备的MAC地址生成链路本地地址。
- IPv6对组播和对流的支持比IPv4强。
- IPv6对网络层的数据进行加密。
- IPv6的地址长度从32位增加到了128位,可支持更多的地址需求。
应用层协议:HTTP,FTP,TFTP,TELNET,DNS,EMAIL,SNMP等。
其中DNS,TFTP和SNMP因为一次性传输的数据很少,所以是建立在UDP议之上的;
而HTTP,FTP,TELNET,EMAIL等协议是建立在TCP协议之上的。
网络层和链路层的关系(端点到端点之间传输)
协议规定怎么发,网络层规定发的区域,链路层实现发的通道
IP地址和掩码可以得到网络号,缩小网络区域,网关是下一个跳点
迭代器失效的几种情况
迭代器不允许一边读一边修改
- 当通过迭代器插入一个元素,所有选代器就都失效了
- 当通过迭代器删除一个元素,当前删除位置都后面所有的元素的选代器就都失效了
当通过迭代器更新容器元素以后,要及时对选代器进行更新,insert/erase方法都会返回新位置的选代器
序列式容器迭代器失效
序列式容器:vector、deque
当前元素的iterator被删除后,其后所有元素的迭代器都会失效,因为对其进行erase操作后,其后的每个元素都会向前移动一个位置。vector的erase有返回值,可以会返回下一个有效的迭代器。
void VectorTest()
{
vector<int> vec;
for (int i = 0; i < 5; i++)
{
vec.push_back(i);
}
vector<int>::iterator it;
cout << sizeof(it) << endl;
for (it = vec.begin(); it != vec.end(); )
{
if (*it==3)
{
it = vec.erase(it);//更新迭代器it
}
it++;
}
for (it = vec.begin(); it != vec.end(); it++)
cout << *it << " ";
cout << endl;
}
vector迭代器失效问题总结
- 当执行erase方法时,指向删除节点的迭代器全部失效,指向删除节点之后的全部迭代器也失效
- 当进行push_back()方法时,end操作返回的迭代器肯定失效。
- 当插入(push_back)一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时first和end操作返回的迭代器都会失效。
- 当插入(push_back)一个元素后,如果空间未重新分配,指向插入位置之前的元素的迭代器仍然有效,但指向插入位置之后元素的迭代器全部失效。
deque迭代器失效总结:
- 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用都会失效,但是如果在首尾位置添加元素,迭代器会失效,但是指针和引用不会失效
- 如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器全部失效
- 在其首部或尾部删除元素则只会使指向被删除元素的迭代器失效。
关联式容器迭代器失效
关联式容器:map、set、multimap、multiset
当前元素的iterator被删除后,只有当前元素的迭代器会失效,因为map和set使用红黑树实现,插入删除一个结点不会对其他结点产生影响,但是erase操作无返回值,所以需要采用erase(iterator++)的方式删除迭代器
erase(it++)的执行过程:这句话分三步走,先把iter传值到erase里面,然后iter自增,然后执行erase,所以iter在失效前已经自增了。
void mapTest()
{
map<int, int>m;
for (int i = 0; i < 10; i++)
{
m.insert(make_pair(i, i + 1));
}
map<int, int>::iterator it;
for (it = m.begin(); it != m.end(); )
{
if (it->first==5)
m.erase(it++);
it++;
}
for (it = m.begin(); it != m.end();it++)
{
cout << (*it).first << " ";
}
cout << endl;
}
C++技术面相关提问
1 C++this指针干什么用的?
在C++中对象都是通过一个模板来实现的,针对这个模板中的方法,不同的对象调用时需要明确是谁在调用,使用this指针可以解决这个问题
2 C++的new和delete,什么时候用new[ ]申请,可以用delete释放?
new 和 delete 是运算符
malloc 和free 是C的库
new 不仅可以开辟内存还可以完成初始化操作,malloc仅开辟内存
new的种类
- int *pl = new int(20); //正常开辟
- int *p2 =new(nothrow)int; //不抛异常的开辟
- const int *p3=new const int(40); //开辟一个常量,使用const类型来接受
- int data=0; int *p4= new(&data)int(50); //定位new,在指定地方开辟内存
delete先调用析构,之后再free
new 先开辟内存,在调用构造函数
对于普通的变量来说,new 和delete[]可以混用,但是有自定义的对象就不可以,因为会在返回的数组指针之前开辟一块空间存储对象的个数。自定义的类类型,有析构函数,为了调用正确的析构函数,那么开辟对象数组的时候会多开辟4个字节,记录对象的个数
3 C++的static关键字的作用(我从elf结构,链接过程来回答)?
从面向过程:static可以修饰全局变量、函数=>被static修饰以后,在符号表中,符号的作用城就从golbal所有文件可见,变成local,只有当前文件能看见
局部变量=>.data .bss
从面向对象:static可以修饰成员变量,修饰成员方法(不再产生this指针)
4 C++的继承
类和类之间的关系 两个好处:代码复用、基类生成纯虚函数接口等待派生类重写使用多态
5 C++的继承多态,空间配置器,vector和llst的区别,map,多重map?
多态:静态(编译时期)多态:函数重载、模板、动态(运行时期)多态:虚函数
空间配置器alloactor:容器使用,把内存开辟和对象构造分开,把对象析构和内存释放分开,初始化容器时,不需要构造一些对象,删除对象时,把对象析构掉,不需要释放内存
vector和llst的区别:数组和链表
红黑树:五个性质、插入3种情况(最多旋转2次)、删除4种情况(最多旋转3次)
6 C++如何防止内存泄露?智能指针详述?
内存泄漏:分配的堆内存(没有名字,只能用指针来指向)没有释放,也再没有机会释放了
7 C++如何调用C语言语句?
C和C++生成符号的方式不同,C和C++语言之间的API接口是无法直接调用的,C语言的函数声明必须扩在extern“C”{}里面
8 C++什么时候会出现访问越界?
访问数组越界
容器访问越界
字符串处理,没有添加”0'字符。导致访问字符串的时候越界了
使用类型强转,让一个大类型书(派生类)的指针指向一块小内存(基类对象)了,然后指针解引用,访问的内存就越界了!
9 C++中类的初始化列表?
可以指定对象成员变量的初始化方式,尤其是指定成员对象的构造方式;
10 shared ptr引l用计数存在哪里?
11 STL、map底层、deque底层、vector里的empty()和size()的区别、的数对象?
12编译链接全过程
预编译(#开头的指令,#pramga在连接阶段)——编译(编译过程中符号不分配虚拟地址,因为不知道在哪用)——汇编===》 二进制可重定向obj文件 *.o
链接:
- 所有.o文件段的合并(各个段对应合并),符号表合并后,符号解析(所有符号的引用都要找到符号定义的地方)
- 符号的重定向(链接的核心,解析完成后给符号分配虚拟地址)===》可执行文件
13 初始化全局变量和未初始化全局变量有什么区别?
.dara(初始化,且初始值不为0) .bss(未初始化,初始化为0)
14 构造函数和析构的数可不可以是虚的数,为什么?
15 构造函数和析构的数中能不能抛出异常,为什么?
构造函数不能抛异常,对象创建失败,就不会调用对象的析构函数了析构函数不能抛异常,后面的代码就无法得到执行了
16 宏和内联函数的区别
define、inline
预编译阶段(字符串替换)
编译阶段(在函数调用点,通过函数的实参把函数代码直接展开调用)
宏没有办法调试,inline的数可以调试(debug版本下inline就和普通函数一样,有标准的函数调用开销
#define 可以定义常量,代码块,函数块\
inline只是修饰函数
17 局部变量存在哪里?
18 拷贝构造函数,为什么传引用而不传值?
19 内联函数和普通函数的区别(从反汇编角度来回答)?
20 如何实现一个不可以被继承的类?
派生类的初始化过程:基类构造-派生类构造基类的构造函数私有化
21 什么是纯虚函数;为什么要有纯虚函数:虚函数表放在哪里的?
virtual void fung0=0;纯虚的数=》 抽象类(不能实例化对象的,可以定义指针和引用)
一般定义在基类里面,基类不代表任务实体,它的主要作用之一就是给所有的派生类保留统一的纯虚函数接口,让派生类进行重写,方便的使用多态机制。因为基类不需要实例化。
虚函数表 在 编译阶段产生的!运行时,加载到rodata段
22 说一下C++中的const,const与static的区别?
const定义的叫常量,它的编译方式是:编译过程中,把出现常量名字的地方,用常量的值进行替换
const还可以定义常成员方法,Test*this => const Test *this 普通对象和常对象就都可以调用了!
const和static的区别
面向过程:
const:全局变量,局部变量,形参变量
static:全局变量,局部交量500s:不能修饰函数 static;可以修饰函数
面向对象:
const:常方法/成员变量Test*this m>const Test*this 依赖对象
static:静态方法/成员变量Test*this=>没有了!不依赖于对象 通过类作用城访问
23 四种强制类型转换?
24 解释deque的实现原理
动态开辟的二维数组
#define MAP SIZE 2
#define QUE_SIZE(T) 4096/sizeof(T)
第二维数组默认开辟的大小就是QUE SIZE(int)1024
双端队列 两端都有队头和队尾 两端都可以插入删除O(1)
扩容:把第一维数组按照2倍的方式进行扩容2-4-8-16。。。扩容以后,会把原来的第二维的数组,从新一维数组的第oldsize/2开始存放 (内存利用率好)
25 虚函数、多态
一个类 =》工虚函数 =》编译阶段=》该类产生一张虚函数表=》运行时,加载到.rodata
用指针或者引用=》调用 虚函数 =》指针访问对象的头四个字节vfptr=》yftable中取虚函数的地址,进行动态绑定调用
26 虚析构函数、智能指针
Base *p = new Derive()
delete p;//析构函数的调用,动态绑定
智能指针:管理资源的生命周期
27 异常机制
try...catch 异常栈的展开,可以把代码中所有的异常抛到统一的地方进行处理!
28 早绑定和晚绑定
早绑定(静态绑定):普通函数的调用,用对象调用虚的数cal编译阶段已经知道调用哪个函数
晚绑定(动态绑定):用指针/月引用调用虚函数的时候,都是动态绑定pyfp->ytable->virual addr => call eaX
29 指针和引用的区别
30 智能指针交叉引用问题怎么解决?
定义对象的时候用强智能指针shared_pr,而引用对象的时候用弱智能指针weak_ptr
当通过weak_ptr访问对象成员时,需要先调用weak_ptr的l0ck提升方法,把weak_ptr提升成shared_ptr强智能指针,再进行对象成员调用。
31 重载
重载,因为C++生成函数符号,是依赖函数名字+参数列表
编译到的数调用点时,根据函数名字和传入的实参(个数和类型),和某一个函数重载匹配的话,那么就直接调用相应的的数雷载版本(静态的多态 都是在编译阶段处理的!)
32 讲一下map的底层实现,avl和rbtree有什么区别?
红黑树 av(平衡树 维护平衡 旋转)和rbtree(不是一颗平衡树)
33 假如map的键是类类型,那么map底层是如何调整的?
红黑树[key,value],对key进行比较的,less< key类类型 operator<运算符的重载函数