操作系统面经 - 进程与线程

(分为知识点问题和零散问题)

目录

1、进程线程定义与区别

1.1 什么是进程

1.2 什么是线程

1.3 线程组成

1.3 为什么引入进程/线程,两者的优点

1.4 线程与进程的区别 / 选择 

1.5 进程线程私有和共享的资源

2 进程线程通信同步互斥

2.1 进程间通信

2.2 线程间通信(同步/互斥)

3 僵尸进程与孤儿进程

4 进程调度

4.1 三级调度

4.2 调度算法

4.3 死锁

5 多线程的实现 与 java thread原理


1、进程线程定义与区别

1.1 什么是进程

  1、进程是系统资源分配的最小单位,它是程序执行时的一个实例。

  2、程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列。

  3、进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

1.2 什么是线程

    1、线程是CPU调度的最小单位,它是进程的一个执行流。

    2、一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量

    3、线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。

对于无线程系统,进程是资源调度、分配的独立单位。

1.3 线程组成

线程,有时称为轻量级进程,是CPU使用的基本单元。

线程由线程ID、程序计数器、寄存器集合和堆栈组成。与属于同一进程的其他线程共享其代码段、数据段和其他操作系统资源(如打开文件和信号)。

线程有四种状态:新生状态、可运行状态、被阻塞状态、死亡状态。状态之间的转换如下图所示:

1.3 为什么引入进程/线程,两者的优点

引入进程是为了提高CPU的执行效率,减少因为程序等待带来的CPU空转以及其他计算机硬件资源浪费而提出的。进程优点:具有动态性(进程是程序的一次执行过程,动态产生且动态消亡),并发性, 异步性每一个进程按照自定逻辑,不考虑其他进程的运行,各自占用CPU);独立性(进程是系统分配资源的最小单位, 有了线程后,调度CPU的单位是线程)。

引入线程是为了减少进程间切换和创建的开销,提高执行效率和节省资源。线程优点:创建、切换代价低,占用资源少,可并行数多,在等待慢速I/O操作的同时,可执行其他计算任务,计算任务分解到多线程中实现,适合I/O密集型应用,线程可以同时等待不同的I/O操作。

1.4 线程与进程的区别 / 选择 

先考虑两者的关系:

    1、线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。

    2、一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个进程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。进程间不会相互影响,每一个进程拥有一个完整的虚拟空间地址,并不依赖线程而独立存在。而一个线程挂掉将导致整个进程挂掉。

    3、系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,出了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。与进程的控制表PCB相似,线程也有自己的控制表TCB,但是TCB中所保存的线程状态比PCB表中少多了。

之后对比与优劣:

    1、从数据共享、同步维度来讲,多进程之间数据是分开的所以同步是简单的,但是共享是复杂的,需要用到IPC机制,及进程间通信。而线程之间数据是共享的,所以数据共享简单,但是同步复杂。所以基于这一点上两者各有优势。

    2、从内存、CPU、创建切换维度来讲,多进程占用内存多,创建销毁切换复杂,CPU利用率低、速度慢。但是多线程占用内存少,创建销毁切换简单,CPU利用率高、速度快。所以基于这一点上线程占有优势。(系统要每次都给进程分配/回收资源,如内存空间,I/O设备等)

    3、从可靠性维度来讲,多进程间互不影响,而一个线程挂掉,整个进程都挂掉。进程好。

    4、从编程、调试维度,进程编程简单,调试简单。

    5、从分布式维度,多进程适用于多核、多机分布式,如果一台机器不够,拓展到多台机器比较简单。多线程适用于多核分布式。

最后如何选择:

    1、如果需要频繁穿甲胺销毁则优先使用线程

    2、如果需要大量计算的优先使用线程。

    3、步骤之间强相关的处理用线程,弱相关的处理用进程

    4、要拓展到多机分布的用进程,多核分布的用线程。

1.5 进程线程私有和共享的资源

进程之间私有和共享的资源

  • 私有:地址空间、堆、全局变量、栈、寄存器

  • 共享:代码段,公共数据,进程目录,进程 ID

线程之间私有和共享的资源

  • 私有:线程栈,寄存器,程序计数器

  • 共享:堆,地址空间,全局变量,静态变量

2 进程线程通信同步互斥

2.1 进程间通信

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

参考:https://www.cnblogs.com/zgq0/p/8780893.html (里面的代码非常好)

