面试知识汇总

如何介绍项目:

https://www.sohu.com/a/259724527_775404

自我介绍:https://www.jianshu.com/p/008fc86a1f28

4W字的后端面试知识点总结:

https://www.cnblogs.com/aobing/p/12849591.html

后端技术框架:

https://www.cnblogs.com/loren-Yang/p/11073536.html

数据结构

Java HashMap原理:

https://yikun.github.io/2015/04/01/Java-HashMap工作原理及实现/

冒泡排序参考:https://www.cnblogs.com/bigdata-stone/p/10464243.html

Java 主要排序方法为
java.util.Arrays.sort(),对于原始数据类型使用三向切分的快速排序,对于引用类型使用归并排序。对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。

三向切分快速排序对于有大量重复元素的随机数组可以在线性时间内完成排序。是双路(只有大于小于的进阶版本)

缓存算法参考:

https://www.cnblogs.com/hongdada/p/10406902.html

LRU实现方式:

HashMap存value->Node 与DoubleLinkedList保证存删都是O(1)

链表

单链表指的是链表中的元素的指向只能指向链表中的下一个元素或者为空,元素之间不能相互指向。也就是一种线性链表。

双向链表即是这样一个有序的结点序列,每个链表元素既有指向下一个元素的指针,又有指向前一个元素的指针,其中每个结点都有两种指针,即left和right。left指针指向左边结点,right指针指向右边结点。

循环链表指的是在单向链表和双向链表的基础上,将两种链表的最后一个结点指向第一个结点从而实现循环。

表的顺序表示的优点是随机存取表中的任意元素,但是在做插入或删除操作时,需移动大量元素。

表的链式表示,在随机插入元素时没有顺序表示的缺陷,但同时不能对元素进行随机存取。

无锁多线程链表:

使用CAS
因为CAS的互斥性,在若干个线程CAS相同的对象时,只有一个线程会成功,失败的线程就可以以此判定目标对象发生了变更。改进后的代码(代码仅做示例用,不保证正确)

无锁的实现,本质上都会依赖于CAS的互斥性。

但是存在ABA问题,如果CAS之前,pred后的item被移除,又以相同的地址值加进来,但其value变了,此时CAS会成功,但链表可能就不是有序的了。pred->val
< new_item->val > item->val

为了解决这个问题,可以利用指针值地址对齐的其他位来存储一个计数,用于表示pred->next的改变次数。当insert拿到pred时,pred->next中存储的计数假设是0,CAS之前其他线程移除了pred->next又新增回了item,此时pred->next中的计数增加,从而导致insert中CAS失败。

一个无环无向连通图 称为树

哈希表

参考:https://www.cnblogs.com/zzdbullet/p/10512670.html

哈希函数也叫散列函数,它对不同的输出值得到一个固定长度的消息摘要。理想的哈希函数对于不同的输入应该产生不同的结构,同时散列结果应当具有同一性(输出值尽量均匀)和雪崩效应(微小的输入值变化使得输出值发生巨大的变化)。

哈希函数的构造方法 一般都使用 除留余数法 其余在参考

(还有
乘法散列法:乘法散列函数的计算分为几个步骤,用关键字k乘以一个常数A,提取kA的小数部分,然后用槽的个数m乘以这个值,最后向下取整。
全域散列法:我觉得这种算法更像是一种散列函数的综合。当一个使用者恶意地把n个关键字放入一个槽中的时候,复杂度会变成O(n),这不是我们想看到的,因此衍生出了这种思路。)

处理冲突法:

1. 开放定址法

这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi
,将相应元素存入其中。

2. 再哈希法

这种方法是同时构造多个不同的哈希函数:

Hi=RH1(key) i=1,2,…,k

当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

3. 链地址法(拉链法)

这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。

拉链特别长的时候,有什么好办法能够解决的吗?主要是为了解决查询的效率问题,我想过把拉链变为一棵红黑树,除了这个办法以外还有什么好办法吗?

一般是引入rehash机制,通过判断哈希表的桶的比例,当超过1/2等时,就自动扩充哈希表的桶到2倍。这样就可以降低拉链的长度等。java中HashMap
的实现,有个装载因子,默认3/4,如果超过就会自动扩容,然后rehash

例:

拉链法

发生哈希碰撞但是key值不同

4、建立公共溢出区

这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表

Rehash和负载因子

负载因子表示一个散列表的空间的使用程度,有这样一个公式:initailCapacity*loadFactor=HashMap的容量。

所以负载因子越大则散列表的装填程度越高,也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低。

反之,负载因子越小则链表中的数据量就越稀疏,此时会对空间造成烂费,但是此时索引效率高。

HashMap有三个构造函数,可以选用无参构造函数,不进行设置。默认值分别是16和0.75.

官方的建议是initailCapacity设置成2的n次幂,laodFactor根据业务需求,如果迭代性能不是很重要,可以设置大一下。

当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用

initailCapacity,loadFactor会影响到HashMap扩容。

HashMap每次put操作是都会检查一遍
size(hashmap中元素个数)>initailCapacity*loadFactor
是否成立。如果不成立则HashMap扩容为以前的两倍(数组扩成两倍),

然后重新计算每个元素在数组中的位置,然后再进行存储。这是一个十分消耗性能的操作。

所以如果能根据业务预估出HashMap的容量,应该在创建的时候指定容量,那么可以避免resize().

参考:https://www.cnblogs.com/yuanblog/p/4441017.html

Go Map原理

如下图所示,当往map中存储一个kv对时,通过k获取hash值,hash值的低八位和bucket数组长度取余,定位到在数组中的那个下标,hash值的高八位存储在bucket中的tophash中,用来快速判断key是否存在,key和value的具体值则通过指针运算存储,当一个bucket满时,通过overfolw指针链接到下一个bucket。

阅读一下map存储的源码,如下图所示,当往map中存储一个kv对时,通过k获取hash值,hash值的低八位和bucket数组长度取余,定位到在数组中的那个下标,hash值的高八位存储在bucket中的tophash中,用来快速判断key是否存在,key和value的具体值则通过指针运算存储,当一个bucket满时,通过overfolw指针链接到下一个bucket。

具体参考:https://my.oschina.net/renhc/blog/2208417

https://cloud.tencent.com/developer/article/1468799

非常好的解释!

红黑树

红黑树定义和性质

红黑树是一种含有红黑结点并能自平衡的二叉查找树。它必须满足下面性质:

性质1:每个节点要么是黑色,要么是红色。

性质2:根节点是黑色。

性质3:每个叶子节点(NIL)是黑色。

性质4:每个红色结点的两个子结点一定都是黑色。

性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

从性质5又可以推出:

性质5.1:如果一个结点存在黑子结点,那么该结点肯定有两个子结点

红黑树插入一定是红色的!

很好理解
参考:
https://blog.csdn.net/qq_36610462/article/details/83277524

详细参考:https://www.jianshu.com/p/e136ec79235c

或者维基百科 红黑树

应用:

1、广泛用于C++的STL中,Map和Set都是用红黑树实现的;

2、著名的Linux进程调度Completely Fair
Scheduler,用红黑树管理进程控制块,进程的虚拟内存区域都存储在一颗红黑树上,每个虚拟地址区域都对应红黑树的一个节点,左指针指向相邻的地址虚拟存储区域,右指针指向相邻的高地址虚拟地址空间;

3、IO多路复用epoll的实现采用红黑树组织管理sockfd,以支持快速的增删改查;

4、Nginx中用红黑树管理timer,因为红黑树是有序的,可以很快的得到距离当前最小的定时器;

5、Java中TreeMap, TreeSet的实现;

红黑树与B+树区别

参考:https://www.cnblogs.com/tiancai/p/9024351.html

红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree
作为索引结构,这是因为使用 B+ 树访问磁盘数据有更高的性能。

(一)B+ 树有更低的树高

平衡树的树高 O(h)=O(logdN),其中 d 为每个节点的出度。红黑树的出度为 2,而 B+
Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多。

(二)磁盘访问原理(还有范围访问)

操作系统一般将内存和磁盘分割成固定大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次
I/O 就能完全载入一个节点。

如果数据不在同一个磁盘块上,那么通常需要移动制动手臂进行寻道,而制动手臂因为其物理结构导致了移动效率低下,从而增加磁盘数据读取时间。B+
树相对于红黑树有更低的树高,进行寻道的次数与树高成正比,在同一个磁盘块上进行访问只需要很短的磁盘旋转时间,所以
B+ 树更适合磁盘数据的读取。

(三)磁盘预读特性

为了减少磁盘 I/O
操作,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的磁盘旋转时间,速度会非常快。并且可以利用预读特性,相邻的节点也能够被预先载入。

二叉 ALV 红黑树的区别

AVL树是最先发明的自平衡二叉查找树:就是平衡二叉树空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

由于维护ALV这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树。

红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长

1、红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。

2、平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。

RB-Tree是功能、性能、空间开销的折中结果

AVL需要维护从被删除节点到根节点root这条路径上所有节点的平衡,旋转的量级为O(logN),而RB-Tree最多只需要旋转3次实现复衡,只需O(1),所以说RB-Tree删除节点的rebalance的效率更高,开销更小!

基本上主要的几种平衡树看来,红黑树有着良好的稳定性和完整的功能,性能表现也很不错,综合实力强,在诸如STL的场景中需要稳定表现。

前缀树

下面我们来讲一下对于给定的字符串集合{W1, W2, W3, …
WN}如何创建对应的Trie树。其实上Trie树的创建是从只有根节点开始,通过依次将W1, W2,
W3, … WN插入Trie中实现的。所以关键就是之前提到的Trie的插入操作。

具体来说,Trie一般支持两个操作:

1. Trie.insert(W):第一个操作是插入操作,就是将一个字符串W加入到集合中。

2. Trie.search(S):第二个操作是查询操作,就是查询一个字符串S是不是在集合中。

参考:https://www.cnblogs.com/vincent1997/p/11237389.html

经典问题:海量数据处理 - 10亿个数中找出最大的10000个数(top K问题)

海量数据处理参考:https://www.cnblogs.com/linguanh/p/8532641.html

https://blog.csdn.net/djrm11/article/details/87924616

去重:

https://www.jianshu.com/p/f6042288a6e3

topK最大数解决方案:

分治+去重(set/hash/位图bitmap/布隆过滤器)

+ 1.最小最大堆(数据流)

2. Quick Select (当然如果不是数据流的话,使用QuickSelect效率更高
平均时间复杂度降为 O(N))

+合并排序(或quick或堆)

(位图bitmap直接排序了)

topK最大频率:

分治+去重(set/hash/位图bitmap/布隆过滤器)+ HashMap排序遍历 后+大小堆

+合并排序(或quick或堆)

(内存受限原数据文件切割成一个一个小文件,如次啊用hash(x)%M)

top K问题很适合采用MapReduce框架解决,用户只需编写一个Map函数和两个Reduce
函数,然后提交到Hadoop(采用Mapchain和Reducechain)上即可解决该问题。具体而言,就是首先根据数据值或者把数据hash(MD5)后的值按照范围划分到不同的机器上,最好可以让数据划分后一次读入内存,这样不同的机器负责处理不同的数值范围,实际上就是Map。得到结果后,各个机器只需拿出各自出现次数最多的前N个数据,然后汇总,选出所有的数据中出现次数最多的前N个数据,这实际上就是Reduce过程。对于Map函数,采用Hash算法,将Hash值相同的数据交给同一个Reduce
task;对于第一个Reduce函数,采用HashMap统计出每个词出现的频率,对于第二个Reduce
函数,统计所有Reduce task,输出数据中的top K即可。

MapReduce:

参考:https://blog.51cto.com/12445535/2351469

Hadoop分布式文件系统(HDFS)

终极参考:https://www.cnblogs.com/futurehau/p/6041022.html

操作系统

操作系统(Operating
System,简称OS)是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行,操作系统能有效组织和管理系统中的各种软,硬件资源,合理组织计算机系统的工作流程,又控制程序的执行,为用户提供一个良好的操作环境。

作用

通过资源管理,提高计算机系统的效率。

改善人机界面,向用户提供友好的工作环境。

8bit(位)=1Byte(字节)

CPU

(Central Processing
Unit)是一块超大规模的集成电路,主要解释计算机指令以及处理计算机软件中的数据,包括

