程序、进程、线程

  • 程序:由源代码生成的可执行应用。
  • 进程:一个正在运行的程序可以看做一个进程,进程拥有独立运行所需要的全部资源,是资源分配的基本单位。
  • 线程:程序中独立运行的代码段。是调度运行的基本单位。

一个进程是由一或多个线程组成,进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。

进程

进程是资源分配的基本单位
进程是具有一定功能的程序关于某个数据集合上的一次运行活动
一个进程只有一个父进程,但是可以有很多个子进程。

线程

线程是独立调度的基本单位,程序执行的最小单元。

PCB&TCB

PCB: 描述和控制进程的运行,系统为每个进程定义了一个数据结构——进程控制块(PCB)。是进程重要的组成部分,它记录了操作系统所需的、用于描述进程的当前状态和控制进程的全部信息。 操作系统就是根据进程的PCB来感知进程的存在,并依此对进程进行管理和控制。 PCB是进程存在的唯一标识。
PCB主要包括:进程标识信息、处理机状态(PSW)、进程调度信息(状态、优先级等)、进程控制信息 (程序和数据地址、进程同步和通信机制等)。

线程也有自己的私有属性TCB,线程id,寄存器、硬件上下文,而进程也有自己的私有属性进程控制块PCB,这些私有属性是不被共享的,用来标示一个进程或一个线程的标志

常考题:

1、进程和线程的关系

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位
  2. 资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量
  3. 处理机分给线程,即真正在处理机上运行的是线程
  4. 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步
  5. 进程结束后,它所有的线程都将销毁,而线程的结束不会影响同个进程中其他线程的结束。

2、进程与线程的区别

1.资源:进程有自己的独立地址空间,线程没有
2. 调度:进程是资源分配的最小单位,线程是CPU调度的最小单位
3. 通信方式:进程和线程通信方式不同(线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助IPC)
4. 系统开销:进程上下文切换开销大,线程开销小
5. 一个进程挂掉了不会影响其他进程,而线程挂掉了会影响其他线程

3、进程的状态,各个状态之间的切换

3种状态:
运行run:进程已经获得CPU,程序正在执行状态
就绪ready:进程已处于准备好运行的状态,即进程已分配到除CPU外的所有必要资源后,只要再获得CPU,便可立即执行(等待被调度)
阻塞blocked:正在执行的进程由于发生某事件(如I/O请求、申请缓冲区失败等)暂时无法继续执行的状态(等待资源)
注意他们的转换关系:
只有就绪和运行能互相转换。运行到阻塞是单向的,阻塞到就绪也是单向的。
阻塞是因为缺少资源,而这个资源不包括cpu。
图片说明

4、孤儿进程和僵尸进程

基础概念:

  • 程序运行必须加载在内存中,当有过多的就绪态或阻塞态进程在内存中没有运行,因为内存很小,有可能不足。系统需要把他们移动到内存外磁盘中,称为挂起状态。就绪状态的进程挂起就是挂起就绪状态,阻塞进程挂起就称为阻塞挂起状态。
  • 每个进程的产生都有自己的唯一的ID号(pid),并且附带有一个它父进程的ID号(ppid)。进程死亡时,ID被回收。
  • 进程间靠优先级获得CPU资源,时间片段轮换来更新优先级,以保证不会一个进程占据CPU时间过长。每个进程都得到轮换运行,因为这个时间非常短,所以给我们就好像是系统在同时运行好多进程。

僵尸进程

  • 即子进程先于父进程退出后,子进程的PCB需要其父进程释放,但是父进程并没有释放子进程的PCB,这样的子进程就称为僵尸进程,僵尸进程实际上是一个已经死掉的进程。
  • 任何一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构。其不占内存,不能被调度,但是仍然留在那里。
  • 除非父进程主动为他收拾,或者父进程结束后被init收拾。但是如果父进程是个循环进程,不会结束,就不会被收拾。

处理方法:
图片说明
详情请看: 孤儿进程与僵尸进程产生及其处理

孤儿进程

  • 一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
  • 子进程死亡需要父进程来处理,那么意味着正常的进程应该是子进程先于父进程死亡。当父进程先于子进程死亡时,子进程死亡时没父进程处理,这个死亡的子进程就是孤儿进程。
  • 但孤儿进程与僵尸进程不同的是,由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵尸进程那样占用ID,损害运行系统。

