操作系统与编译面试题

主要参考:
《程序员的自我修养》读书总结
编译与链接过程的思考
linux 下动态链接实现原理
研读《程序员的自我修养—链接、装载与库》
程序的静态链接,动态链接和装载

1. 源代码是怎么变成可执行文件的,每一步的作用是什么?(预编译,词法分析,语法分析,语义分析,中间语言生成目标代码生成,汇编,链接)

解答:
源代码变成可执行文件需要经历 4个大步骤:
预处理——>编译——>汇编——>链接

编译流程图.png

引用自:编译与链接过程的思考

1. 预处理:

“预编译器”负责,负责将:

  • 将所有的#define删除,并且展开所有的宏定义

  • 处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif

  • 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。

  • 删除所有注释“//””/* */”.

  • 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。

  • 保留所有的#pragma编译器指令,因为编译器需要使用它们

  • 最终.c文件经过预编译,变为.i文件。

2. 编译

由编译器负责,主要又由词法分析、语法分析、语义分析、优化生成汇编代码五个部分:

  • 词法分析:识别源代码中的各种括号、数字、标点等。比如有(但没有),这一步就能发现错误。

  • 语法分析:这一步会生成语法树,比如2+4就是一颗根节点为+,左右叶子节点分别为24的语法树。如果你只是写2+,在这一步就会报错。

  • 语义分析:这一步主要考虑类型声明、匹配和转换。比如你写2 * "3"在这一步就会报错

  • 中间语言生成:这一步会生成平台无关的三地址码,比如2 + 3会写成t1 = 2 + 3,同时也会把这样在编译期就可以确定的表达式进行优化

  • 目标代码生成:编译器根据三地址码生成依赖于目标机器的目标机器代码,也就是汇编语言

  • 最后.i文件经过编译,得到汇编文件,后缀是.s

3. 汇编

汇编器将汇编语言转换成机器可以执行的语言(完全由01组成).汇编文件经过汇编,变成目标文件,后缀为.o

4.链接
通过调用链接器ld来将多个目标文件以及所依赖的其它库文件链接起来,最后生成可执行文件。

考虑一个.c文件中,用到了另一个.c文件中的变量或函数。在编译这个文件时,我们无法在编译期确定这个变量或函数的地址。只有在把所有目标文件链接起来以后,才能确定。链接器主要负责地址重分配、符号名称绑定和重定位。

2. 应用层、API、运行库、系统调用、操作系统内核之间的关系是什么?

关系图.png

引用自《程序员的自我修养》读书总结

计算机调用结构:

  • 最上层是应用层。不管是浏览器、游戏,还是我们使用的各种开发工具,如XcodeVS汇编器自身等,都属于这一范畴。

  • 第二层是操作系统的运行库。我们在程序里调用系统API,比如文件读写,就是调用了第二层提供的相应服务。这种调用通过操作系统的API完成,它沟通了应用层操作系统的运行库。这也就是为什么不管是在Mac还是Windows上编程,我们都可以调用printf()fread()等函数。因为不同的操作系统的运行库提供了不同底层的实现,但对应用层提供的API总是一样的。

  • 第三层是操作系统内核。操作系统的运行库通过系统调用(System Call)调用系统内核提供的函数。比如fread属于API,它在Linux下会调用read()这个系统调用,而在Windows下会调用ReadFile()这个系统调用。应用程序可以直接调用系统调用,但是这样一来,我们需要考虑各个操作系统下系统调用的不同,而且系统调用由于更加底层,实现起来也就更加困难。最关键的是,系统调用是通过中断来完成的,涉及到堆栈的保存与恢复,频繁的系统调用会影响性能。

  • 第四层是硬件层。程序无法直接访问这一层,只有操作系统的内核,通过硬件厂商提供的接口才能访问。

3. 虚拟内存空间是什么,为什么要有虚拟内存空间。

