操作系统之进程、线程

  • 写在前面

下面我们就将讨论一些关于操作系统原理的知识,这些知识是前沿学者很早就提出的,有些知识可能已经在很多地方有了应用,但也有很多知识可能没有得到应用,但他们都对操作系统的发展起到了积极的推动作用,以至于如今的各式系统才能更完善,在各个领域的支持才能做得更好,特别的,这些概念是从操作系统的大的层面出发,而非针对具体的OS,但是可能会提及到jitid这些知识无论对于深入学习具体的某个操作系统(如Linux,OS X等)还是以后参与系统设计都将起到指导作用。我想只要计算机的体系结构依然是冯洛伊曼体系,那么这些知识对于学习操作系统这个十分低层的知识就十分重要。学习操作系统不想学习任何前沿的技术那样能够得带更多的反馈,但是操作系统知识却是计算机的命脉,你可能不需要学习操作系统的相关知识也能很好的完成工作,但是在面对提升性能或者和操作系统的相关知识又可能束手无策。无论如何我们都应该认真学习操作系统知识。

  • 进程

  • 进程模型

       我们在接触操作系统时最核心的概念就是进程,例如我们在Linux执行一个shell 命令,这个命令就是一个进程。进程就是对正在运行的程序的一种抽象,操作系统的其他概念就是围绕进程展开。概括的讲,一个进程就是一个正在执行的程序的实例,包括程序计数器,寄存器,和变量的当前值。为了方便讨论,在这里我们假设我们的计算机只有一个CPU(虽然现在多核处理器的计算机大行其道),对于多处理器的计算机的相关知识我们会在后面介绍。在多道程序设计系统中,CPU通常是在多个程序之间不停切换,每个程序执行相当短的时间,一般在几十到几百毫秒,这就给我们用户造成了多道程序并行执行的伪并行假象(现在的CPU已经在硬件上支持并行了)。进程和程序之间的关系十分微妙,一个进程是某一种类型的一个活动,它包含程序、输入、输出和状态。进程就好比是程序的一个实例,同一个程序执行两次会得到两个进程。更简单的说,进程是动态的,它不仅包含程序的静态指令,还包含数据信息;而程序就是一些程序指令集合,程序是一个相对静态的概念。

  • 进程的创建

       操作系统需要能够创建一个进程。对于一些简单的只执行一个程序系统(如微波炉中的控制器),可能在系统启动时就已经创建好了需要执行的进程,因为这些进程的内容相对固定,因此相对简单。对于更通用的系统,需要有某在方法在系统需要时按照需要创建进程。如下是四种情况会导致系统创建进程:

  1. 系统初始化。
  2. 正在运行的进程调用了创建进程的系统调用(System Call)。
  3. 用户请求创建一个进程。
  4. 一个批处理系统的初始化。

        启动操作系统时通常会创建很多进程,其中很多进程是前台进程,还有很多后台进程,这些进程与特定的用户没有关系,属于操作系统,这些后台进程具有某些功能,例如Web进程会处理用户的请求,邮箱进程会检查是否有邮件到达。这些守候在后台服务的进程通常被称之为守护进程(daemon)。在UNIX及其衍生系统中可以通过ps命令查看系统有哪些进程,Windows系统可以查看任务管理器。除了在系统初始化时创建进程,正在执行的进程通常也能够创建进程来帮助其完成任务,对于Windows的图形界面,通过双击就能启动应用,这是一种用户主动创建进程的方式。对于批处理系统,当提交一个批处理作业时,批处理操作系统就会创建一个进程来处理批处理作业。在类UNIX系统中,创建进程的系统调用只用fork。在调用fork后,这两个进程拥有相同的内存映像,同样的环境变量,和同样的打开的文件。在UNIX和Windows中,创建了进程后,父子进程拥有不同的地址空间(内存地址集合)。如果某个进程在其地址空间修改了某个内存字,这个修改对其他进程是不可见的,在UNIX中,子进程的初始地址空间是父进程的地址空间的一个副本,这里涉及到了两个不同的地址空间。不可写内存区是共享的,某些UNIX的实现使得程序正文在父子进程之间共享,因此该内存区不可写。或者父子进程共用相同的内存,这种情况下只能通过写时复制(copy-on-write)的方式来共享,如果两者之一需要修改部分内存区域,那么需要显示的复制部分内存区域并使得修改在私有的内存区域发生。对于父子进程,不可变内存区域可以共享,可变内存区域不可共享。

  • 进程的终止

         进程有创建就有终止,进程终止可能是自愿的,也可能是被迫的,以下几种情况会导致进程:

  1. 程序执行完成自动退出(自愿的)
  2. 程序执行遇到错误退出(自愿的)
  3. 严重错误(非自愿)
  4. 被其他进程杀死(非自愿)
  • 进程的层次结构

       在类UNIX操作系统中,父进程和子进程是以一种树的关系维系在一起,整个系统是以一颗init进程为根的树。相反在Windows系统中进程间没有这种关系,所有进程的地位平等。

  • 进程的状态