2.1.1 无名管道(PIPE):

1、特点    

这里只指无名管道。只能半双工通信,即单向交替传输,具有固定的读端和写端。

它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)

它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

2、原理

#include <unistd.h>
int pipe(int fd[2]);    // 返回值:若成功返回0,失败返回-1

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。要关闭管道只需将这两个文件描述符关闭即可。

单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。

3、优缺点

优点:简单方便

缺点:局限于单向通信 / 只能创建在它的进程以及其有亲缘关系的进程之间 / 缓冲区有限

2.1.2 有名管道(FIFO):

1、特点

只能半双工通信,即单向交替传输,具有固定的读端和写端。

FIFO可以在无关的进程之间交换数据,常用于客户-服务器的应用程序中,FIFO作为汇聚点在客户进程和服务器进程之间传递数据,

FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

2、原理

#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);// 返回值:成功返回0,出错返回-1

FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。

‘客户进程—服务器进程“ 通信的实例:

write_fifo的作用类似于客户端,可以打开多个客户端向一个服务器发送请求信息,read_fifo类似于服务器,它适时监控着FIFO的读端,当有数据时,读出并进行处理,但是有一个关键的问题是,每一个客户端必须预先知道服务器提供的FIFO接口

3、优缺点

优点:可以实现任意关系的进程间的通信

缺点:长期存于系统中,使用不当容易出错 / 缓冲区有限

2.1.3 消息队列:

1、特点

消息队列是消息的链表,存放在内核中并由消息队列标识符(ID)标识

消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

并且可以根据消息类型有选择地接收纤细,不像FIFO那样默认接收。

独立于读写进程存在,可以避免同步阻塞问题,比如避免FIFO中同步管道的打开和关闭时可能产生的困难,通过系统调用函数来实现消息发送和接收之间的同步,无需考虑同步问题,方便。

2、缺点

信息的复制需要额外消耗 CPU 的时间,不适宜于信息量大或操作频繁的场合

2.1.4 信号量:

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

1、特点

信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

2、缺点

信号量有限

2.1.5 共享内存:

指两个或多个进程共享一个给定的存储区。无需复制,通信是通过将共享空间缓冲区直接附加到进程的虚拟地址空间中来实现的,这段共享内存由一个进程创建,但多个进程都可以访问。

1、特点

共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

2、缺点

多个进程可以同时操作,所以需要考虑进程间的读写操作的同步问题。

利用内存缓冲区直接交换信息,内存的实体存在于计算机中,只能同一个计算机系统中的诸多进程共享,不方便网络通信

2.1.6 信号:

一种比较复杂的通信方式,用于通知接收进程某个事件已经发生

2.1.7 套接字:

可用于不同计算机间的进程通信 底层是 tcp/ip协议。

1、优点

传输数据为字节级,传输数据可自定义,数据量小效率高

传输数据时间短,性能高

适合于客户端和服务器端之间信息实时交互

可以加密,数据安全性强

2、缺点

对传输的数据进行解析,转化成应用级的数据

2.2 线程间通信(同步/互斥)

线程间通信目的是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

线程同步是指线程之间的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。

线程间通信的方式分为:用户模式和内核模式。

内核模式利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态。方法包含:PV事件+信号量+互斥量

用户模式就是不需要切换到内核态,只在用户态完成操作。方法包含:原子操作(如单一的局部变量)、临界区。

2.2.1 锁机制(原子操作)

包括互斥锁/量(mutex)、读写锁(reader-writer lock)、自旋锁(spin lock)、条件变量(condition)

  • 互斥锁/量(mutex):提供了以排他方式防止数据结构被并发修改的方法。

  • 读写锁(reader-writer lock):允许多个线程同时读共享数据,而对写操作是互斥的。

  • 自旋锁(spin lock)与互斥锁类似,都是为了保护共享资源。互斥锁是当资源被占用,申请者进入睡眠状态;而自旋锁则循环检测保持者是否已经释放锁。

  • 条件变量(condition):可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

2.2.2 信号量机制+事件

无名线程信号量 / 有名线程信号量

2.2.3 信号机制

2.2.4 屏障(临界区)

屏障允许每个线程等待,直到所有的合作线程都达到某一点,然后从该点继续执行。

2.3

3 僵尸进程与孤儿进程