虚拟地址空间:是指应用程序自己认为,自己所处的地址空间。它区别于物理地址空间。后者是真实存在的,比如电脑有一根8G的内存条,物理地址空间就是0~8GbCPUMMU(内存管理单元)负责把虚拟地址转换成物理地址。

引入虚拟地址的好处:

  • 程序员不再关心真实的物理内存空间是什么样的,理论上来说(64CPU的虚拟内存为2^64),程序员有几乎无限大的虚拟内存空间可用,最后只要建立虚拟地址和物理地址的对应关系即可。

  • 操作系统屏蔽了物理内存空间的细节,进程无法访问到操作系统禁止访问的物理地址,也不能访问到别的进程的地址空间,这大大增强了程序安全性。

  • 由虚拟地址空间引申出来的分页(Paging)技术,大大提高了内存的使用效率。要想运行一个程序,不再需要把整个程序都放入内存中执行,我们只要保证将要执行的页在内存中即可,如果不存在则导致页错误。

4. 静态链接和动态链接分别表示什么,大概是怎么实现的?

静态链接:

是指链接阶段将多个目标文件和相关的静态库组合在一起形成一个可执行文件。

静态链接包括两个大部分:一是空间和地址的分配;二是符号解析和重定位

  • 空间和地址分配:

扫描所有的输入目标文件,获得他们的各个段的长度、属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表。这样,连接器将能够获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。

空间和地址分配.png

备注:映射关系就是指可执行文件与进程虚拟地址空间之间的映射。那么,这里程序还没有执行,更不会出现进程,哪里来的进程地址空间呢?此时虚拟存储器便发挥了很大的作用:虽然此时没有进程,但是每个进程的虚拟地址空间的格式都是一致的。所以,为可执行文件的每个段甚至每个符号符号分配地址也就不会有什么错了

  • 符号解析与重定位:

符号解析:解析符号就是将每个符号引用与它输入的可重定位目标文件中的符号表中的一个确定的符号定义联系起来。若找不到,则出现编译时错误。

重定位:根据目标文件的重定位入口所修正的指令寻址方式,进行正确的寻址。

动态链接

是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。

动态链接基本分为三步:先是启动动态链接器本身,然后装载所有需要的共享对象,最后重定位和初始化

  • 动态链接器自举:
    动态链接器有其自身的特殊性:首先,动态链接器本身不可以依赖其他任何共享对象(人为控制);其次动态链接器本身所需要的全局和静态变量的重定位工作由它自身完成(自举代码)
    我们知道,在Linux下,动态链接器ld.so实际上也是一个共享对象,操作系统同样通过映射的方式将它加载到进程的地址空间中。操作系统在加载完动态链接器之后,就将控制权交给动态链接器。动态链接器入口地址即是自举代码的入口。动态链接器启动后,它的自举代码即开始执行。自举代码首先会找到它自己的GOT(全局偏移表,记录每个段的偏移位置)。而GOT的第一个入口保存的就是“.dynamic”段的偏移地址,由此找到动态链接器本身的“.dynamic”段。通过“.dynamic”段中的信息,自举代码便可以获得动态链接器本身的重定位表和符号表等,从而得到动态链接器本身的重定位入口,然后将它们重定位。完成自举后,就可以自由地调用各种函数和全局变量。

  • 装载共享对象:
    完成自举后,动态链接器可执行文件链接器本身的符号表都合并到一个符号表当中,称之为“全局符号表”。然后链接器开始寻找可执行文件所依赖的共享对象:从“.dynamic”段中找到DT_NEEDED类型,它所指出的就是可执行文件所依赖的共享对象。由此,动态链接器可以列出可执行文件所依赖的所有共享对象,并将这些共享对象的名字放入到一个装载集合中。然后链接器开始从集合中取出所需要的共享对象的名字,找到相应的文件后打开该文件,读取相应的ELF文件头“.dynamic”,然后将它相应的代码段和数据段映射到进程空间中。如果这个ELF共享对象还依赖于其他共享对象,那么将依赖的共享对象的名字放到装载集合中。如此循环,直到所有依赖的共享对象都被装载完成为止。

  • 重定位和初始化:
    当上述两步完成以后,动态链接器开始重新遍历可执行文件和每个共享对象的重定位表,将表中每个需要重定位的位置进行修正,原理同前。
    重定位完成以后,如果某个共享对象有“.init”段,那么动态链接器会执行“.init”段中的代码,用以实现共享对象特有的初始化过程

