操作系统学习笔记(二)-- 进程的描述与控制

操作系统学习笔记(一)-- 引论
操作系统学习笔记(三)-- 处理机调度与死锁
tRv9l6.md.png

1. 前趋图和程序执行

  1. 前驱图

    tRv4BD.png
  2. 程序的顺序执行

    tRv74A.png

    特征:

    • 顺序性
    • 封闭性
    • 可再现性
  3. 程序的井发执行

    特征:

    • 间断性
    • 失去封闭性
    • 不可再现性

2. 进程的描述

  1. 进程的定义和特征

    1. 定义

      • 由程序段、相关的数据段和 PCB 三部分便构成了进程实体(又称进程映像)。
    2. 特征

      • 动态性
      • 并发性
      • 独立性
      • 异步性
  2. 进程的基本状态及转换

    1. 三种基本状态

      • 就绪 Ready
      • 执行 Running
      • 阻塞 Block
    2. 三种基本状态的转换

      tRzKeS.png
      • 就绪状态→执行状态:为就绪队列队首的程序分配处理器。
      • 执行状态→就绪状态:时间片用完
      • 执行状态→阻塞状态:I/O请求
      • 阻塞状态→就绪状态:I/O完成
    3. 加入创建和终止的五状态转换

      tWSVk4.png
  3. 挂起操作和进程状态的转换

    ​ 当该操作作用千某个进程时,该进程将被挂起,意味着此时该进程处于静止状态。如果进程正在执行,它将暂停执行。 若原本处千就绪状态, 则该进程此时暂不接受调度。与挂起操作对应的操作是激活操作。

    1. 挂起操作的引入

      • 终端用户的需要
      • 父进程请求
      • 负荷调节的需要
      • 操作系统的需要
    2. 引入挂起原语操作后三个进程状态的转换

      tWPNa8.png
      • 活动就绪(Readya) → 静止就绪(Readys):未被挂起可以接受调度为Readya,挂起原语 Suspend 将该进程挂起后,变为Readys,不能接受调度。
      • 活动阻塞(Blockeda) → 静止阻塞(Blockeds):未被挂起的阻塞为Blockeda,经 Suspend 挂起后,为 Blockeds;处于该状态的进程在其所期待的事件出现后,它将从 Blockeds 变为 Readys 状态。
      • 静止就绪(Readys) → 活动就绪(Readya):处于 Readys 的进程被激活原语 Active 激活。
      • 静止阻塞(Blockeds) → 活动阻塞(Blockeda):。处于 Blockeds 的进程被激活原语 Active 激活。
    3. 引入挂起操作后五个进程状态的转换

      tWPdPg.png
  4. 进程管理中的数据结构

    1. 进程控制块 PCB 的作用

      PCB 的作用是使 在多道程序环境下不能独立运行的程序(含数据)成为一个能独立运行的基本单位一个能与其他进程并发执行的进程。

      • 作为独立运行基本单位的标志
      • 能实现间断性运行方式
      • 提供进程管理所需要的信息
      • 提供进程调度所需要的信息
      • 实现与其它进程的同步与通信
    2. PCB 中的信息

      1. 进程标识符

        • 外部 ~ 用户
        • 内部 ~ OS
      2. 处理机状态

        即处理机的上下文,由下面的寄存器组成

        • 通用寄存器:用户程序可访问,用于暂存信息。
        • 指令计数器:存放要访问的下一条指令的地址。
        • 程序状态字 PSW:含有状态信息,如条件码、执行方式、中断屏蔽标志。
        • 用户栈指针:用于存放过程和系统调用参数及调用地址。
      3. 进程调度信息

        • 进程状态
        • 进程优先级
        • 进程调度其他信息
        • 事件
      4. 进程控制信息

        • 程序和数据的地址
        • 程同步和通信机制
        • 资源清单
        • 链接指针
    3. 进程控制块的组织方式

      • 线性方式

        tWktVx.png
      • 链接方式

        tWkrMd.png
      • 索引方式

        tWkhRg.png

