引用的文章:操作系统——进程通信(IPC)_系统ipc-CSDN博客
面试汇总(五):操作系统常见面试总结(一):进程与线程的相关知识点 - 知乎 (zhihu.com)
二、进程的定义、组成、组成方式及特征_进程的组成部分必须包含-CSDN博客
【操作系统】_7种进程调度算法 - 知乎 (zhihu.com)
一、进程的定义
进程是某个程序在计算机系统中正在运行的实例。进程是操作系统资源分配的基本单位,可以看作是程序在执行过程中的动态表现。
注意:PCB是进程中存在的唯一标志。
从不同角度,进程可以有不同的定义,比较传统典型的定义有:
三者皆强调动态性
- 进程是程序的一次执行过程。
- 进程是一个程序及其数据再处理机上顺序执行时所发生的活动
- 进程是具有独立功能的程序在数据集合上的运行过程,它是系统进行资源分配和调度的一个独立单位。
1.2 进程的组成
进程(进程实体) 由程序段、数据段、PCB 三部分组成
1.2.1 PCB 所管理的各种信息
1.3 进程的组织
在一个系统中,通常有数十、数百乃至数千个PCB。为了能对他们加以有效的管理,应该用适当的方式把这些PCB组织起来。
注:进程的组成讨论的是一个进程内部由哪些部分构成的问题,而进程的组织讨论的是多个进程之间的组织方式问题
1.3.1 链接方式
1.3.2 索引方式
1.4 进程的特征
进程和程序是连个截然不同的概念,相比于程序,进程拥有以下特征:
- 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
- 并发性:任何进程都可以同其他进行一起并发执行;
- 独立性:进程是系统进行资源分配和调度的一个独立单位;
- 结构性:进程由程序,数据和进程控制块三部分组成
- 异步性:进程按各自的独立的速度,向前推进执行
1.5 进程和程序的区别
进程和程序的区别?
- 程序是一个静态的概念,是指令和数据的有序集合。
- 进程是一个动态的概念,是程序在处理机上的一次执行过程。
- 程序是永久的,程序可以作为一种软件资料长期存在,
- 进程是暂时的,进程是有一定生命期的。
进程是一段程序的执行过程。
———原文链接:https://blog.csdn.net/m0_37925202/article/details/78759408
1.6 小结
二、进程的状态和转换
2.1 进程的状态
进程是程序的一次执行。在这个执行的过程中,有时进程正在被CPU处理,有时又需要等待CPU服务,可见,进程的状态是会有各种变化。为了方便对各个进程的管理,操作系统需要将进程合理的划分为几种状态。
进程的三种基本状态:运行态、就绪态、阻塞态
注意:注意:单核处理机环境下,每一时刻最多只有一个进程处于运行态。(双核环境下可以同时有两个进程处于运行态)
进程的另外两种状态:
2.2 进程状态的转换
注意:不能由阻塞态直接转换为运行态,也不能由就绪态直接转换为阻塞态(因为进入阻塞态是进程主动请求的,必然需要进程在运行时才能发出这种请求)
2.3 小结
三、进程控制
3.1 什么是进程控制
进程控制的主要功能就是对系统中的所有进程实施有效的管理,它具有创建进程、撤销进程、实现进程状态转换的功能。
简化理解:进程控制就是要实现进程的状态转换
3.2 如何实现进程控制
3.2.1 什么是原语
用原语实现进程控制。原语的特点就是执行期间不允许中断,只能一气呵成。
这种不可被中断的操作即原子操作
原语采用”关中断指令“和”开中断指令“实现
显然,关/开中断指令的权限非常大,必然是只允许在核心态下执行的特权指令
3.2.2 原语如何实现进程控制
进程控制会导致进程状态的切换,无论哪个原语,要做的无非就是三类事情:
更新PCB中的信息(如修改进程状态标志、将运行环境保存到PCB、从PCB恢复运行环境)
所有的进程控制原语一定会修改进程的状态标志
剥夺当前运行进程的CPU使用权必然需要保存其运行环境
某进程开始运行前必然要恢复其运行环境
将PCB插入合适的队列
分配/回收资源
3.2.3 原语实现对进程的操作
1.原语实现进程创建
2.原语实现进程的终止
3.原语实现进程的阻塞和唤醒
4.原语实现进程切换
3.3 小结
四、进程通信
4.1 什么是进程通信
进程通信就是指进程之间的信息交换。
进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立。
为保证安全,一个进程不能直接访问另一个进程的地址空间。
但是进程之间的信息交换又是必须实现的。为了保证进程间的安全通信,操作系统提供了一些方法。
4.2 进程通信(IPC,InterProcess Communication)的方式
进程间通信的方式:
进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。
4.2.1 管道通信
1.管道:
管道主要包括无名管道和命名管道:无名管道可用于具有亲缘关系的父子进程间的通信,
有名管道除了具有无名管道所具有的功能外,它还允许无亲缘关系进程间的通信。
1. 普通管道PIPE:
1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端
2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
3)它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
特点:
① 管道只能采用半双工通信,只能支持数据的单向传输。如果要实现双向同时通信,则需要建立两个管道。
② 各个进程之间要互斥访问管道。
③ 当管道写满时,写进程的 write() 系统调用将被阻塞,等待读进程将数据取走;管道变空时,读进程的 read() 系统调用将被阻塞。
④ 如果管道没写满,就不允许读;如果没读空,就不允许写。
⑤ 数据一旦读出,就从管道中被抛弃,这意味着读进程最多只能有一个,否则可能会发生读错数据的情况。(一个管道指父进程(或一个子进程)与某个特定子进程通信)
缺点:
速度慢,容量有限,只有父子或兄弟进程能通信。
2. 命名管道FIFO:
1)FIFO可以在无关的进程之间交换数据
2)FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
概念: 同管道一样,不过它允许没有亲缘关系的进程间进行通信。
实现: 有名管道提供了一个路径名与之关联,以 FIFO 的形式存在于文件系统中。即使不相干的进程也可以通过FIFO相互通信,只要它们能访问已经提供的路径。
特点:
① 一种文件类型。
② FIFO 可以在无关进程之间交换数据。
③ FIFO 以一种特殊设备文件形式存在于文件系统中。
④ FIFO 的通信方式类似于在进程中使用文件来传输数据,只不过 FIFO 类型文件同时具有管道的特性。在读数据时,FIFO 管道中同时也清除数据,且先进先出。
————————————————
原文链接:https://blog.csdn.net/qq_37852613/article/details/120025034
4.2. 系统IPC:
4.2.1 消息队列(间接通信)
概念: 是消息的一个链表,存放在内核中,由操作系统维护的以字节序列为基本单位的通信机制。允许一个或多个进程向它写消息,一个或多个进程向它读消息。Linux 维护了一个消息队列向量表:msgque,来表示系统中所有的消息队列。
- 进程间的数据交换以格式化的消息(Message)为单位。进程通过操作系统提供的==“发送/接收消息”==两个原语进行数据交换。
每个消息队列都有一个唯一的标识;只有共享了相同消息队列的进程,才能够通信,才建立连接,连接可以是单向或双向。消息队列可以与多个进程相关联;每对进程可以共享多个消息队列。
原文链接:https://blog.csdn.net/qq_37852613/article/details/120025034
特点:
1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
2)消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
4.2.2 信号量semaphore
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
特点:
1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
2)信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
3)每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
4)支持信号量组。
4.2.3 信号signal
概念: 进程间的软件中断通知和处理机制。操作系统通过信号来通知某一进程发生了某一种预定好的事件;接收到信号的进程可以选择不同的方式处理该信号:①采用默认处理机制——进程中断或退出,②忽略该信号,③自定义该信号的处理函数,执行相应的动作。
实现: 内核为进程生产信号,来响应不同的事件,这些事件就是信号源。信号源可以是:异常、其它进程、终端的终端(Ctrl+C,Ctrl+等)、作业的控制(前台,后台进程的管理等),分配额问题(CPU 超时或文件过大等)、内核通知(IO就绪等)、报警(计时器)。
4.2.4 共享内存(Shared Memory)(直接通信)
概念: 共享内存映射为一段可以被其它很多进程访问的内存。即把一段物理内存映射到多个进程的内存地址空间的通信机制。每个进程的内存地址空间需要明确设置共享内存段,同一个进程的线程共享地址空间。(速度快,但是没有同步)
特点:
- 1)共享内存是最快的一种IPC,因为进程是直接对内存进行存取
- 2)因为多个进程可以同时操作,所以需要进行同步
- 3)信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
4.3 socket通信
socket也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。
五、什么是线程,为什么要引入线程
5.1线程的定义
可以把线程理解为“轻量级进程”。
线程是一个基本的CPU执行单元,也是程序执行流的最小单位。
引入线程之后,不仅是进程之间可以并发,进程内的各线程之间也可以并发,从而进一步提升了系统的并发度,使得一个进程内也可以并发处理各种任务(如QQ视频、文字聊天、传文件)
**进程只作为除CPU之外的系统资源的分配单元(**如打印机、内存地址空间等都是分配给进程的)。
5.2 带来的变化
5.3 线程的属性
5.4 线程的实现方式
5.4.1 用户级线程
用户级线程由应用程序通过线程库实现。
所有的线程管理工作都由应用程序负责(包括线程切换)用户级线程中,线程切换可以在用户态下即完成,无需操作系统干预。
在用户看来,是有多个线程。但是在操作系统内核看来,并意识不到线程的存在。(用户级线程对用户不透明,对操作系统透明)可以这样理解,“用户级线程” 就是“从用户视角看能看到的线程”
5.4.2 内核级线程
内核级线程的管理工作由操作系统内核完成。
线程调度、切换等工作都由内核负责,因此内核级线程的切换必然需要在核心态下才能完成。可以这样理解,“内核级线程” 就是“从操.作系统内核视角看能看到的线程”
重点
操作系统只看见”内核级线程“,因此只有内核级线程才是处理机的分配单位
5.5 多线程模型
在同时支持用户级线程和内核级线程的系统中,由几个用户级线程映射到几个内核级线程的问题引出了“多线程模型”问题。
5.5.1 多对一模型
多对一模型: 多个用户及线程映射到一个内核级线程。每个用 户进程只对应一个内核级
线程。
**优点:**用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高
**缺点:**当一个用户级线程被阻塞后,整个进程都会被阻塞,并发度不高。多个线程不可在多核处理机上并行运行
5.5.2 一对一模型
一对一模型:一个用户及线程映射到一个内核级线程。每个用户进程有与用户级线程同
数量的内核级线程。
**优点:**当一个线程被阻塞后,别的线程还可以继续执行,并发能力强。多线程可在多核
处理机上并行执行。
**缺点:**一个用户进程会占用多个内核级线程,线程切换由操作系统内核完成,需要切换到
核心态,因此线程管理的成本高,开销大。
5.5.3 多对多模型
多对多模型: n用户及线程映射到m个内核级线程(n>=m)。每个用户进程对应m个内核级线程。
克服了多对一模型并发度不高的缺点,又克服了一对一模型中一个用户进程占用太多内核级线程,开销太大的缺点。
线程间通信
锁机制:包括互斥锁、条件变量、读写锁
互斥锁提供了以排他方式防止数据结构被并发修改的方法。
读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
wait/notify 等待
Volatile 内存共享
CountDownLatch 并发工具
CyclicBarrier 并发工具
信号量机制(Semaphore)
包括无名线程信号量和命名线程信号量。
信号机制(Signal)
类似进程间的信号处理。
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
线程小结
面试问题:
请你回答一下fork和vfork的区别
fork的基础知识:
fork:创建一个和当前进程映像一样的进程可以通过fork( )系统调用:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
成功调用fork( )会创建一个新的进程,它几乎与调用fork( )的进程一模一样,这两个进程都会继续运行。在子进程中,成功的fork( )调用会返回0。在父进程中fork( )返回子进程的pid。如果出现错误,fork( )返回一个负值。
最常见的fork( )用法是创建一个新的进程,然后使用exec( )载入二进制映像,替换当前进程的映像。这种情况下,派生(fork)了新的进程,而这个子进程会执行一个新的二进制可执行文件的映像。这种“派生加执行”的方式是很常见的。
在早期的Unix系统中,创建进程比较原始。当调用fork时,内核会把所有的内部数据结构复制一份,复制进程的页表项,然后把父进程的地址空间中的内容逐页的复制到子进程的地址空间中。但从内核角度来说,逐页的复制方式是十分耗时的。现代的Unix系统采取了更多的优化,例如Linux,采用了写时复制的方法,而不是对父进程空间进程整体复制。
- vfork的基础知识:
在实现写时复制之前,Unix的设计者们就一直很关注在fork后立刻执行exec所造成的地址空间的浪费。BSD的开发者们在3.0的BSD系统中引入了vfork( )系统调用。
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
除了子进程必须要立刻执行一次对exec的系统调用,或者调用_exit( )退出,对vfork( )的成功调用所产生的结果和fork( )是一样的。vfork( )会挂起父进程直到子进程终止或者运行了一个新的可执行文件的映像。通过这样的方式,vfork( )避免了地址空间的按页复制。在这个过程中,父进程和子进程共享相同的地址空间和页表项。实际上vfork( )只完成了一件事:复制内部的内核数据结构。因此,子进程也就不能修改地址空间中的任何内存。
vfork( )是一个历史遗留产物,Linux本不应该实现它。需要注意的是,即使增加了写时复制,vfork( )也要比fork( )快,因为它没有进行页表项的复制。然而,写时复制的出现减少了对于替换fork( )争论。实际上,直到2.2.0内核,vfork( )只是一个封装过的fork( )。因为对vfork( )的需求要小于fork( ),所以vfork( )的这种实现方式是可行的。
- 写时复制
Linux采用了写时复制的方法,以减少fork时对父进程空间进程整体复制带来的开销。
写时复制是一种采取了惰性优化方法来避免复制时的系统开销。它的前提很简单:如果有多个进程要读取它们自己的那部门资源的副本,那么复制是不必要的。每个进程只要保存一个指向这个资源的指针就可以了。只要没有进程要去修改自己的“副本”,就存在着这样的幻觉:每个进程好像独占那个资源。从而就避免了复制带来的负担。如果一个进程要修改自己的那份资源“副本”,那么就会复制那份资源,并把复制的那份提供给进程。不过其中的复制对进程来说是透明的。这个进程就可以修改复制后的资源了,同时其他的进程仍然共享那份没有修改过的资源。所以这就是名称的由来:在写入时进行复制。
写时复制的主要好处在于:如果进程从来就不需要修改资源,则不需要进行复制。惰性算法的好处就在于它们尽量推迟代价高昂的操作,直到必要的时刻才会去执行。
在使用虚拟内存的情况下,写时复制(Copy-On-Write)是以页为基础进行的。所以,只要进程不修改它全部的地址空间,那么就不必复制整个地址空间。在fork( )调用结束后,父进程和子进程都相信它们有一个自己的地址空间,但实际上它们共享父进程的原始页,接下来这些页又可以被其他的父进程或子进程共享。
写时复制在内核中的实现非常简单。与内核页相关的数据结构可以被标记为只读和写时复制。如果有进程试图修改一个页,就会产生一个缺页中断。内核处理缺页中断的方式就是对该页进行一次透明复制。这时会清除页面的COW属性,表示着它不再被共享。
现代的计算机系统结构中都在内存管理单元(MMU)提供了硬件级别的写时复制支持,所以实现是很容易的。
在调用fork( )时,写时复制是有很大优势的。因为大量的fork之后都会跟着执行exec,那么复制整个父进程地址空间中的内容到子进程的地址空间完全是在浪费时间:如果子进程立刻执行一个新的二进制可执行文件的映像,它先前的地址空间就会被交换出去。写时复制可以对这种情况进行优化。 - fork和vfork的区别:
- fork( )的子进程拷贝父进程的数据段和代码段;vfork( )的子进程与父进程共享数据段
- fork( )的父子进程的执行次序不确定;vfork( )保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。
- vfork( )保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
- 当需要改变共享数据段中变量的值,则拷贝父进程。
1、请你说一说并发(concurrency)和并行(parallelism)
并发(concurrency):指宏观上看起来两个程序在同时运行,比如说在单核cpu上的多任务。但是从微观上看两个程序的指令是交织着运行的,你的指令之间穿插着我的指令,我的指令之间穿插着你的,在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能,只能提高效率。
并行(parallelism):指严格物理意义上的同时运行,比如多核cpu,两个程序分别运行在两个核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令,也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的cpu都是往多核方面发展。
2、请你说一说有了进程,为什么还要有线程?
线程产生的原因:
进程可以使多个程序能并发执行,以提高资源的利用率和系统的吞吐量;但是其具有一些缺点:
进程在同一时间只能干一件事
进程在执行的过程中如果阻塞,整个进程就会挂起,即使进程中有些工作不依赖于等待的资源,仍然不会执行。
因此,操作系统引入了比进程粒度更小的线程,作为并发执行的基本单位,从而减少程序在并发执行时所付出的时空开销,提高并发性。和进程相比,线程的优势如下:
从资源上来讲, 线程是一种非常"节俭"的多任务操作方式。在linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。
从切换效率上来讲, 运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间。据统计,一个进程的开销大约是一个线程开销的30倍左右。
从通信机制上来讲, 线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进城下的线程之间贡献数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。
除以上优点外,多线程程序作为一种多任务、并发的工作方式,还有如下优点:
1、使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
2、改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序才会利于理解和修改。
3、请问单核机器上写多线程程序,是否需要考虑加锁,为什么?
在单核机器上写多线程程序,仍然需要线程锁。因为线程锁通常用来实现线程的同步和通信。在单核机器上的多线程程序,仍然存在线程同步的问题。因为在抢占式操作系统中,通常为每个线程分配一个时间片,当某个线程时间片耗尽时,操作系统会将其挂起,然后运行另一个线程。如果这两个线程共享某些数据,不使用线程锁的前提下,可能会导致共享数据修改引起冲突。
4、 请问线程需要保存哪些上下文,SP、PC、EAX这些寄存器是干嘛用的?
线程在切换的过程中需要保存当前线程Id、线程状态、堆栈、寄存器状态等信息。其中寄存器主要包括SP PC EAX等寄存器,其主要功能如下:
SP:堆栈指针,指向当前栈的栈顶地址
PC:程序计数器,存储下一条将要执行的指令
EAX:累加寄存器,用于加法乘法的缺省寄存器
5、请你说一说线程间的同步方式,最好说出具体的系统调用
- 信号量:
信号量是一种特殊的变量,可用于线程同步。它只取自然数值,并且只支持两种操作:
P(SV): 如果信号量SV大于0,将它减一;如果SV值为0,则挂起该线程。
V(SV): 如果有其他进程因为等待SV而挂起,则唤醒,然后将SV+1;否则直接将SV+1。
其系统调用为:
sem_wait(sem_t *sem):以原子操作的方式将信号量减1,如果信号量值为0,则sem_wait将被阻塞,直到这个信号量具有非0值。
sem_post(sem_t *sem):以原子操作将信号量值+1。当信号量大于0时,其他正在调用sem_wait等待信号量的线程将被唤醒。 - 互斥量:
互斥量又称互斥锁,主要用于线程互斥,不能保证按序访问,可以和条件锁一起实现同步。当进入临界区 时,需要获得互斥锁并且加锁;当离开临界区时,需要对互斥锁解锁,以唤醒其他等待该互斥锁的线程。其主要的系统调用如下:
pthread_mutex_init: 初始化互斥锁
pthread_mutex_destroy: 销毁互斥锁
pthread_mutex_lock: 以原子操作的方式给一个互斥锁加锁,如果目标互斥锁已经被上锁,pthread_mutex_lock调用将阻塞,直到该互斥锁的占有者将其解锁。
pthread_mutex_unlock: 以一个原子操作的方式给一个互斥锁解锁。 - 条件变量:
条件变量,又称条件锁,用于在线程之间同步共享数据的值。条件变量提供一种线程间通信机制:当某个共享数据达到某个值时,唤醒等待这个共享数据的一个/多个线程。即,当某个共享变量等于某个值时,调用 signal/broadcast。此时操作共享变量时需要加锁。其主要的系统调用如下:
pthread_cond_init: 初始化条件变量
pthread_cond_destroy: 销毁条件变量
pthread_cond_signal: 唤醒一个等待目标条件变量的线程。哪个线程被唤醒取决于调度策略和优先级。
pthread_cond_wait: 等待目标条件变量。需要一个加锁的互斥锁确保操作的原子性。该函数中在进入wait状态前首先进行解锁,然后接收到信号后会再加锁,保证该线程对共享资源正确访问。
6、请你说一下多线程和多进程的不同,以及使用场景
进程是资源分配的最小单位,而线程时CPU调度的最小单位。多线程之间共享同一个进程的地址空间,线程间通信简单,同步复杂,线程创建、销毁和切换简单,速度快,占用内存少,适用于多核分布式系统,但是线程间会相互影响,一个线程意外终止会导致同一个进程的其他线程也终止,程序可靠性弱。而多进程间拥有各自独立的运行地址空间,进程间不会相互影响,程序可靠性强,但是进程创建、销毁和切换复杂,速度慢,占用内存多,进程间通信复杂,但是同步简单,适用于多核、多机分布。
多线程模型主要优势为线程间切换代价较小,因此适用于I/O密集型的工作场景,因此I/O密集型的工作场景经常会由于I/O阻塞导致频繁的切换线程。同时,多线程模型也适用于单机多核分布式场景。
多进程模型的优势是CPU,多进程模型,适用于CPU密集型。同时,多进程模型也适用于多机分布式场景中,易于多机扩展。
7、游戏服务器应该为每个用户开辟一个线程还是一个进程,为什么?
游戏服务器应该为每个用户开辟一个进程。因为同一进程间的线程会相互影响,一个线程死掉会影响其他线程,从而导致进程崩溃。因此为了保证不同用户之间不会相互影响,应该为每个用户开辟一个进程
8、请你说一说死锁发生的条件以及如何解决死锁
死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。死锁发生的四个必要条件如下:
互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源;
请求和保持条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源
不可剥夺条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放
环路等待条件:进程发生死锁后,必然存在一个进程-资源之间的环形链
解决死锁的方法即破坏上述四个条件之一,主要方法如下:
资源一次性分配,从而剥夺请求和保持条件
可剥夺资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺的条件
资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件
9、请你说一说进程状态转换图,动态就绪,静态就绪,动态阻塞,静态阻塞
1、进程的五种基本状态:
- 1)创建状态:进程正在被创建
2)就绪状态:进程被加入到就绪队列中等待CPU调度运行
3)执行状态:进程正在被运行
4)等待阻塞状态:进程因为某种原因,比如等待I/O,等待设备,而暂时不能运行。
5)终止状态:进程运行完毕 - 2、交换技术
当多个进程竞争内存资源时,会造成内存资源紧张,并且,如果此时没有就绪进程,处理机会空闲,I/0速度比处理机速度慢得多,可能出现全部进程阻塞等待I/O。
针对以上问题,提出了两种解决方法:
1)交换技术:换出一部分进程到外存,腾出内存空间。
2)虚拟存储技术:每个进程只能装入一部分程序和数据。
在交换技术上,将内存暂时不能运行的进程,或者暂时不用的数据和程序,换出到外存,来腾出足够的内存空间,把已经具备运行条件的进程,或进程所需的数据和程序换入到内存。
从而出现了进程的挂起状态:进程被交换到外存,进程状态就成为了挂起状态。 - 3、活动阻塞,静止阻塞,活动就绪,静止就绪
1)活动阻塞:进程在内存,但是由于某种原因被阻塞了。
2)静止阻塞:进程在外存,同时被某种原因阻塞了。
3)活动就绪:进程在内存,处于就绪状态,只要给CPU和调度就可以直接运行。
4)静止就绪:进程在外存,处于就绪状态,只要调度到内存,给CPU和调度就可以运行。
从而出现了:
- 活动就绪 —— 静止就绪 (内存不够,调到外存)
- 活动阻塞 —— 静止阻塞 (内存不够,调到外存)
- 执行 —— 静止就绪 (时间片用完)
10、死循环+来连接时新建线程的方法效率有点低,怎么改进?
提前创建好一个线程池,用生产者消费者模型,创建一个任务队列,队列作为临界资源,有了新连接,就挂在到任务队列上,队列为空所有线程睡眠。改进死循环:使用select epoll这样的技术
11、怎么唤醒被阻塞的socket线程?怎样确定当前线程是繁忙还是阻塞?请问就绪状态的进程在等待什么?两个进程访问临界区资源,会不会出现都获得自旋锁的情况?
给阻塞时候缺少的资源;
使用ps命令查看
被调度使用cpu的运行权
单核cpu,并且开了抢占可以造成这种情况。
12、请你来说一说协程
- 1、概念
协程,又称微线程,纤程,英文名Coroutine。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
由协程运行结果可能是12x3yz。在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A。但协程的特点在于是一个线程执行。 - 2、协程和线程区别
那和多线程比,协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
在协程上利用多核CPU呢——多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程。虽然支持不完全,但已经可以发挥相当大的威力了。
13、请你说一下僵尸进程
- 1)正常进程 正常情况下,子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。
当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。
unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息,
就可以得到:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。
但是仍然为其保留一定的信息,直到父进程通过wait / waitpid来取时才释放。保存信息包括: 1进程号the process ID
2退出状态the termination status of the process 3运行时间the amount of CPU time
taken by the process等 - 2)孤儿进程 一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
-
孤儿进程(无害) 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。 孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。 每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。 这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被 init 进程(进 程号为 1)所收养,并由 init 进程对它们完成状态收集工作
原文链接:https://blog.csdn.net/qq_35290785/article/details/93230958 - 3)僵尸进程 一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
僵尸进程是一个进程必然会经过的过程:这是每个子进程在结束时都要经过的阶段。
如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时
处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。
如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
另一个描述:
一、僵尸进程(有害) 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。 我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。 子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。 因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息: 1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号,退出状态,运行时间等) 2、直到父进程通过wait/waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait/waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。 任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。 如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。 如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
一个父进程利用fork创建子进程,如果子进程退出,而父进程没有利用wait 或者 waitpid 来获取子进程的状态信息,那么子进程的状态描述符依然保存在系统中
原文链接:https://blog.csdn.net/qq_35290785/article/details/93230958
- 危害: 如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。
外部消灭: 通过kill发送SIGTERM或者SIGKILL信号消灭产生僵尸进程的进程,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源
内部解决: 1、子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。
2、fork两次,原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。
14、server端监听端口,但还没有客户端连接进来,此时进程处于什么状态?请问如何设计server,使得能够接收多个客户端的请求?
这个需要看服务端的编程模型,如果如上一个问题的回答描述的这样,则处于阻塞状态,如果使用了epoll,select等这样的io复用情况下,处于运行状态。
多线程,线程池,io复用
15、请问怎么实现线程池
1.设置一个生产者消费者队列,作为临界资源
2.初始化n个线程,并让其运行起来,加锁去队列取任务运行
3.当任务队列为空的时候,所有线程阻塞
4.当生产者队列来了一个任务后,先对队列加锁,把任务挂在到队列上,然后使用条件变量去通知阻塞中的一个线程
16、请解释一下,LINUX下的线程,GDI类
LINUX实现的就是基于核心轻量级进程的”一对一”线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。
GDI类为图像设备编程接口类库。
17、系统线程数量上限是多少?
Linux 系统中单个进程的最大线程数有其最大的限制 PTHREAD_THREADS_MAX。
这个限制可以在/usr/include/bits/local_lim.h中查看 ,对 linuxthreads这个值一般是 1024,对于 nptl 则没有硬性的限制,仅仅受限于系统的资源。
这个系统的资源主要就是线程的stack 所占用的内存,用 ulimit -s 可以查看默认的线程栈大小,一般情况下,这个值是8M=8192KB。
18、线程的那些资源共享,那些资源不共享?
- 共享的资源有
a. 堆 由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的)
b. 全局变量 它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的
c. 静态变量虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的
d. 文件等公用资源 这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。 - 独享的资源有
a. 栈 栈是独享的
b. 寄存器 这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC
19、请你说一下线程之间通信的手段
- 使用全局变量
主要由于多个线程可能更改全局变量,因此全局变量最好声明为volatile - 使用消息实现通信
在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。 - 使用事件CEvent类实现线程间通信
Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。
20,说一说进程调度算法有哪些
先来先服务(FCFS)调度算法
FCFS是一种最简单的调度算法,它既可以用于作业的调度,又可以用于进程调度。在作业调度中,算法每次从后备作业队列中选择最先进入该队列的一个或几个作业,将他们调入内存,分配必要的资源,创建进程并放入就绪队列。
在进程调度中,FCFS调度算法每次从就绪队列中选择最先进入该队列的进程,将处理机分噢诶给它,使之投入运行,直到完成或尹某种原因而阻塞时才释放处理机。
FCFS属于不可剥夺(抢占)算法。从表面上看,它对所有作业都是公平的,但是如果有一个长作业先到达系统,就会使后面许多短作业等待很长时间,因此这种方法肯定不能作为分时系统和实时系统的调度方法,但是它常被结合在其他调度策略使用。比如在使用优先级作为调度策略的系统中,往往对多个具有相同优先级的进程按FCFS原则处理。
· 特点分析:算法简单,但是效率低下;对长作业较为有利,对短作业不利;利于CPU繁忙型作业,不利于I/O繁忙型作业。
短作业优先(SJF)调度算法
短作业(进程)优先调度算法是指对短作业(进程)优先调度算法。短作业优先调度算法从后备队列中选择一个或若干估计运行时间最短的作业,将它们调入内存运行;短进程优先(SPF)调度算法是从就绪队列中选择一个估计运行时间最短的进程,将处理机分配给它,使之立即指向,直到完成或发生某时间而阻塞时,才释放处理机。
但是这种算法有着不容忽视的缺点:
①该算法对长作业不利,SJF中长作业的周转时间会增加。更糟的是,若一旦有长作业进入系统的后备队列,由于调度程序总是优先调度那些短作业(即使是后来的短作业也会被优先安排给处理机),导致长作业长期不被调度,饿死在后备队列中。
②完全没有考虑作业的紧迫程度,因而不能保证紧迫的作业会被及时处理。
③由于作业的长短只是根据用户所提供的预估的执行时间而定的,而用户又可能会有意无意地缩短其作业的估计运行时间,使得算法不一定能真正做到短作业优先调度。
但这算法的优点也显而易见:平均等待时间、平均周转时间最少。
优先级调度算法
又称优先权调度算法,它既可以用于作业调度,又可用于进程调度。该算法的优先级用于描述作业运行的紧迫程度。
在作业调度中,优先级调度算法每次从后备作业队列中选择优先级最该的一个或几个作业,将他们调入内存,分配必要的资源,创建进程并放入就绪队列。在进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,并分配处理机,运行。
根据新的更高的优先级进程能否抢占正在执行的进程,可将该调度算法分为如下两种:
①非剥夺(抢占)式优先级调度算法:当一个进程正在处理机上运行时,即使有某个更在重要或者紧迫的进程进入就绪队列,仍然让正在运行的进程继续运行,直到由于自身的原因而主动让出处理机时(任务完成或等待),才把处理机分配给更重要或紧迫的进程。
②剥夺式优先级调度算法:当一个进程正在处理机上运行,若有某个更为重要或紧迫的进程进入就绪队列,则立即暂停正在运行的进程,将处理机分配给更重要或紧迫的进程。
而根据进程创建后其优先级是否可以改变,可以将进程优先级分为一下两种:
①静态优先级:优先级是在创建进程时确定的,并且进程的整个运行期间保持不变。确定静态优先级的主要依据有进程类型、进程对资源的要求、用户要求。
②动态优先级:在进程运行过程中,根据进程情况的变化动态调整优先级。动态调整优先级的主要依据有进程占有CPU的时间的长短、就绪进程等待CPU时间的长短。
一般来说,进程优先级可以参考一下原则:
①系统进程>用户进程。
②交互型进程>非交互型进程(前台进程>后台进程)
③I/O型进程>计算型进程。
高响应比优先调度算法
主要用于作业调度,是对FCFS调度算法和SJF调度算法的一种综合平衡,同时考虑了每个作业的等待时间和估计的运行时间。在每次进行作业调度时,先计算后备队列中每个作业的响应比,从中选出响应比最高的作业投入运行。
响应比的变化规律可描述为:
响应比Rp = (等待时间+要求服务时间)/要求服务时间
根据公式可知:
①作业的等待时间相同时,要求服务时间约旦,响应比越高,有利于短作业。
②要求服务时间相同时,作业的响应比由其等待时间决定,等待时间越长,其响应比越高,因而它实现的是先来先服务。
③对于长作业,作业的响应比可以随等待时间的增加而提高,等待时间足够长时,其响应比便可升到很高,从而可以获得处理机,不会饿死。
时间片轮转调度算法
时间片轮转调度算法主要适用于分时系统。在这种算法中,系统将所有就绪进程按到达时间的先后次序排成一个队列,进程调度程序总是选择就绪队列中的第一个进程执行,即先来先服务的原则,但是仅能运行一个时间片。在使用完一个时间片后,即使进程并未完成其运行,它也必须释放出(被抢占)处理机给下一个就绪的进程,而被抢占的进程返回到就绪队列的末尾重新排队,等候再次运行。
在时间片轮转的调度算法中,时间片的大小对系统性能有很大影响。如果时间片足够大,以至于所以进程都能在一个事件内执行完毕,则时间片轮转调度算法就退化成FCFS算法。如果时间片很小,则处理机将在进程间过于频繁地切换,使得处理机开销增大,而真正用于运行用户进程的时间将减少。因此,时间片的选择要适当,可以根据系统响应时间、就绪队列中的进程数目和系统的处理能力等决定。
多级反馈队列调度算法
多级反馈队列调度算法是时间片轮转算法和优先级调度算法的综合与发展。通过动态调整进程优先级和时间片的大小,多级反馈队列调度算法可以兼顾多方面的系统目标。例如,为了提高系统吞吐量和缩短平均周转时间而照顾短进程;为了获得较好的I/O设备利用率和缩短响应时间而照顾I/O型进程;同时,也不必实现估计进程的执行时间。