操作系统常问面试问题 1 —— 进程和线程相关(定义、结构、占用资源、开销、区别、通信方式等等)

进程和线程

1、什么是进程、什么是线程

1、什么是进程

  • 1、 进程可以认为是程序执行的一个实例,进程是系统进行资源分配的最小单位(必说一句)
  • 2、一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元

2、什么是进程

  • 线程又被称为轻量级的进程,线程是操作系统可识别的最小执行和调度单位。

2、进程和线程的区别

  • 进程是操作系统进行资源分配的最小单位,线程是操作系统可识别的最小执行和调度单位。(开篇必回)
  • 1、包含和被包含: 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
  • 2、系统开销: 每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;
    • 为什么进程切换的开销比线程切换大呢?
      • 每个进程都有属于自己的、私有的、地址连续的虚拟内存,当然我们知道最终进程的数据及代码必然要放到物理内存上,那么必须有某种机制能记住虚拟地址空间中的某个数据被放到了哪个物理内存地址上,这就是所谓的地址空间映射,也就是虚拟内存地址与物理内存地址的映射关系,那么操作系统是如何记住这种映射关系的呢,答案就是页表
      • 进程切换比线程切换开销大是因为进程切换时要切页表,而且往往伴随着页调度,因为进程的数据段代码段要换出去,以便把将要执行的进程的内容换进来。本来进程的内容就是线程的超集。而且线程只需要保存线程的上下文(相关寄存器状态和栈的信息)就好了,动作很小
  • 3、通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预。
  • 4、安全:一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间)

3、进程私有和共享的资源、线程私有和共享的资源

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

  • 私有:地址空间、堆、全局变量、栈、寄存器
  • 共享:代码段,公共数据,进程目录,进程 ID

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

  • 私有:线程栈,寄存器,程序计数器
  • 共享:堆,地址空间,全局变量,静态变量
    在这里插入图片描述

5、进程间通信方式(宏观说明,API使用后续说明)

进程间通信的方式

  • 进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字 socket

5.1、管道

(1)匿名管道:在内核中申请一块固定大小的缓冲区,程序拥有写入和读取的权利,一般使用fork函数实现父子进程的通信。

特征:

  • 1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端,只能进行单向通信
  • 2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
  • 3)它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中

使用:

  • 1、 父进程创建管道,得到两个文件描述符指向管道的两端

  • 2、父进程fork出子进程,子进程也有两个文件描述符指向同一管道。

  • 3、父进程关闭fd[0],子进程关闭fd[1],即父进程关闭管道读端,子进程关闭管道写端(因为管道只支持单向通信)。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
    在这里插入图片描述

命名管道(named pipe)又被称为先进先出队列(FIFO):不同于匿名管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中。命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。值得注意的是,FIFO(first input first output)总是按照先进先出的原则工作,第一个被写⼊的数据将首先从管道中读出。

特点:

  • 命名管道可以用于任何两个进程间的通信,而不限于同源的两个进程。
  • 命名管道作为一种特殊的文件存放在文件系统中,而不是像管道那样存放在内核中。当进程对命名管道的使用结束后,命名管道依然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会自行消失。

5.2、消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标记。(消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点)具有写权限的进程可以按照一定得规则向消息队列中添加新信息,对消息队列有读权限得进程则可以从消息队列中读取信息

特点

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

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

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

优点:
可以实现任意进程间的通信,并通过系统调用函数来实现消息发送和接收之间的同步,无需考虑同步问题,方便

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

5.3、信号量

信号量:它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。

特点

  • a、信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
  • b、信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
  • c、每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
  • d、支持信号量组。

PV操作

  • P操作:负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;
  • V操作:负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。

5.4、信号

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

5.5、共享内存(Shared Memory)

指两个或多个进程共享一个给定的存储区。共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式

特点
1、共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
2、因为多个进程可以同时操作,所以需要进行同步。
3、信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

5.6、套接字

套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

6、线程间通信方式(宏观说明,API前面博客已说明)

锁机制:包括互斥锁/量(mutex)、读写锁(reader-writer lock)、自旋锁(spin lock)

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

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

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

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

