操作系统面经

0.哲库一面:什么是硬链接和软链接?

	软链接(ln -s):就相当于windows的快捷方式,给文件创建一个快速的访问路径,它依赖于原文件;

	硬链接(ln):硬链接的作用就是允许一个文件拥有多个有效路径名,这样用户就可以建立硬链接到重要文件,以防止"误删"功能;

1.简单说下你对并行和并发的理解?

	1.并发:指两个或多个事件在同一时间间隔内发生,这些事件在宏观上是同时发生的,微观上是交替发生的。
	CPU可以从一个进程切换到另外一个进程,在切换前必须要记录当前进程中运行的状态信息,以备下次切换回来的时候可以恢复执行。

	2.并行:指两个或多个事件在同一时刻同时发生。

2.进程、线程、协程的基本概念?(项目2.24)

1.进程:进程是系统进行资源分配和调度的一个独立单位,是系统中的并发执行单位;
2.线程:线程是进程的一个实体,也是cpu调度和分配的最小单位,它是比进程更小的能独立运行的基本单位,也称之为"轻量级线程"3.协程:协程是微线程,在子程序内部执行,可在子程序内部中断,转而执行别的子程序,在适当的时候再返回来接着执行。

项目2.35协程是轻量级线程,轻量级表现在哪里?

1.协程调用与切换比线程效率高。协程执行效率高,协程不需要多线程的锁机制,可以不加锁的访问全局变量,所以上下文的切换非常快。
2.协程占用内存少。执行协程只需要极少的栈内存(大概是4~5KB),而默认情况下,线程栈的大小为1MB。
3.切换开销更少。协程直接操作栈基本没有内核切换的开销,所以切换开销比线程少。

3.进程与线程、线程与协程的区别?(项目2.24)

首先写一些帮助理解的东西:
	线程自己不拥有资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源;
	引入线程后,进程的内涵发生了改变,进程只作为除CPU外的系统资源的分配单元,而线程则作为处理机的分配单元。由于一个进程内部有多个线程,若线程的切换发生
在同一个进程内部,则只需要很少的时间开销。

进程与线程的区别:
1.调度:线程是独立调度的基本单位,而线程切换的代价远低于进程。在同一个进程中,线程的切换不会引起进程的切换;在不同的进程中,线程的切换会引起进程的切换
2.并发性:不仅进程间可以并发执行,线程之间也能并发进行;
3.拥有资源:进程是系统中拥有资源的最小单位,而线程中不拥有资源;要知道,若线程也是拥有资源的单位,则切换线程就需要较大的时空开销,线程也就没有什么意义了!
4.独立性:每个进程都拥有独立的地址空间和资源,除了共享全局变量,不允许其他进程访问;同一进程中的不同线程可以共享系进程的地址空间和资源;
5.系统开销:在创建或撤销进程时,系统都要为之分配或回收进程控制块pcb以及其他资源,如内存空间、I/O设备等。但是线程切换时,只需要保存和设置少量的寄存器内
容,开销比较小。
------------------------------------------------------------------------

线程与协程的区别:
1.协程执行效率高。协程直接操作栈,基本没有内核切换的开销,所以上下文的切换非常快,切换开销比线程更小。
2.协程不需要多线程的锁机制,因为多个协程从属于同一个线程,不存在同时写变量冲突,效率比线程高。
3.一个线程可以有多个协程。

Note:进程与线程比,为什么慢?(项目2.51)

1.进程系统开销显著大于线程开销;线程需要更少的资源;
2.进程切换开销比线程大;
3.进程通信比线程通信开销大。进程通信需要借助管道、队列、共享内存,需要额外申请内存空间,通信繁琐;而线程共享进程的内存,如代码段、数据段、扩展段,通信快
捷简单,同步开销更小。

4.为什么有了进程还需要引入线程呢?(项目2.39)

进程可以使多个程序并发执行,以提高资源的利用率和系统的吞吐量,但是同时也带来了一些缺点:
	1.进程在同一时间只能干一件事情
	2.进程在执行的过程中如果阻塞,整个线程就会被挂起,即使进程中有些工作不依赖于等待的资源,仍然不会执行。
基于以上的缺点,操作系统引入了比进程粒度更小的线程,作为cpu调度和分派的基本单位,线程仅含有少量资源,切换时开销很小,因此减少了程序在并发执行时所付出的
时间和空间开销,提高并发性能。

5.说说进程控制块(PCB)?

	1.PCB是进程存在的唯一标识,这意味着一个进程的存在,必然会有一个PCB,如果进程消失了,那么PCB也会随之消失;

	2.PCB都包含了哪些信息呢?
		a.进程描述信息:进程标识符、用户标识符;
		b.进程当前状态、进程优先级;
		c.有关内存地址空间或虚拟地址空间的信息;
		d.CPU的状态信息,CPU的状态信息都是保存在相应的PCB中,以便进程重新执行时,能从断点处继续执行;

6.进程的状态转换?(项目2.46+2.30)

首先进程的状态有5:创建态、**就绪态、运行态、阻塞态**、结束态
而我们主要关注的是:就绪态、运行态、阻塞态
	就绪态:CPU×  外设√
	运行态:CPU√  外设√
	阻塞态:CPU×  外设×
1.就绪态---->运行态:处于就绪队列的线程被调度后,进程获得处理机的资源;
2.运行态---->就绪态:a:时间片到,让出处理机的资源;b:在抢占式的操作系统中,cpu被更具有优先级的进程抢到;
3.运行态---->阻塞态:等待状态,是主动发生的;
4.阻塞态---->就绪态:获得相应的设备资源,但是仍旧缺少cpu的资源;

7.进程间通信方式有哪些?

	A.管道(半双工通信,通信效率比较低);
		mkfifo myPipe	//创建有名管道;
		echo "hello" > myPipe	//将数据写进管道
		cat < myPipe		//读取管道里的数据

	B.消息队列(消息队列是保存在内核中的消息链表)
		优势:比管道的通信效率高;
		缺陷:1.消息队列不适合比较大数据的传输(因为在内核中每个消息体都有一个最大长度的限制);
		     2.消息队列通信过程中,存在用户态和内核态之间的数据拷贝开销;因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同
理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程;

	共享内存;
	信号量;
	信号;
	Socket(跨网络与不同主机上的进程之间通信,就需要 Socket 通信了);

8.进程间的调度算法有哪些?(项目9)

	1.先来先服务调度算法:每次从就绪队列中选择最先进入该队列的进程,非抢占式的不可剥夺算法,对长作业比较有利,但对短作业不利。
	
	2.短作业优先调度算法:从就绪队列中选择一个或若干运行时间最短的作业,将他们调入内存运行;
	
	3.高优先级优先调度算法:当用于作业调度时,系统将从后备队列中选择若干个优先权最高的作业调入内存。当用于进程调度的时候,该算法是把处理机分配给就绪队
列中优先权最高的进程。

	4.时间片轮转法:每次调度时,把CPU分配给队首进程,并令其执行一个时间片,时间片的大小从几ms到几百ms。当执行的时间片用完时,由一个计时器发出时钟中断请
求,将正在运行的进程移至就绪队列的队尾,然后再把处理机分配给就绪队列中的新的队首进程,同时也让它执行一个时间片。

	5.高响应比优先调度算法:同时兼顾一个进程的等待时间和要求服务时间;
	6.多级反馈队列调度算法:时间片轮转调度算法+多级优先队列优先调度算法;

