字节跳动后端开发面经(附答案版)

目录

C++

C++的三大特性

内存管理

虚函数

智能指针

深拷贝和浅拷贝

lambda表达式

auto

map和unordered_map

计算机网络

ISO七层模型

TCP/IP各层的主要功能

TCP/UDP

HTTP

ping

多路复用与多路分解

子网掩码的作用

防火墙

DNS(域名解析协议)

cookies和session

数据库

MySql数据库

索引

数据完整性

数据库的范式

数据表设计原则

事务

操作系统

select poll epoll

进程和线程的区别

线程安全

操作系统中的锁

死锁以及避免

线程之间的通信方式

进程之间的通信方式

进程调度算法

页面置换算法


C++

C++的三大特性

继承、封装、多态

内存管理

内存分配方式

内存分配方式有三种

  1. 从静态存储区域分配 内存在程序编译的时候就已经分配好了,这块内存在程序的整个运行期间都存在。例如全局变量,static静态成员变量

  2. 在栈上创建 执行函数时,函数内部变量的存储单位可以在栈上创建,函数执行结束时,这些存储单元自动释放。栈内存分配运算置于处理器的指令集中,效率很高,但是分配的内存容量有限

  3. 在堆上分配 也称为动态内存分配。程序在运行的时候用malloc或new申请任意多少内存,程序员自己负责在何时用free或delete来释放这块内存。动态内存的生命周期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。也就是我们常说的内存碎片。

  • 在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

     

    1. 栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。(向下,向着内存地址减小的方向增长)

    2. 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。(向上增长,向着内存地址增加的方向)

      对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。

       

    3. 自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。

    4. 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

    5. 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

虚函数

C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类定义的虚函数进行重写时,需要在派生类中声明该方法为虚方法。当子类重新定义了父类的虚函数后,当父类的指针指向子类对象的地址时,[即B b; A a = &b;] 父类指针根据赋给它的不同子类指针,动态的调用子类的该函数,而不是父类的函数(如果不使用virtual方法,请看后面★*),且这样的函数调用发生在运行阶段,而不是发生在编译阶段,称为动态联编(动态的多态)。而函数的重载可以认为是多态,只不过是静态的。注意,非虚函数静态联编,效率要比虚函数高,但是不具备动态联编能力。

★如果使用了virtual关键字,程序将根据引用或指针指向的 对 象 类 型 来选择方法,否则使用引用类型或指针类型来选择方法。

  1. 构造函数不能是虚函数。从存储空间角度,虚函数对应一个指向vtable虚函数表的指针。这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,因此也就没有办法找到vtable。因此构造函数不能是虚函数。

  2. 析构函数可以为虚函数。将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。

虚函数和多态

多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。举个例子:一个父类类型的指针指向一个子类对象时候,使用父类的指针去调用子类中重写了的父类中的虚函数的时候,会调用子类重写过后的函数,在父类中声明为加了virtual关键字的函数,在子类中重写时候不需要加virtual也是虚函数。 虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。

虚函数的底层实现机制

实现原理:虚函数表+虚表指针

编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针。

举个例子:基类对象包含一个虚表指针,指向基类中所有虚函数的地址表。派生类对象也将包含一个虚表指针,指向派生类虚函数表。看下面两种情况:

如果派生类重写了基类的虚方法,该派生类虚函数表将保存重写的虚函数的地址,而不是基类的虚函数地址。

如果基类中的虚方法没有在派生类中重写,那么派生类将继承基类中的虚方法,而且派生类中虚函数表将保存基类中未被重写的虚函数的地址。注意,如果派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。

智能指针

C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。

智能指针在C++11版本之后提供,包含在头文件中,shared_ptr、unique_ptr、weak_ptr

  1. shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。

  2. unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。

  3. weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。

深拷贝和浅拷贝

  1. 浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同的。

  2. 深拷贝,拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,这样两个指针就指向了不同的内存位置。并且里面的内容是一样的,这样不但达到了我们想要的目的,还不会出现问题,两个指针先后去调用析构函数,分别释放自己所指向的位置。即为每次增加一个指针,便申请一块新的内存,并让这个指针指向新的内存,深拷贝情况下,不会出现重复释放同一块内存的错误。

lambda表达式

lambda表达式是C++11中引入的,它是一种匿名函数,通常它作为一个参数传递给接收函数指针或者函数符的函数使用。在C++的STL算法使用过程中,经常可以看到它的身影。

基本表达式

[capture list] (params list) mutable exception-> return type { function body }

表达式中各个参数的含义如下:

(params list):形参列表 mutable:表示能不能修改捕获的变量 exception:异常设定 return type:返回类型 function body:函数体 虽然lambda中的参数变量很多,通常情况下并不需要把每一个都使用上,根据自己的需要使用即可,大多数情况下可以直接省略->、mutable、exception,不需要返回类型的话也可以省略return type。一个简单的lambda表达式:{return x*x;}。

auto

auto 在很早以前就已经进入了 C++,但是他始终作为一个存储类型的指示符存在,与 register 并存。在传统 C++ 中,如果一个变量没有声明为 register 变量,将自动被视为一个 auto 变量。而随着 register 被弃用,对 auto 的语义变更也就非常自然了。

  • 实践证明 不可以只进行初始化而不加推导

