Matrix Wall

进程与线程

操作系统中最核心的概念就是进程:这是对正在运行程序的一个抽象。

一、进程的定义

  • 进程是程序的一次执行。
  • 进程 = 进程控制块 + 程序 + 数据。
  • 进程是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。

二、进程的结构

一个进程中应该包括:

  • 程序的代码;
  • 程序的数据;
  • 程序计数器中的值,用来指示下一条将运行的指令;
  • 一组通用的寄存器的当前值、堆、栈;
  • 一组系统资源(如打开的文件);

三、进程的创建:4个因素

1.系统初始化。
2.执行了正在运行的进程所调用的进程创建系统调用。
3.用户请求创建一个新进程。
4.一个批处理作业的初始化。

在UNIX系统中,只有一个系统调用可以用来创建新进程:fork

四、进程的终止:4种原因

  1. 正常退出(自愿的)。
  2. 出错退出(自愿的)。
  3. 严重错误(非自愿)。
  4. 被其他进程杀死(非自愿)。

五、进程的状态

进程有三种基本状态:

  • 运行态
    该时刻进程实际占用CPU。
  • 就绪态
    可运行,但因为其他进程正在运行而暂时停止。
    进程已获得除处理机外的所需资源,等待分配处理机资源,只要分配CPU就可执行。
  • 阻塞态
    正在执行的进程,由于发生某种事件而暂时无法执行,便放弃处理机处于暂停状态。

1.运行→阻塞:进程为等待输入而阻塞
2.运行→就绪:调度进程选择另一个程序
3.就绪→运行:调度进程选择这个进程
4.阻塞→就绪:出现有效输入

  • 在操作系统发现进程不能继续运行下去时,发生转换1。
  • 系统认为一个运行进程占用处理器的时间已经过长,决定让其他进程使用CPU时间时,会发生转换2。
  • 在系统已经让所有其他进程享有了它们应有的公平待遇而重新轮到第一个进程再次占用CPU运行时,会发生转换3。
  • 当进程等待的一个外部事件发生时(如一些输入达到),则发生转换4。

转换2、3都是由进程调度程序引起的。

调度程序的主要工作就是决定应当运行哪个进程何时运行以及它应该运行多长时间

process

六、进程的实现

操作系统为了实现进程模型,维护着一张叫做进程表(process table)的表格,每个进程占用一个进程表项。(在学校的教材上这个进程表项叫做进程控制块PCB)。

进程控制块PCB是进程的唯一标志。

进程控制块中包含了进程状态的重要信息,包括:

  • 程序计数器(PC)
  • 堆栈指针
  • 内存分配状态
  • 所打开文件状态
  • 账号和调度信息
  • 优先级
  • 互斥和同步机制

正是这些信息保证了进程在经历了各种转换后能再次启动,就像从未被中断过一样。


七、线程

上面的内容中,我们讨论了关于进程的话题,但是在实际运用当中并不是每次都只运行一个进程的,所以我们就需要提出一个新的实体,来满足一下特性:

  • 实体之间可以并发地执行;
  • 实体之间共享相同的地址空间;

而进程包含了两个概念:资源拥有者可执行单元,这个可执行单元就称为线程

尽管线程必须在某个进程中执行,但是线程和它的进程是不同的概念,并且可以分别处理。

进程用于把资源集中到一起,而线程则是在CPU上被调度执行的实体。

在同一个进程中并行运行多个线程,是对在同一台计算机上并行运行多个进程的模拟。

  1. 在前一种情形中,多个线程共享同一个地址空间和其他资源。
  2. 在后一种情形下,多个进程共享物理内存、磁盘、打印机个和其他资源。

八、引入线程的目的

  • 减小进程切换的开销
  • 提高进程内的并发程度
  • 共享资源

那么问题来了,引入进程和线程的好处分别是什么?

  • 引入进程的好处
    多个程序可以并发执行,改善资源使用率,提高系统效率。
  • 引入线程的好处
    减少并发程序执行时所付出的时空开销,使得并发粒度更细,并发性更好。

九、多线程的原因:

1.主要原因是,在许多应用中同时发生着多种活动,其中某些活动随着时间的推移会被阻塞,通过将这些应用程序分解成可以准并行运行的多个顺序线程,程序设计模型会变得更简单。
2.线程比进程更加轻量级,所以线程比进程更加容易(更快)创建和撤销。
3.如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动重叠进行,从而加快应用程序的执行速度。
4.在多CPU系统中,多线程是有益的,并且真正的并行有了实现的可能。

十、线程中包括:

  • 程序计数器:记录接着要执行哪一条指令。
  • 寄存器:保存线程当前的工作变量。
  • 堆栈:记录执行历史。

线程概念试图实现的是,共享一组资源的多个线程的执行能力,以便这些线程可以为完成某一任务而共同工作。

十一、线程的实现

在用户空间中实现线程

把整个线程包放在用户空间中,这样用户级线程包可以在不支持线程的操作系统上实现,通过这一方法就可以用函数库实现线程。

user-thread

#include #include 
#include 
#include 

#define NUMBER_OF_THREADS 10

void *print_hello_world(void *tid)
{
    /*本函数输出线程的标识符,然后退出。*/
    printf("Hello World.Greetings from thread %d0", tid);
    pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
    /*主程序创建10个进程,然后退出。*/
    pthread_t threads[NUMBER_OF_THREADS];
    int status, i;

    for(i = 0; i < NUMBER_OF_THREADS; i++){
        printf("Main here.Creating thread %d0\n", i);
        status = pthread_creat(&threads[i], NULL, print_hello_world, (void *)i);

        if(status != 0){
            printf("Oops.pthread_creat return error code %d0", status);
            exit(-1);
        }
    }
    exit(NULL);
}

在内核中实现线程

内核级线程就是内核有好几个分身,一个分身可以处理一件事的意思。这用来处理非同步事件很有用, 内核可以对每个非同步事件生个分身来处理。

内核级线程的操作非常轻便,几乎没有负担,而且对内核的结构有帮助。支持内核级线程的内核称作多线程内核。

kernel-thread

例子:Windows 95/98/NT/2000, Solaris,Tru64 UNIX,Linux


以上的内容只是对进程与线程的概念以及一些其他的基础问题做了简单的阐述,在操作系统中关于进程内容的核心知识还得是进程的调度与通信,这两个问题留到下一篇讨论。