3. 进程控制

  1. 操作系统内核

    分类:

    • 系统态(管态):高权限,能执行一切指令,传统 OS 运行在此。
    • 应用态(目态):低权限,只能运行一部分指令,应用程序运行在此。

    功能:

    1. 支撑功能

      • 中断处理
      • 时钟管理
      • 原语操作
    2. 资源管理功能

      • 进程管理
      • 存储器管理
      • 设备管理
  2. 进程的创建与撤销

    1. 创建

      • 方式:由系统程序创建、由父进程创建
      • 引起创建的事件:用户登录、作业调度、提供服务、应用请求
      • 过程:
        ① 申请空白PCB
        ② 为新进程分配资源
        ③ 初始化进程控制块
        ④ 将新进程插入就绪队列
    2. 撤销

      • 方式:由操作系统终止、有进程发出请求

      • 引起撤销的事件:正常结束、异常结束(越界错误、运行超时)、外界干预(父进程请求、父进程终止)

      • 过程:

        ① 根据标识符找到该进程,读取状态

        ② 若正处于执行状态,立即终止该,并置调度标志为真,用于指示该进程被终止后应重新进行调度

        ③ 若该进程还有子孙进程,一并终止

        ④ 将该进程所有资源还给父进程或 OS

        ⑤ 将该进程 PCB 移出PCB表

  3. 进程的阻塞与唤醒

    1. 引起阻塞或唤醒的主要事件:

      • 请求系统服务
      • 启动某些操作
      • 新数据尚未到达
      • 无新工作可做
    2. 进程阻塞过程

      ​ 正在执行的进程,如果发生了上述某事件,进程便通过调用阻塞原语 block 将自己阻塞(主动行为)。立即停止执行,把进程控制块中的现行状态由“执行“改为阻塞,并将 PCB 插入阻塞队列。

    3. 进程唤醒过程

      ​ 调用唤醒原语 wakeup, 首先把被阻塞的进程从等待该事件的阻塞队列中移出, 将其 PCB 的现行状态由阻塞改为就绪,然后再将该 PCB 插入到就绪队列中。

  4. 程序的挂起与激活

    激活过程:(挂起为其逆过程)

    • 将进程从外存调入内存
    • 检查该进程现行状态
    • 若是静止就绪,改为活动就绪;若是静止阻塞,改为活动阻塞

4. 进程同步