9.CPU的上下文切换?

	1.各个进程之间是共享CPU资源的,在不同的时候进程之间需要切换,让不同的进程可以在CPU中执行,那么一个进程切换到另一个进程运行,就是进程的上下文切换;
	
	2.看进程的上下文切换之前,先来看看CPU的上下文切换?
	任务是交给 CPU 运行的,那么在每个任务运行前,CPU需要知道任务从哪里加载,又从哪里开始运行;所以,操作系统需要事先帮CPU设置好CPU寄存器和程序计数器;
	CPU的寄存器是CPU内部一个容量小,但是速度极快的内存(缓存);
	CPU的程序计数器则是用来存储CPU正在执行的指令位置、或者即将执行的下一条指令的位置;
	所以说,CPU寄存器和程序计数器是CPU运行任何任务之前,所必须依赖的环境,这些环境就叫做CPU上下文;
	CPU上下文切换就是先把前一个任务的CPU上下文(CPU寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后跳转到程序计数器所
指的新位置,运行新任务。

10.进程的上下文切换?
在这里插入图片描述

	1.进程是由内核管理和调度的,所以进程的切换只能发生在内核态;那就意味着进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内
核堆栈、寄存器等内核空间的资源;

	2.会把切换所需的信息保存在进程的PCB,当要运行另外一个进程的时候,我们需要从这个进程的PCB取出上下文,然后恢复到CPU中,这使得这个进程可以继续执行;

	3.进程的上下文开销是很关键的,我们希望它的开销越小越好,这样可以使得进程可以把更多时间花费在执行程序上,而不是耗费在上下文切换;

11.线程的上下文切换?

	1.当两个线程不是属于同一个进程,则切换的过程就跟进程上下文切换一样;
	2.当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据;

16.什么是缓冲区溢出,有什么危害?

	缓冲区为暂时置放输入输出资料的内存。
	缓冲区溢出是指当计算机向缓冲区填充数据时超过了缓冲区本身的容量,溢出的数据覆盖在合法数据上。
	造成缓冲区溢出的原因:程序中没有仔细检查用户输入是否合理。
	危害:a.程序崩溃导致拒绝服务
		 b.跳转并执行一段恶意的代码

17.分页存储管理和分段存储管理相关?

---------------------------------分页存储管理---------------------------
	页面:进程中的块称为页或者页面;在Linux下,每一页的大小为4KB;
	页框:内存中的块称为页框或页帧;
	分页存储管理的逻辑地址结构:32,0-11表示业内偏移量,12-31表示页号;
	页表:为了方便找到每个页面对应的物理块,系统为每个进程建立一张页表,它记录页面在内存中对应的物理块号,页表一般存放在内存中;
	如何实现地址的变换:
		a.根据逻辑地址结构计算出页号、页内偏移量;
		b.判断页号是否越界;
		c.查询页表,看页号对应的页表项,通过页号找到对应的物理块号;
		d.用内存块号和页内偏移量得到物理地址;
		e.访问目标内存单元;
	单级页表存在的问题:
		a.页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框;
		b.没必要让整个页表常驻内存,因为进程在一段时间内可能只需要访问某几个特定的页面,因此可以将长长的页表进行分组,使每个内存块刚好可以放入一个分
组;
		c.另外要为离散分配的页表再建立一张页表,称目录页表或外层页表	
		d.!!!!!页面会产生内部碎片;
		
---------------------------------分段存储管理---------------------------
	分段:段式管理方式按照用户进程中自然段划分逻辑空间,段内要求连续,段间不要求连续;
	分段存储管理的逻辑地址结构:32,0-15表示段内偏移量,16-31表示段号;
	段表: 段号--段长--本段在内存中的始址,段表是由很多段表项组成的,各个段表项的长度是相同的,段号是隐藏的,不占存储空间;

	问题:
		a:分段存储会有内存碎片的问题(会产生外部碎片,不会产生内部碎片);
		b(Swap机制+内存交换效率低下):(1)解决内存碎片的问题就是内存交换,在Linux中也就是我们常见的Swap空间。可以把音乐程序占用的那256MB内存写到硬盘
上,然后再从硬盘上读回来到内存里。不过再读回的时候,我们不能装载回原来的位置,而是紧紧跟着那已经占用了的512M的内存后面,这样就能空缺出连续的256M空间。
			(2)那为什么说Swap的效率比较低呢?对于多进程的系统来说,用分段的方式,外部内存碎片式很容易产生的,产生了外部内存碎片,那不得不Swap内存区域,这
个过程会产生性能瓶颈。因为硬盘的访问速度比内存慢太多了,每一次内存交换,我们都需要把一大段连续的内存数据写到硬盘上。所以如果内存交换的时候,交换的是一
个占内存空间很大的程序,这样整个机器就会显得卡顿。
	
-------------------------------分页和分段的区别-------------------------
	a.页是信息的物理单位,分页的主要目的是为了实现离散分配,提高内存利用率,分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的;
	b.段是信息的逻辑单位,分段的主要目的是更好地满足用户要求,一个段通常包含着一组属于一个逻辑模块地信息,分段对用户是可见地,用户编程时需要显示地给出段
名;
	c.页的大小固定且由系统决定,段的长度却不固定,决定于用户编写的程序;

项目11.简述linux的系统态与用户态,什么时候会进入系统态?(项目2.16)

1.内核态与用户态:操作系统的两种运行级别内核态拥有最高权限,可以访问所有系统指令:用户态只能访问一部分指令;
2.什么时候进入内核态:共三种方式:a、系统调用;b、异常;c、设备中断。其中,系统是主动的,另外两种是被动的。
3.为什么区分内核态与用户态:在CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃。比如:清内存、设置时钟等。所以区分内核态与用户态主要
是出于安全的考虑。

项目2.12.简述一下LRU算法

1.LRU算法用于缓存淘汰,思路使将缓存中最近最少使用的对象删除掉。
2.实现方式:哈希表+双向链表    
	list<pair<int, int>> cache;
    unordered_map<int, list<pair<int, int>>::iterator> map;
    当需要插入新的数据项的时候,首先判断缓存是否已满,如果未满的话,直接在链表的头部插入新数据项即可;如果缓存已满,则首先把链表尾部的数据项删除掉,并且
删掉哈希表对应的key,然后将数据项插在链表的头部,并且重新建立新的key用map记录;
	当访问数据项的时候,如果数据项在链表中存在,则把该节点移至链表的首部,否则返回-1,这样链表的尾部节点就是最近最久未访问过的数据项。
class LRUCache {
public:
    LRUCache(int capacity) {
        this->cap = capacity;
    }

    int get(int key) {
        if (map.find(key) == map.end()) return -1;

        auto key_value = *map[key];     //这里一定要预先记录map[key]对应的值;因为map[key]记录的是迭代器,
        cache.erase(map[key]);
        cache.push_front(key_value);
        map[key] = cache.begin();
        return key_value.second;
    } 

    void put(int key, int value) {
        if (map.find(key) == map.end()) {
            if (cache.size() == cap) {
                map.erase(cache.back().first);
                cache.pop_back();
            }
            cache.push_front({key, value});
            map[key] = cache.begin();
        }
        else {
            cache.erase(map[key]);
            cache.push_front({key, value});
            map[key] = cache.begin();
        }
    }
private:
    int cap;
    list<pair<int, int>> cache;
    unordered_map<int, list<pair<int, int>>::iterator> map;
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

项目2.13.一个线程占多大内存?

	指令: ulimit -a;
	在Linux系统中,一个线程占8MB的内存大小。

项目2.17.简述一下虚拟内存和物理内存,为什么要用虚拟内存?好处是什么?

	1.物理内存:物理内存有4个层次,分别是寄存器、高速缓存、主存、磁盘;
		寄存器:速度最快、量少、价格贵;
		高速缓存:次之;
		主存:再次之;
		磁盘:速度最慢、最多、价格便宜;
	操作系统会对物理内存进行管理,有一个部分称之为内存管理器,它的主要工作是有效的管理内存,记录哪些内存是正在使用的,在进程需要时分配内存以及在进程完成
时回收内存。
------------------------------------------------------------------------
	2.虚拟内存:操作系统为每一个进程分配一个独立的地址空间,但是是虚拟内存。虚拟内存和物理内存存在映射关系,通过页表寻址完成虚拟地址和物理地址的转换。
	
	虚拟存储器的定义和特征:基于局部性原理,在程序装入时,仅须将程序当前运行的少数页面或段先装入内存,而将其余部分暂留在外存,便可启动程序。在程序执行过
程中,当访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂不使用的内存换出到外存上,从而腾出空间存放
将要调入内存的信息。这样,操作系统好像为用户提供了一个比实际内存容量大得多的存储器。
	
	3.为什么要引入虚拟内存?
		多道程序并发执行的时候不仅使进程之间共享了处理器,而且同时共享了主存。然而,随着对处理器需求的增长,进程的执行速度会以某种合理的方式慢下来。但
是,若同时运行的进程太多,则需要很多的内存,当一个程序没有内存空间可用时,那么它甚至无法运行。所以,在物理上扩展内存相对有限的条件下,应尝试以一些其他可行
的方式在逻辑上扩充内存。

	4.虚拟内存技术的实现:
		a.若访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,因此操作系统要提供请求调页(或请求调段)功能;
		b.若内存空间不够,由操作系统负责将内存中暂时使用不到的信息换出到外存,因此操作系统要提供页面置换(或段置换)功能;

	5.简单说说请求分页管理方式吧:页表机制+缺页中断;

	6.页面置换算法:进程运行时,若其访问的页面不在内存中而需要将其调入,但内存已经没有空闲空间时,就需要从内存中调出一页程序或数据,送入磁盘的对换区。
		a.最佳(OPT)置换算法:淘汰的是将来最长时间内不再访问的页面;
		b.先进先出(FIFO)置换算法:淘汰在内存中驻留时间最久的页面;
		c.最近最久未使用(LRU)置换算法:选择最近最长时间未访问过的页面予以淘汰
		d.时钟(CLOCK)置换算法:为每个页面设置一个访问位,当页面首次被装入或者访问时,其访问位被置为1,在选择一页淘汰时,只需检查页的访问位,若为0,就选择
该页换出,若为1,则将它置为0,暂不换出,给予该页第二次驻留页面的机会,再依次顺序检查下一个页面。

	7.什么是抖动?
		页面置换过程中,一种糟糕的情形是,刚刚换出的页面马上又要换入主存,刚刚换入的页面马上又要换出主存,这种频繁的页面调度行为称为抖动或者颠簸。
	
	7.1造成抖动的原因?
		系统中同时运行的进程太多,由此分配给每个进程的物理块太少,不能满足进程正常运行的基本要求,致使每个进程在运行时频繁地出现调页,必须请求系统将所缺
页面调入内存,这样会使每个进程花费大量地时间用于页面地换入/换出,几乎不再去做任何有效地工作。

	7.2如何解决抖动?
		驻留集:请求分页存储管理中给进程分配的内存块的集合;
		工作集:某段时间间隔内,进程要访问的全部页面的集合;
		当分配给进程的物理块小于工作集的大小,则该进程就有可能频繁地缺页,所以为了防止这种抖动现象,一般来说分配给进程的物理块数(即驻留集大小)要大于工
作集的大小;

项目2.18什么是逻辑地址与物理地址,逻辑地址到物理地址是怎么映射的?

	逻辑地址:程序编译后,每个目标模块都要从0号单元开始编址,这称为该目标模块的相对地址(逻辑地址)

	物理地址:在内存中实际的地址,它是地址转换的最终地址,进程在运行时执行指令和访问数据,最后都要通过物理地址从主存中存取。

	操作系统引入了虚拟内存,进程持有的虚拟地址会通过CPU芯片中的内存管理单元(MMU)的映射关系,来转换成物理地址,然后再通过物理地址访问内存。
------------------------------------映射--------------------------------
进程在内存中的分布一般是非连续分配管理方式。

1.进程中的块成为页或者页面;内存中的块成为页框,进程在执行时需要申请主存空间,即要为每个页面分配主存中的可用页框,那么就产生了页和页框的一一对应;
2.进程中的逻辑地址结构:32位表示,0-11是页内偏移量,12-31是页号;
3.为了便于在内存中找到进程的每个页面所对应的物理块,系统为每个进程建立一张页表,页表是由页表项组成的,每个页面表记录了对应页号的块号,页表一般也存放在
内存中。

总的来说,如何实现地址的转换?
1.先根据逻辑地址结构算出进程的页号;
2.找到页号以后,我们根据页表找到页号对应的内存中的块号;
3.再有逻辑地址结构读出业内偏移量;
4.实际的物理地址=块号的起始地址+业内偏移量;

以二级页表为例:
	因为页表项也需要占不少内存中的连续页框,为解决这个问题,我们引入了二级页表。二级页表的逻辑地址结构32:
	0-11:业内偏移量;12-21:二级页号;22-31:一级页号;
	a.按照地址结构将逻辑地址拆分成三部分;
	b.从pcb中读书目录页表起始地址,根据一级页号查找页目录表,找到下一级页表在内存中的存放位置;
	c.根据二级页号查找页表,找到最终想访问的内存块号;
	d.结合页内偏移量得到最终的物理地址;

项目2.21.说说进程空间从低位到高位都有些什么?
在这里插入图片描述

用户空间(3G):存放在地位
1.代码段.text:存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量;
2.数据段.data:存放程序中已初始化的全局变量和静态变量的一块内存区域;
3.BSS段.bss:存放程序中未初始化的全局变量和静态变量的一块内存区域;
4.可执行程序在运行时又会多出两个区域:堆区和栈区。
	a.堆区:动态申请内存用,堆从低地址向高地址增长;
	b.栈区:存储局部变量、函数参数值。栈从高地址向低地址增长,是一块连续的空间;
5.还有一个共享区:对于堆栈之间;
内核空间(1G):存放在高位
	主要存放进程的相关信息(PCB)、内存管理等相关信息。

32位系统能访问4GB以上的内存吗?

正常情况下是不可以的。因为计算机中采用二进制,每位数只有01两个状态,32位正好是232次方,正好是4G,所以大于4G就没办法表示了,而在32位系统中,因其它原因
还需要占用一部分空间,所以内存只能识别3G多。要使用4G以上的内存就只能换64位的操作系统了。

使用PAE技术就可以实现32位系统能访问4GB以上的内存。

项目2.25.简述Linux中的fork()函数的作用?

1.fork()函数用来创建一个子进程。对于父进程,fork()函数返回新创建的子进程的PID。对于子进程,fork()函数调用成功会返回0.如果创建出错,fork()函数返回-12.fork()函数失败的原因:a.当前系统的进程数已经达到了系统规定的上限;b.系统内存不足;
3.实际上更准确来说,Linux中fork()通过写时拷贝实现。写时拷贝是一种可以推迟甚至可以避免拷贝数据的技术。
  内核此时并不复制整个进程的地址空间,而是让父子进程共享一个地址空间。只用在需要写入时才会复制地址空间,从而使每个进程都有自己的地址空间。(一定要注
 意此时所说的空间是虚拟地址空间,虚拟地址空间一样,但是虚拟地址空间所映射到的实际物理内存是不一样的)。也就是说资源的复制是在需要写入时才会进行,在此之
 前,只有以只读方式共享。

项目2.26:请你说说什么是孤儿进程,什么是僵尸进程,如何解决僵尸进程?

1.孤儿进程:是指一个父进程退出后,而它的一个或者多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并且由init进程
对它们的完整状态进行收集工作。
2.僵尸进程:每个进程结束后,都会释放自己地址空间中的用户去的数据,内核区PCB没有办法自己释放掉,需要父进程去释放。
  进程终止时,父进程尚未回收子进程在内核区的资源,此时子进程变为僵尸进程。
  僵尸进程不能被kill -9杀死。
  这样会导致一个问题,如果父进程不调用wait()waitpid()的话,那么保留的那段信息信息就不会被释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限
的,如果有大量的僵尸进程,将没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应该避免。
3.一般为了防止产生僵尸进程,在fork子进程之后我们都要及时使用wait系统调用;同时,当子进程退出的时候,内核都会给父进程一个SIGCHLD信号,所以我们可以建立一个
捕获SIGCHLD信号的信号处理函数,在函数体中调用wait,就可以清理退出的子进程以达到防止僵尸进程的目的。

项目2.27:什么是守护进程?如何实现(不是很熟悉)?

1.守护进程:守护进程是运行在后台的一种生存期较长的特殊进程。它独立于控制终端,处理一些系统级别的任务。
2.如何实现?
	a.创建子进程,终止父进程。方法是调用fork()产生一个子进程,然后使父进程退出;
	b.子进程调用setsid()创建一个新的会话。目的是让该进程脱离控制终端,因为守护进程是不拥有控制终端的。
	c.设置掩码->清除进程的umask以确保守护进程创建文件和目录时拥有所需要的权限。
	d.更改工作目录,将当前目录更改为根目录。
	e.重定向文件描述符。

项目2.28:说说进程同步的方式有哪些?

1.首先来说说什么是进程同步。 所谓进程同步就是当有一个进程在对内存进行操作时,其它进程都不可以对这个内存地址进行操作,直到该进程完成操作,其它进程才能
对该内存地址进行操作,而其它进程则处于等待状态。

2.同步方式:
	a.信号量:是一个计数器,可以用来控制多个进程对共享资源的访问,信号量用于实现进程间的互斥与同步。P操作(递减操作)可以用于阻塞一个进程,V操作(增加操
作)可以用于解除阻塞一个进程。
	b.管程:管程的特性保证了进程互斥,无须程序员自己实现互斥,从而降低了死锁的可能性,同时管程提供了条件变量,可以让程序员灵活地的实现进程同步。
	
	管程实现:利用共享数据结构抽象表示系统中的共享资源,而把对数据结构实施操作的过程定义为一组过程,进程对共享资源的申请、释放等操作,都通过这组过程
来实现,这组过程还可以根据资源情况,或接受或阻塞进程的访问,确保每次只有一个进程使用共享资源,就能实现进程互斥。
	管程其实和类class差不多,管程把对共享资源的操作封装起来,每次仅允许一个进程进入管程,从而实现进程互斥。

项目2.32:进程通信中的管道实现原理是什么?

1.实现原理:操作系统在内核中开辟一块缓冲区(称为管道)用于通信。管道是一种两个进程间进行单向通信的机制。因为这种单向性,管道又称为半双工管道,所以其使用
还是有一定的局限性。半双工是指数据只能由一个进程流向另一个进程(一个管道负责读,一个管道负责写);如果是全双工通信,需要建立两个管道。 同时管道又分为匿
名管道和有名管道,无名管道只能用于具有亲缘关系的进程之间的通信(父子进程或者兄弟进程),可以看作一种特殊的文件,管道本质是一种文件;命名管道可以允许无亲缘
关系进程间的通信。
	#include <unistd.h>   int pipe(int fd[2]); 
2.pipe()函数创建的管道处于一个进程中间,因此一个进程在由pipe()创建管道后,一般再使用fork()建立一个子进程,然后通过管道实现父子进程间的通信。管道两端可
分别用描述字fd[0]以及fd[1]来描述。注意管道两端的任务是固定的,即一端只能用于读,由描述字fd[0]表示,称为管道读端;另一端只能用于写,由描述字fd[1]来表示,
称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将发生错误。一般文件的I/O函数都可以用于管道。如close()read()write()等。
3.具体步骤:
	a.父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端
	b.父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
	c.父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了
进程间通信。

项目2.33:简述mmap的原理和使用场景?

在这里插入图片描述

mmap是一种把文件映射到内存中的方法。即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系。实
现这样的映射关系后,进程就可以采用指针的方式读写操作这一块内存,而系统会自动回写数据到对应的文件磁盘上,即完成了对文件的操作而不必调用read、write等系统
调用函数。可以利用此技术完成进程间通信(没关系的进程+有关系的进程)。

使用场景:
	1.对同一块区域频繁读写操作;
	2.可用于实现用户空间和内核空间的高效交互;
	3.可提供进程间共享内存已经相互通信;
	4.可实现高效的大规模数据传输;

补充:说说什么是共享内存?如何实现?

1.什么是共享内存?
	共享内存就是拿出一块虚拟地址空间来映射到相同的物理内存中去,这样这个进程写入的东西另一个进程马上就看到了,都不需要拷贝来拷贝去,大大提高了进程通
信的效率;

2.如何实现?
	调用相关函数创建一个指定大小的内存,可以获取内存的编号,将该编号与相关的进程关联即可;

3.共享内存和内存映射的区别?
	a.共享内存可以直接创建,内存映射依赖磁盘文件;
	b.共享内存执行的效率更高;
	c.共享内存中所有的进程操作的是同一块物理内存,内存映射中每个进程在自己的虚拟地址空间中有一个独立的内存;
	d.数据安全:运行进程的电脑死机了,共享内存中的数据消失了;内存映射中的数据由于磁盘文件还在,所以内存映射中的数据仍然存在;
	e.生命周期:进程退出,共享内存还在(共享内存删除的条件是:首先要标记删除,其次是与共享内存相关联的进程数为0,进程退出的话,该进程与共享内存会自
动取消关联);进程退出,内存映射消失;
	

项目2.34:互斥量能不能在进程中使用?

!!
不同的进程之间,存在资源竞争或并发使用的问题,所以需要互斥量。

进程中也需要互斥量,因为一个进程中可以包含多个线程,线程与线程之间需要通过互斥的手段进行同步,避免导致共享数据修改引起冲突。可以使用互斥锁,属于互斥量的
一种。

项目2.35协程是轻量级线程,轻量级表现在哪里?(见上面!)

项目2.36说说线程间通信的方式有哪些?+项目2.37线程同步的方式的有哪些?

线程间的通信方式包括:临界区、互斥量、信号量、条件变量、读写锁;

1.临界区:每个线程中访问临界资源的代码段成为临界区(临界资源是一次仅允许一个线程使用的公共资源)。每次只准允许一个线程进入临界区,进入后不允许其它线程进
入。
2.互斥量:采用互斥对象机制,只有拥有互斥对象的线程才可以访问。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
3.信号量:计数器,允许多个线程同时访问同一个资源。
4.条件变量:通过条件变量通知操作的方式来保持线程同步。
5.读写锁:读写锁与互斥量类似。但互斥量要么是锁住状态,要么就是不加锁状态。读写锁一次只允许一个线程写,但允许多个线程读,这样效率就比互斥锁要高。0

项目2.38说说什么是死锁,产生的条件,如何解决?

1.死锁:是指多个进程在执行过程中,因争夺资源而造成了互相等待,导致各进程都阻塞,无法向前推进的现象。比如:两只羊过独木桥,若两只羊互不相让均在等待对方让
步,争着过桥,就产生了死锁。

总体来说就是对不可剥夺的资源的不合理分配就会产生死锁。

2.产生的条件:
	a.互斥条件:进程对所分配的资源不允许其它进程访问,若其它进程访问就只能等待,直到进程使用完成后释放资源。
	b.不剥夺条件:进程已经获得的资源,只能自己释放,不可剥夺。
	c.请求保持条件:进程获得一定的资源后,又对其它资源发出请求,但该资源被其它进程所占有,那么请求就会阻塞,而且该进程不会释放自己所持有的资源。
	d.循环等待条件:若干进程之间形成一种头尾相接的循环等待的条件。

3.死锁预防?既然死锁产生有4个必要条件,防止死锁的发生只需破坏其中任意一个即可。
	a.破坏互斥条件:但是系统中有些资源确实是不能被其它进程同时使用,所以不太现实。
	b.破坏不可剥夺条件:当进程的新资源未得到满足时,自己所持有的资源可被其它进程强行剥夺。
	c.破坏请求保持条件:进程一次性获得运行所需要的全部资源。
	d.破坏循环等待条件:资源有序分配。

4.死锁避免?:避免死锁同样属于事先预防策略,但并不是事先采取某种限制措施破坏死锁的必要条件,而是在资源动态分配过程中,防止系统进入不安全状态。
	a.系统安全状态:避免死锁的方法中,允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配的安全性。所谓安全序列就是指如果系统按照这种序
列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态,当然安全序列可能有多个。
	b.银行家算法:把操作系统视为银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。操作系统按照银行家
制定的规则为进程分配资源。
	进程运行之前先声明对各种资源的最大需求量,当进程在执行中继续申请资源时,先测试该进程已占用的资源与本次申请的资源之和是否超过该进程声明的最大需求
量。若超过则拒绝分配资源,若未超过则再测试系统现在的资源能否满足该进程尚需的最大需求量,若能满足则按当前的申请资源分配,否则也需要推迟。
	
5.死锁的检测和解除?
	a.检测:看某些资源分配图能不能消去所有的边;
	b.解除:.资源剥夺法:挂起某些死锁进程,并抢占它的资源,将这些资源分配给其它的死锁进程。但应该防止被挂起的进程长时间得不到资源而处于资源匮乏的状态。
		Ⅱ.撤销进程法:强制撤销部分甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。
		Ⅲ.进程回退法:让一(或多)个进程回退到足以回避死锁的地步,进程回退时资源释放资源而非被剥夺。

项目2.41单核机器上写多线程程序,是否要考虑加锁,为什么?

仍然需要。

原因:因为线程锁通常用来实现线程的同步和通信。在单核机器上的多线程程序,仍然存在线程同步的问题。因为在抢占式操作系统中,通常为每个线程分配一个时间片,当
某个时间片耗尽时,操作系统会将其挂起,然后运行另外一个线程。如果这两个线程共享某些数据,不使用线程锁的前提下,可能会导致共享数据被修改而引起冲突。

项目2.42说说多线程和多进程的不同?

1.一个线程从属于一个进程,一个进程可以包含多个进程。
2.一个线程挂掉,对应的进程挂掉,多线程也挂掉;一个进程挂掉,不会影响其它进程,多进程稳定。
3.进程系统开销显著大于线程开销;线程需要的资源更少。
4.多个进程在执行时拥有各自独立的内存单元,多个线程共享进程的内存,如代码段、数据段、扩展段;但每个线程拥有自己的栈段和寄存器组。
5.通信方式不一样。
6.多进程适应于多核、多机分布;多线程适用于多核。

项目2.43简述互斥锁的机制,互斥锁与读写锁的区别?

1.互斥锁机制:mutex,用于保证在任何时刻,都只有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。
2.互斥锁和读写锁:
	a.读写锁区分读者和写者,而互斥锁不分。
	b.互斥锁同一时间只允许一个线程访问对象,无论读写;读写锁同一时间内只允许一个写者,但是允许多个读者同时读对象。

项目2.44说说什么是信号量,有什么用?

1.概念:信号量本质是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据;
	
2.信号量表示的资源的数量,控制信号量的方式有两种原子操作:
	A.P操作:这个操作会把信号量减1,相减后如果信号量小于0,则表示资源已被占用,进程需要阻塞等待;相减后如果信号量大于等于0,则表示还有资源可用,进
程可正常继续执行;
	B.V操作:这个操作会把信号量加1,相加后如果信号量小于等于0,表明当前有阻塞中的进程,于是会将该进程唤醒;相加后如果信号量大于0,表明当前进程没有
阻塞中的进程;

项目2.45说说自旋锁和互斥锁,并说说二者的使用场景?

1.自旋锁和互斥锁的概念:
	自旋锁与互斥锁比较类似,都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个持有者,即只能有一个执行单元获得
锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,而是一直循环在那里看是否
该自旋锁的持有者已经释放了锁,"自旋"一词就是因此而得名。锁一旦被释放,就会被等待的线程立即获取,而不需要经过唤醒和上下文切换。

2.使用场景:
	互斥锁用于临界区持锁时间比较长的操作:
		a.临界区有IO操作
		b.临界区代码复杂或者循环量大
		c.临界区竞争非常激烈
		d.单核处理器
	自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下。

项目2.45.1补充!!悲观锁和乐观锁?

	悲观锁:做事比较悲观,它认为多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,要先上锁;
	
	乐观锁:做事比较乐观,它假定冲突的概率很低;它是先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果有发现
其他线程已经修改过这个资源,就放弃本次操作;

项目2.46进程、线程的中断切换过程是怎么样的?

上下文切换指的是内核(操作系统的核心)在CPU上对进程或者线程进行切换。

1.进程上下文切换:
	a.保护被中断进程的处理器现场信息;
	b.修改被中断进程的进程控制块相关信息(PCB),如进程状态等;
	c.把被中断进程的进程控制块加入有关队列;
	d.选择下一个占有处理器运行的进程;
	e.根据被选中进程设置操作系统用到的地址转换和存储保护信息
		Ⅰ.切换页目录已使用新的地址空间;.切换内核栈和硬件上下文(包括分配的内存、数据段、堆栈段等);
	f.根据被选中进程恢复处理器现场;

2.线程上下文切换:
	a.保护被中断线程的处理器现场信息;
	b.修改被中断线程的线程控制块相关信息,如线程状态等;
	c..把被中断线程的进程控制块加入有关队列;
	d.选择下一个占有处理器运行的线程
	e.根据被选中线程设置操作系统用到的存储保护信息;.切换内核栈和硬件上下文(切换堆栈,以及各寄存器);
	f.根据被选中线程恢复处理器现场;

项目2.48多线程和单线程有什么区别,多线程编程要注意什么,多线程加锁需要注意什么?

1.区别:
	a.多线程从属于一个进程,单线程也从属于一个进程;一个线程挂掉都会导致从属的进程挂掉。
	b.一个进程里有多个线程,可以并发执行多个任务;一个进程里只有一个线程,就只能执行一个任务。
	c.多线程并发执行多任务,需要切换内核栈与硬件上下文,有切换的开销;单线程不需要切换,没有切换的开销。
	d.多线程并发执行多任务,需要考虑同步的问题;单线程不需要考虑同步的问题。
	
2.编程注意:多线程需要考虑同步的问题。线程间的同步方式包括互斥锁、信号量、条件变量、读写锁。

3.加锁注意:主要需要注意死锁的问题,破坏死锁的必要条件从而避免死锁。

项目2.49说说sleep和wait的区别?

1.sleep
	A.sleep是一个延时函数,让进程或线程进入休眠,休眠完毕后继续运行;
	B.在linux下面,sleep函数的参数是秒,而windows下面sleep的函数参数是毫秒。

2.wait
	A.wait是父进程回收子进程PCB资源的一个系统调用。
	B.进程一旦调用了wait函数,就立即阻塞自己本身,然后由wait函数自动分析当前进程的某个子进程是否已经退出,当找到一个已经变成僵尸的子进程,wait就会收
集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞,直到有一个出现为止。

3.区别:
	A.sleep是一个延时函数,让进程或线程进入休眠,休眠完毕后继续运行;
	B.wait是父进程回收子进程PCB资源的一个系统调用。

项目2.50 中断相关?

	1·先说说什么是中断吧?
	在计算机中,中断是系统用来响应硬件设备请求的一种机制,操作系统收到硬件的中断请求,会打断正在执行的过程,然后调用内核中的中断处理程序来响应请求。
	当前中断处理程序没有执行完之前,系统中其他的中断请求都无法被响应,也就说中断有可能会丢失,所以中断处理程序要短且快。(点外卖打电话的例子)

	2.硬盘的读写速度是非常慢的,当进程要从硬盘读取数据时,CPU 不需要阻塞等
待数据的返回,而是去执行另外的进程。当硬盘数据返回时,CPU 会收到个中断,于是 CPU 再继续运行这个进程。

补充一些自己面试冷门的东西

1.创建线程用什么API,如何销毁线程?

1.创建线程
	pthread_t tid;
	int ret = pthread_create(&tid, NULL, callback, NULL);

2.销毁线程
  detach分离:
    #include <pthread.h>
    int pthread_detach(pthread_t thread);
        - 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。
          1.不能多次分离,会产生不可预料的行为。
          2.不能去连接一个已经分离的线程(所谓连接就是回收子线程的资源),会报错。
        - 参数:需要分离的线程的ID
        - 返回值:
            成功:0
            失败:返回错误号

2.线程的切换需要切换到核心态吗?
在这里插入图片描述

	1.用户线程:在用户空间实现的线程,不是由内核管理的线程,是由用户态的线程库来完成线程的管理;
	用户级线程的整个线程的管理和调度,操作系统是不直接参与的,而是由用户线程库函数来完成线程的管理,包括线程的创建、终止、同步和调度等;
	
	2.内核级线程:在内核中实现的线程,是由内核管理的线程;
	内核线程是操作系统管理的,线程对应的TCB自然是放在操作系统里的,这样线程的创建、终止和管理都是由操作系统负责的;
	
	A.多对一模型:将多个用户级线程映射到一个内核级线程;
		优点:线程管理是在用户空间进行的,因而效率比较高;
		缺点:如果一个线程在访问内核时发生阻塞,则整个进程都会发生阻塞;在任何时刻,只有一个线程能访问内核,多个线程不能同时在多个处理机上运行;

	B.一对一模型:将每个用户级线程映射到一个内核级线程;
		优点:当一个线程被阻塞后,允许调度另一个线程运行,所以并发能力比较强;                   
		缺点:每创建一个用户线程,相应地就要创建一个内核线程,开销比较大;

	C.多对多模型:将n个用户级线程映射到m个内核级线程上,要求n>=m;
		特点:克服了多对一模型并发度不高的问题,又克服了一对一模型的一个用户进程占用太多内核级线程而开销太大的缺点。此外,还有上述两种模型的优点。
	

--------------------------------------涉及项目比较多------------------------------------------
补充2.50说说端口复用?

课上演示了一个小例子,当主动断开sever时,sever就会处于TIME_WAIT状态,要经过2MSL的时间才会主动释放连接资源;在这2MS的时间内想要重新连接sever就会报
bind Address already in use的错误,经过2MSL的时间后再重新连接即可;
server不能重新连接的原因在于:此时资源还未释放,端口号仍旧被占用,如果不想等待这2MSL的时间就需要用到端口复用;

端口复用最常用的用途是:
    1.防止服务器重启之前绑定的端口还未释放;
    2.程序突然退出而系统没有释放端口;

项目2.50说说线程池的设计思路,线程池中线程的数量由什么来确定?在这里插入图片描述
在这里插入图片描述

1.为什么要引入线程池?
	在刚接触完进程和线程后,如果有多个客户端访问服务器端,主线程或者主进程只负责监听是否有客户端连接,一旦有客户端连接进来,我们就创建一个子线程或者子进
程负责和客户端进行通信。
	但是创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作流程,消耗系统资源的时间
,可能导致系统资源不足。

2.线程池的设计思路?
	a.设置一个任务队列(双端队列list),该队列为临界资源,用来存储任务信息;
	b.设置保护队列的互斥锁;
	c.初始化n个线程;
	d.使用信号量来记录任务队列上任务的数量;
	e.当任务队列来了一个任务后,先对队列加锁,把任务挂到队列上,然后使用条件变量去通知阻塞中的一个线程来处理;
	主线程和所有子线程通过一个共享的工作队列来同步,子线程都睡眠在该工作队列上。当有新的任务到来时,主线程将任务添加到工作队列中,这将唤醒正在等待任
务的子线程,不过只有子线程将获得新任务的"接管权",它可以从工作队列中取出任务并执行之,而其他子线程将继续睡眠在工作队列上。

3.线程池中线程的数量是怎么确定的?
	线程池中的线程数量最直接的限制因素是CPU的处理器的数量N:如果你是4核的,对于CPU密集型的任务(如视频剪辑等消耗CPU计算资源的任务)来说,那线程池中的线
程数量最好也设置为4(或者+1防止其他因素造成的线程阻塞)。
	对于I/O密集型的任务,一般要多于CPU的核数,因为线程间竞争的不是CPU的计算资源而是IO,IO的处理一般比较慢,多余CPU核数的线程将为CPU争取更多的任务,不至
于在线程处理IO的过程中造成CPU空闲导致资源浪费。

4.线程池的核心线程和普通线程:
	若任务队列可以存放100个任务,此时为空,线程池里有10个核心线程;
	若突然来了10个任务,那么刚好10个核心线程直接处理;
	若突然来了90个任务,此时核心线程来不及处理,那么有80个任务先入队列,再创建核心线程处理任务;
	倘如核心线程一直占用,却有一些任务仍要执行,那么我们就需要再创建普通线程来执行任务;

5.线程池有哪些状态?这些状态是怎么切换的?
	1.RUNNING:
		状态说明:线程池处于RUNNING状态时,能够接收新的任务,以及对已添加的任务进行处理;
		状态切换:线程池的初始化状态是RUNNING,换句话说,线程池一旦被创建,就会处于RUNNING状态,并把线程池中的任务数为0;
	2.SHUTDOWN:
		状态说明:线程池处于SHUTDOWN状态时,不接收新的任务,但能够处理已添加的任务;
		状态切换:调用线程池的shutdown()接口时,线程池由RUNNING->SHUTDOWN
	3.STOP:
		状态说明:线程池处于STOP状态时,不接收新任务,不处理已添加的任务,并别会中断正在处理的任务;
		状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN)-->STOP;
	4.TIDYING:
		状态说明:当所有的任务已终止,信号量记录的任务数量为0,线程池会变为TIDYING状态。当线程池变为TIDYDING状态时,会执行terminated()函
数,terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING状态时进行相应的处理,就可以通过重载terminated()函数来实现;
		状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由SHUWDOWN->TYDING。当线程池在STOP状态下,线程池中执行的任务
为空时,就会有STOP-->TIDYING;
	5.TERMINATED:
		状态说明:线程池彻底终止,就变成TERMINATED状态;
		状态切换:线程池处在TIDYING状态时,执行terminated()之后,就会由TIDYING---->TERMINATED;
		

项目2.52简述Linux零拷贝原理?DMA技术之前
DMA技术

	1.为什么要有DMA技术?
	在没有DMA技术之前,如上图所示,整个数据的传输过程是需要CPU亲自参与搬运数据的过程,而且这个过程,CPU是不能做其他事情的;所以就发明了DMA技术。

	2.什么是DMA(直接内存访问)技术?
	在进行I/O设备和内存的数据传输的时候,数据搬运的工作全部交给DMA控制器,而CPU不再参与任何与数据搬运相关的事情,这样CPU就可以去处理别的事务;

	3.DMA技术如上图所示?
	a.用户进程调用read方法,向操作系统发出I/O请求, 请求读取数据到自己的内存缓冲区中,进程进入阻塞状态;                          
	b.操作系统收到请求后,进一步将I/O请求发送DMA,然后让CPU执行其他任务;
	c.DMA进一步将I/O请求发送给磁盘;
	d.磁盘收到DMA的I/O请求,把数据从磁盘读取到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被读满后,向DMA发起中断信号,告知自己缓冲区已满;
	e.DMA收到磁盘的信号,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,此时不占用CPU,CPU可以执行其他任务;
	f.当DMA读取了足够多的数据,就会发送中断信号给CPU;
	g.CPU收到DMA的信号,知道数据已经准备好,于是将数据从内核拷贝到用户空间,系统调用返回;
	Conclusion:其实整个过程也有CPU的参与,CPU虽然不负责数据的传输工作,但是CPU需要告诉DMA传输什么数据,从哪里传输到哪里去;
	

项目2.53 2.54 2.55说一说T/O多路复用技术以及各自的应用场景,优缺点?最高效的是什么?为什么?

为什么要使用IO多路复用?:服务更多的用户
	TCP socket调用流程是最简单、最基本的,他基本只能一对一通信,因为使用的是同步阻塞的方式,当服务端在还没处理完一个客户端的网路I/O时,或者读写操作
发生阻塞时,其他客户端是无法与服务端连接的;
	
	可是如果我们的服务器同时只能服务一个客户,那这样就太浪费服务器的资源了,于是我们需要改进这个网络I/O模型,以支持更多的客户端;

	改进一:多进程模型,为每个客户端分配一个进程来处理请求,主进程负责监听客户端的连接,一旦accept()后就会获得通信的文件描述符,然后调用fork()函数创建一
个子进程,子进程会复值父进程的通信文件描述符、内存地址空间、程序计数器、执行的代码等等,因此子进程就可以专门负责和客户端进行通信;
		进程太多容易产生僵尸进程,并且每个进程都会占据系统的资源,进程的上下文切换比较复杂,不仅包含虚拟内存、栈、全局变量等用户区的资源,还包括了内核
堆栈、寄存器等内核空间资源;
	
	改进二:多线程模型,既然多进程模型在上下文切换的时候比较复杂,我们就搞一个比较轻量级的模型来应对多用户请求-->多线程模型;因为线程在切换时仅涉及到少
量的栈、寄存器内容等,开销是比较少的,但是频繁创建和销毁线程依旧会带来不少的开销;
	
	改进三:线程池,使用线程池可以避免线程的频繁创建和销毁问题,所谓的线程池就是提前创建若干个线程,这样当有新链接建立时,将这个已连接的socket放入到一个
队列里,然后线程池里的线程负责从队列中取出已连接的socket进程处理;需要注意的是,这个线程池是全局的,每个线程都会操作,为了避免多个线程竞争,我们需要加锁。


上述这些改进的缺陷在于,如果我同时来了一万个客户,意味着我要维持10000个进程或者线程,那么操作系统是扛不住的,因此引入了IO多路复用!!

	1.I/O多路复用技术:select、poll、epoll都是IO多路复用的机制,I/O多路复用就是通过一种机制,使得一个进程可以监听多个文件描述符,一旦某个文件描述符就绪
(一般是读就绪或者写就绪),就能通知应用程序进行相应的读写操作。

	2.1select主旨思想:
		a.首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中;
		b.调用一个select函数,监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O操作时,该函数才会返回;
		c.select函数是阻塞的,函数对该文件描述符的检测操作是由内核完成的;
		d.在返回时,它会告诉进程有多少(哪些)描述符要进行I/O操作;
	2.2select缺点:
		a.每次调用select,都需要把存放文件描述符的集合从用户态拷贝到内核态,这个开销在文件描述符很多时会比较大;
		b.同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;
		c.select支持的文件描述符数量太小了,默认是1024;
		d.select最麻烦的地方在于,当我们创建一个列表存放fd时,我们会把相应的文件描述符置为1,然后将该集合交给内核去管理,内核检测到事件发生后,会把发生
事件的文件描述符置为1,而那些没有事件发生的文件描述符置为0,然后将集合再次提交给用户,用户遍历集合就可以发现有事件到来的文件描述符。但是当我们再次调用
select函数监听文件描述符集合时,先前没有事件到来的文件描述符就不在该集合中了,但是我们仍旧应该监听它们的文件描述符。
	2.3select补充:在客户端和服务器通信的时候,一直有一个疑惑,我文件描述符列表里到底存放哪些文件描述符,其实是存放服务端监听的文件描述符以及用于通信的
文件描述符;
		当监听的文件描述符有变化时说明有客户端连接进来,我们需要进行accept接收并得到一个用于通信的文件描述符,再把这个通信的文件描述符加入到文件描述
符列表中;
		当通信的文件描述符有变化时,我们要for循环遍历所有有变化的通信文件描述符,进行读取信息;

	3.1poll主旨思想:
		a.创建一个pollfd类型的数组来存放文件描述符以及通过pollfd中的events字段设置要监听的该文件描述符的事件(or);
		b.调用poll()函数将该数组交给内核去管理,内核会将发生的事件传递给pollfd中的revents字段;
		c.遍历所有文件描述符的revents来判断该文件描述符所发生的事件;
	3.2poll特点:
		a.由于是自己定义的数组,所以没有文件描述符的限制;
		b.每次都需要将pollfd数组从用户态拷贝到内核态,开销比较大;
	
select和poll最主要的缺陷在于:我们需要遍历所有的文件描述符才能知道具体的哪几个文件描述符发生了变化;
	
	4.epoll主旨思想:
		a.创建一个epoll实例,就相当于在内核中创建了一个数据;
		b.数据包含两个比较重要的数据,一个是需要检测的文件描述符的信息(红黑树),还有一个是就绪列表(双向链表),用来存放检测到数据改变的文件描述符信息;
		c.我们仅仅通过遍历list就可以知道哪些文件描述符发生了事件,而不需要遍历所有的文件描述符;(核心!)

	5.为什么epoll相比于select和poll高效?
		a.每次调用select()poll()都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会比较大;而epoll保证了每个fd在整个过程中只需拷贝一次;
		b.每次调用select()epoll()函数需要遍历所有的fd,而epoll只需要把所有的fd存放至红黑树中,仅查看就绪列表(list)就可以知道有没有就绪的fd了;
		c.select仅支持1024个文件描述符,poll和epoll没有这个限制;

	6.epoll的效率是一直都比select高吗?
		a.表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多回调函数,回
调函数主要负责将数据从红黑树上添加到就绪链表中;
		b.select低效是因为每次都需要轮回查询,但低效也是相对的,视情况而定;

	7.如果5000个客户端连接进来,调用select会怎样?会报错吗还是怎样?
		从上面我们不难得知,select支持的文件描述符为1024,超过1024个后就会发生段错误;

项目2.56简述同步与异步的区别,阻塞和非阻塞的区别?

阻塞与非阻塞的区别:
	1.调用者调用了某个函数,等待这个函数返回,期间什么都不做,不停的检查这个函数有没有返回,必须等待这个函数返回后才能进行下一步动作;线程或者进程会被挂
起,不占用CPU资源,CPU会调度其它的线程或者进程。
	2.非阻塞等待,每隔一段时间就去检查IO事件是否就绪,没有就绪就可以做其他事情。

同步与异步的区别:
	1.同步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),数据的读写都是由请求方A自己来完成的(不管是阻塞还是非阻塞)。
	  所有的操作都做完,才返回给用户结果。即写完数据库之后,再响应用户,用户体验不好。
	2.异步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),向B传入请求的事件以及事件发生时通知的方式,A就可以处理其它逻辑了,当B监听到