5. 可执行文件的结构如何?(分为哪些段)

可执行文件的结构.png
  • ELF Header: 用来描述整个文件的组织,文件类型,机器类型,程序入口地址

  • Program Header Table:描述了ELF文件该如何被操作系统映射到进程的虚拟空间

(备注:可执行文件中有多个Segment,从装载的角度,我们目前只关心两个“Load”类型的Segment,因为只有它是需要被映射的,其他的诸如“NOTE”、“TLS”、“GNU_STACK”都是在装载时起辅助作用的)

  • Segment 1: 目标文件中可读可执行的section的集合,如.text,.init

  • Segment 2: 目标文件中可读可写的section的集合。如.data,.bss

  • Section Header Table optional:ELF Header中描述的节区头描述表的起始位置,文件包含了多少个节,这个数组就有多少个成员,每个成员描述了对应一个节区的名字,偏移地址虚拟地址可读写信息等.

6. 可执行文件是怎么装载进内存的,为什么要分段,分页,页错误是什么?

1). 可执行文件的装载:

  • 比如当我们在Linux系统bash下输入一行命令执行某个ELF程序时,在用户层面,bash进程会调用fork()系统调用创建一个新的进程,然后新的进程调用execve()来执行ELF文件,原先的bash进程返回等待刚启动新进程结束,然后继续等待用户输入命令。

  • 新创建的进程,操作系统会为它创建一个独立的虚拟地址空间(虚拟空间实际上并不是创建空间而是创建映射函数所需要的数据结构。)

  • 在进入execve()函数调用后,Linux内核就开始进行真正的装载工作。在内核中execve()系统调用相应的入口sys_execve()来进行参数的检查复制,然后调用do_execve()函数来查找被执行的文件,读取文件的前128个字节来判断文件的格式是elf还是其他;接着调用search_binary_handle()函数来判断文件头部的魔数,确定文件的格式并且调用相应的装载处理程序。ELF可执行文件的装载处理过程叫load_elf_binary(),主要步骤如下:

  • 检查ELF可执行文件格式的有效性,比如魔数、程序头表中段的数量。

  • 寻找动态链接的“.interp”段,找到动态链接器的路径,以便于后面动态链接时会用上。

  • 读取可执行文件的程序头,并且创建虚拟空间可执行文件的映射关系。

  • 初始化ELF进程环境

  • 将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的ELF可执行文件,它就是ELF文件的文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点就是动态链接器

-【将CPU指令寄存器设置成可执行文件的入口,启动运行】对动态链接来讲,此时就启动了动态链接器。

  • load_elf_binary()执行完毕,返回至do_execve()在返回至sys_execve()时,系统调用的返回地址已经被改写成了被装载的ELF程序的入口地址了。所以,当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到ELF程序的入口地址。此时,ELF可执行文件装载完成。接下来就是动态链接器对程序进行动态链接了。

2). 为什么要分段、分页

  • 在引入分页和分段之前,操作系统是通过连续分配方式来管理存储器,就是说一个进程在内存中是连续存放的。举个例子:内存中有进程1,进程2,进程3进程1先执行完成了,然后释放了所占用的内存空间,之后如果新调入的进程2内存需求大于之前进程1所占用的空间,那么不可能利用这块内存,相对于内存需求更大的进程来说,之前进程1所占用的内存空间就是不能利用的碎片。如果新调入的进程2内存小于之前之前进程1所占用的空间就会留下空隙,也会带来碎片。虽然可以通过“紧凑”的方法进行碎片整理,但开销很大,因此就产生了与连续分配方式相对的离散分配方式,便先后引入分页分段存储管理方式。

  • 分页存储管理就是将进程的逻辑地址空间分成若干大小相等的页,相应地,内存空间也分成若干物理块,页和块大小相等,可将用户程序的任一页放在内存的任一块中,实现离散分配