为保证多个进程能有条不紊地运行,在多道程序系统中,必须引入进程同步机制。

  1. 进程同步的基本概念

    1. 两种形式的制约关系

      • 间接相互制约关系
      • 直接相互制约关系
    2. 临界资源

      ​ 虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所使用,我们把一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如打印机等。此外,还有许多变量、数据等都可以被若干进程共享,也属于临界资源。

    3. 临界区

      在每个进程中访问临界资源的那段代码称为临界区。

      访问临界资源的循环进程描述如下:

      while (TRUE) 
      {
      	进入区
      	临界区
      	退出区
      	剩余区
      }
      
    4. 同步机制应遵循的规则

      • 空闲让进
      • 忙则等待
      • 有限等待
      • 让权等待
  2. 硬件同步机制

    1. 关中断

    2. 利用 Test-and-Set 指令实现互斥

      //lock=FALSE 时,表示该资源空闲; lock=TRUE 时, 表示该资源正在被使用。
      boolean TS(boolean *lock) 
      {
      	Boolean old;
      	old = *lock;
      	*lock = TRUE;
      	return old;
      }
      
      //为每个临界资源设置一个布尔变量lock, 初值为FALSE, 表示该临界资源空闲。进程在进入临界区之前,首先用TS测试lock, 若为FALSE则表示没有进程在临界区可以进入,并将TRUE值赋予lock,即关闭临界资源,使任何进程都不能进入临界区,否则必须循环测试直到TS(s)为TRUE。
      do {
      	while TS(&lock);
      	critical section;
      	lock := FALSE;
      	remainder section;
      } while(TRUE) ; 
      //不符合让权等待原则
      
    3. 利用 Swap 指令实现进程互斥

      void swap(boolean *a, booJean *b)
      {
      	boolean temp ;
      	temp = *a;
      	*a = *b;
      	*b = temp; 
      }
      
      //为每个临界资源设置一个全局的布尔变lock,其初值为false,在每个进程中再利用一个局部布尔变量key
      do {
      	key=TRUE;
      	do {
      	swap(&lock , &key);
      } while (key!=FALSE );
      	/*临界区操作*/
      	lock = FALSE;
      } while (TRUE); 
      //不符合让权等待原则
      
  3. 信号量机制

    1. 整型信号量

      //S表示资源数目;P-V操作
      wait(S)
      {
      	while (S<=O);
      	S--;
      }
      signal(S)
      {
      	S++; 
      }
      //不符合让权等待原则
      
    2. 记录型信号量

      typedef struct {
      	int value; //表示资源数目
      	struct process_control_block *list; //用于链接所有等待进程
      } semaphore ; 
      
      //每次wait操作代表有一个进程试图访问临界资源
      wait(semap hore *S) 
      { 
      	S->value--; //临界资源减1之后,≥0代表还有资源可以访问
      	if (S->value < 0) block (S->list); //小于0随即进入等待队列
      }
      
      //每次signal操作代表有一个进程试图释放临界资源
      signal(semaphore *S) 
      {
      	S->value++; //临界资源加1后,小于0代表还没有临界资源可以访问
      	if (S->value <= O) wakeup(S->list); //≤0代表还有进程试图访问临界资源,随即唤醒等待队列中的第一个进程
      }
      
    3. AND 型信号量

      适用于一个进程需要访问两个及以上的临界资源。

      Swait(Sl , S2,..., Sn)
      {
      	while (TRUE)
      	{
      		if (Si>=1 &&... && Sn>=1)
              {
      			for (i=1; i<=n; i++) Si--;
      			break; 
              }
              else 
              {
      			place the process in the waiting queue associated with the frrst Si found with
      			Si<1 , and set the program count of this process to the beginning of Swait operation 
              }
      	}
      }
      
      Ssignal(Sl , S2, .. ., Sn)
      {
      	while (TRUE) 
          {
      		for(i=1; i<=n; i++) 
              {
      			Si++;
      			Remove all the process waiting in the queue associated with Si into the ready queue 
              }
          }
      }
      
    4. 信号量集

      对 AND 信号量机制加以扩充,对进程所申请的所有资源以及每类资源不同的资源需求量在一次 P、V 原语操作中完成申请或释放。

      //资源分配下限值ti,即要求Si≥ti,否则不予分配。一旦允许分配,进程对该资源的需求值为di,即表示资源占用量,进行Si = Si - di操作。
      Swait(S1 t1, d1, ..., Sn, tn, dn);
      
      Ssignal(S1, d1,.. ., Sn, dn);
      
      Swait(S, d, d) //此时在信号量集中只有一个信号量S, 但允许它每次申请d个资源,当现有资源数少于d时,不予分配。
      Swait(S, 1, 1) //此时的信号量集已蜕化为一般的记录型信号量(S > 1时)或 互斥信号量(S = 1时)。
      Swait(S, 1, 0) //当 S≥1 时,允许多个进程进入某特定区;当S变为0后,将阻止任何进程进入特定区。换言之,它相当于一个可控开关。
      
  4. 信号量的应用

    1. 利用信号量实现进程互斥

      //mutex 互斥信号量其初值为1, 当mutex = 1时,表示两个进程皆未进入需要互斥的临界区; mutex = 0,表示有一个进程进入临界区运行,另外一个必须等待,挂入阻塞队列; 当mutex = -1 时,表示有一个进程正在临界区运行,另个进程因等待而阻塞在信号量队列中,需要被当前已在临界区运行的进程退出时唤醒。
      semaphore mutex= 1;
      P(A)
      {
          while(1) 
          {
      		wait(mutex);
          	/*临界区*/
      		signal(mutex);
      		/*剩余区*/
      	}
      }
      
      P(B)
      {
          while(1)
          {
      		wait(mutex);
      		/*临界区*/
      		signal(mutex);
      		/*剩余区*/
          }
      }
      //wait(mutex) signal(mutex) 必须成对地出现,缺少wait(mutex)将会导致系统混乱,不能保证对临界资源的互斥访问; 缺少signal(mutex)将会使临界资源永远不被释放,从而使因等待该资源而阻塞的进程不能被唤醒。
      
    2. 利用信号量实现前趋关系

      tW88Q1.png
      p1() { S1; signal(a); signal(b);}
      p2() { wait(a); S2; signal(c); signal(d);}
      p3() { wait(b); S3; signal(e);}
      p4() { wait(c); S4; signal(t);}
      p5() { wait(d); Ss; signal(g);}
      p6() { wait(e); wait(f); wait(g); S6;}
      main() 
      {
          semaphore a, b, c, d, e, f, g;
          a.value = b.value = c.value = 0;
          d.value = e.value = 0;
          f.value = g.value = 0;
          cobegin
              pl(); p2(); p3(); p4(); p5(); p6(); 
          coend
      }
      