map和unordered_map

实现机理

  1. map: map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。

  2. unordered_map: unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。

优缺点以及适用处

map:

优点:

有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作 红黑树,内部实现一个红黑书使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率非常的高 缺点:

空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间

适用处:对于那些有顺序要求的问题,用map会更高效一些

unordered_map:

优点: 因为内部实现了哈希表,因此其查找速度非常的快 缺点: 哈希表的建立比较耗费时间 适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map 总结:

内存占有率的问题就转化成红黑树 VS hash表 , 还是unorder_map占用的内存要高。 但是unordered_map执行效率要比map高很多 对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的

计算机网络

ISO七层模型

 

TCP/IP各层的主要功能

采用一种五层协议的结构,自下而上包括物理层,数据链路层,网络层,传输层和应用层。

应用层协议主要包括如下几个:FTP、TELNET、DNS、SMTP、NFS、HTTP。

文件传输协议FTP(File Transfer Protocol),一般上传下载用FTP服务,数据端口是20H,控制端口是21H。 TELNET服务是用户远程登录服务,使用23H端口,使用明码传送,保密性差、简单方便。 域名解析服务DNS(Domain Name Service),提供域名到IP地址之间的转换,使用端口53。 简单邮件传输协议SMTP(Simple Mail Transfer Protocol),用来控制信件的发送、中转,使用端口25。 网络文件系统NFS(Network File System),用于网络中不同主机间的文件共享。 超文本传输协议HTTP(Hypertext Transfer Protocol),用于实现互联网中的WWW服务,使用端口80。

传输层协议,TCP/UDP

网络层协议:ICMP

1 第五层——应用层(application layer)

  • 应用层(application layer):是体系结构中的最高。直接为用户的应用进程(例如电子邮件、文件传输和终端仿真)提供服务。

  • 在因特网中的应用层协议很多,如支持万维网应用的HTTP协议,支持电子邮件的SMTP协议,支持文件传送的FTP协议,DNS,POP3,SNMP,Telnet等等。

  1. 第四层——运输层(transport layer)

  • 运输层(transport layer):负责向两个主机中进程之间的通信提供服务。由于一个主机可同时运行多个进程,因此运输层有复用和分用的功能

  • 复用,就是多个应用层进程可同时使用下面运输层的服务。

  • 分用,就是把收到的信息分别交付给上面应用层中相应的进程。

  • 运输层主要使用以下两种协议: (1) 传输控制协议TCP(Transmission Control Protocol):面向连接的,数据传输的单位是报文段,能够提供可靠的交付。 (2) 用户数据包协议UDP(User Datagram Protocol):无连接的,数据传输的单位是用户数据报,不保证提供可靠的交付,只能提供“尽最大努力交付”。

  1. 第三层——网络层(network layer)

  • 网络层(network layer)主要包括以下两个任务:

  • (1) 负责为分组交换网上的不同主机提供通信服务。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组或包进行传送。在TCP/IP体系中,由于网络层使用IP协议,因此分组也叫做IP数据报,或简称为数据报。

  • (2) 选中合适的路由,使源主机运输层所传下来的分组,能够通过网络中的路由器找到目的主机。

  • 协议:IP,ICMP,IGMP,ARP,RARP

  1. 第二层——数据链路层(data link layer)

  • 数据链路层(data link layer):常简称为链路层,我们知道,两个主机之间的数据传输,总是在一段一段的链路上传送的,也就是说,在两个相邻结点之间传送数据是直接传送的(点对点),这时就需要使用专门的链路层的协议。

  • 在两个相邻结点之间传送数据时,数据链路层将网络层交下来的IP数据报组装成帧(framing),在两个相邻结点之间的链路上“透明”地传送帧中的数据。

  • 每一帧包括数据和必要的控制信息(如同步信息、地址信息、差错控制等)。典型的帧长是几百字节到一千多字节。

  • 注:”透明”是一个很重要的术语。它表示,某一个实际存在的事物看起来却好像不存在一样。”在数据链路层透明传送数据”表示无力什么样的比特组合的数据都能够通过这个数据链路层。因此,对所传送的数据来说,这些数据就“看不见”数据链路层。或者说,数据链路层对这些数据来说是透明的。 (1)在接收数据时,控制信息使接收端能知道一个帧从哪个比特开始和到哪个比特结束。这样,数据链路层在收到一个帧后,就可从中提取出数据部分,上交给网络层。 (2)控制信息还使接收端能检测到所收到的帧中有无差错。如发现有差错,数据链路层就简单地丢弃这个出了差错的帧,以免继续传送下去白白浪费网络资源。如需改正错误,就由运输层的TCP协议来完成。

  1. 第一层——物理层(physical layer)

  • 物理层(physical layer):在物理层上所传数据的单位是比特。物理层的任务就是透明地传送比特流。

TCP/UDP

  1. TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。

  2. TCP的缺点: 慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击

  3. UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击……

  4. UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。 基于上面的优缺点,那么: 什么时候应该使用TCP: 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输 …………

  5. 什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP, 有些应用场景对可靠性要求不高会用到UPD,比如长视频,要求速率