正常情况下,子进程是通过父进程创建的,子进程再创建新的进程。那么父进程结束的时候,他的子进程应该都结束了。

但是子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。这个过程中会导致孤儿进程和僵尸进程问题。

3.1 僵尸进程

现象:进程号被占用,但这个进程已经退出了。

原因:一个父进程使用fork创建子进程,在子进程退出(exit)的时候,为了保证只要父进程想知道子进程结束时的状态信息就可以得到,在每个进程退出的时候,内核释放该进程所有的资源(包括打开的文件,占用的内存等 )但是仍然保留进程号,退出状态,运行时间等(这时用ps命令就能看到子进程的状态是“Z”)。直到父进程通过wait / waitpid来取时才释放。如果父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

危害:统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。

3.2 孤儿进程

现象:没有父进程的进程。一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。

危害:没什么危害,可解决。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init进程(进程号为1),而init进程会循环地wait()他的子进程,对这些孤儿进程完成状态收集工作。因此孤儿进程本身并不会有什么危害。

3.3 孤儿、僵尸进程 例子

黑客可以创建一个这样的进程,它定期产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程退出之后的事情,则一概不闻不问。这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。

严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程就能瞑目而去了。

4 进程调度

进程调度方式主要是指具有不同优先级的进程到来时如何分配CPU,调度方式主要有 可剥夺 与 不可剥夺 两种。

可剥夺是当具有更高优先级的进程到来时,会强行的将正在运行进程的CPU资源分配给更高优先级的进程;不可剥夺则是必须等待正在运行的进程自动释放占用的CPU,才会将CPU再次分配。

4.1 三级调度

通常在操作系统中,一个作业从提交到完成需要经历三级调度。

高级调度:又称为长调度、作业调度、接纳调度。它决定处于输入池中的哪个后备作业可以调入主系统做好运行准备,称为一个或一组就绪进程。在系统中每个作业只需经过一次高级调度。

中级调度:又称为中程调度、对换调度。它决定处于交换区的哪个就绪进程可以调入内存,直接参与对CPU的竞争;而在内存资源不足时,为了将进程调入内存,则必须将内存中处于阻塞状态的进程调出至交换区,这相当于将处于内存的进程与交换区的进程交换位置。

低级调度:又称为短程调度、进程调度。它决定内存中的哪个就绪进程可以占用CPU,低级调度是操作系统中最活跃最核心的调度程序。

4.2 调度算法

1)先来先服务

这是最简单的方式,即按照作业提交或进程变更为就绪态的次序分配CPU。这种调度方式明显利于需要长作业的情况,但不利于需要频繁中断的作业。主要用于宏观调度。

2)时间片轮转

时间片轮转即每个进程都运行一定时间,它主要用于微观调度,目的是提升资源的利用率。时间片的长度可以从几毫秒到数百毫秒不等,又有固定时间片和可变时间片两种。

3)优先级调度

这种方式要求进程都有一个优先级,系统调度时总选择优先级更高的进程占用CPU,优先级的分配有两种方式:

静态优先级——在创建时就确定进程优先级,直至进程终止优先级也不会发生变化,通常根据三种因素决定:进程类型、资源需求、用户要求。

动态优先级——在创建时也赋予一个优先级,但运行过程中可被改变,以便调度更合理。如在就绪队列中等待越长则优先级将提高,而进程被执行一个时间片后则降低。

4)多级反馈调度

进程优先级考虑如下情况:

  1. I/O型进程——让其进入最高优先级队列,以及时响应I/O交互进程。

  2. 计算型进程——每次执行都会降低优先级,以期最终采用较长时间片执行,减少调度次数。

  3. 少数I/O型进程——I/O完成后则应逐渐降低优先级,不应长期处于高优先队列中。

它是时间片轮转及优先级的综合。优点有三:照顾短进程提高系统吞吐量、缩短平均轮转时间;照顾I/O需求较高的进程,提升I/O设备利用率和缩短响应时间;无需估计进程执行耗时,动态调整优先级。

4.3 死锁

原因:系统资源不足 / 资源分配不当 / 进程运行推进顺序不合适

产生条件:互斥 / 请求和保持 / 不剥夺 / 环路

预防:

  • 打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。

  • 打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。

  • 打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。

  • 打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。

  • 有序资源分配法

  • 银行家算法

5 多线程的实现 与 java thread原理

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值