5. 经典进程的同步问题

  1. 生产者-消费者问题

    • 问题描述

      ​ 一组生产者进程和一组消费者进程共享一个初始为空、大小为n的缓冲区,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。

    • 问题分析

      1. 关系分析。生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,他们也是同步关系。

      2. 整理思路。这里比较简单,只有生产者和消费者两个进程,正好是这两个进程存在着互斥关系和同步关系。那么需要解决的是互斥和同步PV操作的位置。

      3. 信号量设置。信号量mutex作为互斥信号量,它用于控制互斥访问缓冲池,互斥信号量初值为1;信号量full用于记录当前缓冲池中“满”缓冲区数,初值为0。信号量empty 用于记录当前缓冲池中“空”缓冲区数,初值为n。

    int in=0, out=0; //指向生产、消费的指针
    semaphore mutex=1; //临界区互斥信号量
    semaphore empty=n;  //空闲缓冲区
    semaphore full=0;  //缓冲区初始化为空
    void producer() //生产者进程
    { 
        do{
            produce an item in nextp;  //生产数据
            P(empty);  //获取空缓冲区单元 /*wait(empty);wait(mutex); 或 Swait(empty, mutex );*/
            P(mutex);  //进入临界区
            add nextp to buffer;  //将数据放入缓冲区
            in=(in+ 1) % n; //指针后移
            V(mutex);  //离开临界区,释放互斥信号量 /*signal(mutex);signal(full); 或 Ssignal(mutex, full);*/
            V(full);  //满缓冲区数加1
        }while(TRUE)
    }
    void consumer() //消费者进程
    {  
        do{
            P(full);  //获取满缓冲区单元 /*wait(full);wait(mutex); 或 Swait(full, mutex );*/
            P(mutex);  // 进入临界区
            remove an item from buffer;  //从缓冲区中取出数据
            out=(out+l) % n; //指针后移
            V (mutex);  //离开临界区,释放互斥信号量 /*signal(mutex);signal(empty); 或 Ssignal(mutex, empty);*/
            V (empty) ;  //空缓冲区数加1
            consume the item;  //消费数据
        }while(TRUE)
    }
    

    该类问题要注意对缓冲区大小为 n 的处理,当缓冲区中有空时便可对empty变量执行 P操作,一旦取走一个产品便要执行 V操作 以释放空闲区。对empty和full变量的 P操作 必须放在对 mutex 的 P操作 之前。

    如果生产者进程先执行P(mutex),然后执行P(empty),消费者执行P(mutex),然后执行P(fall),这样可不可以?

    答案是否定的。设想生产者进程已经将缓冲区放满,消费者进程并没有取产品,即 empty = 0,当下次仍然是生产者进程运行时,它先执行P(mutex)封锁信号量,再执行P(empty)时将被阻塞,希望消费者取出产品后将其唤醒。轮到消费者进程运行时,它先执行P(mutex),然而由于生产者进程已经封锁mutex信号量,消费者进程也会被阻塞,这样一来生产者、消费者进程都将阻塞,都指望对方唤醒自己,陷入了无休止的等待。同理,如果消费者进程已经将缓冲区取空,即 full = 0,下次如果还是消费者先运行,也会出现类似的死锁。不过生产者释放信号量时,mutex、full先释放哪一个无所谓,消费者先释放mutex还是empty都可以。

  2. 哲学家进餐问题

    • 问题描述

      ​ 一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭,如图2-10所示。哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿的时候,才试图拿起左、 右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。

    • 问题分析

      1. 关系分析。5名哲学家与左右邻居对其中间筷子的访问是互斥关系。

      2. 整理思路。显然这里有五个进程。本题的关键是如何让一个哲学家拿到左右两个筷子而不造成死锁或者饥饿现象。那么解决方法有两个,一个是让他们同时拿两个筷子;二是对每个哲学家的动作制定规则,避免饥饿或者死锁现象的发生。

      3. 信号量设置。定义互斥信号量数组Ch0PstiCk[5] = {1, 1, 1, 1, 1}用于对5个筷子的互斥访问。

    //对哲学家按顺序从0~4编号,哲学家i左边的筷子的编号为i,哲学家右边的筷子的编号为(i+l)%5。
    semaphore chopstick[5] = {1,1,1,1,1}; //定义信号量数组chopstick[5],并初始化
    void Pi() //i号哲学家的进程
    {  
        do{
            P(chopstick[i]); //取左边筷子 /*wait(chopstick[i]);wait(chopstick[(i+l)%5]); 或 Sswait(chopstick[(i+1)%5], chopstick[i]);*/
            P(chopstick[(i+1)%5]); //取右边篌子
            eat; //进餐
            V(chopstick[i]); //放回左边筷子 /*signal(chopstick[i]);signal(chopstick[(i+l)%5]); 或 Ssignal(chopstick[(i+1)%5], chopstick[i]);*/
            V(chopstick[(i+l)%5]); //放回右边筷子
            think; //思考
        } while(1);
    }
    

    该算法存在以下问题:
    当五个哲学家都想要进餐,分别拿起他们左边筷子的时候(都恰好执行完wait(chopstick[i]);)筷子已经被拿光了,等到他们再想拿右边的筷子的时候(执行 wait(chopstick[(i+l)%5]);)就全被阻塞了,这就出现了死锁。

    ​ 为了防止死锁的发生,可以对哲学家进程施加一些限制条件:

    • 至多只允许有四位哲学家同时去拿左边的筷子,最终能保证至少有 1 位哲学家能 够进餐,并在用毕时能释放出他用过的两只筷子,从而使更多的哲学家能够进餐
    • 仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐
    • 规定奇数号哲学家先拿左边的筷子,然后再去拿右边的筷子;而偶数号哲学家则相反。按此规定将是 1、2号哲学家竞争 1号筷子,3、4号哲学家竞争 3号筷子。即五位哲学家都先竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有 1位哲学家 能获得两只筷子而进餐.
  3. 读者-写者问题

    • 问题描述

      有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:
      ① 允许多个读者可以同时对文件执行读操作;
      ② 只允许一个写者往文件中写信息;
      ③ 任一写者在完成写操作之前不允许其他读者或写者工作;
      ④ 写者执行写操作前,应让已有的读者和写者全部退出。

    • 问题分析

      1. 关系分析。由题目分析读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。

      2. 整理思路。两个进程,即读者和写者。写者是比较简单的,它和任何进程互斥,用互斥信号量的P操作、V操作即可解决。读者的问题比较复杂,它必须实现与写者互斥的同时还要实现与其他读者的同步,因此,仅仅简单的一对P操作、V操作是无法解决的。那么,在这里用到了一个计数器,用它来判断当前是否有读者读文件。当有读者的时候写者是无法写文件的,此时读者会一直占用文件,当没有读者的时候写者才可以写文件。同时这里不同读者对计数器的访问也应该是互斥的。

      3. 信号量设置。首先设置信号量count为计数器,用来记录当前读者数量,初值为0; 设置mutex为互斥信号量,用于保护更新count变量时的互斥;设置互斥信号量rw用于保证读者和写者的互斥访问。

    int count=0;  //用于记录当前的读者数量
    semaphore mutex=1;  //用于保护更新count变量时的互斥
    semaphore rw=1;  //用于保证读者和写者互斥地访问文件
    void writer() //写者进程
    {  
        do{
            P(rw); // 互斥访问共享文件
            Writing;  //写入
            V(rw) ;  //释放共享文件
        }while(TRUE)
    }
    void reader () //读者进程
    {  
        do{
            P (mutex) ;  //互斥访问count变量
            if (count==0)  //当第一个读进程读共享文件时
                P(rw);  //阻止写进程写
            count++;  //读者计数器加1
            V (mutex) ;  //释放互斥变量count
            reading;  //读取
            P (mutex) ;  //互斥访问count变量
            count--; //读者计数器减1
            if (count==0)  //当最后一个读进程读完共享文件
                V(rw) ;  //允许写进程写
            V (mutex) ;  //释放互斥变量 count
        }while(TRUE)
    }
    void main() 
    {
    	cobegin
    	reader(); writer();
    	coend 
    }
    