TCP三次握手和四次挥手

三次握手的机制是为了保证建立一个安全可靠的连接。这个连接必须是一方主动打开,另一方被动打开的。在客户端与服务器端传输的TCP报文中,双方的确认号ack和序号Seq的值,都是在彼此ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。此后客户端和服务器端进行正常的数据传输。这就是“三次握手”的过程。为什么要进行第三次握手:如果只有两次握手的话,客户端知道服务器有接受和发送能力,但服务器只知道客户端发送能力,不知道有接受能力,因此不可靠。

三次握手中,前两次不可以携带数据,但是第三次就可以携带数据。因为如果前两次携带数据,可能会造成攻击。

四次挥手即TCP连接的释放(解除)。连接的释放必须是一方主动释放,另一方被动释放。与“三次挥手”一样,在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性,一旦出现某一方发出的TCP报文丢失,便无法继续"挥手",以此确保了"四次挥手"的顺利完成。

“握手”三次,“挥手”四次

TCP建立连接时之所以只需要"三次握手",是因为在第二次"握手"过程中,服务器端发送给客户端的TCP报文是以SYN与ACK作为标志位的。SYN是请求连接标志,表示服务器端同意建立连接;ACK是确认报文,表示告诉客户端,服务器端收到了它的请求报文。即SYN建立连接报文与ACK确认接收报文是在同一次"握手"当中传输的,所以"三次握手"不多也不少,正好让双方明确彼此信息互通。

TCP释放连接时之所以需要“四次挥手”,是因为FIN释放连接报文与ACK确认接收报文是分别由第二次和第三次"握手"传输的。

为何建立连接时一起传输,释放连接时却要分开传输?

建立连接时,被动方服务器端结束CLOSED阶段进入“握手”阶段并不需要任何准备,可以直接返回SYN和ACK报文,开始建立连接。 释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。

TCP之滑动窗口

滑动窗口实现了TCP流控制。首先明确滑动窗口的范畴:TCP是双工的协议,会话的双方都可以同时接收和发送数据。TCP会话的双方都各自维护一个发送窗口和一个接收窗口。各自的接收窗口大小取决于应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率)。各自的发送窗口则要求取决于对端通告的接收窗口,要求相同。滑动窗口解决的是流量控制的的问题,就是如果接收端和发送端对数据包的处理速度不同,如何让双方达成一致。接收端的缓存传输数据给应用层,但这个过程不一定是即时的,如果发送速度太快,会出现接收端数据overflow,流量控制解决的是这个问题。

TCP的滑动窗口都是以字节为单位的。凡是已发送过的数据,在未收到确认之前,都必须暂时保留,以便在超时重传时使用。发送窗口里面的序号表示允许发送的序号,发送窗口后沿的后面部分表示已经发送并且已经得到确认。

对比滑动窗口和拥塞窗口

滑动窗口是控制接收以及同步数据范围的,通知发送端目前接收的数据范围,用于流量控制,接收端使用。拥塞窗口是控制发送速率的,避免发的过多,发送端使用。因为tcp是全双工,所以两边都有滑动窗口。 两个窗口的维护是独立的,滑动窗口主要由接收方反馈缓存情况来维护,拥塞窗口主要由发送方的拥塞控制算法检测出的网络拥塞程度来决定的。

TCP可靠性的实现方式

  1. 确认应答(ACK)机制

  2. 超时重传机制

  3. 连接管理机制

  4. 流量控制

  5. 拥塞控制

如何用UDP实现可靠性传输

可以参考TCP,引入序列号,保证数据顺序;引入确认应答,保证对端收到了数据;引入超时重传,如果隔一段时间没有应答,就重新发送数据。

HTTP

  1. 简介

    HTTP协议(Hyper Text Transfer Protocol,超文本传输协议),是用于从万维网(WWW: World Wide Web )服务器传输超文本到本地浏览器的传送协议。

    HTTP基于TCP/IP通信协议来传递数据。

    HTTP基于客户端/服务端(C/S)架构模型,通过一个可靠的链接来交换信息,是一个无状态的请求/响应协议。

  2. 特点

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

    (2)HTTP是媒体独立的:只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。

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

HTTP报文是面向文本的,报文中的每一个字段都是一些ASCII码串,各个字段的长度是不确定的。HTTP有两类报文:请求报文和响应报文。

请求报文:由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成

 

HTTP协议的请求方法有GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT。

HTTP响应也由三个部分组成,分别是:状态行、消息报头、响应正文。

状态行格式如下:

HTTP-Version Status-Code Reason-Phrase CRLF

其中,HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。状态代码由三位数字组成,第一个数字定义了响应的类别,且有五种可能取值。

  • 1xx:指示信息--表示请求已接收,继续处理。

  • 2xx:成功--表示请求已被成功接收、理解、接受。

  • 3xx:重定向--要完成请求必须进行更进一步的操作。

  • 4xx:客户端错误--请求有语法错误或请求无法实现。

  • 5xx:服务器端错误--服务器未能实现合法的请求。

