嵌入式开发工程师笔试面试指南-操作系统-1
1 操作系统基础
1 操作系统基本特性⭐⭐⭐⭐⭐
1 并发,并发是系统中程序能够并发执行的特征,可使得操作系统有效提高系统中的资源利用率,增加系统的吞吐量。
并发和并行
并发:两个或者多个事件在同一时刻发生。
并行:两个或者多个时间在同一时间间隔内发生。在多道程序下,并发性是指在一段时间内宏观上有多道程序同时运行,但是单处理机系统中,每一时刻只能有一道程序执行。
2 共享 在操作系统环境下的资源共享成为资源复用,是指系统中资源可供内存中多个并发执行的进程共同使用。
互斥共享和同时访问
互斥共享:系统的某些资源,虽然可以提供给多个进程使用,但应规定在一段时间内,只允许一个进程访问资源。
同时访问:系统中还有另一类资源,允许在一段时间内由多个进程同时对他们进行访问
并发和共享是操作系统的两个最基本的特征,它们互为存在的条件。一方面资源共享是以进行并发执行作为条件的,若系统不允许并发执行也就不存在资源共享问题;另一方面,若系统不能对资源共享实施有效管理,以协调好诸进程对资源共享的访问,也必然会影响到诸进程并发执行的程度,甚至无法并发执行。
3 虚拟 通过某种技术将一个物理实体变为若干个逻辑上对应的物的功能。在操作系统中可以通过时分复用技术和空分复用技术来实现虚拟的。
时分复用技术:可利用某设备为一用户服务的空闲时间,又转去为其他用户服务,使设备得到最充分的利用。
空分复用技术:利用存储器的空闲空间分区域存放和运行其他的多道程序,以此来提高内存的利用率。
4 异步 在多道程序环境下,系统允许多个进程并发执行。在单处理机程序环境下,由于系统只能运行一个进程,其余进程只能等待。由于资源等因素的限制,使进程的执行通常不是一气呵成的,而是走走停停的方式运行。
2 操作系统主要功能⭐⭐⭐⭐⭐
操作系统主要包括以下几个方面的功能 :
(1)CPU管理:其工作主要是进程调度,在单用户单任务的情况下,处理器仅为一个用户的一个任务所独占,进程管理的工作十分简单。但在多道程序或多用户的情况下,组织多个作业或任务时,就要解决处理器的调度、分配和回收等问题。
(2)存储管理,分为几种功能:存储分配、存储共享、存储保护、存储扩张。
(3)设备管理,分为以下功能:设备分配、设备传输控制、设备独立性。
(4)文件管理:文件存储空间的管理、目录管理、文件操作管理、文件保护。
(5)作业管理,是负责处理用户提交的任何要求。
3 CPU工作原理⭐⭐⭐⭐⭐
CPU的运行原理就是:控制单元在时序脉冲的作用下,将程序计数器里所指向的指令地址送到地址总线上去,然后CPU将这个地址里的指令读到指令寄存器进行译码。对于执行指令过程中所需要用到的数据,会将数据地址也送到地址总线,然后CPU把数据读到CPU的内部存储单元(就是内部寄存器)暂存起来,最后命令运算单元对数据进行处理加工。这个过程不断重复,直到程序结束。
4 CPU流水线⭐⭐⭐⭐⭐
CPU执行一条指令时,分为几个步骤,CPU并不会等一条指令完全执行完才执行下一条指令,而是像流水一样。
经典MIPS五级流水线将执行的生命周期分为五个部分:
1 取指
2 译码
3 执行
4 访存
5 写回
5 内核态和用户态的区别⭐⭐⭐⭐⭐
内核态和用户态是操作系统中的两种不同的运行态,它们的主要区别在于权限和特权级别的不同。
1 内核态(又称核心态、特权态)是操作系统的特权级运行态,只有操作系统内核本身才能够运行在内核态。在内核态运行时,操作系统拥有完全的管理权限和访问计算机硬件资源的权利,能够执行任何操作。
2 用户态(又称普通态、非特权态)是应用程序的运行态,应用程序只能运行在用户态。在用户态下,应用程序执行的指令和操作受到限制,不能直接访问和操作计算机硬件资源,必须通过操作系统提供的接口或系统调用来访问硬件资源。
什么时候进入内核态:共有三种方式:a、系统调用。b、异常。c、设备中断。其中,系统调用是主动的,另外两种是被动的。
总之,内核态和用户态的主要区别在于操作系统的权限和特权级别不同。在内核态下,操作系统拥有完全的权限和访问硬件资源的权利;而在用户态下,应用程序只能受到限制的操作,需要通过操作系统提供的特定接口来访问硬件资源。操作系统能够很好地利用这种不同特权级别来保证计算机系统的安全和稳定性,防止应用程序对计算机系统造成不必要的损害。
6 系统调用⭐⭐⭐⭐⭐
系统调用(System call)是操作系统提供给应用程序的一组接口,用于让应用程序访问操作系统提供的服务和资源。当应用程序需要访问操作系统底层的硬件资源或进行一些特殊的操作时,就需要通过系统调用发出请求,由操作系统代表应用程序完成请求的操作,操作系统将执行结果返回给应用程序。
系统调用可以被看作是应用程序与操作系统之间的桥梁。应用程序通过系统调用接口向操作系统发出请求,一旦请求被接收,操作系统就会在内核态下执行相应的操作,然后将结果返回给应用程序。系统调用是操作系统提供的底层接口,它能够访问各种硬件资源和系统资源,如文件系统、进程管理、网络通信等。
常见的系统调用包括:
1 文件 IO 系统调用:open, read, write, close 等
2 进程控制系统调用:fork, execve, getpid, wait 等。
3 网络通信系统调用:socket, bind, listen, accept, connect 等。
总之,系统调用是操作系统提供的向应用程序提供底层服务和操作系统资源的接口。它为应用程序提供了丰富的功能和服务,是操作系统和应用程序之间交互的主要方式之一。
7 物理内存层次⭐⭐⭐⭐⭐
物理内存有四个层次,分别是寄存器、高速缓存、主存、磁盘。
寄存器:速度最快、量少、价格贵。
高速缓存:次之。
主存:再次之。
磁盘:速度最慢、量多、价格便宜。
操作系统会对物理内存进行管理,有一个部分称为内存管理器(memory manager),它的主要工作是有效的管理内存,记录哪些内存是正在使用的,在进程需要时分配内存以及在进程完成时回收内存。
8 存储类型⭐⭐⭐⭐⭐
硬件存储器是指计算机系统中用来存储数据和程序的物理设备,如内存、硬盘、光盘等。硬件存储器按照工作原理和使用方式可以分为不同的类型。
随机存取存储器(RAM):是一种易失性存储器,用于临时存储程序和数据。RAM 存储器存取速度快,但电源关闭后存储的信息被释放,不能永久存储。
只读存储器(ROM):是一种只能读取不能写入的存储器,用于存放计算机系统的基本信息、固件和程序,如BIOS和操作系统等。
可编程只读存储器(PROM):是一种可编程的只读存储器,通过烧录来存储程序和数据。
闪存存储器(Flash Memory):是一种非易失性存储器,可作为计算机系统永久存储的媒介,如固态硬盘、USB闪存盘等。具有容量大、读取速度快、传输速率高等优点,已逐渐替代了旧的硬盘和光盘存储器。
磁盘存储器:包括硬盘和软盘,是常用的存储设备之一,可以永久存储数据并较容易被更新。
光盘存储器:包括CD、DVD等,是存储容量较大的永久性存储设备,不易受到磁场干扰,可长期保存。
除此之外,还有一些特殊的硬件存储器类型,如高速缓存存储器(Cache Memory)等,它们都有着不同的特征和使用方式,对于特定的计算机应用场景,需要根据实际需求进行选择和使用。
ROM | 只读存储器(Read-Only Memory) | 是一种只能读出事先所存数据的固态半导体存储器。其特性是一旦储存资料就无法再将之改变或删除。 |
RAM | 随机存取存储器(random access memory)又称作“随机存储器” | 是与CPU直接交换数据的内部存储器,也叫主存(内存)。它可以随时读写,而且速度很快。 |
SRAM | 静态随机存取存储器(Static Random-Access Memory) | 随机存取存储器的一种。所谓的“静态”,是指这种存储器只要保持通电,里面储存的数据就可以恒常保持。然而,当电力供应停止时,SRAM储存的数据还是会消失。 |
DRAM | 动态随机存取存储器(DRAM) | DRAM里面所储存的数据就需要周期性地更新。要刷新充电一次,否则内部的数据即会消失。 |
EPROM | (Erasable Programmable ROM),可擦除可编程ROM | 芯片通过紫外线可重复擦除和写入,解决了PROM芯片只能写入一次的弊端。EPROM芯片在写入资料后,还要以不透光的贴纸或胶布把窗口封住,以免受到周围的紫外线照射而使资料受损。使用并不方便。 |
PSRAM | 全称Pseudo static random access memory。指的是伪静态随机存储器。 | 内部的内存颗粒跟SDRAM的颗粒相似,但外部的接口跟SDRAM不同,不需要SDRAM那样复杂的控制器和刷新机制,PSRAM的接口跟SRAM的接口是一样的。PSRAM 内部自带刷新机制。 |
EEPROM | (electrically erasable, programmable, read-only )是一种电可擦除可编程只读存储器 | 其内容在掉电的时候也不会丢失。在平常情况下,EEPROM与EPROM一样是只读的,需要写入时,在指定的引脚加 上一个高电压即可写入或擦除,而且其擦除的速度极快 |
Flash | - | 它的主要特点是在不加电的情况下能长期保持存储的信息。就其本质而言,Flash Memory属于EEPROM(电擦除可编程只读存储器)类型。它既有ROM的特点,又有很高的存取速度,而且易于擦除和重写,功耗很小。 |
NOR Flash | - | NOR Flash的特点是芯片内执行(XIP, eXecute In Place),这样应用程序可以直接在flash闪存内运行,不必再把代码读到系统RAM中。NOR Flash的传输效率很高,在1~4MB的小容量时具有很高的成本效益,但是很低的写入和擦除速度大大影响了它的性能。 |
NAND Flash | - | NAND Flash结构能提供极高的单元密度,可以达到高存储密度,并且写入和擦除的速度也很快。应用NAND Flash的困难在于flash的管理需要特殊的系统接口。 |
2 进程⭐⭐⭐⭐⭐
1 进程的定义
进程是指在计算机中执行的一个程序。每个进程都有自己的内存空间和系统资源,如文件、端口、线程等。进程是计算机操作系统中的一个基本概念,是操作系统进行资源分配和调度的最小单位。每个进程都有一个唯一的标识符(进程号),可以在操作系统中进行管理和控制。
在操作系统中,每个进程都有自己的地址空间和指令和数据,这个地址空间通常由内核进行分配和管理。在运行时,进程可以创建和销毁多个线程,每个线程可以在进程的地址空间中独立运行。
每个进程可分为以下几个部分:
1 程序代码区:包含执行程序的指令代码
2 数据区:包含程序执行时所需的数据
3 堆:动态分配的内存空间
4 栈:运行时函数调用、返回和局部变量存储的空间
操作系统通过进程控制块(Process Control Block,PCB)来记录和管理每个进程的状态、所有权等信息,每个进程的PCB包含进程标识符、进程状态、CPU寄存器状态、内存分配状态、打开文件状态信息等。操作系统可以通过PCB来进行进程的调度和管理,以保证计算机系统的高效运行。
2 进程的特征
进程具有以下特征:
1 动态性:进程是动态产生和消失的,系统中可以同时存在多个进程,新的进程可以随时被创建,而已有的进程也可以随时被销毁。
2 独立性:每个进程都有自己的内存空间和系统资源,进程之间不会相互干扰。
3 并发性:多个进程可以同时运行,计算机系统可以快速地进行多任务处理,提高效率,有利于实现多人共享系统资源。
4 异步性:不同进程的执行速度不同,各个进程之间的执行顺序和时间不受约束,执行顺序和速度都是不可预知的。
5 独立的地址空间:每个进程有自己的地址空间,进程之间的数据是相互隔离的,提高了计算机系统的安全性。
6 同步与通信:不同的进程之间可以进行通信,可以利用操作系统提供的信号量、共享内存、管道等机制来实现进程间的同步和通信。
7 共享与互斥:在进程并发执行时,可能会有多个进程要对同一资源进行访问,通过锁机制实现资源的互斥访问,避免了资源冲突和死锁问题。
3 进程的五种基本状态及转换
进程有五种基本状态:创建状态、就绪状态、运行状态、阻塞状态和终止状态。
1 创建状态:当操作系统接受到用户进程创建的请求时,会为进程分配资源,并将进程的PCB加入到系统的进程队列中。
2 就绪状态:当进程具备运行条件时,它被加入就绪队列中,等待操作系统为其分配CPU执行时间。
3 运行状态:当进程获得CPU执行权后,进程开始运行,执行其指令,直到结束或者进入阻塞状态。
4 阻塞状态:当进程在运行时需要等待其它的事件发生,比如等待I/O的完成,或者等待系统调用的结果,进程会从运行状态转换到阻塞状态。
5 终止状态:当进程完成其任务或者因为异常情况终止时,进程会从任何状态转移到终止状态,回收并释放系统资源。
进程状态之间的转换如下:
1 从创建状态到就绪状态:系统为进程分配资源后,进程进入就绪状态,等待系统分配CPU执行时间。
2 从就绪状态到运行状态:当进程获得CPU执行资格时,进程进入运行状态,开始执行。
3 从运行状态到就绪状态:当进程执行完其指令后, 或者因为时间片用完而被中断时,进程从运行状态转换到就绪状态。
4 从运行状态到阻塞状态:当一个进程在执行中需要等待其它事件的发生,比如等待I/O完成时,进程会从运行状态转换为阻塞状态,等待事件完成。
5 从阻塞状态到就绪状态:当一个进程等待的事件完成时,它就转移到就绪状态,等待系统重新为其分配CPU执行时间。
6 从运行状态到终止状态:当进程执行完成或者因为异常情况终止时,它就转移到终止状态,系统回收并释放该进程所占有的资源。
4 创建进程的方式?
创建进程的方式可以分为进程的创建和进程的替换两种情况。
一、进程的创建(fork)
在 UNIX/Linux 系统中,可以使用 fork 系统调用来创建一个新的进程。系统会复制一份与父进程相同的子进程,但是父子进程之间的内存空间是独立的,父子进程之间通过进程间通信的方式进行数据交换和通信。在 fork 成功后,父进程和子进程各自返回不同的值,而且每个进程会拥有自己唯一的进程 ID。
二、进程的替换(exec)
进程的替换是指当前进程被另一个程序替换成新的进程,这个新进程会完全接管原有的进程的资源,包括进程 ID 等。在 UNIX/Linux 系统中,可以使用 exec 系统调用来进行进程的替换。
一般来说,可以先使用 fork 系统调用创建一个新的进程,然后再使用 exec 系统调用加载新的程序代码,从而实现进程的替换。这样可以保证在进程替换后,依然保留原进程的一些状态和资源信息。
5 什么时候用进程?
使用进程的情况:
- 需要独立的地址空间和系统资源:如果任务需要运行在独立的环境中,不同任务之间的数据隔离较为重要,那么可以选择使用进程。
- 需要更高的安全性和稳定性:如果一个任务的崩溃不应该影响其他任务的正常运行,使用进程可以保证更高的安全性和稳定性。
- 并行计算需求:如果任务需要充分利用多核处理器的计算能力,可以通过多个独立的进程并行执行来提高计算效率。
6 进程控制
进程控制指的是操作系统通过对进程的调度、调度策略、进程优先级等方式来控制进程的执行。进程控制由操作系统进行,是操作系统对进程状态的监控和管理。具体的控制方式包括以下几种:
1 进程调度:指操作系统在多个就绪进程中选取一个进程分配CPU执行时间的过程。进程调度的目的是提高CPU的利用率和系统的响应速度。
2 进程同步:多个进程之间为了协作完成某一任务,必须要进行同步。操作系统提供了各种机制,如信号量、互斥锁、条件变量等来实现进程间的同步。
3 进程通信:进程通信是指多个进程之间交换信息和共享数据的过程。进程通信是通过操作系统提供的IPC机制来实现的,如管道、消息队列、共享内存、Socket等。
4 进程安全:进程安全指保证进程能够正确、合法的执行,并防止进程间相互干扰和冲突的一组机制。进程安全是操作系统对进程进行的重要控制之一,其技术手段包括:鉴权、进程隔离、进程权限控制等。
5 死锁处理:在操作系统中,进程之间可能会因为资源的竞争和协作等原因产生死锁。操作系统提供了死锁预防和死锁解除的机制,如银行家算法、超时机制、资源剥夺等方式来避免和解除死锁。
7 进程同步方式
在多进程操作系统中,由于多个进程都需要共享系统资源,同时对这些资源进行访问可能会产生冲突,因此需要进行同步与互斥。常见的进程同步方式包括:
信号量: 信号量是一个整形变量,可以用来控制多个进程对共享资源的访问。在信号量机制中,对共享资源进行互斥访问的进程会对信号量进行P(proberen)操作,而释放共享资源的进程则进行V(verhogen)操作。当信号量为1时,表示共享资源可以被访问,为0时表示已经被占用。
互斥锁:互斥锁是一种二元信号量,只有0和1两种状态,可以用来保证多个进程互斥地访问共享资源。只有获取到锁的进程才能访问共享资源,其他进程需要等待锁的释放。当锁被占用时,其他进程对锁的请求会被阻塞,直到锁被释放。
条件变量:条件变量可以用来实现进程间的同步和通信。条件变量通常与互斥锁一起使用,当共享资源的某个条件未得到满足时,会阻塞等待,当获取到满足条件的资源时,可以通过条件变量通知阻塞的进程获取共享资源。
读写锁: 读写锁是一种特殊的锁,用于在数据读取操作多于写入操作的情况下提高并发性能。具体来说,读写锁允许多个读者访问,但只允许一个写者访问。当写操作正在被执行时,所有读和写操作都将被阻塞。
消息队列:消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除;消息队列可以实现消息的随机查询,可以按照消息的类型读取。
管程:一个进程通过调用管程的一个过程进入管程。在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。
总之,进程同步是保证多进程共享资源的正确使用的关键,以上介绍的同步方式只是提供了一些常见的机制,实际上还有很多不同的技术可以用于进程同步。
8 进程通信方式
在多进程操作系统中,进程通信是实现多个进程之间交换数据和信息的一种非常重要的机制。常见的进程通信方式包括:
1 管道: 管道是一种进程间的半双工数据通信方式,有两种类型:匿名管道和命名管道。匿名管道是一种无名的单向管道,只能在有亲缘关系的进程之间使用;命名管道是一种有名的单向FIFO管道,允许无亲缘关系的进程之间使用。
2 消息队列: 消息队列是一种消息传递机制,进程可以通过向消息队列发送消息来实现与其他进程的通信。消息队列通过一个标识符进行标识,允许不同的进程通过该标识符进行访问。
3 共享内存: 共享内存是一种高效的进程通信方式,多个进程可以通过共享内存来访问同一份数据。共享内存允许多个进程在其地址空间中映射同一块物理内存,从而在各进程间实现数据的共享。
4 信号: 信号是一种异步通信机制,用于向进程发送通知。进程不需要在任何时候等待信号的到来,它可以在任何时候处理信号的到来。信号可以用于进程间的通信,但是仅仅限于一些标准信号,通讯是不可靠的。
5 Socket: Socket 是一种跨机器和跨平台的进程间通信方式,是应用层通信接口(API)的一种实现方式,可以在不同的计算机间通信。Socket 的实现方式包括 TCP、UDP、IP 等协议。
综上所述,不同的进程通信方式各有优劣,并且根据实际需要选择合适的进程通信方式进行开发。
9 进程通信中的管道实现原理是什么?
操作系统在内核中开辟一块缓冲区(称为管道)用于通信。管道是一种两个进程间同一时刻进行单向通信的机制。因为这种特性,管道又称为半双工管道,所以其使用是有一定的局限性的。半双工是指同一时刻数据只能由一个进程流向另一个进程(一端负责读,一端负责写);如果是全双工通信,需要建立两个管道。
管道分为无名管道和命名管道,无名管道只能用于具有亲缘关系的进程直接的通信(父子进程或者兄弟进程),可以看作一种特殊的文件,管道本质是一种文件;命名管道可以允许无亲缘关系进程间的通信。
管道原型如下:
#include <unistd.h> int pipe(int fd[2]);
管道两端可分别用描述字fd[0]以及fd[1]来描述。注意管道的两端的任务是固定的,即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另 一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将发生错误。一般文件的 I/O 函数都可以用于管道,如close()、read()、write()等。
通信是指两个进程之间的信息交互,而pipe()函数创建的管道处于一个进程中间,单个进程中的管道几乎没有任何用处。因此一个进程在由 pipe()创建管道后,一般再使用fork() 建立一个子进程,然后通过管道实现父子进程间的通信。父子进程都有读端和写端,子进程的是从父进程复制过来的。
具体步骤如下:
1 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
10 什么是信号量,有什么作用?
概念:信号量本质上是一个计数器,用于多进程对共享数据对象的读取,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻可以有多个进程对资源进行访问。
原理:由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),具体的行为如下:
(1)P(sv)操作:如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行(信号量的值为正,进程获得该资源的使用权,进程将信号量减1,表示它使用了一个资源单位)。
(2)V(sv)操作:如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1(若此时信号量的值为0,则进程进入挂起状态,直到信号量的值大于0,若进程被唤醒则返回至第一步)。
作用:用于多进程对共享数据对象的读取,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻可以有多个进程对资源进行访问。
#include <iostream> #include <semaphore> #include <thread> std::counting_semaphore<1> semaphore; // 创建一个信号量,初始值为 1 int counter = 0; // 共享资源 void IncrementCounter() { semaphore.acquire(); // P(sv) 操作 // 访问共享资源 counter++; std::cout << "Counter: " << counter << std::endl; semaphore.release(); // V(sv) 操作 } int main() { constexpr int NumThreads = 3; std::vector<std::thread> threads; // 创建多个线程并启动 for (int i = 0; i < NumThreads; i++) { threads.push_back(std::thread(IncrementCounter)); } // 等待所有线程完成 for (auto& thread : threads) { thread.join(); } return 0; }
11 多进程内存共享可能存在什么问题?如何处理?
多进程内存共享可能存在以下问题:
- 竞争条件(Race Condition):当多个进程同时访问和修改共享内存时,由于执行顺序的不确定性,可能导致数据不一致或不正确的结果。
- 数据同步问题:不同的进程可能以不同的速度访问共享内存,导致数据在读取和更新之间的时间差异,进而引发数据不一致的问题。
- 死锁(Deadlock):如果多个进程在访问共享内存时发生互相等待的情况,可能导致死锁,使得进程无法继续执行。
为了处理多进程内存共享的问题,可以采取以下措施:
- 使用互斥锁(Mutex):通过在访问共享内存之前获取互斥锁,并在访问完成后释放锁,可以确保同一时间只有一个进程访问共享内存,从而避免竞争条件。
- 使用信号量(Semaphore):通过使用信号量来同步进程的访问,可以控制同时访问共享内存的进程数量,从而避免数据同步问题和死锁。
- 使用条件变量(Condition Variable):条件变量可以用于进程间的通信和同步,它可以在特定条件满足时唤醒等待的进程,从而避免忙等待和减少资源消耗。
- 使用进程间通信机制(IPC):使用操作系统提供的进程间通信机制,如管道、消息队列、共享内存、套接字等,可以实现进程间的数据传输和同步,确保共享数据的正确性和一致性。
12 多进程的优缺点
多进程的优点:
- 独立性:每个进程拥有独立的内存空间,一个进程的崩溃不会影响其他进程的执行。
- 安全性:进程之间通过操作系统提供的通信机制交互,可以有效隔离和保护数据。
- 可扩展性:可以更容易地在多个机器上部署,实现分布式计算,提高系统的处理能力和吞吐量。
多进程的缺点:
- 开销:创建和管理进程的开销较大,包括内存和资源的分配、上下文切换等,可能影响系统性能。
- 通信复杂:进程间通信需要使用特定的IPC机制,编写和维护较为复杂。
13 互斥量能不能在进程中使用?
能。
不同的进程之间,存在资源竞争或并发使用的问题,所以需要互斥量。
进程中也需要互斥量,因为一个进程中可以包含多个线程,线程与线程之间需要通过互斥的手段进行同步,避免导致共享数据修改引起冲突。可以使用互斥锁,属于互斥量的一种。
14 Linux的fork的作用
fork函数用来创建一个子进程。对于父进程,fork()函数返回新创建的子进程的PID。对于子进程,fork()函数调用成功会返回0。如果创建出错,fork()函数返回-1。
fork()函数不需要参数,返回值是一个进程标识符PID。返回值有以下三种情况:
(1) 对于父进程,fork()函数返回新创建的子进程的PID。
(2) 对于子进程,fork()函数调用成功会返回0。
(3) 如果创建出错,fork()函数返回-1。
fork()函数创建一个新进程后,会为这个新进程分配进程空间,将父进程的进程空间中的内容复制到子进程的进程空间中,包括父进程的数据段和堆栈段,并且和父进程共享代码段。这时候,子进程和父进程一模一样,都接受系统的调度。因为两个进程都停留在fork()函数中,最后fork()函数会返回两次,一次在父进程中返回,一次在子进程中返回,两次返回的值不一样,如上面的三种情况。
14 父进程、子进程的关系以及区别
父进程和子进程是操作系统中进程间的一种关系,它们既有紧密的联系,又在多个方面存在区别,以下是详细介绍:
关系
- 创建与被创建:父进程通过系统调用(如 fork 等)创建子进程,子进程是父进程的一个副本,继承了父进程的许多属性。
- 资源继承:子进程会继承父进程的部分资源,如打开的文件描述符、信号处理方式、当前工作目录等。这使得子进程在一定程度上可以基于父进程已有的资源进行后续的操作。
- 执行顺序:父进程和子进程的执行顺序是不确定的,取决于操作系统的调度算法。它们可以并发执行,也可能在某一时刻只有一个进程在运行。
- 进程层次结构:在操作系统的进程树中,父进程是子进程的上一级节点,多个子进程可以有同一个父进程,形成一种层次化的进程组织结构,便于操作系统进行管理和调度。
区别
- 进程ID:每个进程在系统中都有一个唯一的进程ID。父进程在创建子进程时,会将子进程的进程ID分配给子进程。
- 进程关系:父进程与子进程之间建立了一种层次关系,父进程是子进程的创造者和管理者。
- 资源继承:子进程会继承父进程的大部分属性和资源,包括打开的文件、环境变量和当前工作目录等。
- 进程通信:父进程和子进程可以通过进程间通信机制来进行交互和数据共享,如管道、共享内存、消息队列等。
- 生命周期:父进程和子进程的生命周期是相互独立的。子进程可以在父进程退出后继续存在,成为孤儿进程,由系统的init进程接管管理。
16 什么是守护进程,如何创建?
守护进程是运行在后台的一种生存期长的特殊进程。它独立于控制终端,处理一些系统级别任务。
创建过程如下:
1 创建子进程,终止父进程。
2 调用setsid()创建一个新会话。
3 将当前目录更改为根目录。
4 重设文件权限掩码。
5 关闭不再需要的文件描述符。
17 孤儿进程与僵尸进程,如何解决僵尸进程
孤儿进程,是指一个父进程退出后,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并且由init进程对它们完整状态收集工作。
僵尸进程,是指一个进程使用fork函数创建子进程,如果子进程退出,而父进程并没有调用wait()或者waitpid()系统调用取得子进程的终止状态,那么子进程的进程描述符仍然保存在系统中,占用系统资源,这种进程称为僵尸进程。
所以两者的区别是:孤儿进程是父进程已退出,子进程未退出;而僵尸进程是父进程未退出,子进程已退出。
如何解决僵尸进程:
(1)一般,为了防止产生僵尸进程,在fork子进程之后我们都要及时使用wait系统调用;同时,当子进程退出的时候,内核都会给父进程一个SIGCHLD信号,所以我们可以建立一个捕获SIGCHLD信号的信号处理函数,在函数体中调用wait(或waitpid),就可以清理退出的子进程以达到防止僵尸进程的目的。
(2)使用kill命令。
打开终端并输入下面命令:
$ ps aux | grep Z
会列出进程表中所有僵尸进程的详细内容。
然后输入命令:
$ kill -s SIGCHLD pid(父进程pid)
这样子进程退出后,父进程就会收到信号了。
或者可以强制杀死父进程:
$ kill -9 pid(父进程pid)
这样父进程退出后,这些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并且由init进程对它们完成状态收集工作。
18 一个进程可以创建多少线程,和什么有关
理论上,一个进程可用虚拟空间是2G,默认情况下,线程的栈的大小是1MB,所以理论上一个进程可以创建2048个线程,当然更改编译器的设置可以创建多余2048个线程
因此,一个进程可以创建的线程数由可用虚拟空间和线程的栈的大小共同决定,只要虚拟空间足够,那么新线程的建立就会成功。如果需要创建超过2K以上的线程,减小你线程栈的大小就可以实现了,虽然在一般情况下,你不需要那么多的线程。过多的线程将会导致大量的时间浪费在线程切换上,给程序运行效率带来负面影响。
19 什么是进程上下文、中断上下文?
进程上下文是进程执行时的环境,包含用户地址空间(代码、数据、堆、栈)、寄存器上下文(程序计数器、通用寄存器、状态寄存器等的值)以及系统内核数据结构(如进程控制块)。它是进程运行的完整环境,进程切换时需保存和恢复上下文,以保证其下次能正确继续执行。
中断上下文是 CPU 响应中断时的现场信息及中断处理环境。中断发生时,CPU 会保存当前进程部分寄存器上下文作为中断现场,以便中断处理完恢复。同时,中断处理程序有自己的执行环境,包括中断向量表、中断服务例程等。中断上下文生命周期短,且处理时一般不允许进程调度,以保证及时、正确地响应外部事件。
20 说说进程调度算法有哪些?
- 先来先服务(FCFS):按照进程到达的先后顺序进行调度。先到达的进程先被执行,直到该进程完成或被阻塞。
- 短作业优先(SJF):根据进程的执行时间来进行调度,执行时间短的进程先被执行。该算法可以最小化平均等待时间,但可能导致长作业饥饿。
- 最短剩余时间优先(SRTF):在执行过程中,根据剩余执行时间来动态调度进程。如果一个新到达的进程的剩余执行时间比当前进程的剩余执行时间短,则进行切换。
- 优先级调度:为每个进程分配一个优先级,并按照优先级来调度进程。具有较高优先级的进程先被执行,较低优先级的进程可能会被较高优先级的进程长时间抢占。
- 时间片轮转调度(Round Robin):将可用的CPU时间划分为固定大小的时间片(如10ms),每个进程在一个时间片内被执行,然后被放到就绪队列的末尾。这样可以保证每个进程都有机会执行,并提供一种均衡的调度。
- 多级反馈队列调度:将就绪队列划分为多个队列,每个队列具有不同的时间片大小。新到达的进程首先进入第一个队列,如果在该队列执行完后还有剩余时间,则进入下一个队列,以此类推。这个算法可以根据进程的特性分配不同的优先级。
3 线程⭐⭐⭐⭐⭐
1 线程的定义
线程是操作系统中调度的基本单位,一个进程可以包含多个线程。线程是一条执行路径,有自己的程序计数器、寄存器和栈,但没有自己的地址空间和全局变量等资源,它们共享所属进程的资源和数据。
线程与进程不同,不需要独立的内存空间和系统资源,因此线程之间的切换比进程之间的切换更加快速和经济。多线程编程是一种利用多线程并发执行来提高程序性能和效率的编程模型。线程可以并行执行,这意味着多个线程可以同时执行不同的任务。在多核处理器上,多个线程可以同时运行,进一步提高了应用程序的性能。
多线程可以提高程序的响应速度和并发性能,也可以实现程序内部的并行计算,但多线程编程也会增加程序的复杂性,需要仔细地处理线程间的同步和通信等问题。线程间的共享数据也是一个需要特别注意的问题,需要使用特殊的同步机制来保证数据的正确性。
2 线程的实现
线程的实现通常由操作系统提供支持,主要通过线程库来实现。操作系统提供了一些系统调用来创建、启动和管理线程,而线程库则提供了一些封装和抽象,使得程序员可以更方便地使用线程。
线程库通常提供了一些函数接口,例如 pthread 库(POSIX 线程库)提供了一些创建、退出、调度、同步以及线程属性管理等函数:
pthread_create: 创建线程
pthread_exit: 结束线程
pthread_join: 等待线程结束
pthread_mutex_lock/pthread_mutex_unlock: 互斥锁加锁/解锁操作
pthread_cond_wait/pthread_cond_signal: 等待条件变量/发送信号到条件变量
线程库的实现通常通过操作系统提供的原语,封装了一些有用的线程控制和同步功能,可以实现多线程执行和线程间的通信。通过线程库,程序员可以更加方便地使用线程,而不需要直接与操作系统底层的线程调度机制打交道。
3 什么时候用线程?
使用线程的情况:
- 共享数据和资源:如果任务之间需要共享数据和资源,并且数据同步和通信较为频繁,使用线程可以更方便地访问和操作共享资源。
- 轻量级任务:如果任务比较轻量级,且并行执行可以提高效率,使用线程可以进行更快速的切换和调度,减少开销。
- 实时性要求:如果任务对实时性要求较高,使用线程可以更快响应事件和处理任务。
4 一个线程占多大内存?
一个线程在Linux系统中大约占用8MB的内存。这是因为Linux系统中的线程栈是通过缺页异常来进行内存分配的,不是所有的栈空间都会被实际分配内存。因此,8MB只是一个上限,实际的内存消耗会略微超过实际需要的内存。这个差额主要是由于内部损耗(每个线程内部的一些开销)所引起的,通常在4KB范围内.
5 进程、线程、协程是什么,区别是什么?
进程、线程和协程都是并发编程中常用的概念,它们各自有自己的特点和用途,下面是它们的简单介绍和区别:
进程(Process)
进程是操作系统中分配资源和调度的基本单位,它是正在运行的一个程序。每个进程拥有自己的地址空间、代码段、堆栈、数据段、进程控制块(PCB)等资源,进程之间内存独立,相互独立运行,进程间通信需要通过进程间通信(IPC)实现。
线程(Thread)
线程是进程的执行单元,是比进程更轻量级的调度单位。一个进程可以包含多个线程,同一进程内的多个线程共享进程的地址空间和资源,但每个线程有自己的堆栈、程序计数器、局部变量等值。线程之间通过共享的变量或信息进行通信或同步。
协程(Coroutine)
协程是一种用户级的轻量级线程,也称为微线程。协程可以在同一个线程中,通过协作式任务切换实现多任务并发。一个协程可以暂停执行,保存其当前状态,稍后重新恢复执行,协程之间通过yield和resume等语义来协作执行,可以更加细粒度的控制程序执行顺序。
区别:
1 进程拥有独立的地址空间和全局变量等资源,线程和协程则可以共享资源;
2 进程是操作系统中资源分配的基本单位,线程是调度的基本单位,协程可以在用户态中实现调度;
3 进程之间通信需要通过进程间通信(IPC),线程之间通信可以通过共享变量或信息实现,协程之间通信也是共享变量或信息;
4 线程和协程通常比进程轻量级,创建和上下文切换的开销较小,因此在处理大量并发任务时,相较于进程更加高效。协程比线程更加轻量与灵活,可以避免锁竞争、上下文切换等开销。
6 协程是轻量级线程,轻量级表现在哪里?
1 协程调用跟切换比线程效率高:协程执行效率极高。协程不需要多线程的锁机制,可以不加锁的访问全局变量,所以上下文的切换非常快。
2 协程占用内存少:执行协程只需要极少的栈内存(大概是4~5KB),而默认情况下,线程栈的大小为1MB。
3 切换开销更少:协程直接操作栈基本没有内核切换的开销,所以切换开销比线程少。
7 线程间通信的方式有哪些?
线程间的通信方式包括互斥量、信号量、条件变量、读写锁:
1 互斥量:采用互斥对象机制,只有拥有互斥对象的线程才可以访问。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
2 信号量:计数器,允许多个线程同时访问同一个资源。
3 条件变量:通过条件变量通知操作的方式来保持多线程同步。
4 读写锁:读写锁与互斥量类似。但互斥量要么是锁住状态,要么就是不加锁状态。读写锁一次只允许一个线程写,但允许一次多个线程读,这样效率就比互斥锁要高。
8 线程同步方式有哪些?
线程间的同步方式包括互斥锁、信号量、条件变量、读写锁:
1 互斥锁:采用互斥对象机制,只有拥有互斥对象的线程才可以访问。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
2 信号量:计数器,允许多个线程同时访问同一个资源。
3 条件变量:通过条件变量通知操作的方式来保持多线程同步。
4 读写锁:读写锁与互斥量类似。但互斥量要么是锁住状态,要么就是不加锁状态。读写锁一次只允许一个线程写,但允许一次多个线程读,这样效率就比互斥锁要高。
9 多线程的优缺点
多线程的优点:
- 轻量级:线程之间共享同一进程的内存空间和资源,创建和切换线程的开销较小。
- 资源共享:线程直接访问进程的共享资源,数据共享更方便,提高系统效率。
- 响应性:线程的创建和销毁速度快,可以更快地响应用户的请求。
多线程的缺点:
- 安全性问题:多个线程共享数据时需考虑同步和锁机制,以避免数据竞争和不一致的结果。
- 内存占用:每个线程都需要独立的栈空间和线程数据结构,增加内存消耗。
- 上下文切换开销:线程切换需要保存和恢复上下文,增加系统开销。
10 什么是线程同步和互斥
线程同步是指通过一定的机制确保多个线程按照一定的顺序和规则共享资源或进行协调工作,以避免出现并发访问导致的问题,例如竞态条件、数据不一致等。
互斥是一种用于保护共享资源的机制,确保同一时间只有一个线程可以访问该资源,避免出现数据竞争和冲突。通过使用互斥锁(Mutex),只有获得锁的线程才能进入临界区(访问共享资源的代码段),其他线程需要等待锁的释放。
同步是一种更广泛的概念,它意味着协调多个线程间的执行顺序和行为,以确保它们按照一定的规则和顺序执行。同步可以通过互斥来实现,但也可以使用其他的同步机制,如信号量、条件变量、事件等。
- 线程A想要访问共享资源之前,它会尝试获取互斥锁。
- 如果互斥锁当前没有被其他线程占用,线程A成功获得锁,并进入临界区,开始执行操作。
- 同时,线程B也想要访问共享资源,但发现互斥锁已经被线程A占用,所以线程B需要等待。
- 当线程A完成操作后,它会释放互斥锁,而此时线程B会被唤醒并尝试获取锁。
- 如果没有其他线程占用互斥锁,线程B可以获取锁并进入临界区,开始执行自己的操作。
- 当线程B完成操作后,它也会释放互斥锁,以便其他线程可以获取锁并执行操作。
11 线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?
线程同步是指协调多个线程之间的执行顺序和行为,以保证数据的一致性和正确性。同步可以通过使用互斥锁、条件变量、信号量等机制来实现。
阻塞是指当线程遇到某些条件而无法继续执行时,暂时挂起线程的状态。具体而言,当线程发起一个阻塞式的操作(如等待I/O、获取锁、等待条件满足等)时,它会进入阻塞状态,暂停执行,直到条件满足或被唤醒。
同步机制在某些情况下可能会导致线程阻塞。例如,在互斥锁的场景中,当一个线程尝试获取互斥锁但锁已经被其他线程占用时,它会被阻塞,等待锁的释放。直到获取到锁之后,线程才能继续执行。
然而,并不是所有的同步操作都会导致线程阻塞。例如,在使用无锁数据结构或者一些并发原语(如原子操作)的情况下,线程可以在不阻塞的情况下实现同步。
同样地,阻塞也不一定意味着同步。线程阻塞是由于等待特定条件的满足或是在某些操作完成之前无法继续执行。而同步是为了协调线程之间的顺序和行为,以保证数据的一致性。阻塞有时可能会与同步相关,但阻塞本身并不代表同步。
同步可能导致线程阻塞,但阻塞不一定与同步相关,它可能是由于其他因素引起的。
12 并发,同步,异步,互斥,阻塞,非阻塞的理解
并发:
并发是指多个任务或操作在同一时间段内执行,它们相互独立,不一定按照严格的顺序执行。
同步:
同步是为了协调多个任务或操作之间的顺序和行为,以确保数据的一致性。在同步中,任务或操作可能会按照特定的顺序执行或等待其他任务的完成,以满足特定的条件。
异步:
异步是指任务或操作可以独立于当前线程继续执行,而不需要等待其他任务完成。在异步操作中,任务可以在后台或另一个线程中执行,并且可以提供结果或通知以后再处理。
互斥:
互斥是指通过一种机制来确保同一时间只有一个任务或线程可以访问共享资源。它通过锁或信号量等机制实现,以避免数据竞争和冲突。
阻塞:
阻塞是指当一个线程或任务在执行过程中遇到某种条件而无法继续进行时,暂停执行,等待条件满足或被唤醒。在阻塞状态下,资源通常不可用,直到条件满足。
非阻塞:
非阻塞是指任务或操作在执行过程中不会暂停等待条件满足,而是立即返回并继续执行其他任务。非阻塞操作可以持续进行,而不会受到其他任务的影响。
13 进程和线程的区别?
进程(Process)和线程(Thread)都是操作系统中的概念,主要用于描述运行中的程序。
进程指的是一个执行中的程序,它是系统资源分配的基本单位,拥有独立的内存空间、数据栈、文件句柄和其他系统资源,进程间相互独立,可互相通信。在操作系统中,每个进程都有一个唯一的进程ID,可以通过这个ID来识别和管理进程。
线程指的是一个进程中的一个执行单元,是CPU调度和分派的基本单位,也是系统资源分配的基本单位。与进程相比,线程更轻量级,线程间通信比进程更加高效,线程之间共享同一进程的内存空间和其他系统资源,所以线程之间的切换速度比进程之间的切换速度更快。
总起来说,所谓进程,就是一个具有一定独立功能的程序关于某个数据集合上的一次运行活动,它是操作系统进行资源分配、调度和运行的基本单位;而线程则是进程的一个执行实例,是比进程更小的独立运行的基本单位。
14 有了进程,为什么还要有线程?
原因
进程在早期的多任务操作系统中是基本的执行单元。每次进程切换,都要先保存进程资源然后再恢复,这称为上下文切换。但是进程频繁切换将引起额外开销,从而严重影响系统的性能。为了减少进程切换的开销,人们把两个任务放到一个进程中,每个任务用一个更小粒度的执行单元来实现并发执行,这就是线程。
线程与进程对比
(1)进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
但多个线程共享进程的内存,如代码段、数据段、扩展段,线程间进行信息交换十分方便。
(2)调用 fork() 来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销依然不菲。
但创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。
15 单核机器上写多线程程序,是否要考虑加锁,为什么?
在单核机器上写多线程程序,仍然需要线程锁。
原因:因为线程锁通常用来实现线程的同步和通信。在单核机器上的多线程程序,仍然存在线程同步的问题。因为在抢占式操作系统中,通常为每个线程分配一个时间片,当某个线程时间片耗尽时,操作系统会将其挂起,然后运行另一个线程。如果这两个线程共享某些数据,不使用线程锁的前提下,可能会导致共享数据修改引起冲突。
16 多线程和多进程的不同?
(1)一个线程从属于一个进程;一个进程可以包含多个线程。
(2)一个线程意外死亡,可能导致进程挂掉,多线程也可能挂掉;一个进程挂掉,不会影响其他进程,多进程稳定。
(3)进程系统开销显著大于线程开销;线程需要的系统资源更少。
(4)多个进程在执行时拥有各自独立的内存单元,多个线程共享进程的内存,如代码段、数据段、扩展段;但每个线程拥有自己的栈段和寄存器组。
(5)多进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈;多线程切换时只需要切换硬件上下文和内核栈。
(6)通信方式不一样。
(7)多进程适应于多核、多机分布;多线程适用于多核。
17 进程和线程相比,为什么慢?
1 进程系统开销显著大于线程开销;线程需要的系统资源更少。
2 进程切换开销比线程大。多进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈;多线程切换时只需要切换硬件上下文和内核栈。
3 进程通信比线程通信开销大。进程通信需要借助管道、队列、共享内存,需要额外申请空间,通信繁琐;而线程共享进程的内存,如代码段、数据段、扩展段,通信快捷简单,同步开销更小。
18 什么是内核线程和用户线程?
内核线程和用户线程都是操作系统中的线程概念,它们之间最主要的区别在于受谁管理。
内核线程(Kernel Thread)是由操作系统内核管理和调度的线程,所有的内核线程都是在内核态下运行的,它们具有访问系统资源的能力,是操作系统的核心部分之一。内核线程通常由操作系统自身创建并管理,不依赖于任何特定的应用程序,所以在内核线程中可以访问所有的硬件设备和系统资源,是操作系统实现各种服务功能的基础。
用户线程(User Thread)是由应用程序程序自身创建和管理的线程,所有的用户线程都是在用户态下运行的,它们不能直接访问系统资源和硬件设备,只能通过系统调用向操作系统请求服务和资源。用户线程的创建和管理都由应用程序自身完成,所以用户线程和应用程序之间具有相对独立的关系。
在实际应用中,内核线程和用户线程大多数存在于混合型多线程应用程序中。一般来说,应用程序会将若干个用户线程映射到一个或多个内核线程上来执行,从而兼顾了用户线程的轻量化和内核线程的高效性能。
19 如何实现线程池?程池中线程的数量由什么确定?
线程池主要包含以下几个部分:任务队列、线程池管理器、工作线程和任务等。实现线程池需要以下几个步骤:
- 初始化线程池管理器,包括创建指定数量的工作线程,构建任务队列等。
- 向任务队列中添加任务。
- 工作线程从队列中取出任务并执行。
- 每个工作线程都要一直循环工作,直到被停止。
- 线程池管理器需要时刻监控线程池中的状态,维护线程池的大小,动态增加或减少线程的数量。
以下是一个简单的线程池实现示例:
#include <iostream> #include <vector> #include <queue> #include <mutex> #include <condition_variable> #include <thread> using namespace std; class ThreadPool { public: ThreadPool(size_t size) { if (size < 1) size = 1; // 至少有一个线程 for (size_t i = 0; i < size; ++i) { threads.emplace_back([this]() { while (true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(queue_mutex); condition.wait(lock, [this]() { return stop || !tasks.empty(); }); // 等待有任务或停止事件 if (stop && tasks.empty()) return; task = std::move(tasks.front()); tasks.pop(); } task(); } }); } } ~ThreadPool() { { std::unique_lock<std::mutex> lock(queue_mutex); stop = true; } condition.notify_all(); for (auto &thread : threads) thread.join(); } template<class F> void push(F &&f) { { std::unique_lock<std::mutex> lock(queue_mutex); tasks.emplace(std::forward<F>(f)); } condition.notify_one(); } private: std::vector<std::thread> threads; std::queue<std::function<void()>> tasks; std::mutex queue_mutex; std::condition_variable condition; bool stop = false; };
在这个示例代码中,我们使用std::vector<std::thread>
来保存线程池中的所有工作线程,使用std::queue<std::function<void()>>
来保存所有的任务。在实现中,每个线程都会不断从任务队列中获取任务执行,如果队列为空,则线程会进入等待状态。同时,线程池还支持动态添加任务,当有新的任务加入任务队列时,线程池会自动唤醒一个线程进行处理。最后,当需要销毁线程池时,线程池会通知所有工作线程停止工作,并等待所有线程完成任务。
线程数量和哪些因素有关:CPU,IO、并行、并发
如果是CPU密集型应用,则线程池大小设置为:CPU数目+1 如果是IO密集型应用,则线程池大小设置为:2*CPU数目+1 最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
所以线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
20 进程和线程相比,为什么慢?
- 进程系统开销显著大于线程开销;线程需要的系统资源更少。
- 进程切换开销比线程大。多进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈;多线程切换时只需要切换硬件上下文和内核栈。
- 进程通信比线程通信开销大。进程通信需要借助管道、队列、共享内存,需要额外申请空间,通信繁琐;而线程共享进程的内存,如代码段、数据段、扩展段,通信快捷简单,同步开销更小。
11 进程调度算法 ⭐⭐⭐⭐⭐
1 先来先服务调度算法
先来先服务(FCFS,First Come First Served)算法是操作系统中最早出现的进程调度算法之一,也是最简单的一种算法。它的工作原理是按照进程到达的时间先后,依次运行,直到当前进程结束或者阻塞,才会运行下一个进程。
在FCFS算法中,如果一个进程持续时间过长,会导致其他进程等待时间过长,从而影响系统整体的响应速度。缺点是无法保证短作业优先,因为长作业的到来会阻塞短作业的运行,从而导致平均等待时间和平均周转时间较长。
FCFS算法可以比较容易地实现和理解,但是对于一个实际的操作系统而言,它仍然具有许多问题。因此,FCFS算法现在在大多数情况下都只是作为其他进程调度算法的基础,而不是作为一种真正的优化算法使用。
2 短作业(进程)优先调度算法
短作业优先调度算法(SJF,Shortest Job First)是一种进程调度算法,通常是根据进程需要的CPU时间来排序调度的。它是一种非抢占式算法,在任意时间片都不会剥夺正在执行的进程,直到该进程完成或发生阻塞。
SJF算法可以保证优先短作业的运行,减少了平均等待时间和平均周转时间。如果没有新的短作业到达,SJF算法可以获得最佳的平均等待时间和平均周转时间。
但是,短作业优先调度算法存在一个很大的问题,即不利于长作业的处理。若有一个长作业在前面占据了一个核心,而后面的短作业们需要等待很长时间,这就导致了短作业在等待长作业时浪费了很多时间。这种现象称为“饥饿状态”。
因此,最优的策略是优先调度短作业,但是也不能完全无视长作业的存在,以防止CPU资源的浪费。因此,在SJF算法中通常进行一定的调整,例如加入抢占机制等,以便更好地处理长短作业间的关系。
3 高优先级优先调度算法
高优先级优先调度算法是一种进程调度算法,它按照优先级高低划分为多个队列,每个队列内的进程按照先来先服务的原则进行调度,优先级高的进程先被执行。如果有多个进程拥有相同的优先级,则按照先来先服务的原则进行调度。
该调度算法的优点是可以保证高优先级进程优先被服务,缩短高优先级进程的响应时间。但它也存在一些缺点,如可能出现低优先级进程一直得不到调度的情况,从而容易引发饥饿问题。
这种调度算法通常应用于实时操作系统中,对于那些对响应时间要求比较高的任务进行处理,例如一些需要及时响应的控制系统、测控系统等。
4 时间片轮转法
时间片轮转法是一种常见的进程调度算法。它将CPU的时间分成一个个时间片,每个进程被分配一个固定大小的时间片。当轮到该进程运行时,它只能运行一个时间片,时间片结束后CPU将转移到下一个进程。
如果当前进程在一个时间片之内没有执行完,它就会被暂停,被放到一个就绪队列的末尾,等待下一次的调度。这样,每个进程都能获得一定数量的CPU运行时间,避免了长时间等待的饥饿状态。
时间片轮转法能够保证每个进程都有机会获得CPU时间,是一种比较公平的调度算法。同时,它还可以兼顾短作业和长作业,因为长作业能够在多个时间片内完成。
然而,时间片的大小也是一个需要考虑的问题。时间片过小,会导致频繁的上下文切换,会浪费大量的CPU时间;时间片过大,会导致响应时间较长,不利于及时响应一些实时任务。
因此,为了兼顾各种因素,实际中通常需要根据具体情况来设置时间片的大小。
5 多级反馈队列调度算法
多级反馈队列调度算法是一种比较复杂的进程调度算法,它将进程按照运行时间长短分配到多个队列中,每个队列都有不同的优先级。一般来说,初始时将进程分配到优先级最低的队列,每个队列的优先级顺序逐渐升高。
在多级反馈队列调度算法中,每个队列都有一个时间片,进程在一个队列内运行的时间片用完之后,如果没有完成任务,它将会被移动到更高优先级的队列中,以此类推。如果一个进程完成了它的任务,它将被从队列中移除。
这种调度算法的优点是能够很好地兼顾长作业和短作业,优先级较低的进程在等待队列中也会得到一定的时间片,从而避免了某些进程一直得不到服务的情况。同时,该算法也能够动态地调整进程的优先级,从而更加公平地分配CPU时间。
然而,多级反馈队列调度算法也存在一些问题。例如,算法的效率不高,每个进程需要在多个队列之间移动,会造成大量的上下文切换。另外,该算法的实现比较复杂,需要考虑多个队列之间的调度问题。
#嵌入式##操作系统面试总结##承诺提供免费技术答疑# 本专栏主要是介绍嵌入式开发岗位相关知识和学习攻略。“C/C++软件开发岗位”也可以参考。 该专栏覆盖了嵌入式求职过程中99%常见面试题,详细讲解了嵌入式软件开发岗位、学习攻略、项目经验分享、面试心得,从技术面,HR面,主管面,谈薪一站式服务。订阅即赠送简历模板、内推机会,需要的同学点击我头像私信即可!