-分段式存储管理就是用户可以把进程按逻辑关系划分为若干个大小不等段,每个段都是从0开始编址,定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相连接,也实现离散分配

  • 分页分段两者都属于存储器管理方式中的离散分配方式,都要采用地址映射机构来实现地址变换。

  • 不同点在于:
    a.是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,.是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要;
    b.页的大小固定,由系统确定,将逻辑地址划分为页号页内地址是由机器硬件实现的.而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分;
    c. 分页的作业地址空间是一维的.分段的地址空间二维的.

打个比方,比如说你去听课,带了一个纸质笔记本做笔记。笔记本有100张纸,课程有语文、数学、英语三门,对于这个笔记本的使用,为了便于以后复习方便,你可以有两种选择。

  • 第一种是,你从本子的第一张纸开始用,并且事先在本子上做划分:第2张第30张纸记语文笔记,第31到60张纸记数学笔记,第61到100张纸记英语笔记,最后在第一张纸做个列表,记录着三门笔记各自的范围。这就是分段管理第一张纸段表
  • 第二种是,你从第二张纸开始做笔记,各种课的笔记是连在一起的:第2张纸是数学,第3张是语文,第4张英语……最后呢,你在第一张纸做了一个目录,记录着语文笔记在第3、7、14、15张纸……,数学笔记第2、6、8、9、11……英语笔记在第4、5、12……。这就是分页管理第一张纸叫页表。你要复习哪一门课,就到页表里查寻相关的纸的编号,然后翻到那一页去复习

3)页错误是什么
页错误又叫页缺失,在引入分页机制的操作系统中,一个进程的代码和数据被放置在一个虚拟的地址空间中,地址空间按固定长度划分为好多页。同时,物理内存也按固定长度划分为好多帧,因为物理内存小硬盘空间大,为了在内存里放置更多的进程,操作系统的设计者决定把页映射内存帧或硬盘的虚拟内存文件中。
进程的可视范围是它自己的地址空间,它并不知道某一页映射到内存里还是硬盘上,进程只管自己运行。当进程需要访问某一页时,操作系统通过查看分页表,得知被请求的页在内存里还是硬盘里。若在内存里,则进行地址翻译;若在硬盘里,则发生页缺失。操作系统立即阻塞该进程,将硬盘里对应的页换入内存,然后使该进程就绪(可以继续运行)。

7. 进程的内存格局是怎样的?(堆、栈、全局/静态区,代码区,常量区)

进程的内存格局.png
  • 内核态内存空间 (Kernel space): 供内核使用,其大小一般比较固定,但 32 位系统64 位系统的值不一样。

  • 栈(stack):用于存储局部变量函数参数,由编译器自动分配和释放。调用一个方法或函数会将一个新的栈桢(stack frame)压入栈中。栈桢在函数返回时被清理。另外,持续的重用栈空间有助于使活跃的栈内存保持在CPU缓存中,从而加速访问。进程中的每一个线程都有属于自己的栈。

  • 内存映射段(Memory Mapping Segment):内核将文件的内容直接映射到内存。内存映射是一种方便高效的文件I/O 方式,所以它被用于加载动态库。创建一个不对应于任何文件的匿名内存映射也是可能的,此方法用于存放程序的数据。在Linux中,如果你通过malloc请求一大块内存,C 运行库将会创建这样一个匿名映射而不是使用堆内存‘大块’意味着比MMAP_THRESHOLD还大,缺省是 128KB,可以通过mallopt调整。

  • 堆(heap):用于运行时内存分配,一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统释放。该区存放由new、malloc产生的动态数据。

  • BSS 段(): 保存的是未被初始化静态变量全局变量,它们的值不是直接在程序的源代码中设定的。BSS内存区域匿名的:它不映射到任何文件。如果你写static int cntActiveUsers,则cntActiveUsers的内容就会保存在BSS中。

  • 数据段(Data segment): 存放已经初始化过的静态变量全局变量,这个内存区域不是匿名的。它映射了一部分的程序二进制镜像,也就是源代码中指定了初始值的静态变量。所以,如果你写static int cntWorkerBees = 10,则cntWorkerBees的内容就保存在数据段中了,而且初始值为10

  • 代码段(Text segment): 用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。