常见状态代码、状态描述的说明如下。

  • 200 OK:客户端请求成功。

  • 400 Bad Request:客户端请求有语法错误,不能被服务器所理解。

  • 401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。

  • 403 Forbidden:服务器收到请求,但是拒绝提供服务。

  • 404 Not Found:请求资源不存在,举个例子:输入了错误的URL。

  • 500 Internal Server Error:服务器发生不可预期的错误。

  • 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常。

    举个例子:HTTP/1.1 200 OK(CRLF)。

HTTP和HTTPS

 超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。

  为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS,为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

HTTPS的工作原理

  1. 客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。

  2. Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。

  3. 客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。

  4. 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。

  5. Web服务器利用自己的私钥解密出会话密钥。

  6. Web服务器利用会话密钥加密与客户端之间的通信。

ping

使用到的协议如下:

  1. DNS,作用:把域名转换成为网络可以识别的ip地址

  2. ARP,作用:根据IP地址获取MAC物理地址

  3. ICMP,作用:TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息(如网络通不通、主机是否可达、路由是否可用等网络本身的消息)。

多路复用与多路分解

每个运输层的报文段中设置了几个字段,包括源端口号和目的端口号等。多路分解就是,在接收端,运输层检查这些字段并标识出接收套接字,然后将该报文定向到该套接字。其工作方式可以简单地认为是这样的,主机上个每个套接字被分配一个端口号,当报文到达主机时,运输层检查报文段中的目的端口号,并将其定向到相应的套接字。

多路复用就是从源主机的不同套接字中收集数据块,并为每个数据块封装上首部信息从而生成报文段,然后将报文段传递到网络层中去。

网络上主机间的进程间通信,实质上是通过套接字来实现的。在运输层中面向连接的网络传输多使用TCP,而TCP套接字和UDP套接字之间有一个细微的差别,就是,TCP套接字是由一个四元组(源IP地址、源端口号,目的IP地址,目的端口号)来标识的。这样,当一个TCP报文段从网络到达一台主机时,主机会使用全部4个值来将报文段定向,即多路分解到相应的套接字。与UDP不同的是,两个具有不同源IP或源端口号的到达的TCP报文段将被重定向到两个不同的套接字。

子网掩码的作用

  1. 用于屏蔽IP地址的一部分以区别网络标识和主机标识,并说明该IP地址是在局域网上,还是在远程网上;

  2. 用于将一个大的IP网络划分为若干小的子网络。

防火墙

  1. 过滤型防火墙

    过滤型防火墙是在网络层与传输层中,可以基于数据源头的地址以及协议类型等标志特征进行分析,确定是否可以通过。在符合防火墙规定标准之下,满足安全性能以及类型才可以进行信息的传递,而一些不安全的因素则会被防火墙过滤、阻挡。

  2. 应用代理类型防火墙

    应用代理防火墙主要的工作范围就是在OIS的最高层,位于应用层之上。其主要的特征是可以完全隔离网络通信流,通过特定的代理程序就可以实现对应用层的监督与控制。

  3. 复合型

    目前应用较为广泛的防火墙技术当属复合型防火墙技术,综合了包过滤防火墙技术以及应用代理防火墙技术的优点,譬如发过来的安全策略是包过滤策略,那么可以针对报文的报头部分进行访问控制;

DNS(域名解析协议)

DNS是一种可以将域名和IP地址相互映射的以层次结构分布的数据库系统。DNS系统采用递归查询请求的方式来响应用户的查询,为互联网的运行提供关键性的基础服务。目前绝大多数的防火墙和网络都会开放DNS服务,DNS数据包不会被拦截,因此可以基于DNS协议建立隐蔽信道,从而顺利穿过防火墙,在客户端和服务器之间传输数据。

域名服务主要是基于UDP实现的,服务器的端口号为53

 

cookies和session

会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。

cookies(客户端)

Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。具有不可跨域名性。

Session(服务器端)

Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。

Session机制决定了当前客户只会获取到自己的Session,而不会获取到别人的Session。各客户的Session也彼此独立,互不可见。URL地址重写是对客户端不支持Cookie的解决方案。URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。

区别

  1. cookie数据存放在客户的浏览器上,session数据放在服务器上.

  2. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。

  3. 最大的区别在于生存周期,一个是IE启动到IE关闭.(浏览器页面一关 ,session就消失了),一个是预先设置的生存周期,或永久的保存于本地的文件。(cookie)

 

数据库

MySql数据库

MySQL是一种关系型数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。

加锁范围:MySQL里面的锁可以分为:全局锁、表级锁、行级锁

索引

索引是定义在table基础之上,有助于无需检查所有记录而快速定位所需记录的一种辅助存储结构,由一系列存储在磁盘上的索引项组成,每一种索引项由索引字段和行指针构成。

作用:提高数据的查询速度

  1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

  2. 可以大大加快 数据的检索速度,这也是创建索引的最主要的原因。

  3. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

  4. 在使用分组和排序 子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

  5. 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

常见的需要用到索引的情况:

  1. 在经常需要搜索的列上,可以加快搜索的速度;

  2. 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;

  3. 在经常用在连接的列上,这 些列主要是一些外键,可以加快连接的速度;

  4. 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;

  5. 在经常需要排序的列上创 建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;

  6. 在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。