进程的状态:创建,就绪态、运行态、阻塞态、终止进程在就绪、阻塞、运行这三种状态之间进行轮转。轮转关系如下:

                             进程间的状态转换 
转化1:当正在运行的程序需要执行I/O操作(等待用户输入,等待打印机)时,会发生转化1,由运行态转到阻塞态,CPU会被调度程序调度其他的进程继续执行,而被阻塞的进程则等待I/O事件发生。 
转化2:当进程使用完了调度程序分配给进程的CPU的使用权,常见的情况是进程使用完了CPU执行的时间,就会发生转换2,由运行态转化的就绪态,通常需要保存进程的状态(程序计数器,堆栈信息,寄存器值等),并将进程加入到操作系统内部维护的就绪队列,以便下次被调用程序再次调用使用CPU执行程序。 
转化3:当新创建的进程或者刚刚由运行态转到就绪态的进程再次被调度程序(Scheduler)调度获得CPU使用权就会发生转换1。 
转化4:当等待I/O时间的进程的I/O实践发生了,这时他不能直接执行,需要先转入到就绪态,等待在后续被Scheduler调度从而再次获得执行。
转化2和转化3会使用到调度程序,用户可能都感受不到它的存在,调度程序需要决定应当那个程序运行,何时运行,运行多长时间(这在后面的调度算法时会讲到) 。

  • 进程的实现

       为了实现进程模型,操作系统需要维护一张表格,既进程表(process table),每个进程占用进程表中的一项(也有称之为进程控制块),该表项包含进程的一些重要信息,例如程序计数器,堆栈指针,内存分配状态,打开的文件状态,账号信息和调度信息(优先级等),以及进程又运行态转化到就绪态和阻塞态需要保存的信息,以便进程再次执行的时候能够接着上一次执行的位置执行,就好像进程从没有中断过一样。

  • 线程

  • 线程引入

       为什么在已经有了进程的基础之上还需要引入线程,有很多理由说明线程是非常有必要的。系统需线程(多线程)的原因:如果某个进程中有多个活动,多个活动之间没有相互的关联,对于传统的单线程进程,一些活动的执行可能会被其他活动因为执行时间过长或者发生I/O而阻塞,因此如果引入线程(多线程)将极大的提升系统的性能,同时程序的设计模型也变得什么清晰简单。同时,线程相对于进程是更加轻量级的,线程的创建、销毁、调度都比进程完成这些操作需要更小的代价,创建一个线程比创建一个进程快上10~100倍,因此对于大量线程需要动态创建时这一特性将十分有益。在性能上,如果多个线程都属于CPU密集型,那么在性能上不会获得太大的提升,但是对于多道程序设计中,同时存在CPU密集型进程和I/O密集型程序,多线程运行这些程序之间重叠执行,在性能上将获得很大的提升。相信用Web服务器的例子用来说明为什么多线程是有益的并且也是有必要的会非常具有说服力。在高并发场景下,面对大量同时间到来的并发请求,需要多个线程之间进行相互协作完成请求,并且还要保证用户请求的正确性和响应速度。典型的,Web应用中会有专门的线程(分派线程,dispatcher)负责从网络中读取用户请求,如果有新的请求到来,它不会自己处理请求而是将请求转发给工作线程(worker thread)来处理用户的具体请求,例如结算购物车等,这里体现了多个线程间分工明确又相互协作来完成工作,多线程能够很好的胜任这份工作,这一切看似也理所因当。但试想一下如果没有多线程而是使用进程来处理呢?传统的进程模型就相当于只有一个线程的进程,那么一个线程将既要完成分发程序的工作(从网络中读取新的请求),又要进行实际的业务逻辑处理(复杂且长时间的计算,长时间的I/O操作),并且在处理一个请求期间到来的其他请求几乎是无法处理或者被处理时间会很长,这对于现在的应用和用户都是无法接受的(那样用户会很沮丧,因为这是一种糟糕的程序设计)。通过对比我们就可以看出多线程对与我们的程序设计是有很大的帮助的,很多复杂的程序设计也需要借助多线程来完成工作。

  • 经典的线程模型

        对于进程而言,同一进程中的线程共享进程的地址空间,打开的文件,环境变量等,同一个进程的线程可以创建甚至删除一个线程,因为在进程内部线程没有类似于进程之间的地址空间空间,因此就没有保护措施来保证一个线程不被其他线程销毁。这是显然的,因为同一个进程中的线程是是用于协同完成某一任务或多个任务。与进程的状态类似,线程的状态也也有创建、就绪、运行、阻塞、终止这几种中的一个,并且线程间的状态切换和上面的进程间的状态切换十分类似。每个线程有自己的数据结构,包括程序计数器(指示接下来要执行哪一行指令),寄存器,堆栈、状态、调度信息等。

  • 线程相关的库函数

    thread_create: 创建新的线程,thread_create的参数用于新创建的线程的名字。

    thread_exit: 该过程将在线程退出时执行,接着线程就消失了,不再被调度。

    thread_yield: 该过程允许一个线程自动放弃CPU而让其他线程执行,不同于进程,进程可以使用时钟中断强制线程让出CPU执行权,该库过程可用于一个线程主动放弃CPU执行权来让其他线程获得执行权,也可以用于一个线程等待另一个线程执行完任务。

  • POSIX线程(Portable Operation System Interface of UNIX)