5、进程同步

进程同步的主要任务:是对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地共享资源和相互合作,从而使程序的执行具有可再现性。
同步机制遵循的原则:

  • 空闲让进;
  • 忙则等待(保证对临界区的互斥访问);
  • 有限等待(有限代表有限的时间,避免死等);
  • 让权等待,(当进程不能进入自己的临界区时,应该释放处理机,以免陷入忙等状态)

生产者消费者模型

6、进程间通信

主要分为:管道、系统IPC(包括消息队列、信号量、共享存储)、SOCKET
管道主要分为:普通管道PIPE 、流管道(s_pipe)、命名管道(name_pipe)

管道

管道是一种半双工的通信方式,数据只能单向流动,而且只能在有血缘关系的进程间使用,进程的血缘关系通常是指父子进程关系。

命名管道(named pipe)

半双工的通信方式,但是它允许无亲缘关系关系进程间通信

信号量

信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段

消息队列( message queue )

消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

共享内存

就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。*它往往与其他通信机制,如信号量等配合使用,来实现进程间的同步和通信。 *

套接字( socket )

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

几种方式的比较:

管道:速度慢、容量有限
消息队列:容量收到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。
信号量:不能传递复杂信息,只能用来同步。
共享内存:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全。

7、上下文切换

对于单核单线程CPU而言,在某一时刻只能执行一条CPU指令。上下文切换(Context Switch)是一种将CPU资源从一个进程分配给另一个进程的机制。从用户角度看,计算机能够并行运行多个进程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。

8、为什么进程上下文切换比线程上下文切换代价高?

进程切换和线程切换:

1.切换页目录以使用新的地址空间
2.切换内核栈和硬件上下文

线程切换不需要第一步

区别:

1、线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。

2、另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor's Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。

9、线程之间的通信方式

  • 互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
  • 信号量:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
  • 事件(信号):通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。

!!!10.CPU调度算法

非抢占式调度与抢占式调度

  • 非抢占式:分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程
  • 抢占式:操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式

1、FIFO或First Come, First Served (FCFS)先来先服务

  • 调度的顺序就是任务到达就绪队列的顺序。
  • 公平、简单(FIFO队列)、非抢占、不适合交互式。
  • 未考虑任务特性,平均等待时间可以缩短。
  • 该进程一直运行到完成或发生某事件而阻塞后才放弃处理机。

2、最短作业优先(SJF)

  • 最短的作业(CPU区间长度最小)最先调度
  • SJF可以保证最小的平均等待时间
  • 在所有的作业可以同时运行时,SJF才是最优化的
  • 例子:*现有A(8)B(4)C(4)D(4) 4个任务
    如果按顺序执行A周转时间为8,B为12,C为16,D为20,平均14分钟
    如果按SJF执行周转时间为4,8,12,20,平均为11分钟。

3、最短剩余时间优先(SRJF)

  • SJF的可抢占版本,比SJF更有优势
  • 当一个新的作业到达时,其整个时间同当时进程的剩余时间做比较。如果新的进程比当前进程需要的时间短,则当前进程挂起,运行新的进程。

4、轮转调度算法Round-Robin(RR)

  • 设置一个时间片,按时间片来轮转调度
  • 优点: 定时有响应,等待时间较短;缺点: 上下文切换次数较多
  • 时间片太大,响应时间太长;吞吐量变小,周转时间变长;当时间片过长时,退化为FCFS

5、优先权调度

  • 每个任务关联一个优先权,调度优先权最高的任务
  • 注意:优先权太低的任务一直就绪,得不到运行,出现“饥饿”现象

6、多级队列调度

  • 按照一定的规则建立多个进程队列
  • 不同的队列有固定的优先级(高优先级有抢占权)
  • 不同的队列可以给不同的时间片和采用不同的调度方法
    属于最高级的进程运行一个时间片,次高级的2个,再次一级4个。。。
  • 存在问题1:没法区分I/O bound和CPU bound
  • 存在问题2:也存在一定程度的“饥饿”现象