索引优缺点

索引的好处?

  1. 通过创建索引,可以在查询的过程中,提高系统的性能;

  2. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性;

  3. 在使用分组和排序子句进行数据检索时,可以减少查询中分组和排序的时间;

索引的坏处?

  1. 创建索引和维护索引要耗费时间,而且时间随着数据量的增加而增大;

  2. 索引需要占用物理空间,如果要建立聚簇索引,所需要的空间会更大;

  3. 在对表中的数据进行增加删除和修改时需要耗费较多的时间,因为索引也要动态地维护;

索引的分类

什么是稠密索引和稀疏索引?

  • 稠密索引:对于主文件中每一个记录都对应一个索引项;

    • 候选键属性的稠密索引:先查索引,然后再依据索引读主文件;

    • 非候选键属性的稠密索引:

      • 主文件按索引字段排序,索引文件中的索引字段值无重复;

      • 主文件索引字段未排序,但索引文件中的索引字段值是有重复的;

      • 主文件索引字段未排序且索引文件中的索引字段值无重复,这时可以引入指针桶来处理;

  • 稀疏索引:对于主文件中部分记录有索引项和它对应(要求主文件必须是按对应索引字段属性排序存储);

什么是主索引和辅助索引?

  • 主索引:对每个存储块有一个索引项,每个存储块的第一个记录叫锚,通常建立在有序文件的基于主码的排序字段上,属于稀疏索引。

  • 辅助索引:是定义在主文件的任一或多个非排序字段上的辅助存储结构,属于稠密索引。

    补充:一个主文件可以有一个主索引,但可以有多个辅助索引。

什么是聚簇索引和非聚簇索引?

  • 聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据,主文件按照对应字段排序存储,索引文件无重复排序存储。

  • 非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,主文件并没有按照对应字段排序存储,索引文件有重复排序存储。

索引的结构

  1. B树

    即二叉搜索树, B树的搜索,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中;否则,如果查询关键字比结点关键字小,就进入左儿子;如果比结点关键字大,就进入右儿子;如果左儿子或右儿子的指针为空,则报告找不到相应的关键字; 如果B树的所有非叶子结点的左右子树的结点数目均保持差不多(平衡),那么B树的搜索性能逼近二分查找;但它比连续内存空间的二分查找的优点是,改变B树结构(插入与删除结点)不需要移动大段的内存数据,甚至通常是常数开销;

  2. B-树

    是一种多路搜索树(并不是二叉的)

  3. B+树

    1. B树:二叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于走右结点;

    2. B-树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;

    3. B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;

数据完整性

数据冗余是指数据库中存在一些重复的数据,数据完整性是指数据库中的数据能够正确反应实际情况。

数据完整性是指数据的可靠性和准确性,数据完整性类型有四种:

  1. 实体完整性:实体的完整性强制表的标识符列或主键的完整性

    (通过唯一约束,主键约束或标识列属性)

  2. 域完整性:限制类型(数据类型),格式(通过检查约束和规则),可能值范围

    (通过外键约束,检查约束,默认值定义,非空约束和规则)

  3. 引用完整性(参照完整性):在删除和输入记录时,引用完整性保持表之间已定义的关系。引用完整性确保键值在所有表中一致,不能引用不存在的值。如果一个键。

    (通过外键,所引用表的列必须是主键)

  4. 定义完整性:用户自己定义的业务规则,比如使用触发器实现自定义业务规则。

数据库的范式

为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。

  1. 第一范式(确保每列保持原子性) --- 保证原子性

  2. 第二范式(确保表中的每列都和主键相关) ---解决部分依赖

  3. 第三范式(确保每列都和主键列直接相关,而不是间接相关) -- 解决传递依赖

 第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;  第二范式:2NF是对记录的惟一性约束,要求记录有惟一标识,即实体的唯一性;  第三范式:3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余。

数据表设计原则

  1. 一致性原则:对数据来源进行统一、系统的分析与设计,协调好各种数据源,保证数据的一致性和有效性。

  2. 完整性原则:数据库的完整性是指数据的正确性和相容性。要防止合法用户使用数据库时向数据库加入不合语义的数据。对输入到数据库中的数据要有审核和约束机制。

  3. 安全性原则:数据库的安全性是指保护数据,防止非法用户使用数据库或合法用户非法使用数据库造成数据泄露、更改或破坏。要有认证和授权机制。

  4. 可伸缩性与可扩展性原则:数据库结构的设计应充分考虑发展的需要、移植的需要,具有良好的扩展性、伸缩性和适度冗余。

  5. 规范化:数据库的设计应遵循规范化理论。规范化的数据库设计,可以减少数据库插入、删除、修改等操作时的异常和错误,降低数据冗余度等。

事务

特性

数据库事务是指作为单个逻辑工作单元执行的一系列操作(SQL语句)。这些操作要么全部执行,要么全部不执行。

  1. 原子性 (atomicity):强调事务的不可分割.

  2. 一致性 (consistency):事务的执行的前后数据的完整性保持一致.

  3. 隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰

  4. 持久性(durability) :事务一旦结束,数据就持久到数据库

