面试总结

来源:互联网 发布:photoshop加密软件 编辑:程序博客网 时间:2024/06/03 07:24

一、C++面试题

1. 全局变量和局部变量的区别

(1)从作用域:

全局变量具有全局作用域,只需在一个源文件中定义,就可以作用于所有的源文件。全局变量对所有的函数都是可见的。

局部变量具有局部作用域,它只被初始化一次,自从第一次初始化直到程序运行结束一直都存在。静态局部变量只对定义自己的函数体始终可见。

(2)内存分配

全局变量在静态存储区分配空间(数据段),局部变量在栈里分配空间。

2. 数组和链表的区别

数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加或删除一个元素,需要移动大量元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。

链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。

  (1) 从逻辑结构角度来看
     a, 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
     b,链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
  (2)从内存存储角度来看
     a,(静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
     b, 链表从堆中分配空间, 自由度大但申请管理比较麻烦.

3. 指针和引用的区别

虽然使用引用和指针都可以间接访问另一个值,但他们之间有两个重要区别

引用总是指向某个对象,定义引用没有初始化是错误的。

赋值行为的差异,给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象。

相同点 都是地址的概念;指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

★ 区别:

    (1)指针是一个实体,而引用仅是个别名;

    (2). 引用只能在定义时被初始化一次,之后不可变;指针可变;

    (3). 引用没有 const,指针有const,const 的指针不可变;

    (4). 引用不能为空,指针可以为空;

    (5.) “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof指针”得到的是指针本身(所指向的变量或对象的地址)的大小;

4. 全局变量和局部变量、函数参数三者的区别(内存分配方面)

C++中的变量可能分配在5个地方中的一个:栈、堆、自由存储区、全局/静态存储区、常量存储区。

其中,局部变量,函数的形参是分配在栈中的。这段内存由编译器自动分配、释放。

全局变量和静态变量存储在数据段

new和delete用来申请的释放上的内存。

malloc和free用来释放自由存储区上的内存。

全局变量\static都分配在全局/静态存储区中。

常量则分配在常量存储区中。

扩展:堆和栈的区别

(1)stack的空间由操作系统自动分配和释放,heap上的空间手动分配和释放

(2)stack空间有限(1MB),heap是很大的自由存储区(4GB)

(3)malloc 和new函数分配的内存在堆上

(4)程序在编译时对变量和函数分配内存都在栈上,且程序运行过程中函数调用时参数的传递也在栈上

(5)栈是线性结构,堆是链式结构。

6    堆向高内存地址生长  栈向低内存地址生长

5. 不同类型的变量在内存中的位置:

1,局部变量、函数参数存放在上。静态局部变量,并不是在调用函数时分配,在函数返回时释放,而是像全局变量一样静态分配在.data数据段,但它的作用域只在函数中起作用。

2,堆,给动态分配内存使用。

3,全局变量、静态变量位于.data数据段;未初始化变量则位于.bss未初始化数据段。

4,const修饰的全局变量在.rodata只读数据段(const变量在定义时必须初始化,如果未初始化将被设为0或空),只读数据段在.text同一个segment

5,代码段即存储程序文本。指令指针中的指令就从这里取得。这个段一般是可以被共享的:你可以使用两个编辑器(vi?)来编辑文本,则它们共享一个代码段。

6. STL (标准模板库)

STL的优点:(1)可以方便、容易的实现搜索数据或对数据排序等一系列算法(2)调试程序更加安全和方便(3)STL是跨平台的

标准模板库是一个基于模板的容器类库,包括链表、列表、队列和堆栈,标准模板库还有很多常用的算法,包括排序和查找。

模板(template)

 类及结构等各种数据类型和函数的宏。模板定义:模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。

函数模板的一般形式如下:

Template <class或者也可以用typename T>

返回类型 函数名(形参表)
{//函数定义体 }

定义一个类模板:

Template < class或者也可以用typename T >
class类名{
//类定义......
};

 

7. 容器

容器是包含其他对象的对象。可容纳一些数据的模板类,STL中有vector、set、map、multimap和deque等容器。

8. 泛型

泛型编程是一种基于发现高效算法的最抽象表示的编程方法。泛型编程假定有某些基本的法则在支配软件组件的行为,并且基于这些法则有可能设计可互操作的模块,甚至还可以使用此法则去指导我们的软件设计,STL就是一个泛型编程的例子。

9. C++中怎样实现多态

通过重载和虚函数。重载是编译时,虚函数是运行时

10 介绍下虚函数

虚函数(Virtual Function):在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数。 虚函数必须是类的非静态成员,且不能是构造函数。虚函数的作用是实现动态联翩,也就是在程序的运行阶段动态的选择合适的成员函数。在定义了虚函数后,在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型
纯虚函数(Pure Virtual Function):基类中没有实现体的虚函数称为纯虚函数(有纯虚函数的基类称为虚基类)。
C++  “虚函数”的存在是为了实现面向对象中的“多态”,即父类类别的指针(或者引用)指向其子类的实例,然后通过父类的指针(或者引用)调用实际子类的成员函数。通过动态赋值,实现调用不同的子类的成员函数(动态绑定)。正是因为这种机制,把析构函数声明为虚函数可以防止在内存泄露。

虚基类:(有纯虚函数的基类称为虚基类)不能实例化(就是不能用它来定义对象),只能声明指针或者引用。

构造函数和析构函数可以是虚函数吗

构造函数不可以是虚函数,析构函数可以是虚函数。

因为如果构造函数是虚函数,它将在执行期间被构造,而执行期间则需要对象已经建立,构造函数所完成的工作就是为了建立合适的对象,因此在没有构建好对象时不可能执行多态(虚函数的目的就是在于实现多态)的工作。

应该将基类的析构函数定义为虚函数,因为这样当利用delete删除一个指向派生类定义的指针时,系统会调用相应的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数,造成内存泄露。

虚拟类是怎样的

虚基类:(有纯虚函数的基类称为虚基类)不能实例化(就是不能用它来定义对象),只能声明指针或者引用。

虚基类的使用,和为多态而实现的虚函数不同,是为了解决多重继承的二义性问题

如果想使公共的基类只产生一个复制,则可以将这个基类说明为虚基类。当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次。

假设类D是类B和类C公用派生类,而类B和类C又是类A的派生类,如图11.21所示。设类A有数据成员data和成员函数fun;派生类BC分别从类A继承了datafun,此外类B还增加了自己的数据成员data_b,类C增加了数据成员data_c。如果不用虚基类,就会在类D中保留了类A成员data的两份拷贝,分别表示为int B::dataint C::data。同样有两个同名的成员函数,表示为void B::fun()void C::fun()。类B中增加的成员data_b和类C中增加的成员dat_c不同名,不必用类名限定。此外,类D还增加了自己的数据成员data_d和成员函数fun_d

注意:虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。

声明虚基类的一般形式为:
   class 派生类名: virtual继承方式 基类名
即在声明派生类时,将关键字 virtual加到相应的继承方式前面,经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次。

需要注意:为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。

 

如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。

http://c.biancheng.net/cpp/biancheng/view/238.html

 

扩展:

在C++中,obj是一个类的对象,p是指向obj的指针,该类里面有个数据成员mem,请问obj.mem和p->mem在实现和效率上有什么不同。
答案是:只有一种情况下才有重大差异,该情况必须满足以下3个条件:
(1)、obj 是一个虚拟继承的派生类的对象
(2)、mem是从虚拟基类派生下来的成员
(3)、p是基类类型的指针

当这种情况下,p->mem会比obj.mem多了两个中间层。(也就是说在这种情况下,p->mem比obj.mem要明显的慢)

 

class A
{
public:
    int a;
};
class B : virtual public A
{
public:
   int b;
};
class C :virtual public A
{
public:
   int c; 
};
class D : public B, public C
{
public:
   int d;
};

面这种菱形的继承体系中,如果没有virtual继承,那么D中就有两个A的成员int a;继承下来,使用的时候,就会有很多二义性。而加了virtual继承,在D中就只有A的成员int a;的一份拷贝,该拷贝不是来自B,也不是来自C,而是一份单独的拷贝,

在回答这个问题之前,先想一下,sizeof(A),sizeof(B),sizeof(C),sizeof(D)是多少?(在32位x86的linux2.6下面,或者在vc2005下面)

在linux2.6下面,结果如下:sizeof(A) = 4; sizeof(B) = 12; sizeof(C) = 12;sizeof(D) = 24

sizeof(B)为什么是12呢,那是因为多了一个指针(这一点和虚函数的实现一样),那个指针是干嘛的呢?

那么sizeof(D)为什么是24呢?那是因为除了继承B中的b,C中的c,A中的a,和D自己的成员d之外,还继承了B,C多出来的2个指针(B和C分别有一个)。再强调一遍,D中的int a不是来自B也不是来自C,而是另外的一份从A直接靠过来的成员。

如果声明了D的对象d: D d;
那么d的内存布局如下:

vb_ptr: 继承自B的指针

int b: 继承自B公有成员

vc_ptr:继承自C的指针

int c: 继承自C的共有成员

int d: D自己的公有成员

int a: 继承自A的公有成员

 

11. 继承方式有哪几种

公有、保护、私有

1. 公有继承(public)   
公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。   
2. 私有继承(private)   
私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。   
3. 保护继承(protected)  
保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。 

12 类有哪些关系

继承友元 组合

13. 构造函数和析构函数的调用顺序

 

14. 广度优先搜索和深度优先搜索

15, 排序方法有哪些?哪种排序方法最快?

16. C语言和C++的区别

C语言是结构化和模块化的面向过程的语言,C++语言是面向对象的程序设计语言。区别,应该说是编程思想的区别吧,C是基于过程的,强调的是程序的功能,以函数(功能)为中心。C++是面向对象的,强调程序的分层、分类,以抽象为基础,进行对象的定义与展示,即程序设计。

 

C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制)。

C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。 所以C与C++的最大区别在于它们的用于解决问题的思想方法不一样。之所以说C++比C更先进,是因为“ 设计这个概念已经被融入到C++之中 ”。

17 面向过程和面向对象:

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为 面向对象:举个例子,盖一座大楼,你想到的是楼怎么盖,哪里要有柱子,哪里要有梁,哪里楼梯等等(这就是面向对象),至于柱子该怎么建,用什么建,方的圆的,等等,这就是面向过程。 用面向对象思考问题更符合我们人的思考方式。 其实我们人现实生活中都是在面向对象。比如:去饭店吃饭,你只要说明吃什么就可以了,有必要还了解这个菜是怎么做的,是哪里来的,怎么去种这个菜吗? 面向对象也可以说是从宏观方面思考问题,而面向过程可以说是从细节处思考问题。在面向对象中,也存在面向过程。

18. 不使用sizeof,获取整型数的字长

1

2

3

4

5

6

7

8

9

10

#include <stdio.h>

int main(void)

{

    unsigned int i=0;

    int bit;

    i=~0;

    for(bit=0;i>0;i>>=1)

        bit++;

    printf("%d\n",bit);

}

19 sizeof

char *a=”abc”; //sizeof=4  为什么(跟系统是32位 64位有没有关系) strlen是多少

char a[]=”abc”; //sizeof=4

 

假设某数据类型的一个变量占 N 字节,那么该数据类型的指针,所指向的地址,是这个 N 个字节中最小单元的地址,说通俗点就是起始地址。。而不是说记录了 N 字节的所有地址。。所以指针大小就是这个起始地址的大小,取决于计算机的字长。。32位计算机就是4字节。。64位计算机就是8字节。。但是又与编译器有关。。比如在32位计算机中,TC指针变量占2字节,其他编译器比如VC++的指针占4字节。。

C++,根据sizeof(指针)的大小判断操作系统的位数可以么

不可以。楼上说的sizeof(int)更加不靠谱。sizeof(指针)的大小只跟当前程序的编译设置有关。比如,你用VC编译一个32位的程序,那么sizeof(void *)的值就是32。就算你在64位的windows上运行这个程序,sizeof(void*)还是32,因为这已经在编译的时候就确定了。同样,如果你编译的是一个64位程序,那么sizeof(void*)就是64,就算将来有128位系统,你这个程序里的sizeof(void*)也仍然是64.sizeof(int)就更加不靠谱,因为C/C++标准只规定了sizeof(int)一定要小于或者等于sizeof(long),但没规定具体应该多大。比如,VC下编译64位程序,sizeof(int)是32。但64位linux程序里,g++的sizeof(int)是64。

20 . 栈溢出有哪些情况?

栈溢出就是缓冲区溢出的一种。 由于缓冲区溢出而使得有用的存储单元被改写,往往会引发不可预料的后果。程序在运行过程中,为了临时存取数据的需要,一般都要分配一些内存空间,通常称这些空间为缓冲区。如果向缓冲区中写入超过其本身长度的数据,以致于缓冲区无法容纳,就会造成缓冲区以外的存储单元被改写,这种现象就称为缓冲区溢出。缓冲区长度一般与用户自己定义的缓冲变量的类型有关。

  栈溢出就是缓冲区溢出的一种。

Windows程序的内存机制大概是这样的,全局变量(局部的静态变量本质也属于此范围)存储于堆内存,该段内存较大,一般不会溢出;函数地址、函数参数、局部变量等信息存储于栈内存,VC6中栈内存默认大小为1M,对于当前日益扩大的程序规模而言,稍有不慎就可能出问题。
(动态申请的内存即new出来的内存不在栈中)

 

 出现栈内存溢出的常见原因有2个:
    1> 函数调用层次过深,每调用一次,函数的参数、局部变量等信息就压一次栈。
    2> 局部静态变量体积太大
    第一种情况不太常见,因为很多情况下我们都用其他方法来代替递归调用(反正我是这么做的),所以只要不出现无限制的调用都应该是没有问题的,起码深度几十层我想是没问题的,这个我没试过但我想没有谁会把调用深度作那么多。检查是否是此原因的方法为,在引起溢出的那个函数处设一个断点,然后执行程序使其停在断点处, 然后按下快捷键Alt+7调出callstack窗口,在窗口中可以看到函数调用的层次关系。

    第二种情况比较常见了,我就是犯了这个错误,我在函数里定义了一个局部变量,是一个类对象,该类中有一个大数组,大概是1.5M。
    
    解决办法大致说来也有两种:
    1> 增加栈内存的数目
    2> 使用堆内存
    增加栈内存方法如下,在vc6种依次选择Project->Setting->Link,在Category中选择output,在Reserve中输入16进制的栈内存大小如:0x10000000,然后点ok就可以了。

 

 

堆栈溢出一般都是由堆栈越界访问导致的。例如函数内局部变量数组越界访问,或者函数内局部变量使用过多,超出了操作系统为该进程分配的栈的大小也会导致堆栈溢出。

21. 空指针和悬浮指针的区别

(1)空指针:   指向0 或 NULL 的指针

悬浮指针: 内存空间已经被收回的指针

 

使用空指针运行时,一定会立刻出错,你马上就可以知道并解决它。

而使用悬浮指针运行时,不一定会马上出错。这个错误会一直隐藏,你却没发现,而可能导致灾难性后果。

所以书上说:尽管同样是奔溃,空指针是一种可预料的奔溃,这样调试起来会方便得多。

(2) 野指针是说指针指向的对象不存在了,而空指针就是说没有指向任何地址,你可以理解成整数中的0

(3)当delete一个指针的时候,实际上是让编译器释放内存,但指针本身依然存在。这时它就是一个迷途指针

当使用以下语句时,可以把迷途指针改为空指针:

       Myptr = 0;

通常,如果在删除一个指针后又把它删除了一次,程序会变得非常不稳定,任何情况都有可能发生,但是如果像只是删除一个空指针,则什么事情都不会发生,这样做非常安全。

使用迷途指针或空指针(如Myptr=0)是非法的,而且有可能造成程序崩溃。如果指针是空指针,尽管同样是崩溃,但它同迷途指针造成的崩溃相比是一种可预料的崩溃。这样调试起来方便很多

22 new/deletemalloc/free的区别

(1)malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

(2) 对于非内部数据类型的对象而言,只用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执析造函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析造函数的任务强加于malloc/free。

因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。new/delete不是库函数,而是运算符。

4,C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

5、new可以认为是malloc加构造函数的执行。new出来的指针是直接带类型信息的。而malloc返回的都是void指针。

 

new 建立的是一个对象,
malloc分配的是一块内存.

 

new      是一个操作符,可以重载   
malloc 是一个函数,可以覆盖   
new    初始化对象,调用对象的构造函数,对应的delete调用相应的析构函数   
malloc 仅仅分配内存,free仅仅回收内存

 

1、malloc/free是C/C++中的方法(函数),new/delete是C++中的操作符。

     2、malloc申请的是heap区的内存空间,而new则是申请的free store区的内存空间。

     3、使用free之前要判断,使其free的指针是!NULL的,使用delete则无须判断。

     4、free掉的内存是该指针指向的一段内存空间,里面应该是空的。而delete掉的内存是里面确实存有数据或者对象的。

new和delete用来申请的释放堆上的内存。

malloc和free用来释放自由存储区上的内存。

23. Constdefine的区别

const 与 #define的比较

    C++ 语言可以用const来定义常量,也可以用 #define来定义常量。但是前者比后者有更多的优点:

(1)   const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。

(2)   有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

 

在C++中不推荐用宏,尽量少用。因为C++是强类型的语言,希望通过类型检查来降低程序中的很多错误,而宏只是在编译期前做简单替换,绕过了类型检查,失去了强类型系统的优势支撑。

(1) 编译器处理方式不同
      define宏是在预处理阶段展开。
      const常量是编译运行阶段使用。
(2) 类型和安全检查不同
      define宏没有类型,不做任何类型检查,仅仅是展开。
      const常量有具体的类型,在编译阶段会执行类型检查。
(3) 存储方式不同
      define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
      const常量会在内存中分配(可以是堆中也可以是栈中)。
(4) const  可以节省空间,避免不必要的内存分配。 例如:   
      const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。 

24智能指针:

智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,把一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。

 

每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。

 

智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。

事实上,智能指针能够做的还有很多事情,例如处理线程安全,提供写时复制,确保协议,并且提供远程交互服务。有能够为这些ESP (Extremely Smart Pointers)创建一般智能指针的方法,但是并没有涵盖进来。

由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见。

用智能指针便可以有效缓解这类问题

http://blog.csdn.net/xt_xiaotian/article/details/5714477

 

 

 

二、操作系统

1. 操作系统有哪几大部分

进程管理、任务管理、内存管理、文件管理

2. 进程和线程的区别

(1) 进程是什么?

程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念进程就是为了在CPU上实现多道编程而提出的。

(2) 有了进程为什么还要线程?

进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

因为要并发,我们发明了进程,又进一步发明了线程。只不过进程和线程的并发层次不同:进程属于在处理器这一层上提供的抽象;线程则属于在进程这个层次上再提供了一层并发的抽象。如果我们进入计算机体系结构里,就会发现,流水线提供的也是一种并发,不过是指令级的并发。这样,流水线、线程、进程就从低到高在三个层次上提供我们所迫切需要的并发!

进程与线程的区别

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

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

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

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

3. Cache的工作原理:程序访问的局部性

时间局部性: 一个存储项被访问,可能很快再访问.

空间局部性: 存储项被访问,它的邻近项可能很快被访问.

根据局部性原理,可以在主存和CPU之间设置一个高速的容量相对较小的存储器,存放当前正在执行的程序和数据,当程序运行时,不必从主存取指令和数据,而访问这个高速存储器即可.

Cache与主存之间可以采取的地址映射方式有以下三种:

全相联映射方式

直接相联映射方式

组组相联映射方式

Cache可以采用的替换算法主要有以下几种:

先入后出(FILO)算法

随机替换(RAND)算法

先入先出(FIFO)算法

近期最少使用(LRU)算法

提高cache的性能有以下三种方法:减少命中时间,减少未命中率,减少未命中惩罚。

在采用cache的系统中,同样一个数据可能既存在于cache中,也存在于主存中,两者数据相同则具有一致性,数据不相同就叫做不一致。

 

Cache主要有两种写策略写直达法(write through)和写回法(write back)

4. 同步和并行同步和异步:

操作系统中,同步是指程序在执行某一个操作时一直等待直到操作完成。异步指程序在执行某一个操作时,只是发出开始的指令,由另外的并行程序执行这段代码,当完成时再通知调用者。

并发和并行:

并发是逻辑上的同时发生。是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。

并行是物理上的同时发生,指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。

5. 操作系统中的通信方法有哪些

应用程序之间要通信就只能通过操作系统提供的通信服务,如:信号量、管道、消息、共享内存等,而不能直接访问彼此的地址空间。

6. 进程间的通信机制有哪些?优缺点分别是什么?

低级通信:信号机制

高级通信:管道 IPC Socket

低级通信只能传递状态和整数值(控制信息),包括进程互斥和同步所采用的信号量和管程机制。优点是速度快,缺点是(1)传送信息量小,效率低,每次通信传递的信息量固定,若传递较多信息则需要进行多次通信。(2)编程复杂:用户直接实现通信的细节,编程复杂,容易出错。

高级通信:能够传送任意数量的数据,包括三类:共享存储区、管道、消息。

管道:有两种限制:半双工,只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道。实现形式有pipe()和FIFO

IPC 共享存储器系统和消息传递系统。共享存储器系统:相互通信的进程通过共享数据结构和存储区进行通信,第一种低效,只适用于传递少量数据,第二种可以传送大量数据。

消息队列:每个消息不定长,由类型和正文组成。通过指定多种消息类型,可以在一个消息队列中建立多个虚拟信道。

套接字:双向的,数据格式为字节流或报文,主要用于网络通信,支持CS模式和P-to-P模式,本机或网络中的两个或多个进程进行交互,提供TCP/IP协议支持。

 

 

三、网络

1. TCP连接三次握手过程,(为什么要三次)解除时四次过程(为什么四次)

三次连接:(1)主机A通过向主机B发送一个含有同步序列号(SYN,syn=j)的标志位的数据段请求建立连接(客户端向服务器端)(同步比特置1,同时选择一个序号j,表明在后面传送数据时第一个数据字节的序号是j)

(2)主机B收到主机A的请求后,用一个带有ACK(ack=j+1)和同步序列号标志位(syn=k)的数据段响应主机A。

(3)主机A收到这个数据段后,再发送一个确认应答(ack=k+1),确认已收到主机B的数据段。

三次连接的目的:防止已失效的连接请求报文段突然又传到了服务端,而产生错误。解决网络中存在延迟的重复分组问题。(建立可靠连接)

 

四次挥手:

主机A的应用进程先向其TCP发出释放连接请求,不再发送数据。TCP通知对方要释放从A到B的连接,终止比特FIN置1,序号u等于传送数据的最后一个字节的序号+1

主机B的TCP收到释放连接通知后,发出确认,序号为u+1,同时通知应用进程,A到B的连接释放,连接处于半关闭状态。

主机B向主机A的数据发送结束后,其应用进程通知TCP释放连接。主机B发出的报文段FIN=1,序号w=等于前面已经传送过的数据的最后一个字节的序号+1,ACK=u+1

主机A对主机B的连接释放报文段发出确认,将ACK置1,ACK=w+1, seq=u+1,这样从B到A的反方向连接释放掉。整个连接全部释放。

2. OSI模型有哪些层?UDP协议位于哪一层?

物理层数据链路层 网络层 传输层 表示层 会话层 应用层

物理层:用于在物理介质上传输可靠的原始比特流,是数据链路层在两个主机之间交换数据的有效途径。设备:集线器。协议:RS232 RS485

数据链路层:以帧为单位在两个相邻节点间的线路上无差错的传输数据,并且接收方在收到一帧数据时,对接收到的数据进行检测,如果数据存在差错,就通知发送方重新发送这一帧数据。设备:交换机 协议:PPP HDSL 802.11

网络层:用于选择合适的网间路由和交换节点,以确保数据能够在经过很多数据链路或经过很多通信子网的计算机之间及时地传递。设备:路由器。 协议:IP协议。ARP ICMP

传输层:根据通信子网的特性,对网络资源进行最佳的利用,该层通过可靠和不可靠两种方式以报文为单位在源主机与目标主机的会话层建立维护和取消传输连接。TCP UDP RTP

会话层:用于建立、管理和终止进程间的会话,该层通过在数据中插入校验点,从而可以保证数据的同步传输。

表示层:用于对上层数据进行转换,如对数据的加密、解密、压缩和格式等进行转换,以确保数据能够在两个主机的应用层进行正确的传输。

应用层:用于规定应用进程在通信时所应遵循的协议,如收发邮件、浏览网页、上传和下载文件等,从而可以满足用户的实际需要。DHCP DNS FTP HTTP POP3

 

四、Linux

1. linux内核代码主要模块

arch includeinit mm drivers ipc fs kernel net scripts documentation

2. linux中的中断机制

为什么会有中断?内核的一个主要功能是处理硬件外设IO,由于处理器速度一般比外设快很多,轮询方式效率不高,内核应当处理其他任务,只有当外设真正完成/准备好了才转过来处理外设IO。中断机制就是满足上述条件的一种解决办法。

在ARM中,中断、异常、系统调用统称为异常,ARM的异常有7种,复位、未定义指令、软中断、取指令异常、取数据异常、中断、快速中断。

从发展过程来看:中断(interrupt)最初被用来替换I/O操作的轮询处理方式,以提高I/O处理的效率。随后,中断又包含了自陷(trap,也称为内部中断或是软件中断)的功能。后来,中断的概念得到进一步扩大,被定义为导致程序正常执行流程发生改变的事件

广义的中断通常被分为中断、自陷和异常(exception)等类别。

    中断是由于CPU外部的原因而改变程序执行流程的过程,是被动的,属于异步事件,又称为硬件中断。

    自陷是由专设的指令,如X86中的“INT n”,ARM中的SWI指令。在程序中有意产生的,是主动的。则为同步事件;自陷是显式的事件;

    异常是由于CPU因无法完成一些指令而产生的,如除以0、映射失败,等等。异常没有对应的处理器指令。当异常事件发生时,处理器也需要无条件地挂起当前运行的程序,执行特定的处理程序。

 

中断的执行流程

中断处理的过程分为:中断检测、中断响应、中断处理

    ①中断检测:中断检测在每条指令结束时进行,检测是否有中断请求或是否满足异常条件。没有中断信号:处理器继续运行,并通过取指周期取当前程序的下一条指令;有中断信号:将进入中断响应,对中断进行处理。 

    ②中断响应:中断响应是由处理器内部硬件完成的中断序列,而不是由程序执行的。

    ③中断处理:即执行中断服务程序。中断服务程序的主要内容

保存上下文:保存中断服务程序将要使用的所有寄存器的内容,以便于在退出中断服务程序之前进行恢复;

获取中断相关的其他信息;

对中断进行具体的处理——用户中断服务;

恢复保存的上下文;

执行中断返回指令使CPU的控制返回到被中断的程序继续执行。

如果对一个中断的处理还没有完成,又发生了另外一个中断,则称系统中发生了多个中断。对于多个中断的处理有两种方式:

非嵌套的中断处理方式:在处理一个中断的时候,禁止再发生中断。处理中断的时候,将屏蔽所有其他的中断请求。新的中断将被挂起,当处理器再次允许中断时,再由处理器进行检查。如果程序执行过程中发生了中断,在执行中断服务程序的时候将禁止中断;

中断服务程序执行完成后,恢复正常执行流程被中断的程序之前再使能中断,并由处理器检查是否还有中断。

非嵌套中断处理方式使中断能够按发生顺序进行处理。没有考虑优先级,使高优先级中断不能得到及时的处理,甚至导致中断丢失。

嵌套的中断处理方式定义中断优先级,允许高优先级的中断打断低优先级中断的处理过程。中断被划分为多个优先级,中断服务程序只屏蔽那些比当前中断优先级低或是与当前中断优先级相同的中断,在完成必要的上下文保存后即使能中断。

高优先级中断请求到达的时候,需要对当前中断服务程序的状态进行保存,然后调用高优先级中断的服务程序。

当高优先级中断的服务程序执行完成后,再恢复先前的中断服务程序继续执行

总结:中断处理过程:

根据需要进入核心态-保存上下文-根据中断号,找到中断处理函数进行处理-恢复上下文-根据需要返回用户态

中断能否被打断?中断是不是临界区?

硬中断不可以被打断,软中断可以。非嵌套的中断有临界区。

尽管内核在处理一个中断时可以接受一个新的中断,但在内核代码中还存在一些临界区,在临界区中,中断必须被禁止。

当CPU正在执行一个与中断相关的内核控制路径时,linux不允许进程切换。

3. 驱动的作用是什么

(1)驱动程序(DeviceDriver)全称为“设备驱动程序”,是一种可以使计算机和设备通信的特殊程序,可以说相当于硬件的接口操作系统只能通过这个接口,才能控制硬件设备的工作,假如某设备的驱动程序未能正确安装,便不能正常工作。驱动程序提供了硬件到操作系统的一个接口以及协调二者之间的关系,而因为驱动程序有如此重要的作用,所以人们都称“驱动程序是硬件的灵魂”、“硬件的主宰”,同时驱动程序也被形象的称为“硬件和系统之间的桥梁”。

(2)驱动程序就是一组程序,一个比较特别的软件,它具有一般程序的一些特性,可以形象地把它理解为是搭建在计算机硬件设备与操作系统之间的桥梁,它的作用就是使操作系统能够正确地识别、管理、使用相应的硬件设备 

(3)驱动程序是硬件厂商根据操作系统编写的配置文件,是添加到操作系统中的一小块代码,其中包含有关硬件设备的信息。当你安装新硬件时,驱动程序是一项不可或缺的元件。可以说没有驱动程序,计算机中的硬件就无法工作。

 

驱动程序是连接底层的硬件和上层的API函数的纽带,有了驱动程序模块,就可以把操作系统的API函数和底层的硬件分离开来。硬件的改变、删除或者添加,只需要随之改变、删除或者添加提供给操作系统的相应的驱动程序就可以了。而不会影响到API函数的功能,更不会影响到用户的应用程序

4. linux中设备管理机制

设备管理的体系结构分为5层,从上到下为:API层、设备管理层、驱动逻辑层、硬件抽象层、设备。

为了实现设备管理,可以提供三张表:驱动程序地址表、设备名表、文件表。驱动程序地址表:管理系统中的驱动程序,表项为各个驱动程序的功能函数。

设备名表:管理系统中所有设备,通过设备名来区分各设备。

文件表:用来记录当前打开的设备。

 

 

0 0
原创粉丝点击