信号量机制(Semaphore):为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。

信号机制(Signal):类似进程间的信号处理

7、有了进程,为什么还要有线程

线程产生的原因:

  • 进程可以使多个程序能并发执行,以提高资源的利用率和系统的昋吐量:但是其具有一些缺点:
    • 进程在同一时间只能干一件事
    • 进程在执行的过程中如果阻塞,整个进程就会挂起,即使进程中有些工作不依赖于等待的资源,仍然不会执行

因此,操作系统引入了比进程粒度更小的线程,作为并发执行的基本单位,从而减少程序在并发执行时所付出的时空开销,提高并发性。和进程相比,线程的优势如下:

  • 从资源上来讲,线程是一种非常“节俭“的多任务操作方式。在1inux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵“的多任务工作方式。
  • 从切换效率上来讲,运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间。
  • 从通信机制上来讲,线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进城下的线程之间贡献数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便

8、多进程与多线程间的对比、优劣与选择

在这里插入图片描述
在这里插入图片描述
选择

  • 1)需要频繁创建销毁的优先用线程
    这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

  • 2)需要进行大量计算的优先使用线程
    所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。

  • 3)强相关的处理用线程,弱相关的处理用进程

什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。

9、进程间的状态有哪些

在这里插入图片描述

  • 1.运行态,运行态指的就是进程实际占用CPU时间片运行时

  • 2.就绪态,就绪态指的是可运行,但因为其他进程正在运行而处于就绪状态

  • 3.阻塞态,除非某种外部事件发生,否则进程不能运行

10、僵尸进程、孤儿进程

孤儿进程(无害):

  • 当父进程先结束,子进程此时就会变成孤儿进程,孤儿进程会自动向上被init进程收养,init进程完成对状态收集工作。而且这种过继的方式也是守护进程能够实现的因素。

僵尸进程(有害):

  • 一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。

产生背景

  • 每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息:
    • 包括进程号the process ID,
    • 退出状态the termination status of the process,
    • 运行时间the amount of CPU time taken by the process等)。
  • 直到父进程通过wait / waitpid来取时才释放。但这样就导致了问题,如果进程不调用wait / waitpid的话,
    那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程.

危害场景

  • 有个进程,它定期的产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程。
  • 僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程

解决方法:

kill发送SIGTERM或者SIGKILL信号杀死该父进程

Ubuntu命令找到系统僵尸进程并杀死:

  • 1、 ps aux | grep 'Z' 来找到僵尸进程
  • 2、pstree -p -s PID来寻找编号为PID进程也就是僵尸进程的父级进程

11、守护进程

Linux Daemon(守护进程)是运行在后台的一种特殊进程,并且不被任何终端产生的终端信息所打断。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。

注意:
一个守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。

作用
Linux 服务器在启动时也需要启动很多系统服务,它们向本地或网络用户提供了 Linux 的系统功能接口,直接面向应用程序和用户,而提供这些服务的程序就是由运行在后台的守护进程来执行的。

Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括:

  • 系统日志进程syslogd、
  • web服务器httpd、
  • 邮件服务器sendmail
  • 数据库服务器mysqld等。

12、进程和PCB(进程控制块)

在Linux成功fork进程后,会在系统中创建一个task_struct(也称PCB, process control block),用来描述当前进程的状态、进程间关系、优先级和资源等信息。

在这里插入图片描述

标识符: 与进程相关的唯一标识符,用来区别正在执行的进程和其他进程。
状态:描述进程的状态,因为进程有挂起,阻塞,运行等好几个状态,所以都有个标识符来记录进程的执行状态。
优先级:如果有好几个进程正在执行,就涉及到进程被执行的先后顺序的问题,这和进程优先级这个标识符有关。
程序计数器:程序中即将被执行的下一条指令的地址。
内存指针: 程序代码和进程相关数据的指针。
上下文数据: 进程执行时处理器的寄存器中的数据。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表等。
记账信息: 包括处理器的时间总和,记账号等等。

猜你喜欢

转载自blog.csdn.net/JMW1407/article/details/107646230