6. 进程通信

  1. 类型

    进程通信是指进程之间的信息交换。PV操作是低级通信方式,髙级通信方式是指以较高的效率传输大量数据的通信方式。高级通信方法主要有以下三个类。

    1. 共享存储器系统

      • 基于共享数据结构的通信方式:通信效率低下,属于低级通信。
      • 基于共享存储区的通信方式:属于高级通信。
    2. 管道(pipe)通信系统

      ​ 一种特殊方式。所谓“管道”,是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名pipe文件。向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入(写)管道;而接收管道输出的接收进程(即读进程),则从管道中接收(读)数据。为了协调双方的通信,管道机制必须提供以下三方面的协调能力:互斥、同步和确定对方的存在。

    3. 消息传递系统

      ​ 以格式化的消息(Message)为单位。若通信的进程之间不存在可直接访问的共享空间,则必须利用操作系统提供的消息传递方法实现进程通信。进程通过系统提供的发送消息和接收消息两个原语进行数据交换。

      • 直接通信方式:发送进程直接把消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息。
      • 间接通信方式:发送进程把消息发送到某个中间实体中,接收进程从中间实体中取得消息。这种中间实体一般称为信箱,这种通信方式又称为信箱通信方式。该通信方式广泛应用于计算机网络中,相应的通信系统称为电子邮件系统。
    4. 客户机-服务器系统

      • 套接字Socket
        • 基于文件型
        • 基于网络型
      • 远程过程调用和远程方法调用
  2. 消息传递通信的实现方式

    1. 直接消息传递系统

      • 直接通信原语

        • 对称寻址方式

          send(recei ver, message); 发送一个消息给接收进程

          receive(sender, message); 接收 Sender 发来的消息

          缺点:进程一旦改变名字,则要找到其所有历史名字以便修改。

        • 非对称寻址方式

          send(P , message); 发送一个消息给进程

          receive (id, message); 接收来自任何进程的消息, id变量可设置为进行通信的发送方进程 id 或名字。

    2. 信箱通信

      • 信箱结构

        • 信箱头,用以存放有关信箱的描述信息,如信箱标识符、信箱的拥有者、信箱口令、信箱的空格数等;
        • 信箱体,由若干个可以存放消息(或消息头)的信箱格组成,信箱格的数目以及每格的大小是在创建信箱时确定的。
        tWo39S.png
      • 信箱通信原语

        • 邮箱的创建和撤消

        • 消息的发送和接收

          进程之间要利用邮箱进行通信时,必须使用共享邮箱。

          Send(mailbox, message); 个消息发送到指定邮箱

          Receive(mailbox, message); 从指定邮箱中接收一个消息

      • 信箱的类型

        • 私用邮箱:进程自己创建,所有者可以读取消息,其他进程只能发送。进程结束时,邮箱也随之消失。
        • 公用邮箱:OS 创建,经核准的进程可向其发送消息也可读取给自己的消息。在系统运行期间始终存在。
        • 共享邮箱:进程创建,创建时指明为共享邮箱并指出共享进程的名字,所有者和共享者可以读取给自己的消息。进程结束时,邮箱也随之消失。
      • 发送进程和接收进程的四种关系

        • 一对一关系
        • 多对一关系
        • 一对多关系
        • 多对多关系