8. 堆和栈的区别,函数调用和栈的关系

1)堆和栈的区别:

a.申请方式:
-: 由系统自动分配, 例如,在函数中声明一个局部变量 int b; 系统自动在栈中为b开辟空间 。

  • : 需要程序员自己申请,并指明大小。 例如: 在cmalloc函数,如p1 = (char*)malloc(10);在C++中用new运算符如p2 = new char[10];

注意:p1和p2本身是在栈中的

b.申请后系统的响应:

  • 栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢 出

  • 堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
    会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点空闲结点链表
    中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的
    首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。
    另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部
    分重新放入空闲链表中。

c.申请大小限制:
栈:在Windows下,是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意
思是栈顶的地址栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小2M(也有
的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将
提示overflow。因此,能从获得的空间较小。

堆:是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储
空闲内存地址的,自然是不连续的,而链表遍历方向是由低地址高地址堆的大小
受限于计算机系统中有效的虚拟内存。由此可见,获得的空间比较灵活,也比较大。

d.申请效率比较:

  • 栈: 由系统自动分配,速度较快。但程序员是无法控制的。
  • 堆: 是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
    另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是
    直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。

e. 存储内容

  • 栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可 执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈
    的,然后是函数中的局部变量。注意静态变量是不入栈的。
    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地
    址,也就是主函数中的下一条指令,程序由该点继续运行。

  • 堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。

f. 存取效率:

  • 栈: 快
  • 堆: 慢

总结堆和栈的区别可以用如下的比喻:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就 走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自 由度小
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由 度大

9. 进程和线程的区别

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

  • 线程:进程的一个执行单元,是CPU调度分派的基本单位,也被称为称为轻量级进程

关联:

  • 一个进程可以由多个线程或单个线程组成
  • 线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
  • 二者均可并发执行。

区别:

  • 地址空间: 进程拥有独立的地址空间线程共享本进程的地址空间

  • 资源拥有: 进程是拥有系统资源的一个独立单位,而线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),和其他线程共享本进程的相关资源如内存、I/O、cpu等。

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

  • 系统开销: 在进程切换时,涉及到整个当前进程CPU环境的保存环境的设置以及新被调度运行的CPU环境的设置,而线程切换只需保存和设置少量的寄存器的内容,并不涉及存储器管理方面的操作,可见,进程切换的开销也远大于线程切换的开销。

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

10.异步和同步,串行,并发,并行的区别

  • 同步:多个任务情况下,一个任务A执行结束,才可以执行另一个任务B。只存在一个线程。
同步.png
  • 异步:多个任务情况下,一个任务A正在执行,同时可以执行另一个任务B任务B不用等待任务A结束才执行。存在多条线程

    异步.png

  • 并行: 真正的异步,多核CUP可以同时开启多条线程供多个任务同时执行,互补干扰,如上图的并行,其实和异步图例一样。

    并行.png

  • 并发: 是一个伪异步。在单核CUP中只能有一条线程,但是又想执行多个任务。这个时候,只能在一条线程上不停的切换任务,比如任务A执行了20%任务A停下来,线程让给任务B,任务执行了30%停下,再让任务A执行。这样我们用的时候,由于CUP处理速度快,你看起来好像是同时执行,其实不是的,同一时间只会执行单个任务。

    并发.png

  • 串行:是同步线程的实现方式,就是任务A执行结束才能开始执行B,单个线程只能执行一个任务,就如单行道只能行驶一辆车。

详见:帮你快速理解同步 ,异步,并发/并行,串行

