进程
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
特征
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行。
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
线程
线程Thread是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
适用范围
- 服务器中的文件管理或通信控制
- 前后台处理
- 异步处理
特征
- 轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
线程的实体包括程序、数据和线程控制块TCB。
- 独立调度和分派的基本单位
- 可并发执行
- 共享进程资源
进程与线程的区别
-
地址空间和其他资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其他进程不可见。
-
通信:进程通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信--需要进程同步和互斥手段的辅助,以保持数据的一致性。
-
调度和切换:线程上下文切换比进程上下文切换要快得多。
-
在多线程OS中,进程不是一个可执行的实体。
进程间通信
进程间通信指在不同进程之间传播或交换信息。
进程的用户空间是相对独立的,一般来说不能互相访问,进程之间交换数据必须通过内核。在内核中开辟一块缓冲区,进程A把数据从用户空间拷贝到内核缓冲区,其他进程从内核缓冲区读数据,内核提供的这种机制称为进程间通信。
主要分类
进程间通信主要包括管道、系统IPC(消息队列、信号、共享内存)、套接字Socket。
管道
- 普通管道pipe:通常有两种限制,一是单工,只能单向传输;二是只能在父子进程或兄弟进程间使用。
-
流管道s_pipe: 去除了第一种限制,为半双工,可以双向传输。
-
命名管道name_pipe :去除了第二种限制,可以在许多不相关的进程间使用
- 管道:优点是所有的UNIX实现都支持,并且在最后一个访问管道的进程终止后,管道就被完全删除;缺点是管道只允许单向传输或者只能用于父子进程之间。
- 系统IPC:优点是功能强大,可以在毫不相干的进程之间进行通信;缺点是关键字KEY_T使用了内核标识,占用了内核资源,而且只能被显式删除,不能使用SOCKT的一些机制,如select,epoll等。
信号
操作系统通过信号来通知进程系统中发生了某种预先规定好的事件(一组事件中的一个),它也是用户进程之间通信和同步的一种原始机制。一个键盘中断或者一个错误条件(比如进程试图访问它的虚拟内存中不存在的位置等)都有可能产生一个信号。
- 在一个信号的生命周期中有两个阶段:生成和传送。当一个事件发生时,需要通知一个进程,这时生成一个信号。当进程识别出信号的到来,就采取适当的动作来传送或处理信号。在信号到来和进程对信号进行处理之间,信号在进程上挂起(pending)。
主要信号源:异常、其他进程、终端中断、作业控制、分配额、通知、报警
常见的信号:SIGHUP、SIGINT、SIGQUIT、SIGFPE、SIGKILL、SIGALARM、SIGTERM、SIGSTOP、SIGCHILD
缺省动作:异常终止(abort,产生core文件)、退出(exit)、忽略(ignore)、停止(stop)、继续(continue)
管道
管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。
写进程在管道尾端写入数据,读进程在管道首端读出数据。读数据的同时会将数据从管道中移走。
管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样,管道已满时,进程再试图写管道,在其他进程从管道读走数据之前,写进程将一直阻塞。
写进程使用标准的write库函数,这些库函数(read、write等)要求传递一个文件描述符作为参数。
命名管道(FIFO)与管道不同,不是临时的对象,它们是文件系统中真正的实体,可以用mkfifo命令创建。
一个管道是一次性创建的,而FIFO是已经存在的,可以由它的用户打开和关闭。
- 管道不能用来对多个接收者广播数据
- 管道中的数据被当作字节流,因此无法识别信息的边界
- 如果一个管道有多个读进程,那么写进程不能发送数据到指定的读进程。同样,如果有多个写进程,那么没有办法判断是它们中的哪一个发送了数据
消息队列Message Queues
消息队列就是消息的一个链表,它允许一个或多个进程向它写消息,一个或多个进程从中读消息。
- 创建或获得消息队列(MSGGET)
- 发送消息
- 接收消息
- 消息控制
共享内存
通常由一个进程创建,其余进程对这块内存区进行读写。
得到共享内存的两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但它控制存取的是实际的物理内存,所以不常用。
对共享内存做同步,通常使用信号量实现。
信号量
本质上,信号量是一个计数器,它用来记录对某个资源的存取情况。
信号量有两种:互斥信号量和条件信号量。
套接字Socket
在所有提供了TCP/IP协议栈的OS中几乎都提供了socket,所有这些OS,对套接字的编程方法几乎一样。
进程同步
在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。
同步的概念:异步环境下的一组并发进程因直接制约而互相发送消息、进行互相合作、互相等待,使得各进程按一定的速度执行的过程称为进程间的同步。
具有同步关系的一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。
如果我们对一个消息或事件赋以唯一的消息名,则我们可以用过程wait(消息名)表示进程等待合作进程发来的消息,而用过程signal(消息名)表示向合作进程发送消息。
上述的wait(消息名)和signal(消息名)是进程同步的一种实现方法。
使用信号量也可实现进程同步。一般来说,我们可以把各进程之间发送的消息作为信号量看待。与进程互斥时不同的是,这里的信号量只与制约进程及被制约进程有关而不是与整组并发进程有关。因此,我们称该信号量为私用信号量(Private Semaphore)。
使用P,V原语操作实现同步
- 为各并发进程设置私用信号量
- 为私有信号量赋初值
- 利用P,V原语和私用信号量规定各进程的执行顺序
线程同步
当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,其他线程处于等待状态。
线程同步的方式和机制
临界区Critical Section、互斥对象Mutex:主要用于互斥控制;都具有拥有权的控制方法,只有拥有该对象的线程才能执行任务,执行完任务后一定要释放该对象。
信号量Semaphore、事件对象Event:事件对象是以通知的方式进行控制,主要用于同步控制。
- 临界区
通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
- 互斥对象
互斥对象和临界区很像,采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程同时访问。当前拥有互斥对象的线程处理完任务后必须将线程交出,以便其他线程访问该资源。
- 信号量
信号量:信号量也是内核对象。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。
- 事件对象
线程间通信
在多个线程并发执行时,默认情况下CPU是随机切换线程的。
- 使用全局变量
最好使用volatile修饰全局变量,它告诉编译器无需对该变量作任何优化,即无需将它放到一个寄存器中,并且该值可被外部改变。
- 使用消息
WWindows程序设计中,应用程序的每一个线程都有自己的消息队列。我们可在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信目的。
系统提供的线程间发送消息的专用函数PostThreadMessage()。