操作系统--进程与线程

进程与线程

  • 概念
    • 进程:
      对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
    • 线程:
      进程的子任务,是CPU调度和分派的基本单位,实现进程内部的并发;
      线程是操作系统可识别的最小执行和调度单位。
      每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
  • 区别
  1. 拥有资源
    进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。
  2. 调度
    线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
  3. 系统开销
    由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
  4. 通信方面
    线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。
  • 进程间通信IPC
  1. 管道
  2. 消息队列
  3. 信号量
  4. 信号
  5. 共享内存
  6. 套接字SOCKET
  • 线程间的同步方式(线程锁)
  1. 信号量
    它只取自然数值,并且只支持两种操作:
    P(SV): 如果信号量SV大于0,将它减一;如果SV值为0,则挂起该线程。
    V(SV): 如果有其他进程因为等待SV而挂起,则唤醒,然后将SV+1;否则直接将SV+1。
  2. 互斥量
    互斥量又称互斥锁,主要用于线程互斥,不能保证按序访问,可以和条件锁一起实现同步。当进入临界区 时,需要获得互斥锁并且加锁;当离开临界区时,需要对互斥锁解锁,以唤醒其他等待该互斥锁的线程。
  3. 条件变量
    条件变量,又称条件锁,用于在线程之间同步共享数据的值。条件变量提供一种线程间通信机制:当某个共享数据达到某个值时,唤醒等待这个共享数据的一个/多个线程。
  • 线程间通信
  1. 使用全局变量
    主要由于多个线程可能更改全局变量,因此全局变量最好声明为volatile
  2. 使用消息实现通信
    在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。
  3. 使用事件CEvent类实现线程间通信
    Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。
  • 并发与并行
  1. 并发:
    指宏观上看起来两个程序在同时运行,比如说在单核cpu上的多任务。但是从微观上看两个程序的指令是交织着运行的,你的指令之间穿插着我的指令,我的指令之间穿插着你的,在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能,只能提高效率。
  2. 并行:
    指严格物理意义上的同时运行,比如多核cpu,两个程序分别运行在两个核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令,也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的cpu都是往多核方面发展。
  • 进程状态转换图
  1. 5个基本状态
    创建状态:进程正在被创建。
    就绪状态:进程被加入到就绪队列中等待CPU调度运行。
    执行状态:进程正在被运行。
    等待阻塞状态:进程因为某种原因,比如等待I/O,等待设备,而暂时不能运行。
    终止状态:进程运行完毕。
  2. 交换技术
    当多个进程竞争内存资源时,会造成内存资源紧张,并且,如果此时没有就绪进程,处理机会空闲,I/0速度比处理机速度慢得多,可能出现全部进程阻塞等待I/O。
    • 交换技术:换出一部分进程到外存,腾出内存空间。

在交换技术上,将内存暂时不能运行的进程,或者暂时不用的数据和程序,换出到外存,来腾出足够的内存空间,把已经具备运行条件的进程,或进程所需的数据和程序换入到内存。从而出现了进程的挂起状态:进程被交换到外存,进程状态就成为了挂起状态。

  • 虚拟存储技术:每个进程只能装入一部分程序和数据。
  • 进程调度
  1. 批处理系统
    保证吞吐量和周转时间(从提交到终止的时间)
  1. 先来先服务 first-come first-serverd(FCFS)
    非抢占式的调度算法,按照请求的顺序进行调度。
    有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。
  2. 短作业优先 shortest job first(SJF)
    非抢占式的调度算法,按估计运行时间最短的顺序进行调度。
    长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。
  3. 最短剩余时间优先 shortest remaining time next(SRTN)
    最短作业优先的抢占式版本,按剩余运行时间的顺序进行调度。 当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较。如果新的进程需要的时间更少,则挂起当前进程,运行新的进程。否则新的进程等待。
  1. 交互式系统
    快速地进行响应
  1. 时间片轮转
    将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。
  2. 优先级调度
    为每个进程分配一个优先级,按优先级进行调度。
    为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。
  3. 多级反馈队列
    多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。
    每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。
  • 进程的内存空间
  1. 进程的内存模型


    进程的内存模型
  2. 堆和栈的区别
    • 大小限制:
      栈底的地址和栈的最大容量是系统预先规定好的(2M/1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间, stack overflow。因此,能从栈获得的空间较小。堆是用链表来存储的不连续内存区域,大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
    • 申请效率:
      栈由系统自动分配,速度较快。但程序员是无法控制的。堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
    • 存储内容:
      栈存储返回地址,参数,局部变量。堆在这块内存空间中的首地址处记录本次分配的大小,具体内容由程序员安排。
    • 数据访问:

存储在堆中的对象是全局可以被访问的,然而栈内存不能被其他线程所访问,且遵循LIFO原则。

  • 生命周期:
    栈内存的生命周期很短,而堆内存的生命周期从程序的运行开始到运行结束。
  • 进程关系
    进程通常不是凭空独立的出现的,在类Unix系统中,所有的其他进程都是从 进程0 fork 出来的,每个进程都会拥有多个子进程。
  • fork()
  1. fork给父进程返回子进程pid,给其拷贝出来的子进程返回0,
  2. 这也是他的特点之一,一次调用,两次返回。
  3. 实质是在子进程的栈中构造好数据后,子进程从栈中获取到的返回值。

由于现代操作系统的写时复制机制,即使我们知道每个进程都拥有自己独立的地址空间,其实子进程指向的物理内存是和父进程相同的(代码段,数据段,堆栈都指向父亲的物理空间),只有子进程修改了其中的某个值时(通常会先调度运行子进程),才会给子进程分配新的物理内存,并根据情况把新的值或原来的值复制给子进程的内存。

  • 孤儿进程
  1. 一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。
  2. 孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。
  3. 由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。
  • 僵尸进程
  1. 一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。
  2. 僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。
  3. 系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。
  4. 要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 进程所收养,这样 init 进程就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。

你可能感兴趣的:(操作系统--进程与线程)