事件处理完成后,会用事先约定好的通知方式,通知A处理结果(内核完成数据读写)。
	 不用等所有操作做完,就响应用户请求,即先响应用户请求,然后慢慢去写数据库,用户体验比较好。

项目2.57epoll的工作模式以及EPOLLONESHOT事件

1.LT模式(水平触发模式):
	假设委托内核检测读事件------>检测fd的读缓冲区,读缓冲区有数据---->epoll检测到了会给用户发通知。
		a.用户不读数据,数据一直在缓冲区,epoll会一直通知;
		b.用户只读了一部分数据,epoll会通知;
		c.缓冲区的数据读完了,不通知;
	LT模式下,内核告诉你一个文件描述符是否就绪了,然后你可以对就绪的fd进行I/O操作,如果你不做任何操作,内核还是会继续通知你的。                                              

2.ET模式(边沿触发模式):
	ET模式是高速工作方式,只支持非阻塞。在这种模式下,当描述符从未就绪到就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为
那个文教描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。
	Note:如果一直不对这个fd进行I/O操作,内核不会发送更多的通知。
	ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的
阻塞读\阻塞写操作把处理多个文件描述符的任务饿死。

3.EPOLLONESHOT事件:
	即使可以使用ET模式,一个socket上的某个事件还是可能触发多次。这在并发程序中就会引起一个问题,比如一个线程在读取完某个socket上的数据后开始处理这些