7、多级反馈队列

  • 在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务
  • 可以根据“享用”CPU时间多少来移动队列,阻止“饥饿”
  • 最通用的调度算法,多数OS都使用该方法或其变形,如UNIX、Windows等
    图片说明
    例子:
    图片说明
    详细请看: 多级队列调度和多级反馈队列的调度

11.死锁的条件?以及如何处理死锁问题?

定义

如果一组进程中的每一个进程都在等待仅由该组进程中的其他进程才能引发的事件,那么该组进程就是死锁的。或者在两个或多个并发进程中,如果每个进程持有某种资源而又都等待别的进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗地讲,就是两个或多个进程被无限期地阻塞、相互等待的一种状态。

产生死锁的必要条件:

  • 互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
  • 请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
  • 非抢占条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
  • 循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。

如何处理死锁问题:

  1. 忽略该问题。例如鸵鸟算法,该算法可以应用在极少发生死锁的的情况下。
  2. 检测死锁并且恢复。
  3. 仔细地对资源进行动态分配,使系统始终处于安全状态以避免死锁。
  4. 通过破除死锁四个必要条件之一,来防止死锁产生。

12、内存池、进程池、线程池。

  首先介绍一个概念“池化技术 ”。池化技术就是:提前保存大量的资源,以备不时之需以及重复使用。池化技术应用广泛,如内存池,线程池,连接池等等。内存池相关的内容,建议看看Apache、Nginx等开源web服务器的内存池实现。
  由于在实际应用当做,分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。
###线程池
 线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当需要一个开辟一个线程去做具体的工作时,就会唤醒线程池中的某一个睡眠线程,让它去做具体工作,当工作完成后,线程又处于睡眠状态,而不是将线程销毁。

进程池与线程池同理。

内存池:

内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。

13、多线程

解决多任务同时执行的需求,合理使用CPU资源。多线程的运行是根据CPU切换完成,如何切换由CPU决定,因此多线程运行具有不确定性。
优点:
1)适当的提高程序的执行效率(多个线程同时执行)。
2)适当的提高了资源利用率(CPU、内存等)。
缺点:
1)占用一定的内存空间。
2)线程越多CPU的调度开销越大。
3)程序的复杂度会上升。

14、一个程序从开始运行到结束的完整过程(四个过程)

1、预处理:条件编译,头文件包含,宏替换的处理,生成.i文件。
2、编译:将预处理后的文件转换成汇编语言,生成.s文件
3、汇编:汇编变为目标代码(机器代码)生成.o的文件
4、链接:连接目标代码,生成可执行程序

15、线程共享资源和独占资源问题

一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(/私有的)栈(stack),Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不同,一般是一个进程有一个运行时堆,这个堆为本进程中所有线程共享,windows进程还有所谓进程默认堆,用户也可以创建自己的堆。
用操作系统术语,线程切换的时候实际上切换的是一个可以称之为线程控制块的结构(TCB),里面保存所有将来用于恢复线程环境必须的信息,包括所有必须保存的寄存器集,线程的状态等。
图片说明

引用:
1、https://www.baidu.com/link?url=ICp7s35MeAlwp3AwijLZDVTdeDr3buQKhpo8Yhf6kpbxjXggtiXYT7xQn37jM8S1&wd=&eqid=dba7ec930007aa63000000055e74c3b0
2、https://www.baidu.com/link?url=1RuYbg2XJlT1PFD6-T0R0NK7-pPqI5SPHrlZJ1MRIjRANtUfMesRAqWufdwCxpoVmkmtp8WLTUWO6opQUwowfeP87f2y1CbLBuNiLqiHv1m&wd=&eqid=dba7ec930007aa63000000055e74c3b0
3、https://www.baidu.com/link?url=yzXYct2jj-z470ECha5HApWQlgVY1rbBO2y-h0zDkdgwsB5kZ8icpA3tGI7M2QSnFy-pHd4WN8-OpiGh9b0sEWeAsqBSh8osrXXlKpWPmQW&wd=&eqid=dba7ec930007aa63000000055e74c3b0
4、https://www.cnblogs.com/inception6-lxc/p/9073983.html
5、https://www.jianshu.com/p/17529ce1cc60
6、https://blog.csdn.net/Eunice_fan1207/article/details/81387417