运算器(算术逻辑运算单元,ALU,Arithmetic Logic Unit)

高速缓冲存储器(Cache)

数据(Data)

总线(Bus)

存储器

存储程序和各种数据,它采用具有两种稳定状态的物理器件来存储信息,日常使用的十进制数必须转换成等值的二进制数才能存入存储器中。计算机中处理的各种字符,例如英文字母、运算符号等,也要转换成二进制代码才能存储和操作。

输入输出设备

向计算机输入和输出数据和信息的设备,能够接收各种各样的数据,既可以是数值型的数据,也可以是各种非数值型的数据。

系统软件和应用软件

系统软件是指控制和协调计算机以及外部设备,支持应用软件开发和运行的系统,是无需用户干预的各种程序的集合,主要功能是调度,监控和维护计算机系统。例如:操作系统和一系列基本工具(比如:编译器,数据库管理,存储器格式化,用户身份验证,网络连接)

应用软件是和系统软件相对的,是用户可以使用的各种程序设计语言,以及用各种程序设计语言编制的应用程序的集合,分为应用软件包和用户程序。例如:办公室软件,互联网软件,多媒体软件,分析软件,协作软件。

并发(concurrency)和并行(parallelism)

互斥:mutex 同时共享:shared memory

一次仅允许一个进程使用的资源称为临界资源:critical section

异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。

内存结构:

  • .text: 存放源代码

  • .rodata: 存放常量

  • .data: 存放初始化了的全局变量和静态变量

  • .bss: 存放了未初始化的全局变量和静态变量

  • .heap: 存放使用malloc, realloc, free等函数控制的变量

  • .stack: 函数调用时使用栈来保存函数现场,局部变量也存放在栈中

大小端

例如,假设从内存地址 0x0000 开始有以下数据:
0x0000 0x0001 0x0002 0x0003
0x12 0x34 0xab 0xcd

对于0x12345678的存储:

小端模式:(从低字节到高字节)
地位地址 0x78 0x56 0x34 0x12 高位地址

大端模式:(从高字节到低字节)
地位地址 0x12 0x34 0x56 0x78 高位地址

从左往右地址在增加所以:

主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

a)
Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

b)
Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

内核态与用户态

操作系统需要两种CPU状态

内核态(Kernel Mode):运行操作系统程序,操作硬件

用户态(User Mode):运行用户程序

指令划分

特权指令:只能由操作系统使用、用户程序不能使用的指令。 举例:启动I/O 内存清零
修改程序状态字 设置时钟 允许/禁止终端 停机

非特权指令:用户程序可以使用的指令。 举例:控制转移 算数运算 取数指令
访管指令(使用户程序从用户态陷入内核态)

特权环:R0、R1、R2和R3

R0相当于内核态,R3相当于用户态;(Intel的第1环和第2环的意图是使操作系统将设备驱动程序置于该级别,因此它们具有特权,但与内核代码的其余部分有所区别。)

具体参考!:https://www.cnblogs.com/gizing/p/10925286.html

通常来说,以下三种情况会导致用户态到内核态的切换

系统调用

这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。比如前例中fork()实际上就是执行了一个创建新进程的系统调用。

而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int
80h中断。

用户程序通常调用库函数,由库函数再调用系统调用,因此有的库函数会使用户程序进入内核态(只要库函数中某处调用了系统调用),有的则不会。

异常

当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

外围设备的中断

当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,

如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。

堆与栈

栈区(stack)——由编译器自动分配释放
,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆区(heap)———般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收
。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

(栈应该被看成一个短期存储数据的地方,存在在栈中的数据项没有名字,只是按照后进先出来操作罢了。栈经常可以用来在寄存器紧张的情况下,临时存储一些数据,并且十分安全。当寄存器空闲后,我们可以从栈中弹出该数据,供寄存器使用。这种临时存放数据的特性,使得它经常用来存储局部变量,函数参数,上下文环境等。

CPU体系结构

参考:

https://blog.csdn.net/yhb1047818384/article/details/79604976?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160898868516780279121712%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=160898868516780279121712&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-3-79604976.pc_search_result_no_baidu_js&utm_term=CPU%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84%E4%B9%8Bcache%E5%B0%8F%E7%BB%93

Cache(可以LRU)

内存就是主存

(本地磁盘)硬盘属于外存。

计算机存储器按照用途可分为主存储器和辅助存储器,主存储器又称内存储器(简称内存),属辅助存储器又称外存储器(简称外存)。外存通常是磁性介质或光盘,像硬盘,软盘,磁带,CD等,能长期保存信息,并且不依赖于电来保存信息,但是由机械部件带动,速度与CPU相比就显得慢的多。内存指的就是主板上的存储部件,是CPU直接与之沟通,并用其存储数据的部件,存放当前正在使用的数据和程序,它的物理实质就是一组或多组具备数据输入输出和数据存储功能的集成电路,内存只用于暂时存放程序和数据,一旦关闭电源或发生断电,其中的程序和数据就会丢失。

内存和外存的区别:内存处理速度快、存储容量小、断电后信息丢失;外存处理速度慢、存储容量大、信息永久保存。

句柄:https://blog.csdn.net/wyx0224/article/details/83385168

进程&线程

进程是资源分配的基本单位。

线程是独立调度的基本单位。

一个进程中可以有多个线程,它们共享进程资源。

进程可以说是一个程序

进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。

上图为线程状态机

例:线程所请求的I/O完成,故被唤醒,即从阻塞态到就绪态。

进程与线程的区别总结

线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight
Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight
Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈stack和程序计数器(PC),线程之间切换的开销小。

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

进程和线程区别详细参考:

https://thinkwon.blog.csdn.net/article/details/102021274?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai

JVM中一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8
之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。

多进程:操作系统中同时运行的多个程序

多线程:在同一个进程中同时运行的多个任务

Thread (线程) can be implemented in both Kernel Space and User Space

(程序计数器PC:
为了保证程序(在操作系统中理解为进程)能够连续地执行下去,CPU必须具有某些手段来确定下一条指令的地址。而程序计数器正是起到这种作用,所以通常又称为指令计数器。

在程序开始执行前,必须将它的起始地址,即程序的一条指令所在的内存单元地址送入PC,因此程序计数器(PC)的内容即是从内存提取的第一条指令的地址。当执行指令时,CPU将自动修改PC的内容,即每执行一条指令PC增加一个量,这个量等于指令所含的字节数,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序来执行的,所以修改的过程通常只是简单的对PC加1。

当程序转移时,转移指令执行的最终结果就是要改变PC的值,此PC值就是转去的地址,以此实现转移。有些机器中也称PC为指令指针IP(Instruction
Pointer)。)

进程调度算法(操作系统调度方式)

1. 批处理系统

批处理系统没有太多的用户操作,在该系统中,调度算法目标是保证吞吐量和周转时间(从提交到终止的时间)。

先来先服务 first-come first-serverd(FCFS) 短作业优先 shortest job first(SJF)
最短剩余时间优先 shortest remaining time next(SRTN)

2. 交互式系统

交互式系统有大量的用户交互操作,在该系统中调度算法的目标是快速地进行响应。

时间片轮转+优先级调度(抽奖)+多级反馈队列

多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如
1,2,4,8,…。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换
7 次。

每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。

可以将这种调度算法看成是时间片轮转调度算法和优先级调度算法的结合。

3. 实时系统

实时系统要求一个请求在一个确定时间内得到响应。

分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。

参考:https://blog.csdn.net/qq_35642036/article/details/82809812

进程通信

进程同步和通信参考:

http://cyc2018.gitee.io/cs-notes/#/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%20-%20%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86?id=%e8%bf%9b%e7%a8%8b%e5%90%8c%e6%ad%a5

缺页中断:page fault.

进程同步:

1. 临界区

对临界资源进行访问的那段代码称为临界区。

为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。

2. 同步与互斥

同步:多个进程因为合作产生的直接制约关系,使得进程有一定的先后执行关系。

互斥:多个进程在同一时刻只有一个进程能进入临界区。

3. 信号量

信号量(Semaphore)是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P
和 V 操作。

down : 如果信号量大于 0 ,执行 -1 操作;如果信号量等于
0,进程睡眠,等待信号量大于 0;

up :对信号量执行 +1 操作,唤醒睡眠的进程让其完成 down 操作。

down 和 up
操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。

如果信号量的取值只能为 0 或者 1,那么就成为了 互斥量(Mutex) ,0
表示临界区已经加锁,1 表示临界区解锁。

4. 管程

使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。

比如Java里的monitor

进程同步与进程通信很容易混淆,它们的区别在于:

进程同步:控制多个进程按一定顺序执行;

进程通信:进程间传输信息。

进程通信:

1. 管道

只支持半双工通信(单向交替传输);

只能在父子进程或者兄弟进程中使用。

2. FIFO

也称为命名管道,去除了管道只能在父子进程中使用的限制。

FIFO 常用于客户-服务器应用程序中,FIFO
用作汇聚点,在客户进程和服务器进程之间传递数据。

3. 消息队列

相比于 FIFO,消息队列具有以下优点:

消息队列可以独立于读写进程存在,从而避免了 FIFO
中同步管道的打开和关闭时可能产生的困难;

避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;

读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。

4. 信号量

它是一个计数器,用于为多个进程提供对共享数据对象的访问。

5. 共享存储

允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种
IPC。

需要使用信号量用来同步对共享存储的访问。

多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI
共享内存不是使用文件,而是使用内存的匿名段。

6. 套接字 Socket

与其它通信机制不同的是,它可用于不同机器间的进程通信。

协程

协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。

协程是一种轻量级的线程

本质上协程就是用户空间下的线程

协程拥有自己的寄存器register上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源。

如果把线程/进程当作虚拟“CPU”,协程即跑在这个“CPU”上的线程。

协程的特点:

占用的资源更少。

所有的切换和调度都发生在用户态。

不管是进程还是线程,每次阻塞、切换都需要陷入系统调用,先让CPU跑操作系统的调度程序,然后再由调度程序决定该跑哪一个线程。而且由于抢占式调度执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题。

因为协程可以在用户态显示控制切换

协程的优点是可以用同步的处理方式实现异步回调的性能

一个线程的多个协程的运行是串行的。如果是多核CPU,多个进程或一个进程内的多个线程是可以并行运行的,但是一个线程内协程却绝对是串行的,无论CPU有多少个核。毕竟协程虽然是一个特殊的函数,但仍然是一个函数。一个线程内可以运行多个函数,但这些函数都是串行运行的。当一个协程运行时,其它协程必须挂起。

进程、线程、协程的对比

协程既不是进程也不是线程,协程仅仅是一个特殊的函数,协程它进程和线程不是一个维度的。

一个进程可以包含多个线程,一个线程可以包含多个协程。

一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。

协程与进程一样,切换是存在上下文切换问题的。

上下文切换

进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。

线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户无感知。线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”,
切换效率中等。

协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。

不需要进程/线程上下文切换的“线程”,协程

所以协程的开销远远小于线程的开销因为不需要上下文切换

Goroutine

参考:https://segmentfault.com/a/1190000018150987

Goroutines和线程详解:http://shouce.jb51.net/gopl-zh/ch9/ch9-08.html

Go语言最大的特色就是从语言层面支持并发(Goroutine),Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine:主Goroutine。当程序启动时,它会自动创建。

协程不是并发的,而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程。同时它可以运行在一个或多个线程上。

Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating
sequential processes)并发模型。

CSP讲究的是“以通信的方式来共享内存”。

请记住下面这句话:

DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING.

“不要以共享内存的方式来通信,相反,要通过通信来共享内存。”

Go的CSP并发模型,是通过goroutine和channel来实现的:

goroutine
是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。

channel是Go语言中各个并发结构体(goroutine)之前的通信机制。
通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。

在通信过程中,传数据channel <-
data和取数据<-channel必然会成对出现,因为这边传,那边取,两个goroutine之间才会实现通信。

而且不管传还是取,必阻塞,直到另外的goroutine传或者取为止。