为实现可移植的线程程序,IEEE定义了线程标准,如下是一些主要的函数:

系统调用描述
pthread_create创建一个新新线程
pthread_exit结束调用的线程
pthread_join等待一个特定的线程退出
pthread_yield主动释放CPU执行权让其他线程执行
pthread_attr_init创建并初始化一个线程的属性结构
pthread_attr_destroy删除一个线程的属性结构
  • 线程的实现

       有两种方法可以实现线程包(pthread),在用户空间和在内核空间,两种方法各有利弊,混合实现也是有可能的,现在介绍这两种方法并分析他们的优点和不足。

  1. 在用户空间实现线程

       在用户空间实现线程包,这种方法就是在用户空间实现线程包,内核对用户空间的进程包一无所知,内核还是用原来的方式调度进程,即单线程进程。在用户空间包含运行时系统(是一个管理线程的过程集合,如pthread_create,pthread_exit等)。优点:在于用户空间实现线程包可以在不支持线程的操作系统上实现。在用户空间管理线程时,每个进程需要使用进程线程表管理(thread talbe)线程,用来追踪进程中的线程,线程表和内核空间的进程表十分类似,不过它仅仅记录线程的状态,包括程序计数器,堆栈指针,寄存器,状态,调度信息等。用户进程还有一个优点,就是每个进程可以有自己的线程调度算法,用户级线程还有很好的扩展性,因为在内核空间的内核线程需要一些固定的表格空间和堆栈空间,如果内核线程数量庞当将会出现麻烦。

线程模型:

模型

缺点:

①如何实现阻塞系统调用,例如一个线程等待用户输入。如果让线程进行实际的系统调用是不可接受的,因为这回阻塞进程内的所有线程。使用线程的一个目标是,首先是允许每个线程使用阻塞系统调用,但是还要避免被阻塞的线程影响其他的线程执行。②如果一个线程开始执行,其他进程将无法得到执行,除非第一个线程主动放弃CPU。在一个进程中,没有时钟中断,因此不能使用轮转调度的方式调调线程,而且调度程序可能一直没有机会执行。

  • 在内核空间实现线程

       对于在内核空间实现线程,不再需要用户空间的运行时系统,不再需要进程的线程表。相反,内核通过线程表来记录系统中所有的线程。当有线程创建或者线程销毁,则会调用系统调用,该系统调用会更新线程表完成对线程的创建和销毁。内外内核空间还需要维护传统的进程表,以跟踪进程状态。对于所有能阻塞线程的调用都以系统调用的方式实现,这与运行时系统相比性能是十分可观的,并且当线程因为进行系统调用而阻塞时,内核可以选择同一进程中的不同线程或者其他一个进程中的线程继续执行,因为进程中的线程对内核是可见的。因为在内核空间创建和销毁线程的代价很大,某些系统会采用回收的方式管理线程。当一个线程销毁时,它不会真正的被销毁而是被标记为不用,但是其内部的数据结构没有收到影响。如果由进程发出创建线程的系统调用,那么会使用被标记为不可用的线程来节省一些开销。

        虽然在内核空间实现线程能够解决很多问题,但是在内核空间实现线程也存在一些问题:

  1. 当一个多线程进程发出创建一个新的进程的请求时,那么新创建的进程是拥有和父进程相同个数的线程还是只有一个线程?
  2. 另一个问题是信号。在经典模型中,信号是发送给进程而不是线程的,那么对于多线程的进程,一个发送给进程的信号到达时,应该由那个线程来处理信号?通常这种情况可以将信号交给对信号感兴趣的线程处理,但是如果多个线程都对到来的信号感兴趣呢?
  • 混合实现

对于线程的实现方式,在内核空间和用户空间都各有春秋,各自有各自的优缺点。于是乎,线程的实现朝着将内核空间实现和用户空间实现结合起来的方式前进,这样就能将两者的优点结合起来。

采用这种方法,开发者可以决定多少个用户线程和多少个内核线程多路复用,这一模型带来了极大地灵活度。

  • 进程与线程之间的区别与联系 

进程和线程之间的关系想必经过如上的阐述应该能够非常清晰了。进程是系统进行资源分配的单位,如分配内存地址空间,磁盘空间等。而线程则是调度和执行的单位,线程不能够独立执行,必须依存在进程中,并且使用进程提供的资源完成任务。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值