隔离级别

隔离性是指,多个用户的并发事务访问同一个数据库时,一个用户的事务不应该被其他用户的事务干扰,多个并发事务之间要相互隔离。

四种隔离级别

  1. 读未提交(Read uncommitted):这种事务隔离级别下,select语句不加锁。此时,可能读取到不一致的数据,即“读脏 ”。这是并发最高,一致性最差的隔离级别。

  2. 读已提交(Read committed):可避免 脏读 的发生。在互联网大数据量,高并发量的场景下,几乎 不会使用上述两种隔离级别。

  3. 可重复读(Repeatable read):MySql数据库的默认隔离级别。可避免脏读 、不可重复读的发生。

  4. 串行化(Serializable ):可避免 脏读、不可重复读、幻读的发生。

几种问题

  1. 脏读是指一个事务在处理数据的过程中,读取到另一个为提交事务的数据。

  2. 不可重复读是指对于数据库中的某个数据,一个事务范围内的多次查询却返回了不同的结果,这是由于在查询过程中,数据被另外一个事务修改并提交了。 不可重复读和脏读的区别是,脏读读取到的是一个未提交的数据,而不可重复读读取到的是前一个事务提交的数据。而不可重复读在一些情况也并不影响数据的正确性,比如需要多次查询的数据也是要以最后一次查询到的数据为主。

  3. 幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。总的来说,解决不可重复读的方法是 锁行,解决幻读的方式是 锁表

操作系统

select poll epoll

select

使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大

同时每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大

为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024)

poll

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll改变了文件描述符集合的描述方式,

epoll

相对于select来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

进程和线程的区别

定义

  1. 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

  2. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

关系

一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

区别

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比单线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

2) 线程的划分尺度小于进程,使得多线程程序的并发性高。

3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

优缺点

线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。

线程安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

线程安全问题都是由全局变量及静态变量引起的。

  1. 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;

  2. 若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

解决方法:

  1. 屏蔽中断 对于单核CPU来说,最简单的解决办法就是屏蔽中断,因为单核CPU并不存在真正的并发,多线程或者进程实际上还是交替执行的,当屏蔽中断时时钟中断也会屏蔽,CPU只有在发生时钟中断或者其他中断时才会出现对进程进行上下文切换,所以一旦屏蔽中断之后就只有自己这个进程进来,而不必担心别的进程会修改共享数据。

  2. 硬件提供一些原语,比如中断禁用,原子指令等。常见的如下:

    • TSL(test and set lock),测试并加锁

    • XCHG,原子性的交换两个位置的值

    • 信号量(semaphore)

    这三种是硬件实现的原子操作,在这些硬件提供的原子操作的基础上,又提出了对这些原子操作的抽象,比如互斥量,管程。下面就先逐个介绍这些硬件原子操作的原理,介绍完之后,再介绍对他们的抽象,最后举一个例子来说明这些抽象的应用。

  3. 管程解决的最主要的问题就是分离互斥和根据特定条件阻塞。

    • 互斥锁:任意时刻只允许一个进程或者线程进入临界区

    • 一个或多个条件变量:条件变量包含两个操作等待 / 唤醒操作

操作系统中的锁

锁是线程同步时的一个重要的工具,然而操作系统中包含了多种不同的锁。

  1. 信号量(Semaphore)

    信号量分为二元信号量和多元信号量,所谓二元信号量就是指该信号量只有两个状态,要么被占用,要么空闲;而多元信号量则允许同时被N个线程占有,超出N个外的占用请求将被阻塞。信号量是“系统级别”的,即同一个信号量可以被不同的进程访问。

  2. 互斥量 (Mutex)

    和二元信号量类似, 唯一不同的是,互斥量的获取和释放必须是在同一个线程中进行的。如果一个线程去释放一个并不是它所占有的互斥量是无效的。而信号量是可以由其它线程进行释放的。

  3. 临界区(Critical Section)

    术语中,把临界区的锁的获取称为进入临界区,而把锁的释放称为离开临界区。临界区是“进程级别”的,即它只在本进程的所有线程中可见,其它性质与互斥量相同(即谁获取,谁释放)

  4. 读写锁(Read-Write Lock)

    适用于一个特定的场合。比如对于一段线程间访问的数据,如果程序大部分时间都是在读取,而只有很少的时间才会写入,那么使用前面几种锁时,每次读取也是同样 要申请锁的,而这时其它的线程就无法再对此段数据进行读取。可是,多个线程同时对一段数据进行读取时,是不存在同步问题的,那么这些读取时设置的锁就影响 了程序的性能。读写锁的出现就是为了解决这个问题的。

    对于一个读写锁,有两种获取方式:共享(Shared)或独占 (Exclusive)。如果当前读写锁处于空闲状态,那么当多个线程同时以共享方式访问该读写锁时,都可以成功;而此时如果一个线程以独占的方式访问该 读写锁,那么它会等待所有共享访问都结束后才可以成功。在读写锁被独占访问的过程中,再次共享和独占请求访问该锁,都会进行等待状态。

 