(每一个OS线程都有一个固定大小的内存块(一般会是2MB)来做栈,这个栈会用来存储当前正在被调用或挂起(指在调用其它函数时)的函数的内部变量。这个固定大小的栈同时很大又很小。因为2MB的栈对于一个小小的goroutine来说是很大的内存浪费,比如对于我们用到的,一个只是用来WaitGroup之后关闭channel的goroutine来说。而对于go程序来说,同时创建成百上千个goroutine是非常普遍的,如果每一个goroutine都需要这么大的栈的话,那这么多的goroutine就不太可能了。除去大小的问题之外,固定大小的栈对于更复杂或者更深层次的递归函数调用来说显然是不够的。修改固定的大小可以提升空间的利用率允许创建更多的线程,并且可以允许更深的递归调用,不过这两者是没法同时兼备的。

相反,一个goroutine会以一个很小的栈开始其生命周期,一般只需要2KB。一个goroutine的栈,和操作系统线程一样,会保存其活跃或挂起的函数调用的本地变量,但是和OS线程不太一样的是一个goroutine的栈大小并不是固定的;栈的大小会根据需要动态地伸缩。而goroutine的栈的最大值有1GB,比传统的固定大小的线程栈要大得多,尽管一般情况下,大多goroutine都不需要这么大的栈。)

我们先从线程讲起,无论语言层面何种并发模型,到了操作系统层面,一定是以线程的形态存在的。而操作系统根据资源访问权限的不同,体系架构可分为用户空间和内核空间;内核空间主要操作访问CPU资源、I/O资源、内存资源等硬件资源,为上层应用程序提供最基本的基础资源,用户空间呢就是上层应用程序的固定活动空间,用户空间不可以直接访问资源,必须通过“系统调用”、“库函数”或“Shell脚本”来调用内核空间提供的资源。

我们现在的计算机语言,可以狭义的认为是一种“软件”,它们中所谓的“线程”,往往是用户态的线程,和操作系统本身内核态的线程(简称KSE),还是有区别的。

线程模型:

用户级线程模型

内核级线程模型

两级线程模型

Go线程实现模型MPG

(M (thread)也可指内核线程)

M指的是Machine,一个M直接关联了一个内核线程。由操作系统管理。

P指的是”processor”,代表了M所需的上下文环境,也是处理用户级代码逻辑的处理器。它负责衔接M和G的调度上下文,将等待执行的G与M对接。(由环境变量中的GOMAXPROCS决定,通常来说它是和核心数对应)

G指的是Goroutine,其实本质上也是一种轻量级的线程。包括了调用栈,重要的调度信息,例如channel等。

1、P 何时创建:在确定了 P 的最大数量 n 后,运行时系统会根据这个数量创建 n 个 P。

2、M 何时创建:没有足够的 M 来关联 P 并运行其中的可运行的 G。比如所有的 M
此时都阻塞住了,而 P 中还有很多就绪任务,就会去寻找空闲的
M,而没有空闲的,就会去创建新的 M。

以上这个图讲的是两个线程(内核线程)的情况。一个M会对应一个内核线程,一个M也会连接一个上下文P,一个上下文P相当于一个“处理器”,一个上下文连接一个或者多个Goroutine。为了运行goroutine,线程必须保存上下文。

需要P 上下文的目的,是让我们可以直接放开其他线程,当遇到内核线程阻塞的时候。

(全局runqueue是各个P在运行完自己的本地的Goroutine
runqueue后用来拉取新goroutine的地方。P也会周期性的检查这个全局runqueue上的goroutine,否则,全局runqueue上的goroutines可能得不到执行而饿死。)

参考:https://blog.csdn.net/chenchongg/article/details/87605203

详细参考:

http://www.topgoer.com/并发编程/GMP原理与调度.html

特殊的 M0 和 G0

M0

M0 是启动程序后的编号为 0 的主线程,这个 M 对应的实例会在全局变量 runtime.m0
中,不需要在 heap 上分配,M0 负责执行初始化操作和启动第一个 G, 在之后 M0
就和其他的 M 一样了。

G0

G0 是每次启动一个 M 都会第一个创建的 gourtine,G0 仅用于负责调度的 G,G0
不指向任何可执行的函数,每个 M 都会有一个自己的 G0。在调度或系统调用时会使用 G0
的栈空间,全局变量的 G0 是 M0 的 G0。

均衡的分配工作

按照以上的说法,上下文P会定期的检查全局的goroutine
队列中的goroutine,以便自己在消费掉自身Goroutine队列的时候有事可做。假如全局goroutine队列中的goroutine也没了呢?就从其他运行的中的P的runqueue里偷。

每个P中的Goroutine不同导致他们运行的效率和时间也不同,在一个有很多P和M的环境中,不能让一个P跑完自身的Goroutine就没事可做了,因为或许其他的P有很长的goroutine队列要跑,得需要均衡。

该如何解决呢?

Go的做法倒也直接,从其他P中偷一半!

Goroutine 小结

1、开销小

2、调度性能好

(在Golang的程序中,操作系统级别的线程调度,通常不会做出合适的调度决策。例如在GC时,内存必须要达到一个一致的状态。在Goroutine机制里,Golang可以控制Goroutine的调度,从而在一个合适的时间进行GC
垃圾回收。

在应用层模拟的线程,它避免了上下文切换的额外耗费,兼顾了多线程的优点。简化了高并发程序的复杂度。

缺点:

协程调度机制无法实现公平调度。)

Go面试问题汇总:

https://blog.csdn.net/weixin_34128839/article/details/94488565?utm_medium=distribute.pc_relevant_t0.none-task-blog-searchFromBaidu-1.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-searchFromBaidu-1.not_use_machine_learn_pai

为什么要多线程?

参考:https://www.cnblogs.com/xrq730/p/5060921.html!!!

作用:

(1)发挥多核CPU的优势

(2)防止阻塞

(3)便于建模

数据竞争:

顺序一致性的问题是由于某些程序转换引起的,例如我们的例子中交换了无关变量的访问顺序,这不会改变单线程程序的意图,但是会改变多线程程序的意图(例如例子中允许r1和r2都为0)。

只有当代码允许两个线程同时访问相同的共享数据,并且是以某种冲突的方式访问时(例如当一个线程读取数据的同时另一个线程写入该数据),才有可能察觉到这种程序转换。如果程序强制以特定顺序来访问共享变量,那么我们就无法判断对独立变量的访问是否被重排序,就如同在单线程程序中也无法判断。

无限制地同时访问普通共享变量会让程序变得难以处理,一般需要避免这种情况。坚持完全的顺序一致性对我们没有好处。我们将在下文用单独的一节来讨论这个问题。

惊群效应

惊群效应也有人叫做雷鸣群体效应,不过叫什么,简言之,惊群现象就是多进程(多线程)在同时阻塞等待同一个事件的时候(休眠状态),如果等待的这个事件发生,那么他就会唤醒等待的所有进程(或者线程),但是最终却只可能有一个进程(线程)获得这个时间的“控制权”,对该事件进行处理,而其他进程(线程)获取“控制权”失败,只能重新进入休眠状态,这种现象和性能浪费就叫做惊群。

2.惊群效应到底消耗了什么?

我想你应该也会有跟我一样的问题,那就是惊群效应到底消耗了什么?

(1)、系统对用户进程/线程频繁地做无效的调度,上下文切换系统性能大打折扣。

(2)、为了确保只有一个线程得到资源,用户必须对资源操作进行加锁保护,进一步加大了系统开销。

是不是还是觉得不够深入,概念化?看下面:

*1、上下文切换(context
switch)过高会导致cpu像个搬运工,频繁地在寄存器和运行队列之间奔波,更多的时间花在了进程(线程)切换,而不是在真正工作的进程(线程)上面。直接的消耗包括cpu寄存器要保存和加载(例如程序计数器)、系统调度器的代码需要执行。间接的消耗在于多核cache之间的共享数据。

看一下: wiki上下文切换

*2、通过锁机制解决惊群效应是一种方法,在任意时刻只让一个进程(线程)处理等待的事件。但是锁机制也会造成cpu等资源的消耗和性能损耗。目前一些常见的服务器软件有的是通过锁机制解决的,比如nginx(它的锁机制是默认开启的,可以关闭);还有些认为惊群对系统性能影响不大,没有去处理,比如lighttpd。

解决:

历史上,Linux的accpet确实存在惊群问题,但现在的内核都解决该问题了。即,当多个进程/线程都阻塞在对同一个socket的接受调用上时,当有一个新的连接到来,内核只会唤醒一个进程,其他进程保持休眠,压根就不会被唤醒。

Epoll惊群

主进程创建socket,bind,listen后,将该socket加入到epoll中,然后fork出多个子进程,每个进程都阻塞在epoll_wait上,如果有事件到来,则判断该事件是否是该socket上的事件如果是,说明有新的连接到来了,则进行接受操作。为了简化处理,忽略后续的读写以及对接受返回的新的套接字的处理,直接断开连接。

很多博客中提到,测试表明虽然epoll_wait不会像接受那样只唤醒一个进程/线程,但也不会把所有的进程/线程都唤醒。

解决方案:

这里通常代码加锁的处理机制我就不详述了,来看一下常见软件的处理机制和linux最新的避免和解决的办法

(1)、Nginx的解决:

如上所述,如果采用epoll,则仍然存在该问题,nginx就是这种场景的一个典型,我们接下来看看其具体的处理方法。

nginx的每个worker进程都会在函数ngx_process_events_and_timers()中处理不同的事件,然后通过ngx_process_events()封装了不同的事件处理机制,在Linux上默认采用epoll_wait()。

在主要ngx_process_events_and_timers()函数中解决惊群现象。

(2)、SO_REUSEPORT

Linux内核的3.9版本带来了SO_REUSEPORT特性,该特性支持多个进程或者线程绑定到同一端口,提高服务器程序的性能,允许多个套接字bind()以及listen()同一个TCP或UDP端口,并且在内核层面实现负载均衡。

在未开启SO_REUSEPORT的时候,由一个监听socket将新接收的连接请求交给各个工作者处理

在使用SO_REUSEPORT后,多个进程可以同时监听同一个IP:端口,然后由内核决定将新链接发送给哪个进程,显然会降低每个工人接收新链接时锁竞争

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

参考:https://blog.csdn.net/qq_34337272/article/details/81072874

互斥:每个资源要么已经分配给了一个进程,要么就是可用的。

占有和等待:已经得到了某个资源的进程可以再请求新的资源。

不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。

环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。

最好解决:鸵鸟策略直接忽略 其余参考CSNote

产生死锁的原因主要是:

(1) 因为系统资源不足。

(2) 进程运行推进的顺序不合适。

(3) 资源分配不当等。

产生死锁的四个必要条件:

(1)互斥条件:一个资源每次只能被一个进程使用。

