首先说一下,这些东西,有的是必须掌握的,有的是面试时你讲出来就是闪光点。自己把握。把握不好的都搞懂。实在不行背下来。
由于时间关系,总结的比较随意,有的就直接贴链接了,希望理解一下。
第一篇:基础(必须熟稔于心)
1. const关键字(反义词mutable)
(1)定义时必须初始化
(2)指针可以是const指针,也可以是指向const对象的指针
(3)定义为const的形参,在函数体内不能被修改
(4)后面加Const,表示该成员函数不会修改类的成员变量。本质是修饰隐藏的*this指针。加const的成员函数可以被const或非const对象调用,但是普通成员函数(无const修饰)只能被普通对象(无const修饰)调用。
(5)前面加const,表示返回值是const类型的
(6)Const修饰成员变量时,不能在声明时初始化,必须在构造函数的列表里初始化
2. static关键字
(1)在函数中,一个static的变量在此函数被调用过程中维持其值不变
(2)在模块中(不在函数中),一个static变量可以被模块中所有函数访问,但不可以被模块外的其他函数访问。
(3)在模块内,一个static的函数只可以被这一模块内的其他函数调用。
(4)类中的static成员变量属于整个类,不能在类内进行定义,只能在类的作用域中进行定义。
(5)类中的static成员函数属于整个类,不包含this指针,只能调用static成员函数。
(6)static全局变量只能在本文件中使用,限制了它的作用域;而普通全局变量可以在其他文件中使用。
(7)static局部变量必须初始化,普通局部变量不需要;前者所在的函数被多次调用时,依据上一次的结果进行计算,而后者所在的函数被调用时,还是原来的值。虽然静态局部变量在函数调用结束后仍然存在,但其他函数不能引用它。
(8)static函数限定在本文件中使用,虽然其他文件可以知道它的存在,但不能使用;而普通函数默认是extern的,其他文件也可以使用。Static函数有两个好处:一是其他文件可以定义相同名字的函数,不会冲突;二是静态函数不能为其他函数使用。
3. extern关键字
(1)extern C,表示该段代码以C语言进行编译。
(2)extern 放在变量或函数前,说明该变量或函数定义在别的文件中,提示编译器去其他模块中找定义,相当于前向声明。
4. 指针和引用的区别
(1)引用是直接访问,指针是间接访问。
(2)引用是变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间
(3)引用绑定内存空间(必须赋初值),是一个变量别名不能更改绑定,可以改变对象的值。
总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性
5.explicit是干什么用的 ?
声明为explicit的构造函数不能在隐式转换中使用。可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。
6.inline的用法
https://www.cnblogs.com/fnlingnzb-learner/p/6423917.html
7.还有一些关键字,一时间想不起来了,有时间在整理吧。
第二篇:C++中的内存
1. new/delete与malloc/free之间的区别?
(1)malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符
(2)new能够自动分配空间大小,malloc传入参数。
(3)new/delete能进行对对象进行构造和析构函数的调用进而对内存进行更加详细的工作,而malloc/free不能。
既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
2. 浅拷贝与深拷贝?为什么要使用深拷贝?
(1)浅拷贝 char * arr[] = “hello”; char * a = arr;浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一内存空间。
(2)深拷贝 char * arr[] = “hello”; char * a = new char[]; a =arr; 深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经过深拷贝后的指针是指向两个不同地址的指针。
浅拷贝会出现的问题:(1)浅拷贝只是拷贝了指针,使得两个指针指向同一地址,这样在对象结束调用析构函数时,会造成同一份资源析构两次,即delete同一块内存两次,造成程序崩溃;(2)浅拷贝使得两个指针指向同一个地址,任何一方的改动都会影响另一方;(3)同一个空间,第二次释放失败,导师无法操作该空间,造成内存泄漏。
3.深入谈谈堆和栈?
(1)分配和管理方式不同 :
堆是动态分配的,其空间的分配和释放都由程序员控制。
栈由编译器自动管理。栈有两种分配方式:静态分配和动态分配。静态分配由编译器完成,比如局部变量的分配。动态分配由alloca()函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无须手工控制。
(2)产生碎片不同
对堆来说,频繁的new/delete或者malloc/free势必会造成内存空间的不连续,造成大量的碎片,使程序效率降低。
对栈而言,则不存在碎片问题,因为栈是先进后出的队列,永远不可能有一个内存块从栈中间弹出。
(3)生长方向不同
堆是向着内存地址增加的方向增长的,从内存的低地址向高地址方向增长。
栈是向着内存地址减小的方向增长,由内存的高地址向低地址方向增长。
60.内存的静态分配和动态分配的区别?
(1)时间不同。静态分配发生在程序编译和连接时。动态分配则发生在程序调入和执行时。
(2)空间不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。alloca,可以从栈里动态分配内存,不用担心内存泄露问题,当函数返回时,通过alloca申请的内存就会被自动释放掉。
第三篇:类、继承、多态
1. 实现string类
class string { public: String() //初始化 : _pstr(new char[1]) {} String(const char * pstr ); //普通构造函数 : _pstr(new char[strlen(pstr) + 1]()) { strcpy(_pstr,pstr); } String(const String & rhs); //复制构造函数 : _pstr(new char[strlen(pstr) + 1]()) { strcpy(_pstr, rhs.pstr); } String(String && rhs); //移动构造函数,右值引用 : _pstr(rhs._pstr) { rhs.pstr = NULL; } String & operator=(const String & rhs) //重载复制运算符函数 { if(this != & rhs) { delete [] _pstr; _pstr = new char[strlen(rhs._pstr) + 1](); strcpy(_pstr, rhs._pstr); } return *this; } String & operator=(String && rhs) //移动赋值运算符函数 { if(this != &rhs) { delete [] _pstr; _pstr = rhs._pstr; rhs._pstr = NULL; } return * this; } ~String() { delete [] _pstr; } friend std::ostream &operator<<(std::ostream & os, const String & rhs); private: char * _pstr; }; std::ostream & operator<<(std::ostream & os, const String & rhs) { os << rhs._pstr; return os; }
2. 什么是继承?什么是多态?
(1)
(2)C++中多态机制主要体现在两个方面,一个是函数的重载,一个是接口的重写。接口多态指的是“一个接口多种形态”。每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表。所以在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。
多态的基础是继承,需要虚函数的支持,简单的多态是很简单的。子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数,operator=函数,友元函数等等
多态作用:
隐藏实现细节,代码能够模块化;2. 接口重用:为了类在继承和派生的时候正确调用。
多态的两个必要条件:
1. 一个基类的指针或者引用指向派生类的对象;2.虚函数
3. 什么是静态关联?什么是动态关联?
静态关联是程序在编译阶段就能确定实际执行动作,程序运行时才能确定执行的动作叫动态关联。
4. 虚函数是如何实现的?
编译时若基类中有虚函数,编译器为该的类创建一个一维数组的虚表,存放是每个虚函数的地址。基类和派生类都包含虚函数时,这两个类都建立一个虚表。构造函数中进行虚表的创建和虚表指针的初始化。在构造子类对象时,要先调用父类的构造函数,初始化父类对象的虚表指针,该虚表指针指向父类的虚表。执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。每一个类都有虚表。虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。当用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。即如果这个指针/引用是基类对象的指针/引用就调用基类的方法;如果指针/引用是派生类对象的指针/引用就调用派生类的方法,当然如果派生类中没有此方法,就会向上到基类里面去寻找相应的方法。这些调用在编译阶段就确定了。当涉及到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数。
5. 虚函数与纯虚函数的区别?含有纯虚函数的类叫什么?
(1)虚函数与纯虚函数都可以在子类中重写。
(2)纯虚函数只有定义,没有实现;虚函数既要有定义,也要有实现的代码。
(3)纯虚函数 vritual void print() = 0; 虚函数 vritual void print() { XXX };
(4)包含纯虚函数的类叫抽象类,该类不可以创建对象;而含有虚函数的类可以创建对象。
6. 多重继承如何解决?
虚拟继承解决了多重继承的问题。如:A是基类,B、C继承自A,D多重继承自B和C,那么D访问A中的变量时,就会出现二义性错误。如果类B和类C虚拟继承自A,那么类D只会有A的一个对象,这样就解决了二义性问题。或者用成员限定符解决二义性。
7. 派生类与虚函数概述
(1)派生类继承的函数不能定义为虚函数。虚函数是希望派生类重新定义。如果派生类没有重新定义某个虚函数,则在调用的时候会使用基类中定义的版本。
(2)派生类中函数的声明必须与基类中定义的方式完全匹配。
(3)基类中声明为虚函数,则派生类也为虚函数。
8. 为什么析构函数要定义为虚函数?哪些函数不能是虚函数?
(1)如果析构函数不是虚函数,那么释放内存时候,编译器会使用静态联编,认为p就是一个基类指针,调用基类析构函数,这样子类对象的内存没有释放,造成内存泄漏。定义成虚函数以后,就会动态联编,先调用子类析构函数,再基类。
(2)1)普通函数只能重载,不能被重写,因此编译器会在编译时绑定函数。
2)构造函数是知道全部信息才能创建对象,然而虚函数允许只知道部分信息。
3)内联函数在编译时被展开,虚函数在运行时才能动态绑定函数。
4)友元函数 因为不可以被继承。
5)静态成员函数 只有一个实体,不能被继承。父类和子类共有。
9. 析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?
C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。
1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
2)通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
11.动态链接库的两种使用方法及特点?
1).载入时动态链接,模块非常明确调用某个导出函数,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。
2)运行时动态链接。
第四篇:STL
1. STL各类容器(3个顺序+4个关联+1个无序关联)的实现原理及使用情形
(1)vector:可变数组大小。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。
(2)deque:双端队列。支持快速随机访问。在头尾插入或删除碎度很快。
(3)list:双向链表。只支持双向顺序访问。在list的任何位置进行插入或删除操作速度都很快。
(4)set/multiset:只有键值,可以把set当做集合使用。multiset可以存放相同的元素,set只能存放不同的元素。
(5)map/multimap:键值对,每一个元素都是pair,pair的第一个元素是关键字,第二个元素是值。这两者的区别就在于multimap可以存放多个相同的关键字,map则不可以。
(3)与(5)的底层实现都是红黑树,动态平衡二叉树。插入和删除等操作的时间复杂度是O(logn)(6)中的底层实现是哈希函数。
(6)unordered_map 映射
unordered_multimap 多重映射
unordered_set 集合
unordered_multiset 多重集合
- 什么是STL?(面试主要是深入某个点问你)
六大组件:容器、迭代器、适配器、算法、函数对象、配置器(透明)
(1)容器(略,自己看)
(2)迭代器:随机访问迭代器(Random Access Iterator)
双向迭代器(Bidirectional Iterator)
前向迭代器(Forward Iterator)
输入迭代器(Input Iterator)
输出迭代器(Output Iterator)
(3)适配器就是Interface(接口),对容器、迭代器和算法进行包装,但其实质还是容器、迭代器和算法,只是不依赖于具体的标准容器、迭代器和算法类型,容器适配器可以理解为容器的模板,迭代器适配器可理解为迭代器的模板,算法适配器可理解为算法的模板。
常见的容器适配器有:stack、queue、priority_queue(不支持迭代器访问)
前面简要提到了适配器的概念,适配器相当于提供了一个接口,使得某些不适用于特定对象的方法可以被该对象所用,适配器形象的功能图解如所示,图中,容器或函数对象无法直接应用于算法,因此,必须有一种中间过渡机制来实现两者的匹配,这就是适配器,本质上,适配器是使一事物的行为类似于另一事物的行为的一种机制。
(4)STL将算法库分为4组,前3个在algorithm头文件中描述,而第4个在numeric头文件中描述:
非修改式序列操作:不改变容器的内容,如find()、for_each()等。
修改式序列操作:可以修改容器中的内容,如transform()、random_shuffle()、copy等。
排序和相关操作:包括各种排序函数等,如sort()等。
通用数字运算:计算两个容器的内部乘积等。
(5)函数对象是可以以函数方式与()结合使用的任意对象,包括:(functor-仿函数)
函数名;指向函数的指针;重载了()操作符的类对象(即定义了函数operator()()的类)。
(6)一级配置器和二级配置器
空间配置器,就是用来配置、管理和释放空间的,给所有的容器包括算法提供生存空间。
作用:
(1)提高代码复用率,功能模块化。
(2)减少内存碎片问题。
(3)提高内存分配的效率。
(4)有内存不足时的应对措施。
(5)隐藏实际中对存储空间的分配及释放细节,确保所有被分配的存储空间都最终获得释放。
(5)考虑多线程状态。
考虑到小型区块可能导致的内存碎片问题,设置了两级空间配置器。分别为:一级空间配置器、二级空间配置器。当区块大于128字节,调用一级空间配置器;小于等于128字节,为了降低额外开销,用底层较复杂的二级空间配置器。
一级空间配置器
用malloc()、free()、realloc()等C函数执行内存配置、释放、重配置操作,并实现出类似的C++new_hanle的机制
二级空间配置器
SGI二级空间配置器的原理是:当区块小于128字节,则以内存池(memory pool)管理,回收时管理一个用户归还的空间,类似于哈希桶。每次配置一块内存,并维护对应的自由链表(free_list)。为了方便管理,SGI二级配置器会对齐到8个字节。(例:需要30字节的空间,自动调整到32字节)。维护16个free_lists,各自管理大小分别为
8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字节。
3. 什么是智能指针?底层实现?
(1)C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。
(2)理解智能指针需要从下面三个层次:
从较浅的层面看,智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。
智能指针还有一个作用是把值语义转换成引用语义。
(3)智能指针#include<memory>,unique_ptr,shared_ptr,weak_ptr(弱引用智能指针)。
(4)unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。
(5)shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
(6)weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。
第五篇:操作系统编程
1. 多进程与多线程之间的区别?(最好要了解透彻)
1)进程数据是分开的:共享复杂,需要用IPC,同步简单;多线程共享进程数据:共享简单,同步复杂
2)进程创建销毁、切换复杂,速度慢 ;线程创建销毁、切换简单,速度快
3)进程占用内存多, CPU利用率低;线程占用内存少, CPU利用率高
4)进程编程简单,调试简单;线程 编程复杂,调试复杂
5)进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉
6)进程适应于多核、多机分布;线程适用于多核
线程所私有的:
线程id、寄存器的值、栈、线程的优先级和调度策略、线程的私有数据、信号屏蔽字、errno变量、
2. 什么是进程池和线程池?
在面向对象程序编程中,对象的创建与析构都是一个较为复杂的过程,较费时间,所以为了提高程序的运行效率尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。
所以我们可以创建一个进程池(线程池),预先放一些进程(线程)进去,要用的时候就直接调用,用完之后再把进程归还给进程池,省下创建删除进程的时间,不过当然就需要额外的开销了。
利用线程池与进程池可以使管理进程与线程的工作交给系统管理,不需要程序员对里面的线程、进程进行管理。
以进程池为例
进程池是由服务器预先创建的一组子进程,这些子进程的数目在 3~10 个之间(当然这只是典型情况)。线程池中的线程数量应该和CPU数量差不多。
进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级、 PGID 等。
当有新的任务来到时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显得小得多。至于主进程选择哪个子进程来为新任务服务,则有两种方法:
主进程使用某种算法来主动选择子进程。最简单、最常用的算法是随机算法和Round Robin(轮流算法)。
主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。
当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方式是,在父进程和子进程之间预先建立好一条管道,然后通过管道来实现所有的进程间通信。在父线程和子线程之间传递数据就要简单得多,因为我们可以把这些数据定义为全局,那么它们本身就是被所有线程共享的。
3. 进程间的通信方式有哪些?如何实现的?
信号和信号量是不同的,它们虽然都可以用来同步和互斥,但是信号是使用信号处理器来进行的,信号量是使用P,V操作来实现的。
消息队列是比较高级的一种进程间通信方式,因为它真的是可以在进程间传送message,传送普通字符串也可以。
一个消息队列可以被多个进程所共享(IPC((Inter-Process Communication,进程间通信))就是在这个基础上进行的);如果一个进程消息太多,一个消息队列放不下,也可以用多于一个的消息队列(不管管理可能会比较复杂)。共享消息队列的进程所发送的消息除了message本身外还有一个标志,这个标志可以指明该消息将由哪个进程或者哪类进程接受。每一个共享消息队列的进程针对这个队列也有自己的标志,可以用来申明自己的身份。
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信
4. 简述inux中的同步与异步机制?
同步:
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。
例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步:
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。
例如 ajax请求(异步): 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
5.简述阻塞与非阻塞?
阻塞:
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。
有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回,它还会抢占cpu去执行其他逻辑,也会主动检测io是否准备好。
非阻塞
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
再简单点理解就是:
1. 同步,就是我调用一个功能,该功能没有结束前,我死等结果。
2. 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)
3. 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
4. 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者
同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞
阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回
综上可知,同步和异步,阻塞和非阻塞,有些混用,其实它们完全不是一回事,而且它们修饰的对象也不相同。
6.简述Linux中的5种I/O模式?
1)阻塞I/O(blocking I/O)
2)非阻塞I/O (nonblocking I/O)
3) I/O复用(select 和poll,还有epoll) (I/O multiplexing)!!!!!(必须搞懂,超究极容易遇到)
4)信号驱动I/O (signal driven I/O (SIGIO))
5)异步I/O (asynchronous I/O (the POSIX aio_functions))
其中前4种都是同步,最后一种才是异步。
详情见:https://www.cnblogs.com/chaser24/p/6112071.html
7. 什么是死锁?四个死锁的条件?避免死锁的方法?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
产生原因:竞争资源,和进程推进顺序非法
四个条件:
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
四种解决办法:预防(破坏死锁发四种发生条件中的一个或多个)、避免(银行家算法:如果一个进程增加的资源请求会导致死锁,则不允许此分配,记住当时算的那张矩阵图)、检测与解除
8. Linux的任务调度机制是什么?
Linux 分实时进程和普通进程,实时进程应该先于普通进程而运行。实时进程:
1) FIFO(先来先服务调度)
2) RR(时间片轮转调度)。
每个进程有两个优先级(动态优先级和实时优先级),实时优先级就是用来衡量实时进程是否值得运行的。 非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加了第三种优先级,实时优先级。优先级越高,得到CPU时间的机会也就越大。
9.标准库函数与系统调用的区别?
系统调用:是操作系统为用户态运行的进程和硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口,即就是设置在应用程序和硬件设备之间的一个接口层。inux内核是单内核,结构紧凑,执行速度快,各个模块之间是直接调用的关系。linux系统上到下依次是用户进程->linux内核->硬件。其中系统调用接口是位于Linux内核中的,整个linux系统从上到下可以是:用户进程->系统调用接口->linux内核子系统->硬件,也就是说Linux内核包括了系统调用接口和内核子系统两部分;或者从下到上可以是:物理硬件->OS内核->OS服务->应用程序,操作系统起到“承上启下”作用,向下管理物理硬件,向上为操作系服务和应用程序提供接口,这里的接口就是系统调用了。
库函数:把函数放到库里。是把一些常用到的函数编完放到一个lib文件里,供别人用。别人用的时候把它所在的文件名用#include<>加到里面就可以了。一类是c语言标准规定的库函数,一类是编译器特定的库函数。
系统调用是为了方便使用操作系统的接口,而库函数则是为了人们编程的方便。
第六篇:网络编程
1. 分别简述三次握手与四次挥手的过程?
三次握手:C----->SYN K
S------>ACK K+1 SYN J
C------->ACK J+1
DONE!
client 的 connect 引起3次握手
server 在socket, bind, listen后,阻塞在accept,三次握手完成后,accept返回一个fd,
2. tcp和udp之间的区别?
1)基于连接与无连接
2)对系统资源的要求(TCP较多,UDP少)
3)UDP程序结构较简单
4)流模式与数据报模式
5)TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证
6)TCP有拥塞控制和流量控制,UDP没有
TCP提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快
3. select、poll、epoll之间的区别?
https://www.cnblogs.com/Anker/p/3265058.html(参考阅读)
4. epoll有哪些触发模式?
(必须非常详尽的解释水平触发和边缘触发的区别,以及边缘触发在编程中要做哪些更多的确认)
注意:epoll必须深入理解,必须要张口就来,必须随心所欲说出来。
epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值。
也就是说在LT模式的情况下一定要确认收发的数据包的buffer是不是足够大如果收发数据包大小大于buffer的大小的时候就可能会出现数据丢失的情况。
5. 若是有大规模的数据连接,并发模型如何设计?
Epoll+线程池(epoll可以采用libevent处理)