7. 线程(Threads)的基本概念

引入进程的目的,是为了使多道程序并发执行,以提高资源利用率和系统吞吐量;而引入线程,则是为了减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。

  1. 线程的引入

    线程最直接的理解就是“轻量级进程”,它是一个基本的CPU执行单元,也是程序执行的最小单元,由线程ID、程序计数器、寄存器集合和堆栈组成。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。

    引入线程后,进程的内涵发生了改变,进程只作为除CPU以外系统资源的分配单元,线程则作为处理机的分配单元。

  2. 线程与进程的比较

    • 调度的基本单位

      进程较重,在被调度时执行上下文开销较大。将线程作为调度和分派的基本单位,切换代价较低。

      在同一进程中,线程的切换不会引起进程的切换,但从一个进程中的线程切换到另一进程中的线程时,必然会引起进程的切换。

    • 并发性

      进程间,一个进程中的多个/全部线程,不同进程中的线程。

    • 拥有资源

      线程仅拥有必不可少的保证独立运行的资源,共享该进程的所有资源。

    • 独立性

      线程间的独立性远远低于进程间的独立性。

    • 系统开销

      创建和撤销进程的开销远大于线程的开销。

    • 支持多处理机系统

      单线程进程,该进程只能运行在一个处理机上;多线程进程,可将进程中的多个线程分配到多个处理机上,并行执行。

  3. 线程的状态和线程控制块

    1. 三个状态

      • 执行状态,表示线程已获得处理机而正在运行;
      • 就绪状态,指线程已具备了各种执行条件,只须再获得 CPU 便可立即执行;
      • 阻塞状态,指线程在执行中因某事件受阻而处于暂停状态

      线程间的状态转换与进程间的状态转换相同。

    2. 线程控制块 TCB

      ① 线程标识符,为每个线程赋予一个唯一的线程标识符。

      ② 一组寄存器,程序寄存器等。

      ③ 线程运行状态,线程此时运行状态。

      ④ 优先级,描述线程执行的优先程度。

      ⑤ 线程专有存储区,用于线程切换时存放现场保护信息,和与该线程相关的统计信息等。

      ⑥ 信号屏蔽,对某些信号加以屏蔽。

      ⑦ 堆栈指针,保存局部变量和返回地址。

    3. 多线程 OS 中的进程属性

      ① 进程是一个可拥有资源的基本单位。

      ② 多个线程可并发执行。

      ③ 进程已不是可执行的实体 。

8. 线程的实现

实现方式

  1. 以线程为基本调度单位的内核支持线程[与内核紧密相关]
  2. 以进程为基本调度单位的用户级线程[与内核无关]