(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

避免死锁:

死锁的预防是通过破坏产生条件来阻止死锁的产生,但这种方法破坏了系统的并行性和并发性。

死锁产生的前三个条件是死锁产生的必要条件,也就是说要产生死锁必须具备的条件,而不是存在这3个条件就一定产生死锁,那么只要在逻辑上回避了第四个条件就可以避免死锁。

避免死锁采用的是允许前三个条件存在,但通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程链,从而避免死锁。该方法支持多个进程的并行执行,为了避免死锁,系统动态的确定是否分配一个资源给请求的进程。

常用的避免死锁的方法:

1、有序资源分配法

2、银行家算法

解决死锁问题的策略:

1、条件一:互斥条件

条件一念一否定的,因为资源的互斥性是由其自身的性质决定的。但是可以采用虚拟设备技术能排除非共享设备死锁的可能。

2、条件二:不剥夺条件

很难实现。系统一般让资源占有者自己主动释放资源,而不是采用抢占的方式。

3、条件三:占有并等待

在资源分配策略上可以采取静态的一次性资源分配的方法来保证死锁不可能发生,这是一种很保守的静态预防死锁的方法,但是资源利用率低下。

4、条件四:环路条件

在进行资源分配前检查是否会出现环路,预测是否可能发生死锁,只要有这种可能就不予以分配。即采用动态分配资源的方法。

总结来看解决死锁的策略有以下几个:

1、采用资源静态分配方法预防死锁。

2、采用资源动态分配、有效的控制分配方法来避免死锁。

3、当死锁发生时检测出死锁,并设法修复。

进程隔离

计算机程序设计中的进程隔离是将不同的软件进程隔离开来,以防止它们访问不属于它们的内存空间。进程隔离的概念通过为某些程序提供不同的特权级别并限制这些程序可以使用的内存,有助于提高操作系统的安全性虽然有许多进程隔离的实现,但它在web浏览器中经常被用来分隔多个选项卡,并在进程失败时保护核心浏览器本身,但这两种方法的目的是限制对系统资源的访问,并将程序隔离到它们自己的虚拟地址空间中。

“Containers are Linux—Linux is Containers.”

参考:https://opensource.com/article/18/1/history-low-level-container-runtimes

LXC主要是利用cgroups与namespace的功能,来向提供应用软件一个独立的操作系统运行环境。cgroups(即Control
Groups)j
Linux内核提供的一种可以限制、记录、隔离进程组所使用的物理资源的机制。而由namespace来责任隔离控制。

Docker

参考:http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html

Docker 把应用程序及其依赖,打包在 image 文件里面。只有通过这个文件,才能生成
Docker 容器。image 文件可以看作是容器的模板。Docker 根据 image
文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。

image 是二进制文件。实际开发中,一个 image 文件往往通过继承另一个 image
文件,加上一些个性化设置而生成。举例来说,你可以在 Ubuntu 的 image
基础上,往里面加入 Apache 服务器,形成你的 image。

Docker 底层的核心技术包括 Linux 上的名字空间( Namespaces) 、 控制组( Control
groups) 、 Union 文

件系统( Union file systems) 和容器格式( Container format) 。

我们知道, 传统的虚拟机通过在宿主主机中运行 hypervisor
来模拟一整套完整的硬件环境提供给虚拟机的

操作系统。 虚拟机系统看到的环境是可限制的, 也是彼此隔离的。
这种直接的做法实现了对资源最完整的

封装, 但很多时候往往意味着系统资源的浪费。 例如, 以宿主机和虚拟机系统都为
Linux 系统为例, 虚拟

机中运行的应用其实可以利用宿主机系统中的运行环境。

所以windows要打开hypervisor

多线程为什么有一个线程出错其他的就卡死

理论上讲,线程挂掉只是触发了 segment fault
,该信号在系统中默认的处理方式是终结该线程所在的进程,如果对该信号进行屏蔽也是可以的。

但是,重点来了,触发 segment fault 的位置如果是
stack,那么只要进程屏蔽了该信号,那么对其他的线程是没有影响;如果触发 segment
fault 的位置如果是
heap、全局变量等线程共享的部分,那么就算屏蔽了该信号,那么其他线程也会出现问题,只是时间上的事情。

总结来说,

1.如果进程不屏蔽 segment fault 信号,一个线程崩溃,所有线程终结。

2.如果屏蔽 segment fault
信号,且线程崩溃的位置是线程私有位置(stack),那么其他线程没有问题。

3.如果屏蔽 segment fault
信号,且线程崩溃的位置是线程共享位置(heap、全局变量等),那么其他线程也会出现问题。

总结1举例:

在第一个线程threadRuntimeExcept发生数组越界之后,线程异常没有捕获,导致线程异常退出。但是子线程的异常并不能传递到主线程(Runable的run方法没有任何throw),所以主线程仍然是可以运行的。问题在于,threadRuntimeExcept这个线程占有了lock这个锁,并在锁被释放之前异常退出了,那么这个锁就永远被占有了,等到第二个线程试图获取锁的时候,它就会一直阻塞在那。

Future虽然可以知道子线程发生了异常,但是却无法处理异常让子线程继续运行,子线程还是会异常退出。

为什么进程一个挂了不会影响其他?

我认为是进程有自己的独立地址空间

Hypervisor

hypervisor:一种运行在物理服务器和操作系统之间的中间层软件,可以允许多个操作系统和应用共享一套基础物理硬件。可以将hypervisor看做是虚拟环境中的“元”操作系统,可以协调访问服务器上的所有物理设备和虚拟机,所以又称为虚拟机监视器(virtual
machine
monitor)。hypervisor是所有虚拟化技术的核心,非中断的支持多工作负载迁移是hypervisor的基本功能。当服务器启动并执行hypervisor时,会给每一台虚拟机分配适量的内存,cpu,网络和磁盘资源,并且加载所有虚拟机的客户操作系统。

参考:https://blog.csdn.net/bbc955625132551/article/details/71597863

CPU中断

这里简要的介绍下中断的分类。

内核与硬件通信的方式:轮询和中断。轮询(轮询(Polling)是一种CPU决策如何提供周边设备服务的方式,又称“程控输入输出”(Programmed
I/O)。轮询法的概念是:由CPU定时发出询问,依序询问每一个周边设备是否需要其服务,有即给予服务,服务结束后再问下一个周边,接着不断周而复始。)速度太慢,中断被大量采用。

从不同的角度来说,中断(Interrupt)可以有三种分类方法。

中断可以分为同步中断(synchronous)和异步中断(asynchronous)。

中断可分为硬中断和软中断。

中断可分为可屏蔽中断(Maskable interrupt)和非屏蔽中断(Nomaskable interrupt)。

同步中断是在指令执行时由CPU主动产生的,受到CPU控制,其执行点是可控的。

异步中断是CPU被动接收到的,由外设发出的电信号引起,其发生时间不可预测。

一般来说,同步中断又称为异常(exception),异步中断称为中断(interrupt)。

中断(Interruption),也称外中断,指来自CPU执行指令以外的事件的发生,如设备发出的I/O结束中断,表示设备输入/输出处理已经完成,希望处理机能够向设备发下一个输入
/
输出请求,同时让完成输入/输出后的程序继续运行。时钟中断,表示一个固定的时间片已到,让处理机处理计时、启动定时运行的任务等。这一类中断通常是与当前程序运行无关的事件,即它们与当前处理机运行的程序无关。

异常(Exception),也称内中断、例外或陷入(Trap),指源自CPU执行指令内部的事件,如程序的非法操作码、
地址越界、算术溢出、虚存系统的缺页以及专门的陷入指令等引起的事件。对异常的处理一般要依赖于当前程序的运行现场,而且异常不能被屏蔽,一旦出现应立即处理。关于内中断和外中断的联系与区别如图1-2所示。

中断可分为可屏蔽中断(Maskable Interrupt)和非可屏蔽中断(Nomaskable
Interrupt)。

异常可分为故障(fault)、陷阱(trap)和终止(abort)三类。

中断也是需要CPU
check的!也就是说:处理器在每个指令周期都会去查看中断寄存器,如果中断寄存器(source
pending register)有效,也就是发生了中断,那么cpu会执行一系列与中断相关的操作。

CPU上下文(context)切换(switch)

CPU 寄存器(register)和程序计数器(Program Counter,PC)就是 CPU
上下文,因为它们都是 CPU 在运行任何任务前,必须的依赖环境。

CPU 寄存器是 CPU 内置的容量小、但速度极快的内存。

程序计数器则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。

就是先把前一个任务的 CPU 上下文(也就是 CPU
寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。

系统调用:

简单来说,系统调用就是用户程序和硬件设备之间的桥梁。

用户程序在需要的时候,通过系统调用来使用硬件设备。

意义在于:

1)用户程序通过系统调用来使用硬件,而不用关心具体的硬件设备,这样大大简化了用户程序的开发。

2)系统调用使得用户程序有更好的可移植性。

3)系统调用使得内核能更好的管理用户程序,增强了系统的稳定性。

4)系统调用有效的分离了用户程序和内核的开发。

所以,一次系统调用的过程,其实是发生了两次 CPU
上下文切换。(用户态-内核态-用户态)

不过,需要注意的是,系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。这跟我们通常所说的进程上下文切换是不一样的:进程上下文切换,是指从一个进程切换到另一个进程运行;而系统调用过程中一直是同一个进程在运行

所以,系统调用过程通常称为特权模式切换,而不是上下文切换。系统调用属于同进程内的
CPU 上下文切换。但实际上,系统调用过程中,CPU 的上下文切换还是无法避免的。

进程上下文切换

跟系统调用又有什么区别呢

首先,进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。

对同一个 CPU
来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。同样道理,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便尽可能快的执行结束。

发生进程上下文切换的场景

为了保证所有进程可以得到公平调度,CPU
时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,就会被系统挂起,切换到其它正在等待
CPU 的进程运行。

进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。

当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度。

当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行

发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序。

线程上下文切换

线程与进程最大的区别在于:线程是调度的基本单位,而进程则是资源拥有的基本单位。说白了,所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。

所以,对于线程和进程,我们可以这么理解:

当进程只有一个线程时,可以认为进程就等于线程。

当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。

另外,线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。

发生线程上下文切换的场景

前后两个线程属于不同进程。此时,因为资源不共享,所以切换过程就跟进程上下文切换是一样。

前后两个线程属于同一个进程。此时,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据

中断上下文切换

为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,转而调用中断处理程序,响应设备事件。而在打断其他进程时,就需要将进程当前的状态保存下来,这样在中断结束后,进程仍然可以从原来的状态恢复运行。

跟进程上下文不同,中断上下文切换并不涉及到进程的用户态。所以,即便中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存、全局变量等用户态资源。中断上下文,其实只包括内核态中断服务程序执行所必需的状态,包括
CPU 寄存器、内核堆栈、硬件中断参数等。

对同一个 CPU
来说,中断处理比进程拥有更高的优先级,所以中断上下文切换并不会与进程上下文切换同时发生。同样道理,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便尽可能快的执行结束。

另外,跟进程上下文切换一样,中断上下文切换也需要消耗
CPU,切换次数过多也会耗费大量的
CPU,甚至严重降低系统的整体性能。所以,当你发现中断次数过多时,就需要注意去排查它是否会给你的系统带来严重的性能问题。

(1)中断上文:硬件通过中断触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。中断上文可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被中断的进程环境。

(2)中断下文:执行在内核空间的中断服务程序。

什么情况下进行用户态到内核态的切换

1.进程上下文主要是异常处理程序和内核线程。内核之所以进入进程上下文是因为进程自身的一些工作需要在内核中做。例如,系统调用是为当前进程服务的,异常通常是处理进程导致的错误状态等。

2.中断上下文是由于硬件发生中断时会触发中断信号请求,请求系统处理中断,执行中断服务子程序。

参考:

https://blog.csdn.net/czd3355/article/details/85118727

还可以参考:

https://blog.csdn.net/qq_38500662/article/details/80598486

优化

为了避免频繁的上下文切换,还有一种异步非阻塞的开发模型。那就是用一个进程或线程去接收一大堆用户的请求
所谓的I/O复用
然后通过IO多路复用的方式来提高性能(进程或线程不阻塞,省去了上下文切换的开销)

或者协程

内存管理

虚拟内存:

虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。

为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。

从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序成为可能。例如有一台计算机可以产生
16 位地址,那么一个程序的地址空间范围是 0~64K。该计算机只有 32KB
的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。

页的作用:

每个进程都有自己独立的虚存空间,所以操作系统需要为每个进程保存一个页表

进程切换的时候操作系统就会把即将调度运行的那个进程的页表加载MMU,完成地址空间的切换。

页还可以支持更多的虚拟内存

分页系统地址映射

内存管理单元(MMU)管理着地址空间和物理内存的转换,其中的页表(Page
table)存储着页(程序地址空间)和页框(物理内存空间)的映射表。

一个虚拟地址分成两个部分,一部分存储页面号,一部分存储偏移量。

(读取表项内容为(110 1) 页表项最后一位表示是否存在于内存中,1 表示存在)

页面置换算法

在程序运行过程中,如果要访问的页面不在内存中,就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间,系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。

页面置换算法和缓存淘汰策略类似,可以将内存看成磁盘的缓存。在缓存系统中,缓存的大小有限,当有新的缓存到达时,需要淘汰一部分已经存在的缓存,这样才有空间存放新的缓存数据。

页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。

1. 最佳 OPT, Optimal replacement algorithm

所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。

是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。

2. 最近最久未使用 LRU, Least Recently Used

虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU
将最近最久未使用的页面换出。

实现的方法LRU :HashMap存的pair是值-Node + DoubleLinkedList

LinkedList第一个(最前面的)的是最近刚用的

2. LFU Least Frequently Used

如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰

实现:考虑到 LFU
会淘汰访问频率最小的数据,我们需要一种合适的方法按大小顺序维护数据访问的频率。LFU
算法本质上可以看做是一个 top K 问题(K =
1),即选出频率最小的元素,因此我们很容易想到可以用二项堆来选择频率最小的元素,这样的实现比较高效。最终实现策略为小顶堆+哈希表

3. 最近未使用 NRU, Not Recently Used

每个页面都有两个状态位:R 与 M,当页面被访问时设置页面的 R=1,当页面被修改时设置
M=1。其中 R 位会定时被清零。

当发生缺页中断时,NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。

NRU
优先换出已经被修改的脏页面(R=0,M=1),而不是被频繁使用的干净页面(R=1,M=0)。

4. 先进先出 FIFO, First In First Out

选择换出的页面是最先进入的页面。

该算法会将那些经常被访问的页面换出,导致缺页率升高。

实现:维护一个FIFO队列,按照时间顺序将各数据(已分配页面)链接起来组成队列,并将置换指针指向队列的队首。再进行置换时,只需把置换指针所指的数据(页面)顺次换出,并把新加入的数据插到队尾即可。

5. 第二次机会算法

6. 时钟

具体参考CSNOTE

FIFO
、LRU、LFU三种算法的区别参考:https://www.cnblogs.com/hongdada/p/10406902.html

一般都使用LRU, Least Recently Used

分段

虚拟内存采用的是分页技术也就是将地址空间划分成固定大小的页,每一页再与内存进行映射。

下图为一个编译器在编译过程中建立的多个表,有 4
个表是动态增长的,如果使用分页系统的一维地址空间,动态增长的特点会导致覆盖问题的出现。

分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,并且可以动态增长。

Eg:

段页式

程序的地址空间划分成多个拥有独立地址空间的段,每个段上的地址空间划分成大小相同的页。这样既拥有分段系统的共享和保护,又拥有分页系统的虚拟内存功能。

分页与分段的比较

对程序员的透明性:分页透明,但是分段需要程序员显式划分每个段。

地址空间的维度:分页是一维地址空间,分段是二维的。

大小是否可以改变:页的大小不可变,段的大小可以动态改变。

出现的原因:分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。

什么是镜像?

镜像有两种含义,一种是说在网上内容完全相同而且同步更新的两个或多个服务器,除主服务器外,其余的都被称为镜像服务器,目的是为了在主服务器不能服务的时候,不中断服务;另一种含义是指用GHOST或HD
COPY等软件制作的一个盘的打包文件,它可以在将来恢复这个盘的内容,也可以很方便地把相同的内容复制到其它盘里。

镜像(Mirroring)是冗余的一种类型,一个磁盘上的数据在另一个磁盘上存在一个完全相同的副本即为镜像。分软件镜像与硬件镜像,它们的的区别就在于实现镜像所需的CPU周期所处的位置。最终,都是根据程序的指令,为硬件(磁盘,以及磁盘上存储的数据)制作一个镜像副本。镜像主要作备份用,镜像内容可以是系统、光盘、软件,网站,甚至服务器

设备管理

磁盘调度算法:电梯算法

电梯总是保持一个方向运行,直到该方向没有请求为止,然后改变运行方向。

编译&链接

在链接过程中,静态链接(static
linking)和动态链接就出现了区别。静态链接的过程就已经把要链接的内容已经链接到了生成的可执行文件中,就算你在去把静态库删除也不会影响可执行程序的执行;而动态链接这个过程却没有把内容链接进去,而是在执行的过程中,再去找要链接的内容,生成的可执行文件中并没有要链接的内容,所以当你删除动态库时,可执行程序就不能运行。

静态链接与动态链接的区别

大家都知道应用程序有两种链接方式,一种是静态链接,一种是动态链接,这两种链接方式各有好处。

所谓静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。换句话说,函数和过程的代码就在程序的exe文件中,该文件包含了运行时所需的全部代码。当多个程序都调用相同函数时,内存中就会存在这个函数的多个拷贝,这样就浪费了宝贵的内存资源。

动态链接是相对静态链接而言的,动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的DLL之间建立链接关系。当要执行所调用DLL中的函数时,根据链接产生的重定位信息,Windows才转去执行DLL中相应的函数代码。一般情况下,如果一个应用程序使用了动态链接库,Win32系统保证内存中只有DLL的一份复制品。

静态链接的可执行文件能够在其他同类操作系统上直接运行。例如,一个exe文件是在Windows
2000系统上静态链接的,那么将该文件直接拷贝到另一台Windows
2000的机器上,是可以运行的。而动态链接的可执行程序则不一定可以,除非把该exe文件所需的dll文件都一并拷贝过去,或者对方机器上也有所需的相同版本的dll文件,否则不能保证正常运行。

静态链接库是.lib格式的文件,一般在工程的设置界面加入工程中,程序编译时会把lib文件的代码直接链接进目标程序中,因此会增加代码大小,静态链接的可执行文件会大一些,程序运行的时候不再需要其它的库文件,不能手动移除lib代码。

动态链接库是一个包含可由多个程序同时使用的代码和数据的库,它是包含函数和数据的模块,格式.dll。程序运行时动态加载这些模块,运行时可以随意加载和移除,节省内存空间。

动态链接库和静态链接库的相同点是它们都实现了代码的共享,不同点是静态链接库lib文件中的代码被包含exe文件中,该lib中不能再包含其他动态链接或者静态链接的库了。而动态链接库dll可以被调用的exe动态地“引用”和“卸载”,一个dll中可以包含其他动态链接库或者静态链接库。在大型的软件项目中一般要实现很多功能,如果把所有单独的功能写成一个个lib文件的话,程序运行的时候要占用很大的内存空间,导致运行缓慢;但是如果将功能写成dll文件,就可以在用到该功能的时候调用功能对应的dll文件,不用这个功能时将dll文件移除内存,这样可以节省内存空间。

https://blog.csdn.net/fuzhongmin05/article/details/54616520#:~:text=%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93%E6%98%AF%E4%B8%80%E4%B8%AA,%E7%9A%84%E6%A8%A1%E5%9D%97%EF%BC%8C%E6%A0%BC%E5%BC%8F.dll%E3%80%82&text=%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93%E5%92%8C%E9%9D%99%E6%80%81,%E9%9D%99%E6%80%81%E9%93%BE%E6%8E%A5%E7%9A%84%E5%BA%93%E4%BA%86%E3%80%82

Linux:

最常用27个Linux指令:https://www.jianshu.com/p/0056d671ea6d

常用指令参考:http://cyc2018.gitee.io/cs-notes/#/notes/Linux

top: top命令查看linux负载(还有uptime)

详解:https://www.cnblogs.com/niuben/p/12017242.html

Linux 查看端口占用情况可以使用 lsof 和 netstat 命令。

lsof(list open files)是一个列出当前系统打开文件的工具。

Linux文件系统

对分区进行格式化是为了在分区上建立文件系统。一个分区通常只能格式化为一个文件系统,但是磁盘阵列等技术可以将一个分区格式化为多个文件系统。

最主要的几个组成部分如下:

inode:一个文件占用一个 inode,记录文件的属性,同时记录此文件的内容所在的 block
编号;inode 中记录了文件内容所在的 block 编号,但是每个 block
非常小,一个大文件随便都需要几十万的 block。而一个 inode
大小有限,无法直接引用这么多 block
编号。因此引入了间接、双间接、三间接引用。间接引用让 inode 记录的引用 block
块记录引用信息。

block:记录文件的内容,文件太大时,会占用多个 block。

除此之外还包括:

superblock:记录文件系统的整体信息,包括 inode 和 block
的总量、使用量、剩余量,以及文件系统的格式与相关信息等;

block bitmap:记录 block 是否被使用的位图。

具体参考:https://blog.csdn.net/readyone/article/details/110456699

文件默认权限:文件默认没有可执行权限,因此为 666,也就是 -rw-rw-rw- 。

目录默认权限:目录必须要能够进入,也就是必须拥有可执行权限,因此为 777 ,也就是
drwxrwxrwx。

9 位的文件权限字段中,每 3 个为一组,共 3
组,每一组分别代表对文件拥有者、所属群组以及其它人的文件权限。一组权限中的 3
位分别为 r、w、x 权限,表示可读、可写、可执行。

Inode包含文件定向 位置等很多信息 但是文件内容在data block里

建立一个目录时,会分配一个 inode 与至少一个 block。block
记录的内容是目录下所有文件的 inode 编号以及文件名。

Shell

可以通过 Shell 请求内核提供服务,Bash 正是 Shell 的一种。

shell(解释器);bash(解释器的具体型号,版本)

什么是Linux Shell?

概念:Shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行。是在Linux内核与用户之间的解释器程序,现在Linux通常指/bin/bash解释器来负责向内核翻译以及传达用户/程序指令,shell相当于操作系统的“外壳”

可以使用 `指令` 或者 $(指令) 的方式将指令的执行结果赋值给变量。例如
version=$(uname -r),则 version 的值为 4.15.0-22-generic。

输出重定向会将输出内容重定向到文件中,而 tee
不仅能够完成这个功能,还能保留屏幕上的输出。也就是说,使用 tee
指令,一个输出会同时传送到文件和屏幕上。

tr 用来删除一行中的字符,或者对字符进行替换。

Linux 进程管理


fork():一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

1)在父进程中,fork返回新创建子进程的进程ID;

2)在子进程中,fork返回0;

3)如果出现错误,fork返回一个负值;

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

每个进程都有一个独特(互不相同)的进程标识符(process
ID),可以通过getpid()函数或者linux里的ps
获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。

fork执行完毕后,出现两个进程

注意fork会接下去执行而不会执行fork上面的
因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了int
count=0;fork只拷贝下一个要执行的代码到新的进程。

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

引用一位网友的话来解释fork函数返回的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fork函数返回的值指向子进程的进程id,
因为子进程没有子进程,所以其fork函数返回的值为0.

调用fork之后,数据、堆、栈有两份,代码仍然为一份但是这个代码段成为两个进程的共享代码段都从fork函数中返回,箭头表示各自的执行处。当父子进程有一个想要修改数据或者堆栈时,两个进程真正分裂。

SIGCHLD

当一个子进程改变了它的状态时(停止运行,继续运行或者退出),有两件事会发生在父进程中:

出错返回错误信息如下:

EAGAIN

达到进程数上限.

ENOMEM

没有足够空间给一个新进程分配.

得到 SIGCHLD 信号;

waitpid() 或者 wait() 调用会返回。

其中子进程发送的 SIGCHLD 信号包含了子进程的信息,比如进程 ID、进程状态、进程使用
CPU 的时间等。

options 参数主要有 WNOHANG 和 WUNTRACED 两个选项,WNOHANG 可以使 waitpid()
调用变成非阻塞的,也就是说它会立即返回,父进程可以继续执行其它任务。

孤儿进程

一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。

孤儿进程将被 init 进程(进程号为 1)所收养,并由 init
进程对它们完成状态收集工作。

由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。

僵尸进程

一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或
waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait()
或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。

僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。

系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。

要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被
init 进程所收养,这样 init
进程就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。

网络(五层协议):

备注:这里主要是根据五层协议

第一层:物理层,二进制传输,bit(比特流bai)

第二层:数据链路层,介质访问,frame(帧)

第三层:网络层,确定地址和最佳路径,packet(包)

第四层:传输层,端到端连接,segment(段)

第五层:会话层,互连主机通信

第六层:表示层,数据表示

第七层:应用层,为应用程序提供网络服务

HTTP 协议:超文本传输协议,对应于应用层,用于如何封装数据.

TCP/UDP 协议:传输控制协议,对应于传输层,主要解决数据在网络中的传输。

IP 协议:对应于网络层,同样解决数据在网络中的传输。

ISP一般指互联网服务提供商。互联网服务提供商(Internet Service Provider)

目前的互联网是一种多层次 ISP 结构,ISP 根据覆盖面积的大小分为第一层 ISP、区域
ISP 和接入 ISP。互联网交换点 IXP 允许两个 ISP 直接相连而不用经过第三个 ISP。

电路交换:circuit switch

分组交换:packet switch

The types of delays encountered in a packet-switched network are:

Propagation delay

Transmission delay

Queuing delay

Processing delay

Packet switching is the transfer of small pieces of data across various
networks. These data chunks or “packets” allow for faster, more efficient data
transfer.