11. 多并发任务,仅多线程能加快速度么(不能,会变慢,有线程切换的开销)

多并发任务中,CPU只有一个,因此分配给进程的CPU资源是一定的,多线程只不过是轮流抢占CPU资源而已,并不会真正提高处理速度,并且由于线程之间的切换需要一定的开销,因此也会浪费一定的切换时间,这会导致一个任务采用一个拥有两个线程的进程执行所需要的时间比一个线程的进程执行两次所需要的时间要多一些。但多线程的作用主要在于提高了并发数量,比如http请求,如果是单线程,一次只能接收一个请求,多线程则可以同时接收多个请求。

12. 多个线程之间可以共享那些数据

  • 进程代码段

  • 进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、

  • 进程打开的文件描述符

  • 信号的处理器

  • 进程的当前目录

  • 进程用户ID进程组ID

13. 进程之间如何通信

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

IPC的方式通常有管道(包括无名管道和命名管道)消息队列信号量信号共享内存套接字( socket )远程过程调用:RPC等。其中 套接字( socket )远程过程调用:RPC支持不同主机上的两个进程IPC

  • 管道:
    一种半双工的通信方式,数据只能单向流动。既可在程序中使用,也可在shell中使用。
    管道的问题在于他们没有名字,只能在具有亲缘关系(父子进程间)的进程间使用。
    扩展:管道pipe函数创建,提供一个单向数据流。当需要一个双向数据流时,我们必须创建两个管道,每个方向一个。这也就是全双工管道的实现原理:由两个半双工管道构成

  • 命名管道:即FIFO
    命名管道也是半双工通信方式。提供单向数据流。
    克服了管道没有名字的限制,因此允许无亲缘关系的进程间通信,解决了管道的上述问题。
    扩展:
    管道FIFO都是使用通常的readwrite函数访问。
    FIFOmkfifo函数创建。FIFO不同于管道的是,每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一FIFO
    FIFO的真正优势表现在服务器可以是一个长期运行的进程(如守护进程),而且与其客户可以无亲缘关系。

  • 信号量:
    主要作为进程间以及同一进程内不同线程之间的同步手段
    进程间通信处理同步互斥的机制。信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。

  • 信号:
    是一种处理异步事件的方式。
    信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。除了用于进程外,还可以发送信号给进程本身
    信号信号量是不同的,他们虽然都可用来实现同步互斥,但前者是使用信号处理器来进行的,后者是使用P,V操作来实现的。

  • 消息队列:
    消息队列消息的链表,包括Posix消息队列systemV消息队列
    足够写权限的进程都可以向队列中添加消息,有足够读权限的进程都可以读走队列中的消息。
    消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    扩展: 消息队列具有随内核的持续性,这跟管道FIFO不一样。当一个管道FIFO的最后一次关闭发生时,仍在该管道FIFO上的数据将被丢弃。
    SystemV消息队列的问题之一是无法通知一个进程何时在某个队列放置了一个消息,也就是无法窥探一个消息。而Posix消息队列允许异步事件通知,以告知何时有一个消息放置到了某个空消息队列中。
    Posix消息队列缺失的主要特性是从队列中读出指定优先级消息的能力。
    使用管道FIFO时,为在两个方向上交换数据,需要两个IPC通道。而使用消息队列时单个队列就够用,由每个消息的类型来标识该消息是从客户到服务器还是从服务器到客户。

  • 共享内存:
    是由一个进程创建,但多个进程都可以访问的同一块内存空间。是最快的可用IPC形式(因为共享内存区中的单个数据副本对于共享该内存的所有线程或进程都是可用的)。

    是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步通信

    一般IPC形式(管道、FIFO、消息队列)的问题在于,两个进程要交换信息,这些信息必须经由内核传递。而进程间共享内存时,交换数据就不再涉及内核。这些进程必须协调或同步对该共享内存区的使用。

    共享内存区用于客户-服务器文件复制:该共享内存区同时存在客户和服务器的地址空间

image.png

