操作系统之进程与线程浅析(一)
最近在自学操作系统,看到进程与线程一章,小小的总结一下。
一.概念
进程:简单的说,进程是程序的一个运行实例。如果一个程序有多个运行实例,则它们属于不同的进程。每一个进程都在内存中有自己的地址空间,地址空间中存放程序文件,数据以及堆栈。与进程相关的还包括寄存器,进程打开的文件,报警等。
线程:线程是进程中的一个执行过程。线程类似于迷你的进程。线程总是存在于进程中。每个进程至少有一个线程。同一个进程的线程之间共享地址空间,全局变量,打开的文件,报警等。
比较:进程与线程在系统中的层次关系粗略的讲,线程之于进程就像进程之于操作系统。进程与进程之间多表现为一种竞争关系,而线程之间合作的关系更突出一些,因为创建多线程的目的就是为了更好的完成任务。进程之间有父子继承关系,而线程之间是平等的,尽管他们之间也有创建与被创建的关系。
二.创建与结束(以Unix为例)
有4中主要事件可以创建进程:
1.系统初始化。
2.正在运行的进程调用创建新进程的系统调用
3.用户请求
4.批处理作业的初始化。
重点说一下第二种情况。Unix中只有一个系统调用fork可以创建新进程。fork调用创建了进程的一个副本(同样的变量、打开的文件等),返回子进程号,子进程则返回0。通常,子进程接着执行execve或类似的系统调用,以执行一个新的程序。
Unix中与进程相关的系统调用还有:waitpid(等待一个进程的结束),exit(中止进程执行并返回状态)。
通常也有4中情况中止进程:
1.正常退出。
2.出错退出。
3.严重错误。
4.被其他进程杀死。
前两种是进程自愿退出的,后两种是非自愿的。大部分进程是完成了自己的工作而正常退出(调用exit)。第二与第三种情况的区别在于后者是由进程本身引起的(如程序有错),而前者是由用户引起的(如进程需要处理的某个文件不存在)。当别的进程获得相应的授权时,可以调用kill系统调用来杀死进程。
线程的创建:多线程的进程首先由单个线程开始,这个线程可以通过调用库函数(thread_create)来创建新的线程.调用thread_exit可以结束线程。调用thread_join,一个线程可以等待另一个线程的退出。调用thread_yield,一个线程可以主动放弃cpu从而让另一个线程运行。
三.管理
1.线程
进程的三种状态:就绪,运行,阻塞。就绪态指进程可运行,但cpu被其他进程占据;阻塞态指进程被阻塞,除非某种外部事件发生否则进程不能运行。三态的转换:就绪<->运行,运行->阻塞->就绪。
为了实现进程模型,操作系统维护着一张进程表,每个进程占一个进程表项,每个表项保存了进程的重要信息(程序计数器,堆栈指针,内存分配状况,打开的文件状态等),当进程由运行态转换到就绪态或阻塞态时,表项就会得到更新,等待进程的再执行。
还有一个中断向量需要介绍一下。众所周知,相对于cpu的运行速率来讲,内存的存取、I/O的存取速率都低了几个数量级,为了提高cpu的利用率,现代操作系统都采用多道程序设计模型,而中断就可以实现该模型。所谓中断就是cpu停止执行当前进程,而转向中断所指向的程序执行。中断向量(靠近内存底部的固定区域)就包含了中断服务程序的入口地址,每一个I/O类关联了一个中断向量。
有了进程表以及中断向量以后,我们就可以试着理解cpu如何维持多进程的伪并行处理。当一个磁盘中断发生时,假设a进程正在执行,中断硬件将程序计数器、程序状态字压入堆栈,然后跳转到中断向量所指示的地址。上面的工作由硬件完成,剩下的就是软件的工作。首先保存寄存器,也就是更新进程表,此部分工作由汇编程序完成。接着调用一个C过程来处理某个特定中断类型剩下的工作。完成这些以后,相关进程会就绪,接着调用调度程序,决定随后该运行哪一个进程,然后运行该进程。
2.线程
为什么要使用线程?最主要原因是在许多应用中同时发生着多种活动,而其中一些活动可能被阻塞,通过将这些活动分成多个顺序执行的线程,程序设计模型会变得更加简单。具体的讲,多线程可以共享地址空间和所有可用数据,而多进程做不到;更轻量级;提升性能(对于存在大量计算和大量I/O的进程);在多核系统中,并行成为可能。