Often, when a user sends a file across a network, it gets transferred in smaller
data packets, not in one piece. For example, a 3MB file will be divided into
packets, each with a packet header that includes the origin IP address, the
destination IP address, the number of packets in the entire data file, and the
sequence number.

(Circuit Switching 电路交换)

Packet switching 分组交换allows users to equally share bandwidth resources but
makes no promises concerning quality or latency. This is useful for transferring
data that doesn’t require real-time responsiveness. Packet switching places the
intelligence in the end nodes, rather than the phone company facilities, with a
simple underlying network that only directs packets from one side to the other.

(Data Link layer 主要是router及管道传输package相关)

At the data link layer, directly connected nodes are used to perform
node-to-node data transfer where data is packaged into frames.

报文:message

物理层

Physical Layer:

Simplex和duplex communication

通信方式

根据信息在传输线上的传送方向,分为以下三种通信方式:

单工通信:单向传输

半双工通信:双向交替传输

全双工通信:双向同时传输

模拟信号是连续的信号,数字信号是离散的信号。带通调制把数字信号转换为模拟信号。

链路层

链路层是帧 物理层是比特

CRC to check bit error

(协议:Protocol)

广播信道

:一对多通信,一个节点发送的数据能够被广播信道上所有的节点接收到。

主要有两种控制方法进行协调,一个是使用信道复用技术,一是使用 CSMA/CD 协议

信道复用技术

: multiplexing

频分复用(FDM,Frequency Division
Multiplexing)就是将用于传输信道的总带宽划分成若干个子频带(或称子信道),每一个子信道传输1路信号。

时分复用(TDM,Time Division
Multiplexing)就是将提供给整个信道传输信息的时间划分成若干时间片(简称时隙),并将这些时隙分配给每一个信号源使用,每一路信号在自己的时隙内独占信道进行数据传输。

通信是由光来运载信号进行传输的方式。在光通信领域,人们习惯按波长而不是按频率来命名。因此,所谓的波分复用(WDM,Wavelength
Division Multiplexing)其本质上也是频分复用而已

码分复用( CDM, Code Division Multiplexing)是扩展频谱( spread
spectrum)通信的一种形式,实际上人们更常用的名词是码分多址CDMA。每个用户可以在同样的时间使用同样的频带进行通信。为每个用户分配
m bit 的码片,并且所有的码片正交,对于任意两个码片 S和T有等于0。结果1用户发1
结果-1用户发0

统计复用(SDM,Statistical Division
Multiplexing)有时也称为标记复用、统计时分多路复用或智能时分多路复用,实际上就是所谓的带宽动态分配。

CSMA/CD 协议

CSMA/CD 表示载波监听多点接入 / 碰撞检测。

多点接入 :说明这是总线型网络,许多主机以多点的方式连接到总线上。

载波监听
:每个主机都必须不停地监听信道。在发送前,如果监听到信道正在使用,就必须等待。

碰撞检测
:在发送中,如果监听到信道已有其它主机正在发送数据,就表示发生了碰撞。虽然每个主机在发送数据之前都已经监听到信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞。

记端到端的传播时延为 τ,最先发送的站点最多经过 2τ 就可以知道是否发生了碰撞,称
2τ 为 争用期
。只有经过争用期之后还没有检测到碰撞,才能肯定这次发送不会发生碰撞。

当发生碰撞时,站点要停止发送,等待一段时间再发送。这个时间采用
截断二进制指数退避算法 来确定。从离散的整数集合 {0, 1, …, (2k-1)}
中随机取出一个数,记作 r,然后取 r 倍的争用期作为重传等待时间。

互联网用户通常需要连接到某个 ISP 之后才能接入到互联网,PPP 协议是用户计算机和
ISP 进行通信时所使用的数据链路层协议。

PPP 协议

互联网用户通常需要连接到某个 ISP 之后才能接入到互联网,PPP 协议是用户计算机和
ISP 进行通信时所使用的数据链路层协议。

MAC 地址是链路层地址

局域网:LAN 广域网:WAN

以太网:Ethernet

以太网是一种星型拓扑结构局域网。

目前以太网使用交换机替代了集线器,交换机是一种链路层设备,它不会发生碰撞,能根据
MAC 地址进行存储转发。

以太网是目前最为广泛的局域网技术,下面具体讲解网络设备之间连接和数据传输的方法,以及以太网中的两个网络设备进行连接的方法。

以太网工作机制

有了传输介质以后,以太网中的数据就可以借助传输介质进行传输了。以太网采用附加冲突检测的载波帧听多路访问(CSMA/CD)机制,以太网中所有节点都可以看到在网络中发送的所有信息。因此,以太网是一种广播网络。

以太网需要判断计算机何时可以把数据发送到访问介质。通过使用
CSMA/CD,所有计算机都可以监视传输介质的状态,在传输之前等待线路空闲。如果两台计算机尝试同时发送数据,就会发生冲突,计算机会停止发送,等待一个随机的时间间隔,然后再次尝试发送。

当以太网中的一台主机要传输数据时,工作过程如下:

监听信道上是否有信号在传输。如果有,表示信道处于忙状态,则继续帧听,直到信道空闲为止。

若没有监听到任何信号,就传输数据。

传输数据的时候继续监听。如果发现冲突,则执行退避算法。随机等待一段时间后,重新执行步骤(1)。当冲突发生时,涉及冲突的计算机会返回监听信道状态。若未发现冲突,则表示发送成功。

交换机(Switch)

交换机具有自学习能力,学习的是交换表的内容,交换表中存储着 MAC
地址到接口的映射。

比如主机 A 向主机 B 发送数据帧时,交换机把主机 A 到接口 1 的映射写入交换表中

交换机记录了 MAC
地址到其转发接口的交换表项,因此现在交换机就可以直接知道应该向哪个接口发送该帧

集线器

集线器的英文称为“Hub”。“Hub”是“中心”的意思,集线器的主要功能是对接收到的信号进行再生整形放大,以扩大网络的传输距离,同时把所有节点集中在以它为中心的节点上。它工作于OSI(开放系统互联参考模型)参考模型第一层,即“物理层”。集线器与网卡、网线等传输介质一样,属于局域网中的基础设备,采用CSMA/CD(即带冲突检测的载波监听多路访问技术)介质访问控制机制。集线器每个接口简单的收发比特,收到1就转发1,收到0就转发0,不进行碰撞检测。

集线器(hub)属于纯硬件网络底层设备,基本上不具有类似于交换机的"智能记忆"能力和"学习"能力。它也不具备交换机所具有的MAC地址表,所以它发送数据时都是没有针对性的,而是采用广播方式发送。也就是说当它要向某节点发送数据时,不是直接把数据发送到目的节点,而是把数据包发送到与集线器相连的所有节点,如图所示,简单明了。

集线器与交换机区别:

在OSI/RM(OSI参考模型)中的工作层次不同

交换机和集线器在OSI/RM开放体系模型中对应的层次就不一样,集线器是工作在第一层(物理层),而交换机至少是工作在第二层,更高级的交换机可以工作在第三层(网络层)和第四层(传输层)。

(2)交换机的数据传输方式不同

集线器的数据传输方式是广播(broadcast)方式,而交换机的数据传输是有目的的,数据只对目的节点发送,只是在自己的MAC地址表中找不到的情况下第一次使用广播方式发送,然后因为交换机具有MAC地址学习功能,第二次以后就不再是广播发送了,又是有目的的发送。这样的好处是数据传输效率提高,不会出现广播风暴,在安全性方面也不会出现其它节点侦听的现象。用集线器组成的网络称为共享式网络,而用交换机组成的网络称为交换式网络。
共享式以太网存在的主要问题是所有用户共享带宽,每个用户的实际可用带宽随网络用户数的增加而递减。这是因为当信息繁忙时,多个用户可能同时“争用”一个信道,而一个信道在某一时刻只允许一个用户占用,所以大量的用户经常处于监测等待状态,致使信号传输时产生抖动、停滞或失真,严重影响了网络的性能。

(3)带宽占用方式不同

在带宽占用方面,集线器所有端口是共享集线器的总带宽,而交换机的每个端口都具有自己的带宽,这样就交换机实际上每个端口的带宽比集线器端口可用带宽要高许多,也就决定了交换机的传输速度比集线器要快许多。交换机在传输数据时是并行传输,多个端口对之间可以同时传输数据,或者一个端口内的各台计算机之间的交换数据不会影响到另外一个端口内的数据通信。

(4)传输模式不同

集线器只能采用半双工方式进行传输的,因为集线器是共享传输介质的,这样在上行通道上集线器一次只能传输一个任务,要么是接收数据,要么是发送数据。交换机可以是半双工操作,也可以是全双工操作。

虚拟局域网可以建立与物理位置无关的逻辑组,只有在同一个虚拟局域网中的成员才会收到链路层广播信息。比如寻找mac地址
该局域网内的网关路由器

网络层

http协议对应于应用层

tcp协议对应于传输层

ip协议对应于网络层

  • 网络层

网络地址和ip地址:

传统的网络号+主机号的ip和子网掩码的两种

对于IP地址,相信大家都很熟悉,即指使用TCP/IP协议指定给主机的32位地址。IP地址由用点分隔开的4个8八位组构成,如192.168.0.1就是一个IP地址,这种写法叫点分十进制格式。IP地址由网络地址和主机地址两部分组成,分配给这两部分的位数随地址类(A类、B类、C类等)的不同而不同。网络地址用于路由选择,而主机地址用于在网络或子网内部寻找一个单独的主机。一个IP地址使得将来自源地址的数据通过路由而传送到目的地址变为可能。

因为网络层是整个互联网的核心,因此应当让网络层尽可能简单。网络层向上只提供简单灵活的、无连接的、尽最大努力交互的数据报服务。

使用 IP
协议,可以把异构的物理网络连接起来,使得在网络层看起来好像是一个统一的网络。

The IP datagram header format:

http://mars.netanya.ac.il/~unesco/cdrom/booklet/HTML/NETWORKING/node020.html

subnet mask:子网掩码

无分类编址 CIDR 消除了传统 A 类、B 类和 C
类地址以及划分子网的概念,使用网络前缀和主机号来对 IP
地址进行编码,网络前缀的长度可以根据需要变化。

Classless Inter-Domain Routing

CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法,例如 128.14.35.7/20
表示前 20 位为网络前缀。

CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为网络前缀的长度。

(在CIDR表示法中也可以进行进一步的子网划分,和前面的子网划分类似,我们只需要从主机号中借走一定的位数即可,这里与前面的基本子网划分不同,借走2位时可以划分成4个子网,不用减2,其他位数类似。下面通过一个例子来讲解CIDR中的子网划分。)

无分类

无分类编址 CIDR 消除了传统 A 类、B 类和 C
类地址以及划分子网的概念,使用网络前缀和主机号来对 IP
地址进行编码,网络前缀的长度可以根据需要变化。

IP 地址 ::= {< 网络前缀号 >, < 主机号 >}

CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法,例如 128.14.35.7/20
表示前 20 位为网络前缀。

CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为网络前缀的长度。

一个 CIDR 地址块中有很多地址,一个 CIDR
表示的网络就可以表示原来的很多个网络,并且在路由表中只需要一个路由就可以代替原来的多个路由,减少了路由表项的数量。把这种通过使用网络前缀来减少路由表项的方式称为路由聚合,也称为
构成超网 。

在路由表中的项目由“网络前缀”和“下一跳地址”组成,在查找时可能会得到不止一个匹配结果,应当采用最长前缀匹配来确定应该匹配哪一个。

IP管理系统 应该用哈希表的方式

地址解析协议 ARP:

网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP
数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。

IP与MAC地址

IP地址是指互联网协议地址(Internet Protocol Address),是IP
Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

MAC地址又称为物理地址、硬件地址,用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家烧入网卡的,具有全球唯一性。MAC地址用于在网络中唯一标示一个网卡,一台电脑会有一或多个网卡,每个网卡network
interface card都需要有一个唯一的MAC地址。

生产厂商:manufacturer

每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到
MAC 地址的映射表。

ARP 实现由 IP 地址得到 MAC 地址。

如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC
地址的映射,此时主机 A 通过广播broadcast的方式发送 ARP 请求分组,主机 B
收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A
向其高速缓存中写入主机 B 的 IP 地址到 MAC 地址的映射。