数据,而在数据的处理过程中该socket上又有新的数据可读(EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个线程同时操作一个
socket的局面。
	为了确保一个socket连接在任一时刻都只被一个线程处理.可以使用epoll的EPOLLONESHOT事件实现。
	对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描
述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个socket时,其它线程是不可能有机会操作该socket的。
	NOTE:注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其
EPOLLIN事件能被触发,近而让其它线程有机会继续处理这个socket。
	

项目2.58请你说说Reactor、Proactor、模拟Proactor模式

服务器程序通常需要处理三类事件:I/O事件、信号及定时事件。有两种高效的事件处理模式:Reactor模式和Proactor模式,同步I/O模型通常用于实现Reactor模式,异步
I/O模型通常用于实现Proactor模式。

Reactor模式:
	原理:要求主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话就立即将该事件通知工作线程(逻辑单元),将socket可读可写事件放入请求队列,交
给工作线程处理。除此之外,主线程不做任何其它实质性的工作。读写数据、接受新的连接,以及处理客户请求均在工作线程中完成。
	流程:用epoll多路复用。
		1.主线程往epoll内核事件表中注册socket上的读就绪事件;
		2.主线程调用epoll_wait等待socket上有数据可读;
		3.当socket上有数据可读时,epoll_wait通知主线程。主线程则将socket可读事件放入线程池的请求队列中;
		4.睡眠在请求队列上的某个工作线程就会被唤醒,它从socket读取数据,并处理客户请求,然后往epoll内核事件中注册该socket的写就绪事件;
		5.当主线程调用epoll_wait等待socket可写;
		6.当socket可写时,epoll_wait通知主线程,主线程将可写事件放入请求队列;
		7.睡眠在请求队列上的某个工作线程被唤醒,它往socket上写入服务器处理客户请求的结果;


Proactor模式:
	原理:Proactor模式将所有I/O操作都交给主线程和内核来处理(进行读、写),工作线程仅仅负责业务逻辑(内核执行数据读写)。

	流程:用epoll多路复用。
		1.主线程调用aio_read函数向内核注册socket上的读完成事件,并告诉内核用户读缓冲区的位置以及读操作完成时如何通知应用程序;
		2.主线程继续处理其它逻辑(内核执行数据读写);
		3.当socket上的数据被读入用户缓冲区后,内核将向应用程序发送一个信号,以通知应用程序数据已经可用;
		4.应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求后,调用air_write函数向内核注册socket上的写完成事件,
并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序;
		5.主线程继续处理其它逻辑;
		6.当用户缓冲区的数据被写入socket之后,内核将向应用程序发送一个信号,以通知应用程序数据已经发送完毕。
		7.应用程序预先定义好的信号处理函数选择一个工作线程来善后处理,比如决定是否关闭socket。
		
同步I/O模拟Proactor模式:
	原理:主线程执行数据的读写操作,读写完成后,主线程向工作线程通知这一"完成事件"。那么从工作线程的角度来看,它们就直接获得了数据读写的结果,接下来要做
的就是对读写的结果进行逻辑处理。

	流程:
		1.主线程往epoll内核事件表中注册socket上的读就绪事件;
		2.主线程调用epoll_wait等待socket上有数据可读;
		3.当socket上有数据可读时,epoll_wait通知主线程,主线程从socket上循环读取数据,直到没有更多数据可读,然后将读取到的数据封装成一个请求对象并插入
请求队列;
		4.睡眠在请求队列上的工作线程被唤醒,它获得请求对象并且处理客户请求,然后往epoll内核事件表中注册socket上的写就绪事件;
		5.主线程调用epoll_wait等待socket可写;
		6.当socket可写时,epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果。

项目2.59 介绍一次Linux上的5中IO模型

	1.阻塞IO:调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的检查这个函数有没有返回,必须等这个函数返回后才能进行下一个动作;
	2.非阻塞IO:非阻塞等待,每隔一段时间就去检查IO事件是否就绪,没有就绪就可以做其它事情;
	3.信号驱动IO:Linux用套接口进行信号驱动,安装一个信号处理函数,进程继续运行并不阻塞,当IO事件就绪,进程收到SIGIO信号,然后处理IO事件;
	4.IO多路复用:select、poll、epoll;
	5.异步IO:Linux中,可以调用aio_read函数告诉内核用户缓冲区的位置以及写操作完成后如何通知应用程序,然后函数立即返回,当内核将数据拷贝到缓冲区后,再通
知应用程序,用户可以直接去使用数据;

	前四种模型都属于同步模式,因为其中真正的IO操作都将会阻塞进程,只有异步IO模型真正实现了IO操作的异步性。
	异步和同步的区别就在于,异步是内核将数据拷贝到用户去,不需要用户再自己接收数据,直接使用就可以了,而同步是内核通知数据到了,然后用户自己调用函数去接
收数据。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值