数据只需复制两次:一次从输入文件共享内存区,另一次从共享内存区输出文件
然而其他IPC形式(管道、FIFO、消息队列)则需要四次复制:
image.png

另外,默认情况下通过fork派生的子进程并不与其父进程共享内存区。

  • 套接字(socket):
    一般的进程间通信机制,可用于不同机器之间的进程间通信。

  • 远程过程调用:RPC:
    构筑一个应用程序时,如果所有进程都运行在同一台主机,那么可以使用前面的各种IPC方式;如果进程不在同一个主机上,那就要求进程间使用某种形式的网络通信,RPC提供了这样一种工具,它属于隐式网络编程的范畴。

RPC是用于从一个系统(客户主机)上某个程序调用另一个系统上(服务器主机)某个函数的一种方法。

它的调用进程被调用进程可在不同主机上执行,客户和服务器运行在不同主机上,而且过程调用中涉及网络I/O,对于程序员是透明的。

另外,RPC也可用于同一主机上的客户和服务器之间,因此也可认为是另一种形式的消息传递。

详见:进程间通信方式

14. 介绍几种锁,他们的用途和区别

  • 互斥锁:
    用于保护临界区,确保同一时间只有一个线程访问数据。对共享资源的访问,先对互斥量进行加锁,如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。

  • 临界区:
    每个进程中访问临界资源的那段程序称为临界区,每次只允许一个进程进入临界区,进入后不允许其他进程进入。

  • 自旋锁:
    互斥量类似,它不是通过休眠使进程阻塞,而是在获取锁之前一直处于循环检测保持者是否已经释放了锁(自旋)的状态。用在以下情况:锁持有的时间短,而且线程并不希望在重新调度上花太多的成本。自旋锁互斥锁的区别:线程在申请自旋锁的时候,线程不会被挂起,而是处于忙等的状态。

  • 信号量:
    信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段

  • 读写锁(rwlock):
    高级别锁,区分读和写,符合条件时允许多个线程访问对象。处于读锁操作时可以允许其他线程本线程读锁, 但不允许写锁, 处于写锁时则任何锁操作都会睡眠等待;常见的操作系统会在写锁等待时屏蔽后续的读锁操作以防写锁被无限孤立而等待,在操作系统不支持情况下可以用引用计数加写优先等待来用互斥锁实现。 读写锁适用于大量读少量写的环境,但由于其特殊的逻辑使得其效率相对普通的互斥锁自旋锁要慢一个数量级;值得注意的一点是按POSIX标准线程申请读锁并未释放前本线程申请写锁是成功的,但运行后的逻辑结果是无法预测`

  • 递归锁(recursivelock):
    严格上讲递归锁只是互斥锁的一个特例,同样只能有一个线程访问该对象,但允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作; windows下的临界区默认是支持递归锁的,而linux下的互斥量则需要设置参数PTHREAD_MUTEX_RECURSIVE_NP,默认则是不支持

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 谁忘记了谁 谁又记得谁 多年以后 你是否会想起 我知道 我会一直记着 记着 你的名字 记着 你带来的故事 去年夏天...
    木zi小生阅读 355评论 20 14
  • 校园的天空依旧还是那么蓝,我抱着书本,从图书馆慢悠悠走回寝室。我抬头看天,看云,我歪着脑袋看树,就是不想看路。迎面...
    心语年华阅读 418评论 0 0
  • Leangoo是国内我用过最好用 最优秀的项目协作工具及项目管理软件。 它由中国最权威的Scrum中文网研发,融入...
    _晚晚__阅读 480评论 0 2
  • 今天听了一篇关于理性认知的文章,里面谈到的理性三要素十分受用,分别是:有知识,无偏见,合乎逻辑。 其中,有...
    自由行走的卡儿妈阅读 402评论 2 0
  • 时间如同飞驰空中的青铜箭矢,又似轰隆向前的单行列车,转眼间,2016就这样在指缝间悄然划过,无声的和我们道出再见。...
    木四谛阅读 460评论 0 1