节点可以是路由 需要该网关路由器的 MAC 地址才能在链路层通过帧发送

可以说: MAC寻址 只存在于局域网
用来发送帧给该局域网的网关路由器(用来连向外部)

因为我们需要将 IP 数据报被放入一个以太网帧中,该帧将发送到网关路由器。

(这样才可以后续的去DNS解析域名得到ip然后tcp三次握手建立理解 发送http GET报文)

之后到达目的地网关(网络)后 再通过MAC地址找到特定的节点

可以说IP是网络到网络,MAC可以说是(也可以是网络内) 一个节点到另一个节点

还有可能就是局域网都以以太网实现 所以是MAC

局域网为何使用MAC或IP?

ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP
数据报中,但是不属于高层协议。比如ping 延迟 主要用来测试两台主机之间的连通性

或者Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。

网际控制报文协议 ICMP

ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP
数据报中,但是不属于高层协议。

Ping 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。

Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。

虚拟专用网 VPN

由于 IP 地址的紧缺,一个机构能申请到的 IP
地址数往往远小于本机构所拥有的主机数。并且一个机构并不需要把所有的主机接入到外部的互联网中,机构内的计算机可以使用仅在本机构有效的
IP 地址(专用地址)。

通过NAT转换 专用网内部的主机使用本地 IP 地址又想和互联网上的主机通信时,可以使用
NAT 来将本地 IP 转换为全球 IP。

路由器的结构

路由器从功能上可以划分为:路由选择和分组转发。

分组转发结构由三个部分组成:交换结构、一组输入端口和一组输出端口。

网关 GateWay

顾名思义,网关(Gateway)就是一个网络连接到另一个网络的“关口”。

按照不同的分类标准,网关也有很多种。TCP/IP协议里的网关是最常用的,在这里我们所讲的“网关”均指TCP/IP协议下的网关。

简而言之,网关是网络的进口和出口

比如有网络A和网络B,网络A的IP地址范围为“192.168.1.1~192.
168.1.254”,子网掩码为255.255.255.0;网络B的IP地址范围为“192.168.2.1~192.168.2.254”,子网掩码为255.255.255.0。在没有路由器的情况下,两个网络之间是不能进行TCP/IP通信的,即使是两个网络连接在同一台交换机(或集线器)上,TCP/IP协议也会根据子网掩码(255.255.255.0)判定两个网络中的主机处在不同的网络里。而要实现这两个网络之间的通信,则必须通过网关。

路由器

路由器使用一系列算法决定网络间的最短路径。

现在,路由器集成了网关的功能,所以路由器也具有网关的功能。

从网关和路由器的定义来看,如果只是简单地连接两个网络,那么只需要网关就足够了

下面来看看宽带路由器中“高级路由”中的网关和路由器选项:

网关:如果此宽带路由器是网络上唯一一台连接Internet的路由器,选择网关。此时宽带路由器作为网络的进/出口。

路由器:如果网络中还有其他路由器,选择路由器。

总结:

1、对于家庭共享上网,只需要使用网关即可,所以在宽带路由器中选择“网关”选项。

2、对于大型企业网络内部,或大型企业网络连接Internet,由于网络中可能存在其他路由器,所以应当选择宽带路由器中的“路由器”选项,以决定网络间的最短路径。

路由选择协议都是自适应的,能随着网络通信量和拓扑结构的变化而自适应地进行调整。

互联网可以划分为许多较小的自治系统 AS,一个 AS 可以使用一种和别的 AS
不同的路由选择协议。

备注:网关路由器接收到包含 DNS 查询报文的以太网帧后,抽取出 IP
数据报,并根据转发表决定该 IP 数据报应该转发的路由器。

转发表:转发表,又称MAC表,聊到它就不得不提到交换机里,因为交换机就是根据转发表来转发数据帧的。

可以把路由选择协议划分为两大类:

自治系统内部的路由选择:RIP 和 OSPF

自治系统间的路由选择:BGP

RIP 是一种基于距离向量的路由选择协议。距离是指跳数,直接相连的路由器跳数为
1。跳数最多为 15,超过 15 表示不可达。

开放最短路径优先 OSPF,是为了克服 RIP 的缺点而开发出来的。表示 OSPF
不受某一家厂商控制,而是公开发表的;最短路径优先表示使用了 Dijkstra
提出的最短路径算法 SPF。

BGP(Border Gateway Protocol,边界网关协议)

AS 之间的路由选择很困难,主要是由于:

互联网规模很大;

各个 AS 内部使用不同的路由选择协议,无法准确定义路径的度量;

AS 之间的路由选择必须考虑有关的策略,比如有些 AS 不愿意让其它 AS 经过。

BGP 只能寻找一条比较好的路由,而不是最佳路由。

每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP
连接来交换路由信息。

AS:autonomous
system。在互联网中,一个自治系统(AS)是一个有权自主地决定在本系统中应采用何种路由协议的小型单位

默认网关

默认网关指计算机所在网络边界的网关或路由器,因为只有网关和路由器才知道如何到达其他网络。

对于网络内部的计算机来说,只有知道了默认网关的位置才能和网络外部通信。

所以默认网关是一个很重要的设置。但大多数情况下不需要我们手动设置,因为网络中的DHCP服务器可以自动提供默认网关的位置。

对于使用宽带路由器共享上网,默认网关是宽带路由器。

对于拨号上网,默认网关是是运营商处的路由器。

对于网络中有启用了ICS(Internet连接共享)的计算机并共享上网,默认网关就是这台计算机。

参考:https://blog.csdn.net/bytxl/article/details/41897599

传输层:UDP 和 TCP

端到端通信

端到端:end-to-end,指的就是数据传输路径中最两端的两台网络设备之间的通信。

端到端的概念不仅仅是一根网线两端的两台电脑,它是逻辑的,可能是跨地域的。

比如:你家在北京,你给你上海的一个朋友传一个文件,这时候你们俩之间需要建立一个连接,可能是通过qq,可能是通过FTP……虽然中间经过了电信、网通等ISP,但是对于通讯的两端来说,北京的你和你上海的朋友之间,这就是一个端到端的连接。

路由(routing)是指分组从源到目的地时,决定端到端路径的网络范围的进程

用户数据报协议 UDP(User Datagram
Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加
UDP 首部),支持一对一、一对多、多对一和多对多的交互通信。

首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12
字节的伪首部是为了计算检验和临时添加的。

