要秋招了,再来一遍
1.操作系统
什么是操作系统?请简要概述一下
操作系统是管理计算机硬件和软件资源的计算机程序,提供一个计算机用户与计算机硬件系统之间的接口。
向上对用户程序提供接口,向下接管硬件资源。
操作系统本质上也是一个软件,作为最接近硬件的系统软件,负责处理器管理、存储器管理、设备管理、文件管理和提供用户接口。
操作系统有哪些分类?
操作系统常规可分为批处理操作系统、分时操作系统、实时操作系统。
若一个操作系统兼顾批操作和分时的功能,则称该系统为通用操作系统。
常见的通用操作系统有:Windows、Linux、MacOS等。
什么是内核态和用户态?
为了避免操作系统和关键数据被用户程序破坏,将处理器的执行状态分为内核态和用户态。
内核态是操作系统管理程序执行时所处的状态,能够执行包含特权指令在内的一切指令,能够访问系统内所有的存储空间。
用户态是用户程序执行时处理器所处的状态,不能执行特权指令,只能访问用户地址空间。
用户程序运行在用户态,操作系统内核运行在内核态。
如何实现内核态和用户态的切换?
处理器从用户态切换到内核态的方法有三种:系统调用、异常和外部中断。
- 系统调用是操作系统的最小功能单位,是操作系统提供的用户接口,系统调用本身是一种软中断。
- 异常,也叫做内中断,是由错误引起的,如文件损坏、缺页故障等。
- 外部中断,是通过两根信号线来通知处理器外设的状态变化,是硬中断。
并发和并行的区别
- 并发(concurrency):指宏观上看起来两个程序在同时运行,比如说在单核cpu上的多任务。但是从微观上看两个程序的指令是交织着运行的,指令之间交错执行,在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能,只能提高效率(如降低某个进程的相应时间)。
- 并行(parallelism):指严格物理意义上的同时运行,比如多核cpu,两个程序分别运行在两个核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令,也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的cpu都是往多核方面发展。
什么是进程?
进程是操作系统中最重要的抽象概念之一,是资源分配的基本单位,是独立运行的基本单位。
进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文(context)中。
上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
进程一般由以下的部分组成:
- 进程控制块PCB,是进程存在的唯一标志,包含进程标识符PID,进程当前状态,程序和数据地
址,进程优先级、CPU现场保护区(用于进程切换),占有的资源清单等。 - 程序段
- 数据段
进程的基本操作
以Unix系统举例:
- 进程的创建:fork()。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用 fork 时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的 PID。fork函数是有趣的(也常常令人迷惑), 因为它只被调用一次,却会返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。在父进程中,fork 返回子进程的 PID。在子进程中,fork 返回 0。因为子进程的 PID 总是为非零,返回值就提供一个明 确的方法来分辨程序是在父进程还是在子进程中执行。
1 | pid_t fork(void); |
- 回收子进程:当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态中,直到被它的父进程回收(reaped)。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。一个进程可以通过调用waitpid 函数来等待它的子进程终止或者停止。
1 | pid_t waitpid(pid_t pid, int *statusp, int options); |
- 加载并运行程序:execve 函数在当前进程的上下文中加载并运行一个新程序。
1 | int execve(const char *filename, const char *argv[], const char *envp[]); |
- 进程终止:
1 | void exit(int status); |
简述进程间通信方法
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
不同进程间的通信本质:进程之间可以看到一份公共资源;而提供这份资源的形式或者提供者不同,造成了通信方式不同。
进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。
进程如何通过管道进行通信
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
- 其本质是一个伪文件(实为内核缓冲区)
- 由两个文件描述符引用,一个表示读端,一个表示写端。
- 规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区实现。
管道的局限性:
- 数据自己读不能自己写。
- 数据一旦被读走,便不在管道中存在,不可反复读取。
- 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
- 只能在有公共祖先的进程间使用管道。
进程如何通过共享内存通信?
它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
特点:
- 共享内存是最快的一种IPC,因为进程是直接对内存进行操作来实现通信,避免了数据在用户空间
和内核空间来回拷贝。 - 因为多个进程可以同时操作,所以需要进行同步处理。
- 信号量和共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
什么是信号
一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件。 Linux 系统上支持的30 种不同类型的信号。 每种信号类型都对应于某种系统事件。低层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。
- 发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。发送信号可以有如下两种原因:
- 内核检测到一个系统事件,比如除零错误或者子进程终止。
- —个进程调用了kill 函数, 显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
- 接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号。
如何编写正确且安全的信号处理函数
处理程序要尽可能简单。 避免麻烦的最好方法是保持处理程序尽可能小和简单。例如,处理程序可能只是简单地设置全局标志并立即返回;所有与接收信号相关的处理都由主程序执行,它周期性地检查(并重置)这个标志。
在处理程序中只调用异步信号安全的函数。 所谓异步信号安全的函数(或简称安全的函数)能够被信号处理程序安全地调用,原因有二:要么它是可重入的(例如只访问局部变量),要么它不能被信号处理程序中断。
保存和恢复errno。 许多Linux 异步信号安全的函数都会在出错返回时设置errno在处理程序中调用这样的函数可能会干扰主程序中其他依赖于分。解决方法是在进人处理程序时把errno 保存在一个 局部变量中,在处理程序返回前恢复它。注意,只有在处理程序要返回时才有此必要。如果处理程序调用_exit终止该进程,那么就不需要这样做了。
阻塞所有的信号,保护对共享全局数据结构的访问。 如果处理程序和主程序或其他处理程序共享一个全局数据结构,那么在访问(读或者写)该数据结构时,你的处理程序和主程序应该暂时阻塞所有的信号。这条规则的原因是从主程序访问一个数据结构d 通常需要一系列的指令,如果指令序列被 访问d 的处理程序中断,那么处理程序可能会发现d 的状态不一致,得到不可预知的结果。在访问d 时暂时阻塞信号保证了处理程序不会中断该指令序列。
用volatile 声明全局变量。 考虑一个处理程序和一个main 函数,它们共享一个全局变量g 。处理程序更新g,main 周期性地读g, 对于一个优化编译器而言,main 中g的值看上去从来没有变化过, 因此使用缓存在寄存器中g 的副本来满足对g 的每次引用是很安全的。如果这样,main 函数可能永远都无法看到处理程序更新过的值。可以用volatile 类型限定符来定义一个变量,告诉编译器不要缓存这个变量。例如:volatile 限定符强迫编译器毎次在代码中引用g时,都要从内存中读取g的 值。一般来说,和其他所有共享数据结构一样,应该暂时阻塞信号,保护每次对全局变量的访问。
1 | volatile int g; |
用sig_atomic_t声明标志。在常见的处理程序设计中,处理程序会写全局标志来记录收到了信号。主程序周期性地读这个标志,响应信号,再清除该标志。对于通过这种方式来共享的标志,C 提供 一种整型数据类型sig_atomic_t对它的读和写保证会是原子的(不可中断的)。
信号的一个与直觉不符的方面是未处理的信号是不排队的。因为 pending 位向量中每种类型的信号只对应有一位,所以每种类型最多只能有一个未处理的信号。关键思想是如果存在一个未处理的信号就表明至少有一个信号到达了。
进程调度的时机
- 当前运行的进程运行结束。
- 当前运行的进程由于某种原因阻塞。
- 执行完系统调用等系统程序后返回用户进程。
- 在使用抢占调度的系统中,具有更高优先级的进程就绪时。
- 分时系统中,分给当前进程的时间片用完。
不能进行进程调度的情况
- 在中断处理程序执行时。
- 在操作系统的内核程序临界区内。
- 其它需要完全屏蔽中断的原子操作过程中。
进程的调度策略
- 先到先服务调度算法
- 短作业优先调度算法
- 优先级调度算法
- 时间片轮转调度算法
- 高响应比优先调度算法
- 多级队列调度算法
- 多级反馈队列调度算法
进程调度策略的基本设计指标
- CPU利用率
- 系统吞吐率,即单位时间内CPU完成的作业的数量。
- 响应时间。
- 周转时间。是指作业从提交到完成的时间间隔。从每个作业的角度看,完成每个作业的时间也是很关键
- 平均周转时间
- 带权周转时间
- 平均带权周转时间
进程的状态与状态转换
进程在运行时有三种基本状态:就绪态、运行态和阻塞态。
- 运行(running)态:进程占有处理器正在运行的状态。进程已获得CPU,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态; 在多处理机系统中,则有多个进程处于执行状态。
- 就绪(ready)态:进程具备运行条件,等待系统分配处理器以便运行的状态。 当进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行,进程这时的状态称为就绪状态。在一个系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列。
- 阻塞(wait)态:又称等待态或睡眠态,指进程不具备运行条件,正在等待某个时间完成的状态。
各状态之间的转换:
就绪→执行 处于就绪状态的进程,当进程调度程序为之分配了处理机后,该进程便由就绪状态转变成执行状态。
执行→就绪 处于执行状态的进程在其执行过程中,因分配给它的一个时间片已用完而不得不让出处理机,于是进程从执行状态转变成就绪状态。
执行→阻塞 正在执行的进程因等待某种事件发生而无法继续执行时,便从执行状态变成阻塞状态。
阻塞→就绪 处于阻塞状态的进程,若其等待的事件已经发生,于是进程由阻塞状态转变为就绪状态。

