进程和线程

1. 进程的概念

首先,明确程序地定义,即仅仅是代码,其编写所用语言从高级到底层包括例如c、java等高级语言,汇编语言以及机械语言。以C语言为例,通过预处理,编译优化,以及链接库,最终生成可执行文件,我们称之为程序。可以明确,程序是静态的,可执行的。
然后,建立在程序的概念上,进程可以非正式地表述为——正在执行的程序。一个程序只有被装入内存时才能成为进程。与静态的程序代码相比,除了代码段,进程通常还包括堆栈段,数据段以及运行时动态分配的堆。进程通过程序计数器和寄存器的内容来表示当前活动。

进程可能出现的状态有:新的(正在被创建),运行(正在被执行),等待(在等待某个事件),就绪(等待分配处理器),终止(完成执行),具体关系可见下图。

每个进程在操作系统中用进程控制块(PCB)存放与进程相关的信息,这些信息包括:进程状态,程序计数器,CPU寄存器,CPU调度信息,内存管理信息,记账信息和I/O状态。

总结来说,进程与程序最大的区别在于后者是被动实体,是静态的,而前者是动态的,从而较程序多出了运行时的信息以及衍生的数据结构。

2. 进程操作
系统必须提供四种进程操作的机制:识别,创建,执行,终止。相关系统调用有:getpid(),fork() ,exec*() ,wait() ,exit()。
① 进程识别:每个进程有一个独一无二的ID,即PID,可以通过系统调用getpid()获得。
② 进程创建:一个进程可以创建新的进程,分别称为父、子进程。操作系统内核有第一个进程——init(PID=1),其首要工作就是创建更多的进程,从而以此为根生成进程树。
父子进程之间的关系时多样的:资源可以完全共享,部分共享以及不共享;可以同时执行,也可以子进程先执行;在地址空间中,子进程可以时父进程的副本,也可以装入另一个新程序。父进程提前终止,子进程我们称之为孤儿,孤儿使进程树变成森林,其终止不可预知。在linux中有re-parent的操作,init会成为孤儿的“继父”。
③ 进程执行:通常在系统调用fork()之后,一个进程会调用exec(),以用新进程来取代进程的内存空间,将二进制文件装入内存并开始执行。
这种方式下,两个进程能相互通信,并按照各自的方法执行。如果父进程在子进程运行时没有别的操作,采用wait()将自己移出就绪队列等待子进程终止。

④ 进程终止:进程完成执行使用exit()删除自身,进程终止。这时进程可以返回状态值到父进程,所有进程资源会被释放。

3. 线程
线程是CPU使用的基本单元。
线程可以看作进程的子结构。我们之前描述的进程是具有单个控制线程的执行程序,实际上现代操作系统都提供多线程的功能。进程一般由线程ID、程序计数器、寄存器集合和栈组成同一进程下的线程共享代码段、数据段和其他操作系统资源。
采用多线程的优势从应用来看可以提高响应度以及资源的利用率,能更好的支持多任务,并且建立与切换线程相比于进程更为经济,因为线程能共享所在进程的资源。从系统上来看,多线程能够充分的使用多处理器体系结构。但是多线程也使编程更具挑战,有包括区分任务、平衡性、数据分散、测试困难等问题需要处理。
多线程模型由三种:①多对一模型②一对一模型③多对多模型。三种模型定义了三种不同的用户进程和内核进程的映射关系。对比之下,多对一模型下,一个线程发生阻塞,整个进程将会阻塞;一对一模型则具有更好的并发性,一个线程调用系统阻塞时,另一个线程可以继续执行,但是这种模型下,可创建的线程数量是较为有限;多对多模型没有以上二者的缺点,开发人员可创建任意多的用户线程,并且一个线程阻塞不会影响别的线程。

线程库提供了创建和管理线程的API,有两种实现方法:一、在用户空间提供一个没有内核支持的库。二、执行一个由操作系统直接支持的内核级的库。目前主要的线程库有:POSIX Pthread、Win32、Java。

4. 通信
线程和进程的通信模型较为类似,故放在一起讨论。其中通信模型有两种:
1)共享内存模型:采用共享内存的模型需要建立共享内存区域。线程通过shared memory create 和shared memory attach系统调用来获得其他进线程所拥有的内存的访问权。一旦共享变量变得多起来,并且涉及到多种不同线程对象的交互,这种管理会变得非常复杂,极容易出现死锁等问题。

2)消息传递模型:不必通过共享地址空间来实现通信和同步。消息传递工具提供至少两种操作——发送消息和接受消息,且两个线程之间必须要有通信线路。线程彼此之间交换消息来交换信息。

参考资料:
[1] Abraham Silberschatz. 操作系统概念. 高等教育出版社, 2007.3.

原文地址:https://www.cnblogs.com/JK-Z/p/12262060.html