5、条件变量(Condition Variable)

条件变量相当于一种通知机制。多个线程可以设置等待该条件变量,而一旦另外的线程设置了该条件变量(相当于唤醒条件变量)后,多个等待的线程就可以继续执行了。

死锁以及避免

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

产生死锁要满足的条件

  1. 互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放;

  2. 请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放;

  3. 不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用;

  4. 循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。

死锁的解决办法

  • 预防死锁:通过设置一些限制条件,去破坏产生死锁的必要条件;

只要破坏死锁的四个条件之一就可以预防死锁,其中第一个条件互斥是非共享资源必须的,不能够破坏。所以只能破坏其他三个条件。

  1. 破坏请求和保持条件 在进程开始运行之前,一次性获取进程运行的全部资源。缺点:因为某项资源不满足,进程无法启动,而其他已经满足了的资源也不会得到利用,严重降低了资源的利用率,造成资源浪费。容易造饥饿现象。允许进程只获得运行初期所需要的资源,便可以运行。在运行过程中逐步释放掉已经使用完毕并接下来不再需要的资源,再去请求新的资源。优点:资源的利用率会得到提高,也会减少进程的饥饿问题。

  2. 破坏不可抢占条件 当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请。意味着进程已占有的资源会被短暂地释放或者说是被抢占了。这种方法代价较大,反复的申请和释放资源会导致进程的执行被无限的推迟,这不仅会延长进程的周转周期,还会影响系统的吞吐量。

  3. 破坏循环等待条件 系统给进程编号,按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

  • 避免死锁:在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁;

在使用前进行判断,只允许不会产生死锁的进程申请资源。两种方法:1:如果一个进程的请求会导致死锁,则不启动该进程;2:如果一个进程的增加资源请求会导致死锁 ,则拒绝该申请。

  • 检测死锁:允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉;

    建立资源分配表和进程等待表。

  • 解除死锁:该方法与检测死锁配合使用。

    1. 抢占资源:从一个或多个进程中抢占足够数量的资源分配给死锁进程,以解除死锁状态。

    2. 终止(或撤销)进程:终止或撤销系统中的一个或多个死锁进程,直至打破死锁状态。

线程之间的通信方式

  1. 通过共享变量,线程之间通过该变量进行协作通信;

  2. 通过队列(本质上也是线程间共享同一块内存)来实现消费者和生产者的模式来进行通信;

进程之间的通信方式

linux常用的进程间的通讯方式

  1. 管道(pipe):管道可用于具有亲缘关系的进程间的通信,是一种半双工的方式,数据只能单向流动,允许一个进程和另一个与它有共同祖先的进程之间进行通信。

  2. 命名管道(named pipe):命名管道克服了管道没有名字的限制,同时除了具有管道的功能外(也是半双工),它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。

  3. 信号(signal):信号是比较复杂的通信方式,用于通知接收进程有某种事件发生了,除了进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。

  4. 消息队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

  5. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。

  6. 内存映射:内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。

  7. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。

  8. 套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

进程调度算法

  1. 先来先去服务调度算法是一种最简单的调度算法,也称为先进先出或严格排队方案。当每个进程就绪后,它加入就绪队列。当前正运行的进程停止执行,选择在就绪队列中存在时间最长的进程运行。该算法既可以用于作业调度,也可以用于进程调度。先来先去服务比较适合于常作业(进程),而不利于段作业(进程)。

  2. 时间轮转法是基于适中的抢占策略的,以一个周期性间隔产生时钟中断,当中断发生后,当前正在运行的进程被置于就绪队列中,然后基于先来先去服务策略选择下一个就绪作业的运行。这种技术也称为时间片,因为每个进程再被抢占之前都给定一片时间。

  3. 最短进程优先是一个非抢占策略,他的原则是下一次选择预计处理时间最短的进程,因此短进程将会越过长作业,跳至队列头。该算法即可用于作业调度,也可用于进程调度。但是他对长作业不利,不能保证紧迫性作业(进程)被及时处理,作业的长短只是被估算出来的。

  4. 最短剩余时间是针对最短进程优先增加了抢占机制的版本。在这种情况下,进程调度总是选择预期剩余时间最短的进程。当一个进程加入到就绪队列时,他可能比当前运行的进程具有更短的剩余时间,因此只要新进程就绪,调度程序就能可能抢占当前正在运行的进程。像最短进程优先一样,调度程序正在执行选择函数是必须有关于处理时间的估计,并且存在长进程饥饿的危险。

  5. 最高响应比优先

    根据比率:R=(w+s)/s (R为响应比,w为等待处理的时间,s为预计的服务时间)如果该进程被立即调用,则R值等于归一化周转时间(周转时间和服务时间的比率)。R最小值为1.0,只有第一个进入系统的进程才能达到该值。调度规则为:当前进程完成或被阻塞时,选择R值最大的就绪进程,它说明了进程的年龄。当偏向短作业时,长进程由于得不到服务,等待时间不断增加,从而增加比值,最终在竞争中赢了短进程。和最短进程优先、最短剩余时间优先一样,使用最高响应比策略需要估计预计服务时间。

  6. 反馈法

    如果没有关于进程相对长度的任何信息,则最短进程优先,最短剩余时间、最高响应优先比都不能使用。另一种导致偏向短作业的方法是处罚运行时间较长的作业,换句话说,如果不能获得剩余的执行时间,那就关注已执行了的时间。方法为:调度基于被抢占原则(按时间片)并使用动态优先级机制。当一个进程第一次进入系统中时,他被放置在一个优先级队列中,当第一次被抢占后并返回就绪状态时,它被放置在下一个低优先级队列中,在随后的时间里,每当被抢占时,他被降级到下一个低优先级队列中。一个短进程很快被执行完,不会在就绪队列中降很多级,一个长进程会逐渐降级。因此先到的进程和短进程优先于长进程和老进程。在每个队列中,除了优先级在最低的队列中之外,都是用简单的先来先去服务机制,一旦一个进程处于优先级最低的队列中,它就不可能在降级,但会重复的返回该队列,直到运行结束。因此,该队列课按照轮转方式调度。

  7. 多级反馈队列算法,不必事先知道各种进程所需要执行的时间,他是当前被公认的一种较好的进程调度算法。其实施过程如下:

      1)设置多个就绪队列,并为各个队列赋予不同的优先级。在优先权越高的队列中,为每个进程所规定的执行时间片就越小。

      2)当一个新进程进入内存后,首先放入第一队列的末尾,按照先来先去原则排队等候调度。如果他能在一个时间片中完成,便可撤离;如果未完成,就转入第二队列的末尾,同样等待调度.....如此下去,当一个长作业(进程)从第一队列依次将到第n队列(最后队列)后,便按第n队列时间片轮转运行。

      3)仅当第一队列空闲的时候,调度程序才调度第二队列中的进程运行;仅当第1到(i-1)队列空时,才会调度第i队列中的进程运行,并执行相应的时间片轮转。

      4)如果处理机正在处理第i队列中某进程,又有新进程进入优先权较高的队列,则此新队列抢占正在运行的处理机,并把正在运行的进程放在第i队列的队尾。

  8.  

     

     