什么是孤儿进程?僵尸进程?
- 孤儿进程: 父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init进程(1号进程)所收养,并由init进程对他们完成状态收集工作。
- 僵尸进程: 进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait 获waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。
什么是线程?
- 是进程划分的任务,是一个进程内可调度的实体,是CPU调度的基本单位,用于保证程序的实时性,实现进程内部的并发。
- 线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。
- 每个线程完成不同的任务,但是属于同一个进程的不同线程之间共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
为什么需要线程?
线程产生的原因:进程可以使多个程序能并发执行,以提高资源的利用率和系统的吞吐量;但是其具有一些缺点:
- 进程在同一时刻只能做一个任务,很多时候不能充分利用CPU资源。
- 进程在执行的过程中如果发生阻塞,整个进程就会挂起,即使进程中其它任务不依赖于等待的资源,进程仍会被阻塞。
引入线程就是为了解决以上进程的不足,线程具有以下的优点:
从资源上来讲,开辟一个线程所需要的资源要远小于一个进程。
从切换效率上来讲,运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间(这种时间的差异主要由于缓存的大量未命中导致)。
从通信机制上来讲,线程间方便的通信机制。对不同进程来说,它们具有独立的地址空间,要进行数据的传递只能通过进程间通信的方式进行。线程则不然,属于同一个进程的不同线程之间共享同一地址空间,所以一个线程的数据可以被其它线程感知,线程间可以直接读写进程数据段(如全局变量)来进行通信(需要一些同步措施)。
简述线程和进程的区别和联系
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
- 进程在执行过程中拥有独立的地址空间,而多个线程共享进程的地址空间。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)
- 进程是资源分配的最小单位,线程是CPU调度的最小单位。
- 通信:由于同一进程中的多个线程具有相同的地址空间,使它们之间的同步和通信的实现,也变得比较容易。进程间通信 IPC ,线程间可以直接读写进程数据段(如全局变量)来进行通信(需要一些同步方法,以保证数据的一致性)。
- 进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。
- 进程间不会相互影响;一个进程内某个线程挂掉将导致整个进程挂掉。
- 进程适应于多核、多机分布;线程适用于多核。
进程和线程的基本API
进程API以Unix系统为例,线程相关的API属于Posix线程(Pthreads)标准接口。
| 进程原语 | 线程原语 | 描述 |
|---|---|---|
| fork | pthread_create | 创建新的控制流 |
| exit | pthread_exit | 从现有的控制流中退出 |
| waitpid | pthread_join | 从控制流中得到退出状态 |
| atexit | pthread_cancel_push | 注册在退出控制流时调用的函数 |
| getpid | pthread_self | 获取控制流的ID |
| abort | pthread_cancel | 请求控制流的非正常退出 |
多线程模型
- 多对一模型。将多个用户级线程映射到一个内核级线程上。该模型下,线程在用户空间进行管理,效率较高。缺点就是一个线程阻塞,整个进程内的所有线程都会阻塞。几乎没有系统继续使用这个模型。
- 一对一模型。将内核线程与用户线程一一对应。优点是一个线程阻塞时,不会影响到其它线程的执行。该模型具有更好的并发性。缺点是内核线程数量一般有上限,会限制用户线程的数量。更多的内核线程数目也给线程切换带来额外的负担。linux和Windows操作系统家族都是使用一对一模型。
- 多对多模型。将多个用户级线程映射到多个内核级线程上。结合了多对一模型和一对一模型的特点。
进程同步的方法
操作系统中,进程是具有不同的地址空间的,两个进程是不能感知到对方的存在的。有时候,需要多个进程来协同完成一些任务。
当多个进程需要对同一个内核资源进行操作时,这些进程便是竞争的关系,操作系统必须协调各个进程对资源的占用,进程的互斥是解决进程间竞争关系的方法。 进程互斥指若干个进程要使用同一共享资源时,任何时刻最多允许一个进程去使用,其他要使用该资源的进程必须等待,直到占有资源的进程释放该资源。
当多个进程协同完成一些任务时,不同进程的执行进度不一致,这便产生了进程的同步问题。需要操作系统干预,在特定的同步点对所有进程进行同步,这种协作进程之间相互等待对方消息或信号的协调关系称为进程同步。进程互斥本质上也是一种进程同步。
进程的同步方法:
- 互斥锁
- 读写锁
- 条件变量
- 记录锁(record locking)
- 信号量
- 屏障(barrier)
线程同步的方法
操作系统中,属于同一进程的线程之间具有相同的地址空间,线程之间共享数据变得简单高效。遇到竞争的线程同时修改同一数据或是协作的线程设置同步点的问题时,需要使用一些线程同步的方法来解决这些问题。
线程同步的方法:
- 互斥锁
- 读写锁
- 条件变量
- 信号量
- 自旋锁:当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
- 屏障(barrier)
进程同步与线程同步有什么区别
进程之间地址空间不同,不能感知对方的存在,同步时需要将锁放在多进程共享的空间。而线程之间共享同一地址空间,同步时把锁放在所属的同一进程空间即可。
死锁是怎样产生的?
死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。
产生死锁需要满足下面四个条件:
- 互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源。
- 占有并等待条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源。
- 不可抢占条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放。
- 循环等待条件:进程发生死锁后,必然存在一个进程-资源之间的环形链。
如何解决死锁问题?
解决死锁的方法即破坏产生死锁的四个必要条件之一,主要方法如下:
- 资源一次性分配,这样就不会再有请求了(破坏请求条件)。
- 只要有一个资源得不到分配,也不给这个进程分配其他的资源(破坏占有并等待条件)。
- 可抢占资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可抢占的条件。
- 资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏循环等待的条件
什么是虚拟地址,什么是物理地址?
地址空间是一个非负整数地址的有序集合。
在一个带虚拟内存的系统中,CPU 从一个有N=pow(2,n)个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间(virtual address space),现代系统通常支持 32 位或者 64 位虚拟地址空间。
一个系统还有一个物理地址空间(physical address space),对应于系统中物理内存的M 个字节。
地址空间的概念是很重要的,因为它清楚地区分了数据对象(字节)和它们的属性(地址)。
一旦认识到了这种区别,那么我们就可以将其推广,允许每个数据对象有多个独立的地址,其中每个地址都选自一个不同的地址空间。这就是虚拟内存的基本思想。
主存中的每字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。
什么是虚拟内存?
为了更加有效地管理内存并且少出错,现代系统提供了一种对主存的抽象概念,叫做虚拟内存(VM)。虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。通过一个很清晰的机制,虚拟内存提供了三个重要的能力:
- 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。
- 它为每个进程提供了一致的地址空间,从而简化了内存管理。
- 它保护了每个进程的地址空间不被其他进程破坏。
为什么要引入虚拟内存?
虚拟内存作为缓存的工具
- 虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。
- 虚拟内存利用DRAM缓存来自通常更大的虚拟地址空间的页面。
虚拟内存作为内存管理的工具。操作系统为每个进程提供了一个独立的页表,也就是独立的虚拟地址空间。多个虚拟页面可以映射到同一个物理页面上。
- 简化链接: 独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际存放在物理内存的何处。
- 例如:一个给定的 linux 系统上的每个进程都是用类似的内存格式,对于64为地址空间,代码段总是从虚拟地址) 0x400000 开始,数据段,代码段,栈,堆等等。
- 简化加载: 虚拟内存还使得容易向内存中加载可执行文件和共享对象文件。要把目标文件中.text和.data节加载到一个新创建的进程中,Linux加载器为代码和数据段分配虚拟页VP,把他们标记为无效(未被缓存) ,将页表条目指向目标文件的起始位置。
- 加载器从不在磁盘到内存实际复制任何数据,在每个页初次被引用时,虚拟内存系统会按照需要自动的调入数据页。
- 简化共享: 独立地址空间为OS提供了一个管理用户进程和操作系统自身之间共享的一致机制。
- 一般:每个进程有各自私有的代码,数据,堆栈,是不和其他进程共享的,这样OS创建页表,将虚拟页映射到不连续的物理页面。
- 某些情况下,需要进程来共享代码和数据。例如每个进程调用相同的操作系统内核代码,或者C标准库函数。OS会把不同进程中适当的虚拟页面映射到相同的物理页面。
- 简化内存分配: 虚拟内存向用户提供一个简单的分配额外内存的机制。当一个运行在用户进程中的程序要求额外的堆空间时(如 malloc ),OS分配一个适当k大小个连续的虚拟内存页面,并且将他们映射到物理内存中任意位置的k个任意物理页面,因此操作系统没有必要分配k个连续的物理内存页面,页面可以随机的分散在物理内存中。
虚拟内存作为内存保护的工具。不应该允许一个用户进程修改它的只读段,也不允许它修改任何内核代码和数据结构,不允许读写其他进程的私有内存,不允许修改任何与其他进程共享的虚拟页面。每次CPU生成一个地址时, MMU 会读一个 PTE ,通过在 PTE 上添加一些额外的许可位来控制对一个虚拟页面内容的访问十分简单。
- 简化链接: 独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际存放在物理内存的何处。
常见的页面置换算法
当访问一个内存中不存在的页,并且内存已满,则需要从内存中调出一个页或将数据送至磁盘对换区,替换一个页,这种现象叫做缺页置换。当前操作系统最常采用的缺页置换算法如下:
- 先进先出(FIFO)算法:
- 思路:置换最先调入内存的页面,即置换在内存中驻留时间最久的页面。
- 实现:按照进入内存的先后次序排列成队列,从队尾进入,从队首删除。
- 特点:实现简单;性能较差,调出的页面可能是经常访问的
- 最近最少使用( LRU )算法:
- 思路: 置换最近一段时间以来最长时间未访问过的页面。根据程序局部性原理,刚被访问的页面,可能马上又要被访问;而较长时间内没有被访问的页面,可能最近不会被访问。
- 实现:缺页时,计算内存中每个逻辑页面的上一次访问时间,选择上一次使用到当前时间最长的页面
- 特点:可能达到最优的效果,维护这样的访问链表开销比较大
当前最常采用的就是 LRU 算法。
- 最不常用算法( Least Frequently Used, LFU )
- 思路:缺页时,置换访问次数最少的页面
- 实现:每个页面设置一个访问计数,访问页面时,访问计数加1,缺页时,置换计数最小的页面
- 特点:算法开销大,开始时频繁使用,但以后不使用的页面很难置换
请说一下什么是写时复制?
- 如果有多个进程要读取它们自己的那部门资源的副本,那么复制是不必要的。每个进程只要保存一个指向这个资源的指针就可以了。只要没有进程要去修改自己的“副本”,就存在着这样的幻觉:每个进程好像独占那个资源。从而就避免了复制带来的负担。如果一个进程要修改自己的那份资源“副本”,那么就会复制那份资源,并把复制的那份提供给进程。不过其中的复制对进程来说是透明的。这个进程就可以修改复制后的资源了,同时其他的进程仍然共享那份没有修改过的资源。所以这就是名称的由来:在写入时进行复制。
- 写时复制的主要好处在于:如果进程从来就不需要修改资源,则不需要进行复制。惰性算法的好处就在于它们尽量推迟代价高昂的操作,直到必要的时刻才会去执行。
- 在使用虚拟内存的情况下,写时复制(Copy-On-Write)是以页为基础进行的。所以,只要进程不修改它全部的地址空间,那么就不必复制整个地址空间。在fork()调用结束后,父进程和子进程都相信它们有一个自己的地址空间,但实际上它们共享父进程的原始页,接下来这些页又可以被其他的父进程或子进程共享。
实时操作系统的概念
实时操作系统(Real-time operating system, RTOS),又称即时操作系统,它会按照排序运行、管理系统资源,并为开发应用程序提供一致的基础。 实时操作系统与一般的操作系统相比,最大的特色就是“实时性”,如果有一个任务需要执行,实时操作系统会马上(在较短时间内)执行该任务,不会有较长的延时。这种特性保证了各个任务的及时执行。
优先级反转是什么?如何解决
由于多进程共享资源,具有最高优先权的进程被低优先级进程阻塞,反而使具有中优先级的进程先于高优先级的进程执行,导致系统的崩溃。这就是所谓的优先级反转(Priority Inversion)。其实,优先级反转是在高优级(假设为A)的任务要访问一个被低优先级任务(假设为C)占有的资源时,被阻塞.而此时又有优先级高于占有资源的任务(C)而低于被阻塞的任务(A)的优先级的任务(假设为B)时,于是,占有资源的任务就被挂起(占有的资源仍为它占有),因为占有资源的任务优先级很低,所以,它可能一直被另外的任务挂起.而它占有的资源也就一直不能释放,这样,引起任务A一直没办法执行.而比它优先低的任务却可以执行。
目前解决优先级反转有许多种方法。其中普遍使用的有2种方法:一种被称作优先级继承(priority inheritance);另一种被称作优先级极限(priority ceilings)。
优先级继承(priority inheritance) 优先级继承是指将低优先级任务的优先级提升到等待它所占有的资源的最高优先级任务的优先级.当高优先级任务由于等待资源而被阻塞时,此时资源的拥有者的优先级将会自动被提升。
优先级天花板(priority ceilings)优先级天花板是指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级.(这个优先级称为该资源的优先级天花板)。
2.数据库
简述数据库三大范式
第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。
数据库第二范式:关系模式必须满足第一范式,并且所有非主属性都完全依赖于主码。注意,符合第二范式的关系模型可能还存在数据冗余、更新异常等问题。关系模型(学号,姓名,专业编号,专业名称)中,学号->姓名,而专业编号->专业名称,不满足数据库第二范式。
数据库第三范式:关系模型满足第二范式,所有非主属性对任何候选关键字都不存在传递依赖。即每个属性都跟主键有直接关系而不是间接关系。接着以学生表举例,对于关系模型(学号,姓名,年龄,性别,所在院校,院校地址,院校电话)院校地址,院校电话和学号不存在直接关系,因此不满足第三范式。
简述MySQL的架构
MySQL可以分为应用层,逻辑层,数据库引擎层,物理层。
应用层:负责和客户端,响应客户端请求,建立连接,返回数据。
逻辑层:包括SQK接口,解析器,优化器,Cache与buffer。
数据库引擎层:有常见的MyISAM,InnoDB等等。
物理层:负责文件存储,日志等等。
简述执行SQL语言的过程
- 客户端首先通过连接器进行身份认证和权限相关
- 如果是执行查询语句的时候,会先查询缓存,但MySQL 8.0 版本后该步骤移除。
- 没有命中缓存的话,SQL 语句就会经过解析器,分析语句,包括语法检查等等。
- 通过优化器,将用户的SQL语句按照 MySQL 认为最优的方案去执行。
- 执行语句,并从存储引擎返回数据。
简述MySQL的共享锁和排它锁
共享锁也称为读锁,相互不阻塞,多个客户在同一时刻可以同时读取同一个资源而不相互干扰。排他锁也称为写锁,会阻塞其他的写锁和读锁,确保在给定时间内只有一个用户能执行写入并防止其他用户读取正在写入的同一资源。
简述MySQL中的按粒度的锁分类
表级锁: 对当前操作的整张表加锁,实现简单,加锁快,但并发能力低。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突概率高,并发度最低
行锁: 锁住某一行,如果表存在索引,那么记录锁是锁在索引上的,如果表没有索引,那么 InnoDB 会创建一个隐藏的聚簇索引加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
Gap 锁:也称为间隙锁: 锁定一个范围但不包括记录本身。其目的是为了防止同一事务的两次当前读出现幻读的情况。
Next-key Lock: 行锁+gap锁。
如何解决数据库死锁
- 预先检测到死锁的循环依赖,并立即返回一个错误。
- 当查询的时间达到锁等待超时的设定后放弃锁请求。
简述乐观锁和悲观锁
乐观锁:对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁,只有到数据提交的时候才通过一种机制来验证数据是否存在冲突。
悲观锁:对于数据冲突保持一种悲观态度,在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的。
简述InnoDB存储引擎
InnoDB 是 MySQL 的默认事务型引擎,支持事务,表是基于聚簇索引建立的。支持表级锁和行级锁,支持外键,适合数据增删改查都频繁的情况。
InnoDB 采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别。其默认级别是 REPEATABLE READ,并通过间隙锁策略防止幻读,间隙锁使 InnoDB 不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定防止幻行的插入。
简述MyISAM存储引擎
MySQL5.1及之前,MyISAM 是默认存储引擎。MyISAM不支持事务,Myisam支持表级锁,不支持行级锁,表不支持外键,该存储引擎存有表的行数,count运算会更快。适合查询频繁,不适合对于增删改要求高的情况
简述Memory存储引擎
Memory存储引擎将所有数据都保存在内存,不需要磁盘 IO。支持哈希索引,因此查找速度极快。
Memory 表使用表级锁,因此并发写入的性能较低。
索引是什么?
索引是存储引擎中用于快速找到记录的一种数据结构。在关系型数据库中,索引具体是一种对数据库中一列或多列的值进行排序的存储结构。
为什么引入索引?
为了提高数据查询的效率。索引对数据库查询良好的性能非常关键,当表中数据量越来越大,索引对性能的影响越重要。
Mysql有哪些常见索引类型?
数据结构角度
B-Tree索引
哈希索引
R-Tree索引
全文索引物理存储角度
主键索引(聚簇索引):叶子节点存的是整行的数据
非主键索引(二级索引):叶子节点存的主键的值
简述B-tree(B树)与B+树
B树是一种自平衡的多叉树。每个节点都存储关键字值。其左子节点的关键字值小于该节点关键字值,且右子节点的关键字值大于或等于该节点关键字值。
B+树是基于 B树 和叶子节点顺序访问指针进行实现,它具有 B树 的平衡性,并且通过顺序访问指针来提高区间查询的性能。也是是一种自平衡的多叉树。其基本定义与B树相同,不同点在于数据只出现在叶子节点,所有叶子节点增加了一个链指针,方便进行范围查询。
B+树中间节点不存放数据,所以同样大小的磁盘页上可以容纳更多节点元素,访问叶子节点上关联的数据也具有更好的缓存命中率。并且数据顺序排列并且相连,所以便于区间查找和搜索。
B树每一个节点都包含key和value,查询效率比B+树高。
简述Hash索引
哈希索引对于每一行数据计算一个哈希码,并将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。只有 Memory 引擎显式支持哈希索引。
Hash索引不支持范围查询,无法用于排序,也不支持部分索引列匹配查找。
简述自适应Hash索引
InnoDB对于频繁使用的某些索引值,会在内存中基于 B-Tree 索引之上再创键一个哈希索引,这也被称为自适应Hash索引。
简述聚集索引和稀疏索引
聚集索引按每张表的主键构建一棵B+树,数据库中的每个搜索键值都有一个索引记录,每个数据页通过双向链表连接。表数据访问更快,但表更新代价高。
稀疏索引不会为每个搜索关键字创建索引记录。搜索过程需要,我们首先按索引记录进行操作,并按顺序搜索,直到找到所需的数据为止。
简述辅助索引与回表查询
辅助索引是非聚集索引,叶子节点不包含记录的全部数据,包含了一个书签用来告诉InnoDB哪里可以找到与索引相对应的行数据。
通过辅助索引查询,先通过书签查到聚集索引,再根据聚集索引查对应的值,需要两次,也称为回表查询。
简述联合索引和最左匹配原则
联合索引是指对表上的多个列的关键词进行索引。
对于联合索引的查询,如果精确匹配联合索引的左边连续一列或者多列,则mysql会一直向右匹配直到遇到范围查询(>,<,between,like)就停止匹配。Mysql会对第一个索引字段数据进行排序,在第一个字段基础上,再对第二个字段排序。
简述覆盖索引
覆盖索引指一个索引包含或覆盖了所有需要查询的字段的值,不需要回表查询,即索引本身存了对应的值。
为什么数据库不用红黑树用B+树
红黑树的出度为 2,而 B Tree 的出度一般都非常大。红黑树的树高 h 很明显比 B Tree 大非常多,IO次数很多,导致会比较慢,因此检索的次数也就更多。
B+Tree 相比于 B-Tree 更适合外存索引,拥有更大的出度,IO次数较少,检索效率会更高。
基于主键索引的查询和非主键索引的查询有什么区别?
对于select * from 主键=XX,基于主键的普通查询仅查找主键这棵树,对于select * from 非主键=XX,基于非主键的查询有可能存在回表过程(回到主键索引树搜索的过程称为回表),因为非主键索引叶子节点仅存主键值,无整行全部信息。
非主键索引的查询一定会回表吗?
不一定,当查询语句的要求字段全部命中索引,不用回表查询。如select 主键 from 非主键=XX,此时非主键索引叶子节点即可拿到主键信息,不用回表。
简述MySQL使用EXPLAIN 的关键字段
explain关键字用于分析sql语句的执行情况,可以通过他进行sql语句的性能分析。
type:表示连接类型,从好到差的类型排序为
- system:系统表,数据已经加载到内存里。
- const:常量连接,通过索引一次就找到。
- eq_ref:唯一性索引扫描,返回所有匹配某个单独值的行。
- ref:非主键非唯一索引等值扫描,const或eq_ref改为普通非唯一索引。
- range:范围扫描,在索引上扫码特定范围内的值。
- index:索引树扫描,扫描索引上的全部数据。
- all:全表扫描。
key:显示MySQL实际决定使用的键。
key_len:显示MySQL决定使用的键长度,长度越短越好
Extra:额外信息
- Using filesort:MySQL使用外部的索引排序,很慢需要优化。
- Using temporary:使用了临时表保存中间结果,很慢需要优化。
- Using index:使用了覆盖索引。
- Using where:使用了where。
简述MySQL优化流程
- 通过慢日志定位执行较慢的SQL语句
- 利用explain对这些关键字段进行分析
- 根据分析结果进行优化
简述MySQL中的日志log
redo log: 存储引擎级别的log(InnoDB有,MyISAM没有),该log关注于事务的恢复.在重启mysql服务的时候,根据redo log进行重做,从而使事务有持久性。
undo log:是存储引擎级别的log(InnoDB有,MyISAM没有)保证数据的原子性,该log保存了事务发生之前的数据的一个版本,可以用于回滚,是MVCC的重要实现方法之一。
bin log:数据库级别的log,关注恢复数据库的数据。
简述事务
事务内的语句要么全部执行成功,要么全部执行失败。
事务满足如下几个特性:
原子性(Atomicity):
一个事务中的所有操作要么全部完成,要么全部不完成。
一致性(Consistency):
事务执行前后数据库的状态保持一致。
隔离性(Isolation)
多个并发事务对数据库进行操作,事务间互不干扰。
持久性(Durability)
事务执行完毕,对数据的修改是永久的,即使系统故障也不会丢失
数据库中多个事务同时进行可能会出现什么问题?
- 丢失修改
- 脏读:当前事务可以查看到别的事务未提交的数据。
- 不可重复读:在同一事务中,使用相同的查询语句,同一数据资源莫名改变了。
- 幻读:在同一事务中,使用相同的查询语句,莫名多出了一些之前不存在的数据,或莫名少了一些原先存在的数据。
SQL的事务隔离级别有哪些?
读未提交:
一个事务还没提交,它做的变更就能被别的事务看到。
读提交:
一个事务提交后,它做的变更才能被别的事务看到。
可重复读:
一个事务执行过程中看到的数据总是和事务启动时看到的数据是一致的。在这个级别下事务未提交,做出的变更其它事务也看不到。
串行化:
对于同一行记录进行读写会分别加读写锁,当发生读写锁冲突,后面执行的事务需等前面执行的事务完成才能继续执行。
什么是MVCC?
MVCC为多版本并发控制,即同一条记录在系统中存在多个版本。其存在目的是在保证数据一致性的前提下提供一种高并发的访问性能。对数据读写在不加读写锁的情况下实现互不干扰,从而实现数据库的隔离性,在事务隔离级别为读提交和可重复读中使用到。
在InnoDB中,事务在开始前会向事务系统申请一个事务ID,该ID是按申请顺序严格递增的。每行数据具有多个版本,每次事务更新数据都会生成新的数据版本,而不会直接覆盖旧的数据版本。数据的行结构中包含多个信息字段。其中实现MVCC的主要涉及最近更改该行数据的事务ID(DB_TRX_ID)和可以找到历史数据版本的指针(DB_ROLL_PTR)。InnoDB在每个事务开启瞬间会为其构造一个记录当前已经开启但未提交的事务ID的视图数组。通过比较链表中的事务ID与该行数据的值与对应的DB_TRX_ID,并通过DB_ROLL_PTR找到历史数据的值以及对应的DB_TRX_ID来决定当前版本的数据是否应该被当前事务所见。最终实现在不加锁的情况下保证数据的一致性。
读提交和可重复读都基于MVCC实现,有什么区别?
在可重复读级别下,只会在事务开始前创建视图,事务中后续的查询共用一个视图。而读提交级别下每个语句执行前都会创建新的视图。因此对于可重复读,查询只能看到事务创建前就已经提交的数据。而对于读提交,查询能看到每个语句启动前已经提交的数据。
InnoDB如何保证事务的原子性、持久性和一致性?
利用undo log保障原子性。该log保存了事务发生之前的数据的一个版本,可以用于回滚,从而保证事务原子性。
利用redo log保证事务的持久性,该log关注于事务的恢复.在重启mysql服务的时候,根据redo log进行重做,从而使事务有持久性。
利用undo log+redo log保障一致性。事务中的执行需要redo log,如果执行失败,需要undo log 回滚。
MySQL是如何保证主备一致的?
MySQL通过binlog(二进制日志)实现主备一致。binlog记录了所有修改了数据库或可能修改数据库的语句,而不会记录select、show这种不会修改数据库的语句。在备份的过程中,主库A会有一个专门的线程将主库A的binlog发送给备库B进行备份。其中binlog有三种记录格式:
- statement:记录对数据库进行修改的语句本身,有可能会记录一些额外的相关信息。优点是binlog日志量少,IO压力小,性能较高。缺点是由于记录的信息相对较少,在不同库执行时由于上下文的环境不同可能导致主备不一致。
- row:记录对数据库做出修改的语句所影响到的数据行以及对这些行的修改。比如当修改涉及多行数据,会把涉及的每行数据都记录到binlog。优点是能够完全的还原或者复制日志被记录时的操作。缺点是日志量占用空间较大,IO压力大,性能消耗较大。
- mixed:混合使用上述两种模式,一般的语句使用statment方式进行保存,如果遇到一些特殊的函数,则使用row模式进行记录。MySQL自己会判断这条SQL语句是否可能引起主备不一致,如果有可能,就用row格式,
否则就用statement格式。但是在生产环境中,一般会使用row模式。
redo log与binlog的区别?
- redo log是InnoDB引擎特有的,只记录该引擎中表的修改记录。binlog是MySQL的Server层实现的,会记录所有引擎对数据库的修改。
- redo log是物理日志,记录的是在具体某个数据页上做了什么修改;binlog是逻辑日志,记录的是这个语句的原始逻辑。
- redo log是循环写的,空间固定会用完;binlog是可以追加写入的,binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
crash-safe能力是什么?
InnoDB通过redo log保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。
WAL技术是什么?
WAL的全称是Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。事务在提交写入磁盘前,会先写到redo log里面去。如果直接写入磁盘涉及磁盘的随机I/O访问,涉及磁盘随机I/O访问是非常消耗时间的一个过程,相比之下先写入redo log,后面再找合适的时机批量刷盘能提升性能。
两阶段提交是什么?
为了保证binlog和redo log两份日志的逻辑一致,最终保证恢复到主备数据库的数据是一致的,采用两阶段提交的机制。
- 执行器调用存储引擎接口,存储引擎将修改更新到内存中后,将修改操作记录redo log中,此时redo log处于prepare状态。
- 存储引擎告知执行器执行完毕,执行器生成这个操作对应的binlog,并把binlog写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交commit状态,更新完成。
只靠binlog可以支持数据库崩溃恢复吗?
不可以。
历史原因:
- InnoDB在作为MySQL的插件加入MySQL引擎家族之前,就已经是一个提供了崩溃恢复和事务支持的引擎了。InnoDB接入了MySQL后,发现既然binlog没有崩溃恢复的能力,那引入InnoDB原有的redo log来保证崩溃恢复能力。
实现原因:
- binlog没有记录数据页修改的详细信息,不具备恢复数据页的能力。binlog记录着数据行的增删改,但是不记录事务对数据页的改动,这样细致的改动只记录在redo log中。当一个事务做增删改时,其实涉及到的数据页改动非常细致和复杂,包括行的字段改动以及行头部以及数据页头部的改动,甚至b+tree会因为插入一行而发生若干次页面分裂,那么事务也会把所有这些改动记录下来到redo log中。因为数据库系统进程crash时刻,磁盘上面页面镜像可以非常混乱,其中有些页面含有一些正在运行着的事务的改动,而一些已提交的事务的改动并没有刷上磁盘。事务恢复过程可以理解为是要把没有提交的事务的页面改动都去掉,并把已经提交的事务的页面改动都加上去这样一个过程。这些信息,都是binlog中没有记录的,只记录在了存储引擎的redo log中。
- 操作写入binlog可细分为write和fsync两个过程,write指的就是指把日志写入到文件系统的page cache,并没有把数据持久化到磁盘,fsync才是将数据持久化到磁盘的操作。通过参数设置sync_binlog为0的时候,表示每次提交事务都只write,不fsync。此时数据库崩溃可能导致部分提交的事务以及binlog日志由于没有持久化而丢失。
简述MySQL主从复制
MySQL提供主从复制功能,可以方便的实现数据的多处自动备份,不仅能增加数据库的安全性,还能进行读写分离,提升数据库负载性能。
主从复制流程:
在事务完成之前,主库在binlog上记录这些改变,完成binlog写入过程后,主库通知存储引擎提交事物
从库将主库的binlog复制到对应的中继日志,即开辟一个I/O工作线程,I/O线程在主库上打开一个普通的连接,然后开始binlog dump process,将这些事件写入中继日志。从主库的binlog中读取事件,如果已经读到最新了,线程进入睡眠并等待ma主库产生新的事件。
读写分离:即只在MySQL主库上写,只在MySQL从库上读,以减少数据库压力,提高性能。
3.计算机网络
简述OSI七层协议
OSI七层协议包括:物理层,数据链路层,网络层,运输层,会话层,表示层, 应用层
简述TCP/IP五层协议
TCP/IP五层协议包括:物理层,数据链路层,网络层,运输层,应用层
物理层有什么作用
主要解决两台物理机之间的通信,通过二进制比特流的传输来实现,二进制数据表现为电流电压上的强弱,到达目的地再转化为二进制机器码。网卡、集线器工作在这一层。
数据链路层有什么作用
在不可靠的物理介质上提供可靠的传输,接收来自物理层的位流形式的数据,并封装成帧,传送到上一层;同样,也将来自上层的数据帧,拆装为位流形式的数据转发到物理层。这一层在物理层提供的比特流的基础上,通过差错控制、流量控制方法,使有差错的物理线路变为无差错的数据链路。提供物理地址寻址功能。交换机工作在这一层。
网络层有什么作用
将网络地址翻译成对应的物理地址,并决定如何将数据从发送方路由到接收方,通过路由选择算法为分组通过通信子网选择最佳路径。路由器工作在这一层。
传输层有什么作用
传输层提供了进程间的逻辑通信,传输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个传输层实体之间有一条端到端的逻辑通信信道。
会话层有什么作用
建立会话:身份验证,权限鉴定等;
保持会话:对该会话进行维护,在会话维持期间两者可以随时使用这条会话传输局;
断开会话:当应用程序或应用层规定的超时时间到期后,OSI会话层才会释放这条会话。
表示层有什么作用
对数据格式进行编译,对收到或发出的数据根据应用层的特征进行处理,如处理为文字、图片、音频、视频、文档等,还可以对压缩文件进行解压缩、对加密文件进行解密等。
应用层有什么作用
提供应用层协议,如HTTP协议,FTP协议等等,方便应用程序之间进行通信。
TCP与UDP区别
TCP作为面向流的协议,提供可靠的、面向连接的运输服务,并且提供点对点通信
UDP作为面向报文的协议,不提供可靠交付,并且不需要连接,不仅仅对点对点,也支持多播和广播
为何TCP可靠
TCP有三次握手建立连接,四次挥手关闭连接的机制。
除此之外还有滑动窗口和拥塞控制算法。最最关键的是还保留超时重传的机制。
对于每份报文也存在校验,保证每份报文可靠性。
为何UDP不可靠
UDP面向数据报无连接的,数据报发出去,就不保留数据备份了。
仅仅在IP数据报头部加入校验和复用。
UDP没有服务器和客户端的概念。
UDP报文过长的话是交给IP切成小段,如果某段报废报文就废了。
简述TCP粘包现象
TCP是面向流协议,发送的单位是字节流,因此会将多个小尺寸数据被封装在一个tcp报文中发出去的可能性。
可以简单的理解成客户端调用了两次send,服务器端一个recv就把信息都读出来了。
TCP粘包现象处理方法
固定发送信息长度,或在两个信息之间加入分隔符。
简述TCP协议的滑动窗口
滑动窗口是传输层进行流量控制的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,防止发送方发送速度过快而导致自己被淹没。
简述TCP协议的拥塞控制
拥塞是指一个或者多个交换点的数据报超载,TCP又会有重传机制,导致过载。
为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。
当cwnd < ssthresh 时,使用慢开始算法。
当cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
当cwnd = ssthresh 时,即可使用慢开始算法,也可使用拥塞避免算法。
慢开始:由小到大逐渐增加拥塞窗口的大小,每接一次报文,cwnd指数增加。
拥塞避免:cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1。
快恢复之前的策略:发送方判断网络出现拥塞,就把ssthresh设置为出现拥塞时发送方窗口值的一半,继续执行慢开始,之后进行拥塞避免。
快恢复:发送方判断网络出现拥塞,就把ssthresh设置为出现拥塞时发送方窗口值的一半,并把cwnd设置为ssthresh的一半,之后进行拥塞避免。
简述快重传
如果在超时重传定时器溢出之前,接收到连续的三个重复冗余ACK,发送端便知晓哪个报文段在传输过程中丢失了,于是重发该报文段,不需要等待超时重传定时器溢出再发送该报文。
TCP三次握手过程
第一次握手:客户端将标志位SYN置为1,随机产生一个值序列号seq=x,并将该数据包发送给服务端,客户端进入syn_sent状态,等待服务端确认。
第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务端将标志位SYN和ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给客户端以确认连接请求,服务端进入syn_rcvd状态。
第三次握手:客户端收到确认后检查,如果正确则将标志位ACK为1,ack=y+1,并将该数据包发送给服务端,服务端进行检查如果正确则连接建立成功,客户端和服务端进入established状态,完成三次握手,随后客户端和服务端之间可以开始传输数据了
为什么TCP握手需要三次,两次行不行?
不行。TCP进行可靠传输的关键就在于维护一个序列号,三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值。
如果只是两次握手, 至多只有客户端的起始序列号能被确认, 服务器端的序列号则得不到确认。
简述半连接队列
TCP握手中,当服务器处于SYN_RCVD 状态,服务器会把此种状态下请求连接放在一个队列里,该队列称为半连接队列。
简述SYN攻击
SYN攻击即利用TCP协议缺陷,通过发送大量的半连接请求,占用半连接队列,耗费CPU和内存资源。
优化方式:
- 缩短SYN Timeout时间
- 记录IP,若连续受到某个IP的重复SYN报文,从这个IP地址来的包会被一概丢弃。
TCP四次挥手过程
- 第一次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入fin_wait_1状态。
- 第二次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1,服务端进入Close_wait状态。此时TCP连接处于半关闭状态,即客户端已经没有要发送的数据了,但服务端若发送数据,则客户端仍要接收。
- 第三次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入Last_ack状态。
- 第四次挥手:客户端收到FIN后,客户端进入Time_wait状态,接着发送一个ACK给服务端,确认后,服务端进入Closed状态,完成四次挥手。
为什么TCP挥手需要4次
主要原因是当服务端收到客户端的 FIN 数据包后,服务端可能还有数据没发完,不会立即close。
所以服务端会先将 ACK 发过去告诉客户端我收到你的断开请求了,但请再给我一点时间,这段时间用来发送剩下的数据报文,发完之后再将 FIN 包发给客户端表示现在可以断了。之后客户端需要收到 FIN 包后发送 ACK 确认断开信息给服务端。
为什么四次挥手释放连接时需要等待2MSL
MSL即报文最大生存时间。设置2MSL可以保证上一次连接的报文已经在网络中消失,不会出现与新TCP连接报文冲突的情况。
简述DNS协议
DNS协议是基于UDP的应用层协议,它的功能是根据用户输入的域名,解析出该域名对应的IP地址,从而给客户端进行访问。
简述DNS解析过程
客户机发出查询请求,在本地计算机缓存查找,若没有找到,就会将请求发送给dns服务器
本地dns服务器会在自己的区域里面查找,找到即根据此记录进行解析,若没有找到,就会在本地的缓存里面查找
本地服务器没有找到客户机查询的信息,就会将此请求发送到根域名dns服务器
根域名服务器解析客户机请求的根域部分,它把包含的下一级的dns服务器的地址返回到客户机的dns服务器地址
客户机的dns服务器根据返回的信息接着访问下一级的dns服务器
这样递归的方法一级一级接近查询的目标,最后在有目标域名的服务器上面得到相应的IP信息
客户机的本地的dns服务器会将查询结果返回给我们的客户机
客户机根据得到的ip信息访问目标主机,完成解析过程
简述HTTP协议
http协议是超文本传输协议。它是基于TCP协议的应用层传输协议,即客户端和服务端进行数据传输的一种规则。该协议本身HTTP 是一种无状态的协议。
简述cookie
HTTP 协议本身是无状态的,为了使其能处理更加复杂的逻辑,HTTP/1.1 引入 Cookie 来保存状态信息。
Cookie是由服务端产生的,再发送给客户端保存,当客户端再次访问的时候,服务器可根据cookie辨识客户端是哪个,以此可以做个性化推送,免账号密码登录等等。
简述session
session用于标记特定客户端信息,存在在服务器的一个文件里。
一般客户端带Cookie对服务器进行访问,可通过cookie中的session id从整个session中查询到服务器记录的关于客户端的信息。
简述http状态码和对应的信息
1XX:接收的信息正在处理
2XX:请求正常处理完毕
3XX:重定向
4XX:客户端错误
5XX:服务端错误
常见错误码:
301:永久重定向
302:临时重定向
304:资源没修改,用之前缓存就行
400:客户端请求的报文有错误
403:表示服务器禁止访问资源
404:表示请求的资源在服务器上不存在或未找到
转发和重定向的区别
转发是服务器行为。服务器直接向目标地址访问URL,将相应内容读取之后发给浏览器,用户浏览器地址栏URL不变,转发页面和转发到的页面可以共享request里面的数据。
重定向是利用服务器返回的状态码来实现的,如果服务器返回301或者302,浏览器收到新的消息后自动跳转到新的网址重新请求资源。用户的地址栏url会发生改变,而且不能共享数据。
简述http 1.0
规定了请求头和请求尾,响应头和响应尾(get post)
每一个请求都是一个单独的连接,做不到连接的复用
简述http1.1的改进
HTTP1.1默认开启长连接,在一个TCP连接上可以传送多个HTTP请求和响应。使用 TCP 长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
服务端无法主动push
简述HTTP短连接与长连接区别
HTTP中的长连接短连接指HTTP底层TCP的连接。
短连接: 客户端与服务器进行一次HTTP连接操作,就进行一次TCP连接,连接结束TCP关闭连接。
长连接:如果HTTP头部带有参数keep-alive,即开启长连接网页完成打开后,底层用于传输数据的TCP连接不会直接关闭,会根据服务器设置的保持时间保持连接,保持时间过后连接关闭。
简述http2.0的改进
提出多路复用。多路复用前,文件时串行传输的,请求a文件,b文件只能等待,并且连接数过多。引入多路复用,a文件b文件可以同时传输。
引入了二进制数据帧。其中帧对数据进行顺序标识,有了序列id,服务器就可以进行并行传输数据。
http与https的区别
http所有传输的内容都是明文,并且客户端和服务器端都无法验证对方的身份。
https具有安全性的ssl加密传输协议,加密采用对称加密,https协议需要到ca申请证书,一般免费证书很少,需要交费。
简述TLS/SSL, HTTP, HTTPS的关系
SSL全称为Secure Sockets Layer即安全套接层,其继任为TLSTransport Layer Security传输层安全协议,均用于在传输层为数据通讯提供安全支持。
可以将HTTPS协议简单理解为HTTP协议+TLS/SSL
https的连接过程
- 浏览器将支持的加密算法信息发给服务器
- 服务器选择一套浏览器支持的加密算法,以证书的形式回发给浏览器
- 客户端(SSL/TLS)解析证书验证证书合法性,生成对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,用服务器的公钥对客户端密钥进行非对称加密。
- 客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端对称密钥发送给服务器
- 服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
- 服务器将加密后的密文发送给客户端
- 客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成
Get与Post区别
Get:指定资源请求数据,刷新无害,Get请求的数据会附加到URL中,传输数据的大小受到url的限制。
Post:向指定资源提交要被处理的数据。刷新会使数据会被重复提交。post在发送数据前会先将请求头发送给服务器进行确认,然后才真正发送数据。
Get方法参数有大小限制吗
一般HTTP协议里并不限制参数大小限制。但一般由于get请求是直接附加到地址栏里面的,由于浏览器地址栏有长度限制,因此使GET请求在浏览器实现层面上看会有长度限制。
了解REST API吗
REST API全称为表述性状态转移(Representational State Transfer,REST)即利用HTTP中get、post、put、delete以及其他的HTTP方法构成REST中数据资源的增删改查操作:
- Create : POST
- Read : GET
- Update : PUT/PATCH
- Delete: DELETE
浏览器中输入一个网址后,具体发生了什么
- 进行DNS解析操作,根据DNS解析的结果查到服务器IP地址
- 通过ip寻址和arp,找到服务器,并利用三次握手建立TCP连接
- 浏览器生成HTTP报文,发送HTTP请求,等待服务器响应
- 服务器处理请求,并返回给浏览器
- 根据HTTP是否开启长连接,进行TCP的挥手过程
- 浏览器根据收到的静态资源进行页面渲染