传输控制协议 TCP(Transmission Control
Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条
TCP 连接只能是点对点的(一对一)。

序号:seq

用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为
301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。

确认号:ack

期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为
501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A
的确认报文段中确认号就为 701。

确认 ACK :当 ACK=1 时确认号字段有效,否则无效。TCP
规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。(1位bit)

窗口
:窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。

!!:如果要想把一个数据包从主机 A 发送给主机
B,那么在传输之前,数据包上会被附加上主机 B 的 IP
地址信息,这样在传输过程中才能正确寻址。额外地,数据包上还会附加上主机 A 本身的
IP 地址,有了这些信息主机 B 才可以回复信息给主机 A。这些附加的信息会被装进一个叫
IP 头的数据结构里。IP 头是 IP 数据包开头的信息,包含 IP 版本、源 IP 地址、目标
IP 地址、生存时间等信息。

**pseudo
header(伪首部)**伪首部并非TCP&UDP数据报中实际的有效成分。伪首部是一个虚拟的
数据结构,其中的信息是从数据报所在IP分组头的分组头中提取的,既不向下传送也不向上递交,而仅仅是为计算
校验和
checksum。TCP校验和(Checksum)是一个端到端的校验和,由发送端计算,然后由接收端验证。其目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。

伪报头保证TCP&UDP数据单元到达正确的目的地址。因此,伪报头中包含IP地址并且作为计算
校验和需要考虑的一部分。最终目的端根据伪报头和 数据单元计算
校验和以验证通信数据在传输过程中没有改变而且到达了正确的目的地址。

TCP三次握手

假设 A 为客户端,B 为服务器端。

首先
TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态,等待客户的连接请求。

TCP客户进程也是先创建传输控制块TCB 然后客户端A 向 B
发送连接请求报文,(同步序列编号)SYN=1,ACK=0,选择一个初始的序号(就是seq)
x。

B 收到连接请求报文,如果同意建立连接,则向 A
发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。

A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。

B 收到 A 的确认后,连接建立。

TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。

TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。

三次握手的原因

一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。

如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。

如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。

TCP四次挥手

(报文:Message )

以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为
ACK 在连接建立之后都为 1。

A 发送连接释放报文,FIN=1。

B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B
发送数据。

当 B 不再需要连接时,发送连接释放报文,FIN=1 此时的w可以说是v加上传的数据大小

A 收到后发出确认,进入 TIME-WAIT 状态,等待 2
MSL(最大报文存活时间)后释放连接。

注意此时TCP连接还没有释放,必须经过2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

四次挥手的原因

客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT
状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送
FIN 连接释放报文。

TIME_WAIT

客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED
状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:

1. 确保最后一个确认报文能够到达。如果 B 没收到 A
发送来的确认报文,那么就会重新发送连接释放请求报文,A
等待一段时间就是为了处理这种情况的发生。站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次

2.等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。

如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

TCP可靠传输

TCP
使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。

(RTT:Round-Trip Time)(RTO, Retransmission Time Out)

重传次数

TCP数据包重传次数根据系统设置的不同而有所区别。有些系统,一个数据包只会被重传3次,如果重传3次后还未收到该数据包的
ACK
确认,就不再尝试重传。但有些要求很高的业务系统,会不断地重传丢失的数据包,以尽最大可能保证业务数据的正常交互。

TCP 滑动窗口

窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过
TCP
报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。

发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。

接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为
{31, 34, 35},其中 {31} 按序到达,而 {34, 35} 就不是,因此只对字节 31
进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。

TCP传输过程:

https://my.oschina.net/u/3412738/blog/3141416

非常重要!!!:

为了保证数据准确到达,目标机器在收到数据包(包括SYN包、FIN包、普通数据包等)包后必须立即回传ACK包,这样发送方才能确认数据传输成功。

Ack号 = Seq号 + 传递的字节数 + 1

TCP拥塞控制

(TCP 流量控制

流量控制是为了控制发送方发送速率,保证接收方来得及接收。

接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为
0,则发送方不能发送数据。)

如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。

详细参考:

http://cyc2018.gitee.io/cs-notes/#/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%20-%20%E4%BC%A0%E8%BE%93%E5%B1%82
里的TCP 拥塞控制

MSS(Maximum Segment
Size,最大报文长度),是TCP协议定义的一个选项,MSS选项用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度。

参考:!!!!

https://blog.csdn.net/yechaodechuntian/article/details/25429143

慢开始和拥塞避免:

拥塞避免算法:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。

快重传:

这样,发送方共收到了接收方的四个对M2的确认,其中后三个都是重复确认。快重传算法还规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段M3,而不必继续等待M3设置的重传计时器到期。由于发送方尽早重传未被确认的报文段,因此采用快重传后可以使整个网络吞吐量提高约20%。

快恢复:

(快速重传算法首次出现在4.3BSD的Tahoe版本,快速恢复首次出现在4.3BSD的Reno版本,也称之为Reno版的TCP拥塞控制算法。

可以看出Reno的快速重传算法是针对一个包的重传情况的,然而在实际中,一个重传超时可能导致许多的数据包的重传,因此当多个数据包从一个数据窗口中丢失时并且触发快速重传和快速恢复算法时,问题就产生了。因此NewReno出现了,它在Reno快速恢复的基础上稍加了修改,可以恢复一个窗口内多个包丢失的情况。具体来讲就是:Reno在收到一个新的数据的ACK时就退出了快速恢复状态了,而NewReno需要收到该窗口内所有数据包的确认后才会退出快速恢复状态,从而更一步提高吞吐量。

SACK就是改变TCP的确认机制,最初的TCP只确认当前已连续收到的数据,SACK则把乱序等信息会全部告诉对方,从而减少数据发送方重传的盲目性。比如说序号1,2,3,5,7的数据收到了,那么普通的ACK只会确认序列号4,而SACK会把当前的5,7已经收到的信息在SACK选项里面告知对端,从而提高性能,当使用SACK的时候,NewReno算法可以不使用,因为SACK本身携带的信息就可以使得发送方有足够的信息来知道需要重传哪些包,而不需要重传哪些包。)

TCP与UDP区别总结

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文

UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP首部开销20字节;UDP的首部开销小,只有8个字节

6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

TCP与UDP应用场景:

TCP:File transfer, email, web browsing

UDP:Video conferencing, gaming, broadcasts

UDP的应用场景:即时通信。面向数据报方式;网络数据大多为短消息;拥有大量客户端;对数据安全性无特殊要求;网络负担重但对响应速度要求高的场景。eg:
IP电话、实时视频会议等。

TCP的应用场景:对数据准确性要求高,速度可以相对较慢的。eg:
文件传输、邮件的发送与接收等。

如何让UDP变可靠

在不改变UDP协议的情况下能够想到的是在应用层做可靠性设计,但是应用层做可能通用性会差一些,那么在传输层和应用层之间加一层实现UDP的可靠性呢?基于这个想法提出了RUDP(Reliable
UDP),实际上,已经有项目在这么做了,比如Google的QUIC和WebRTC。

为什么?我们寻求在成本、质量、时延上寻找一个平衡

构建于UDP之上的RUDP,增加了数据窗口、拥塞控制、确认机制、重传机制等可靠性保障机制。

参考:https://zhuanlan.zhihu.com/p/163569041

TCP粘包

为什么面试官和大家还是会谈论TCP粘包问题呢?

从上面很容易的出,第一、TCP层传输是流式传输,不会发送数据包。第二、数据包是存在于网络层的概念。那为啥还说TCP粘包问题呢?

自顶而下学习网络的同学都知道应用程序首先要将自己的数据通过套接字发送。应用层交付给TCP的是结构化的数据,结构化的数据到了TCP层做流式传输。

流,最大的问题是没有边界,没有边界就会造成数据粘在一起,这种粘在一起就叫做粘包。当然有同学就要问了,那咋不叫粘段呢?这个。。。

具体描述下什么叫粘包。

TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

一定要参考:

https://my.oschina.net/u/4269462/blog/3195211

应用层

域名系统

**(英文:Domain Name
System,缩写:DNS)**是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库

域名系统 (DNS) 将人类可读的域名 (例如,www.amazon.com) 转换为机器可读的 IP 地址
(例如,192.0.2.44)。

DNS 是一个分布式数据库,提供了主机名和 IP
地址之间相互转换的服务。这里的分布式数据库是指,每个站点只保留它自己的那部分数据。

域名具有层次结构,从上到下依次为:根域名、顶级域名、二级域名。

DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用
UDP
进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传从而保证可靠性。在两种情况下会使用
TCP 进行传输:

如果返回的响应超过的 512 字节(UDP 最大只支持 512 字节的数据)。

区域传送(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。

DNS流程!!!:

https://aws.amazon.com/cn/route53/what-is-dns/

(DNS resolver: DNS解析程序,这一般由用户的 Internet 服务提供商 (ISP) 进行管理)

文件传送协议

FTP 使用 TCP 进行连接,它需要两个连接来传送一个文件:

控制连接:服务器打开端口号 21
等待客户端的连接,客户端主动建立连接后,使用这个连接将客户端的命令传送给服务器,并传回服务器的应答。

数据连接:用来传送一个文件数据。

动态主机配置协议

DHCP (Dynamic Host Configuration Protocol)
提供了即插即用的连网方式,用户不再需要手动配置 IP 地址等信息。

DHCP 配置的内容不仅是 IP 地址,还包括子网掩码、网关 IP 地址。

如果客户端和 DHCP 服务器不在同一个子网,就需要使用中继代理

远程登录协议

TELNET 用于登录到远程主机上,并且远程主机上的输出也会返回。

电子邮件协议

一个电子邮件系统由三部分组成:用户代理、邮件服务器以及邮件协议。

邮件协议包含发送协议和读取协议,发送协议常用 SMTP,读取协议常用 POP3 和 IMAP。

SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME
并没有改动或者取代 SMTP,而是增加邮件主体的结构,定义了非 ASCII 码的编码规则。

Web 页面请求过程

1. DHCP 配置主机信息

2. ARP 解析 MAC 地址

3. DNS 解析域名

4. HTTP 请求页面

详细过程参考网页最后一个:

http://cyc2018.gitee.io/cs-notes/#/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%20-%20%E5%BA%94%E7%94%A8%E5%B1%82?id=web-%e9%a1%b5%e9%9d%a2%e8%af%b7%e6%b1%82%e8%bf%87%e7%a8%8b

RTP/RCTP

参考:https://blog.csdn.net/davidsguo008/article/details/73658422

HTTP:

http(Hypertext transfer
protocol)超文本传输协议,是一种详细规定了浏览器与万维网服务器之间相互通信的规则。

HTTP协议即超文本传送协议(HypertextTransfer Protocol
),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

主要特点

1、简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

2、灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。

3.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

4.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

5、支持B/S及C/S模式。

(127.0.0.1是回送地址,指本地机,一般用来测试使用;也就是说“127.x.x.x”是本机回送地址,即主机IP堆栈内部的IP地址,主要用于网络软件测试以及本地机进程间通信。)

HTTP工作原理

HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。

以下是 HTTP 请求/响应的步骤:

1、客户端连接到Web服务器

一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,http://www.oakcms.cn。

2、发送HTTP请求

通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。

3、服务器接受请求并返回HTTP响应

Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。

4、释放连接TCP连接

若connection
模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection
模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;

5、客户端浏览器解析HTML内容

客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。

例如:在浏览器地址栏键入URL,按下回车之后会经历以下流程:

1、浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;

2、解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;

3、浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为
TCP 三次握手的第三个报文的数据发送给服务器;

4、服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;

5、释放 TCP连接;

6、浏览器将该 html 文本并显示内容;

http 使用 URL( U niform Resource
Locator,统一资源定位符)来定位资源,它可以认为是是 URI(Uniform Resource
Identifier,统一资源标识符)的一个子集,URL 在 URI 的基础上增加了定位能力。URI
除了包含 URL 之外,还包含 URN(Uniform Resource
Name,统一资源名称),它知识用来定义一个资源的名称,并不具备定位该资源的能力。

通过对比网页链接来理解 iOS 上的 URL Schemes,应该就容易多了。

URL Schemes 有两个单词:

URL,我们都很清楚,http://www.apple.com 就是个 URL,我们也叫它链接或网址;

Schemes,表示的是一个 URL 中的一个位置——最初始的位置,即

超详细面试准备(10分钟打遍所有初级后端开发面试)相关推荐

  1. 后端开发面试自我介绍_字节跳动暑期实习后端开发面试经历

    字节跳动后端实习是什么,字节跳动后端实习面试流程是怎样? 今天小编就来帮助大家了解一下字节跳动后端实习面试到底有什么内容. (好了不皮了,开始正文) 字节的面试流程总的来说还是挺享受的,和面试官两人的 ...

  2. 超详细教你10分钟搭建一个高端的B2B2C模式的综合性商城|含来客推V3源码下载

    需要用到服务器(云主机,虚拟空间),域名,源码 1. 服务器可以理解成一台电脑主机,用于处理存储计算传输等用途. 2. 域名比如你想访问一个网站是src1024.com这就是域名,如果你想回家 首先要 ...

  3. 10分钟快速配置sublime2支持jQuery开发

    昨天介绍了javascript的开发工具sublime 2 edit,今天我们将介绍如何10分钟快速配置sublime2支持jQuery开发.希望大家能喜欢着款jQuery开发工具. 相关介绍:使用s ...

  4. 初级Java开发面试必问项!!! 标识符、字面值、变量、数据类型,该学学了!

    最近事情太多,没太时间写博客.今天抽空再整理整理面试中的那点事吧,帮助那些正在找工作或想跳槽找工作的学弟学妹们. 前面我己写过多篇推文,相信看过我文章的伙伴们已经了解掌握了不少.从目前流行的开发技术. ...

  5. Java 后端开发面试总结:25 个技术专题(最全面试攻略)

    另送福利: java 面试准备 准确的说这里又分为两部分: 1.Java 刷题 2.算法刷题 Java 刷题:此份文档详细记录了千道面试题与详解:  !     私信我回复[03]即可免费获取 很多人 ...

  6. 全面了解C++后端开发技能树,C++后端开发面试技术点丨C/C++linux服务器开发丨linux后台开发

    C++后端开发技能树,C++后端开发面试技术点 视频讲解如下: 全面了解C++后端开发技能树,C++后端开发面试技术点丨C/C++linux服务器开发丨linux后台开发丨网络编程丨面试经验 C/C+ ...

  7. 微信小程序详细图文教程-10分钟完成微信小程序开发部署发布(3元获取腾讯云服务器带小程序支持系统)...

    很多朋友都认为微信小程序申请.部署.发布很难,需要很长时间. 实际上,微信和腾讯云同是腾讯产品,已经提供了10分钟(根据准备资源情况,已完成小程序申请认证)完成小程序开发.部署.发布的方式.当然,实现 ...

  8. 微信小程序详细图文教程-10分钟完成微信小程序开发部署发布

    很多朋友都认为微信小程序申请.部署.发布很难,需要很长时间. 实际上,微信和腾讯云同是腾讯产品,已经提供了10分钟(根据准备资源情况,已完成小程序申请认证)完成小程序开发.部署.发布的方式.当然,实现 ...

  9. 微信小程序详细图文教程-10分钟完成微信小程序开发部署发布 小程序趟过的坑,你遇到几个??

    很多朋友都认为微信小程序申请.部署.发布很难,需要很长时间. 实际上,微信和腾讯云同是腾讯产品,已经提供了10分钟(根据准备资源情况,已完成小程序申请认证)完成小程序开发.部署.发布的方式.当然,实现 ...

  10. 后端开发面试自我介绍_一定是最适合你的后端面试指南

    原标题:一定是最适合你的后端面试指南 本文转自公众号:Java面试通关手册 友情提示:阅读+独立思考的方式学习本文效果最佳.本文干货较多,如果大家觉得不错请转发给可能需要本文的朋友哦!最后,不要忘记给 ...

最新文章

  1. 「AI初识境」近20年深度学习在图像领域的重要进展节点
  2. 网站优化中什么样的外链容易被删去?
  3. 分享42个超棒的智能手机摄影画廊
  4. Windows CE Notification API的使用方法
  5. 鸿蒙不算安卓系统吗,加入鸿蒙≠使用鸿蒙,鸿蒙不属于华为也仅限于华为
  6. java中注解的解析_【Java】中的注解与注解解析器
  7. Java黑皮书课后题第2章:*2.19(几何:三角形面积)编写程序,提示用户输入三角形的三个点(x1, y1)(x2, y2)(x3, y3),然后显示它的面积
  8. 干活|常见WAF拦截页面总结
  9. 使用Spring Boot和MongoDB快速进行Web应用原型设计
  10. 秦汉考场科目三路线图_海淀驾校科目三校内考场总结
  11. python进阶22再识单例模式
  12. [AaronYang]C#人爱学不学[6]
  13. 微信java sdk 多公众号_Java版微信公众号支付开发全过程
  14. [TJOI2019]唱、跳、rap和篮球
  15. 360无线wifi路由器连接到服务器,luyou.360.cn如何登录360路由器
  16. 基因测序的云计算平台可能带来的变革与进步
  17. UML用例图-软件需求分析与设计(很详细,很详细,很详细)
  18. 劳务派遣有五险一金吗?
  19. opencv直线拟合cv::fitLine()
  20. 淘宝客API订单接入的一些坑

热门文章

  1. 计算机网络(三)—— 数据链路层(8、9):集线器与交换机的区别、以太网交换机自学习和转发帧的流程
  2. matlab gui双音拨号,用matlab GUI功能模拟DTMF拨号系统.doc
  3. 机器学习 识别图片人物动作_一键学习人物识别说明
  4. 【转】江枫:在ubuntu10.04上安装oracle 10g(学习一下)
  5. js判断指定日期是礼拜几
  6. Android DEX安全攻防战
  7. xpath prase string
  8. 努比亚 Z5 mini刷机包 omni4.4.2修改V4.0 自用版 精简 MIUI特效
  9. 材料力学 4.弯曲内力
  10. fiddler+LR11录制脚本