页面置换算法

地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。

最佳置换算法(OPT)

从主存中移出永远不再需要的页面;如无这样的页面存在,则选择最长时间不需要访问的页面。于所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。

先进先出置换算法(FIFO)

是最简单的页面置换算法。这种算法的基本思想是:当需要淘汰一个页面时,总是选择驻留主存时间最长的页面进行淘汰,即先进入主存的页面先淘汰。其理由是:最早调入主存的页面不再被使用的可能性最大。

最近最久未使用算法(LRU)

利用局部性原理,根据一个作业在执行过程中过去的页面访问历史来推测未来的行为。它认为过去一段时间里不曾被访问过的页面,在最近的将来可能也不会再被访问。所以,这种算法的实质是:当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。

时钟置换算法(CLOCK)

简单的CLOCK算法是给每一帧关联一个附加位,称为使用位。

当某一页首次装入主存时,该帧的使用位设置为1;

当该页随后再被访问到时,它的使用位也被置为1。对于页替换算法,用于替换的候选帧集合看做一个循环缓冲区,并且有一个指针与之相关联。当某一页被替换时,该指针被设置成指向缓冲区中的下一帧。当需要替换一页时,操作系统扫描缓冲区,以查找使用位被置为0的一帧。每当遇到一个使用位为1的帧时,操作系统就将该位重新置为0;如果在这个过程开始时,缓冲区中所有帧的使用位均为0,则选择遇到的第一个帧替换;如果所有帧的使用位均为1,则指针在缓冲区中完整地循环一周,把所有使用位都置为0,并且停留在最初的位置上,替换该帧中的页。由于该算法循环地检查各页面的情况,故称为CLOCK算法,又称为最近未用(Not Recently Used, NRU)算法。

CLOCK算法的性能比较接近LRU,而通过增加使用的位数目,可以使得CLOCK算法更加高效。在使用位的基础上再增加一个修改位,则得到改进型的CLOCK置换算法。这样,每一帧都处于以下四种情况之一:

  1. 最近未被访问,也未被修改(u=0, m=0)。

  2. 最近被访问,但未被修改(u=1, m=0)。

  3. 最近未被访问,但被修改(u=0, m=1)。

  4. 最近被访问,被修改(u=1, m=1)。

算法执行如下操作步骤:

  1. 从指针的当前位置开始,扫描帧缓冲区。在这次扫描过程中,对使用位不做任何修改。选择遇到的第一个帧(u=0, m=0)用于替换。

  2. 如果第1)步失败,则重新扫描,查找(u=0, m=1)的帧。选择遇到的第一个这样的帧用于替换。在这个扫描过程中,对每个跳过的帧,把它的使用位设置成0。

  3. 如果第2)步失败,指针将回到它的最初位置,并且集合中所有帧的使用位均为0。重复第1步,并且如果有必要,重复第2步。这样将可以找到供替换的帧。

改进型的CLOCK算法优于简单CLOCK算法之处在于替换时首选没有变化的页。由于修改过的页在被替换之前必须写回,因而这样做会节省时间。

 

你可能感兴趣的:(面经,c++)