【操作系统】第二章--进程的描述与控制--笔记与理解(2)

笔记理解之后可以进行深入解释→【操作系统】第二章–进程的描述与控制–深入与解释(2)

第二章–进程的描述与控制–笔记与理解(2)

经典进程的同步问题

生产者-消费者问题

生产者消费者流程图

  1. 单生产者-单消费者-单缓冲区:系统中有一个生产者进程、一个消费者进程和一个一次只能放1个产品的缓冲区。生产者进程重复的生产产品并放入到缓冲区中;每当缓冲区中有产品时,消费者进程从缓冲区中取产品进行消费。
    • 单单单

    • 信号量:space,存储位置,初值1;prod,产品,初值0

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
sem_t space,prod;

void *producer(void *p) {
    while(1) {
        sem_wait(&space);
        printf("Put a product\n");
        sem_post(&prod);
    }
    return NULL;
}
void *consumer(void *p) {
    while(1) {
        sem_wait(&prod);
        printf("Get a product\n");
        sem_post(&space);
    }
    return NULL;
}

int main(void) {

    sem_init(&space,0,1);
    sem_init(&prod,0,0);
    pthread_t tid[2];
    pthread_create(&tid[0],NULL,producer,NULL);
    pthread_create(&tid[1],NULL,consumer,NULL);
    sem_destroy(&space);
    sem_destroy(&prod);
    pthread_join(tid[0],NULL);
    
    return 0;
}
  • 运行结果:1
  1. 单生产者-单消费者-多缓冲区
    • 单单多

    • 产品存储:循环队列;信号量:space,存储位置,初值N;prod,产品,初值0

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define N 10
sem_t space,prod;
int BUffer[N];
int in=0,out=0;

void *producer(void *p) {
    while(1) {
        sem_wait(&space);
        printf("Put a product into Buffer[%d]\n",in);
        in = (in+1)%N;
        sem_post(&prod);
    }
    return NULL;
}
void *consumer(void *p) {
    while(1) {
        sem_wait(&prod);
        printf("Get a product from Buffer[%d]\n",out);
        out = (out+1)%N;
        sem_post(&space);
    }
    return NULL;
}

int main(void) {

    sem_init(&space,0,N);
    sem_init(&prod,0,0);
    pthread_t tid[2];
    pthread_create(&tid[0],NULL,producer,NULL);
    pthread_create(&tid[1],NULL,consumer,NULL);
    sem_destroy(&space);
    sem_destroy(&prod);
    pthread_join(tid[0],NULL);

    return 0;
}

  • 运行结果:2
  1. 多生产者-多消费者
    • 多多

    • 信号量:space,存储位置,初值N;prod,产品,初值0;buf,缓冲区,初值1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define M 8
#define N 10
sem_t space,prod,buf;
int Buffer[N];
int in = 0, out = 0;

void *producer(void *p) {
    while(1) {
        sem_wait(&space);
        sem_wait(&buf);
        printf("Put a product into Buffer[%d]\n",in);
        in = (in+1)%N;
        sem_post(&prod);
        sem_post(&buf);
    }
    return NULL;
}
void *consumer(void *p) {
    while(1) {
        sem_wait(&prod);
        sem_wait(&buf);
        printf("Get a product from Buffer[%d]\n",out);
        out = (out+1)%N;
        sem_post(&space);
        sem_post(&buf);
    }
    return NULL;
}

int main(void) {
    
    sem_init(&space,0,N);
    sem_init(&prod,0,0);
    sem_init(&buf,0,1);
    pthread_t tid[M],tid2[M];
    for(int i = 0; i < M; i ++) {
        pthread_create(&tid[i],NULL,producer,NULL);
    }
    for(int i = 0; i < M; i ++) {
        pthread_create(&tid2[i],NULL,consumer,NULL);
    }
    sem_destroy(&space);
    sem_destroy(&prod);
    sem_destroy(&buf);
    pthread_join(tid[0],NULL);

    return 0;

}

  • 运行结果:3

  • 总结:思维导图

读者-写者问题
  • 一个数量集(如文件)被多个并发进程(线程)共享,一些进程(线程)只读取数据集内容(读者),而另一些进程(线程)则只修改数据集内容(写者)。
    • 访问规则:

      1. 允许多个读者同时读取数据集;当有读者读数据集时,不允许任何写者进程写
      2. 当一个写者进程写数据集期间,不允许任何其他写者进程写,也不允许任何读者进程读
      • 即:“读-写互斥”,“写-写互斥”,“读-读允许”
    • 读者写者

    • 变量:readcount,读者数;信号量:sdata,数据集,初值1;srcount,读者数变量,初值1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define M 10
sem_t sdata,srcount;
int readcount = 0;

void *reader(void *p) {
    sem_wait(&srcount);
    readcount++;
    if(readcount == 1) {
        sem_wait(&sdata);
    }
    sem_post(&srcount);
    printf("Reading..\n");
    sem_wait(&srcount);
    readcount--;
    if(readcount == 0) {
        sem_post(&sdata);
    }
    sem_post(&srcount);
    return NULL;
}
void *writer(void *p) {
    sem_wait(&sdata);
    printf("Writing..\n");
    sem_post(&sdata);
    return NULL;
}

int main(void) {

    sem_init(&sdata,0,1);
    sem_init(&srcount,0,1);
    pthread_t tid[M],tid2[M];
    for(int i = 0; i < M; i ++) {
        pthread_create(&tid[i],NULL,writer,NULL);
    }
    for(int i = 0; i < M; i ++) {
        pthread_create(&tid2[i],NULL,reader,NULL);
    }
    sem_destroy(&sdata);
    sem_destroy(&srcount);
    pthread_join(tid[0],NULL);

    return 0;
}

  • 运行结果:4

  • 存在问题:写者进程在实际应用中一般拥有更高操作优先级,但有可能长期推迟

  • 读者-写者-写者优先

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define M 10
sem_t sdata,srcount;
int readcount = 0;

void *reader(void *p) {
    sem_wait(&srcount);
    readcount++;
    if(readcount == 1) {
        sem_wait(&sdata);
    }
    sem_post(&srcount);
    printf("Reading..\n");
    sem_wait(&srcount);
    readcount--;
    if(readcount == 0) {
        sem_post(&sdata);
    }
    sem_post(&srcount);
    return NULL;
}
void *writer(void *p) {
    sem_wait(&sdata);
    printf("Writing..\n");
    sem_post(&sdata);
    return NULL;
}

int main(void) {

    sem_init(&sdata,0,1);
    sem_init(&srcount,0,1);
    pthread_t tid[M],tid2[M];
    for(int i = 0; i < M; i ++) {
        pthread_create(&tid[i],NULL,writer,NULL);
    }
    for(int i = 0; i < M; i ++) {
        pthread_create(&tid2[i],NULL,reader,NULL);
    }
    sem_destroy(&sdata);
    sem_destroy(&srcount);
    pthread_join(tid[0],NULL);

    return 0;
}

  • 运行结果:5
哲学家就餐问题
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <pthread.h>
#include <errno.h>
#include <math.h>
#include <unistd.h>

pthread_mutex_t chop[6];
void *eat_think(void *arg) {
    char phi = *(char *)arg;
    int l,r;
    switch(phi) {
        case 'A':
            l = 5;r = 1;
            break;
        case 'B':
            l = 1;r = 2;
            break;
        case 'C':
            l = 2;r = 3;
            break;
        case 'D':
            l = 3;r = 4;
            break;
        case 'E':
            l = 4;r = 5;
    }
    int i;
    for(;;) {
        sleep(3);
        pthread_mutex_lock(&chop[l]);
        printf("哲学家 %c 获得了筷子 %d\n",phi,l);
        /*
        if(pthread_mutex_trylock(&chop[r]) == EBUSY) {
            pthread_mutex_unlock(&chop[l]);
            continue;
        }
        */
        pthread_mutex_lock(&chop[r]);
        printf("哲学界 %c 获得了筷子 %d\n",phi,r);
        printf("哲学家 %c 正在就餐\n",phi);
        sleep(3);
        pthread_mutex_unlock(&chop[l]);
        printf("哲学家 %c 放下了筷子 %d\n",phi,l);
        pthread_mutex_unlock(&chop[r]);
        printf("哲学家 %c 放下了筷子 %d\n",phi,r);
    }
}

int main() {
    pthread_t A,B,C,D,E;
    for(int i = 0; i < 5 ; i ++) {
        pthread_mutex_init(&chop[i],NULL);
    }
    pthread_create(&A,NULL,eat_think,"A");
    pthread_create(&B,NULL,eat_think,"B");
    pthread_create(&C,NULL,eat_think,"C");
    pthread_create(&D,NULL,eat_think,"D");
    pthread_create(&E,NULL,eat_think,"E");

    pthread_join(A,NULL);
    pthread_join(B,NULL);
    pthread_join(C,NULL);
    pthread_join(D,NULL);
    pthread_join(E,NULL);
    
    return 0;
}
--------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <pthread.h>
#include <errno.h>
#include <math.h>
#include <unistd.h>
#include <semaphore.h>

sem_t chop[5];
void *eat_think(void *arg) {
    int phi = *(int *)arg;
    while(1) {
        sem_wait(&chop[phi]);
        printf("哲学家 %d 获得了右边的筷子 %d\n",phi,phi);
        sleep(3);
        sem_wait(&chop[(phi+1)%5]);
        printf("哲学家 %d 获得了左边的筷子 %d\n",phi,(phi+1)%5);
        sem_wait(&chop[(phi+1)%5]);
        printf("哲学家 %d 放下了左边的筷子 %d\n",phi,(phi+1)%5);
        sem_wait(&chop[phi]);
        printf("哲学家 %d 放下了右边的筷子 %d\n",phi,phi);
        /*
        if(pthread_mutex_trylock(&chop[r]) == EBUSY) {
            pthread_mutex_unlock(&chop[l]);
            continue;
        }
        
        pthread_mutex_lock(&chop[r]);
        printf("哲学界 %c 获得了筷子 %d\n",phi,r);
        printf("哲学家 %c 正在就餐\n",phi);
        sleep(3);
        pthread_mutex_unlock(&chop[l]);
        printf("哲学家 %c 放下了筷子 %d\n",phi,l);
        pthread_mutex_unlock(&chop[r]);
        printf("哲学家 %c 放下了筷子 %d\n",phi,r);
*/
    }
}

int main() {
    for(int i = 0; i < 5 ; i ++) {
        sem_init(&chop[i],0,1);
    }
    pthread_t tid[5];
    for(int i = 0; i < 5 ; i ++) {
        pthread_create(&tid[i],NULL,eat_think,&i);
    }
    for(int i = 0; i < 5 ; i ++) {
        sem_destroy(&chop[i]);
    } 

    pthread_join(tid[0],NULL);
    
    return 0;
}

进程通信

  • 使用方便,高效地传送大量数据
进程通信类型
  1. 共享存储器系统:
    1. 基于共享数据结构的通信方式:适用于传递相对少量的数据,效率低下,低级通信
    2. 共享存储区的通信方式:高级通信,需要通信的进程在通信前,前向系统申请获得功能共享存储区的一个分区,并附加到自己的地址空间,便可进行操作,不再需要时将其归还给共享存储区
  2. 管道通信系统(pipe):
    1. 互斥:一个进程执行操作,其余进程等待
    2. 同步:读写进程的等待与唤醒
    3. 只有确定对方存在才能进行通信
  3. 消息传递系统:高级通信方式
    1. 直接通信方式:发送进程利用OS所提供的发送原语,直接把消息发送给目标进程
    2. 间接通信方式:发送和接收进程,都通过共享中间实体的方式完成进程间的通信
  4. 客户机-服务器系统:
    1. 套接字(socket):一个通信标识类型的数据结构,是进程通信/网络通信的基本构件
      1. 基于文件型:通信双方通过对这个文件的读写实现通信,原理类似于管道
      2. 基于网络型:非对称方式通信,发送者需要提供接收者命名,任何进程都可以发送请求,方便进程间通信的建立
      3. 优势:适用于网络环境中不同计算机间的通信,,每个套接字拥有唯一的套接字标识符,确保了通信双方间逻辑链路的唯一性,便于实现数据传送得到并发服务,,隐藏实现细节,采用统一接口进行处理
    2. 远程过程调用和远程方法调用:调用RPC(一个通信协议),用于通过网络连接的系统
消息传递的实现方式
  1. 直接消息传递系统:
    1. 直接通信原语:
      1. 对称寻址方式:要求发送进程和接收进程都以显示方式提供对方的标识符,不是用于实现进程定义的模块化
      2. 非对称寻址方式:接收进程可能需要与多个发送进程通信,不需要命名发送进程
    2. 消息的格式:采用变长的消息格式,方便了用户
    3. 进程的同步方式:发送进程阻塞,接收进程阻塞,用于进程之间紧密同步,发送接受无缓冲,发送进程不阻塞,接收进程阻塞,发送进程和接收进程均不阻塞
    4. 通信链路:
      1. 单向通路:只允许发送进程或接收进程其中一方发送信息
      2. 双向通路:允许进程A与B互相发送信息
  2. 信箱通信:属于间接通信方式
    • 信箱的结构:信箱头+信箱体
    • 信箱通信原语:用于邮箱的创建和撤销,消息的发送和接收
    • 信箱的类型:
      1. 私用信箱:可采用单向通信来实现,拥有此的进程结束,邮箱也消失
      2. 公用邮箱:双向通信链路实现,一半在系统运行期间始终存在
      3. 共享邮箱:拥有者和共享者都有权取走或发送自己的消息
      4. 关系:一对一,一对多,多对一,多对多

线程的基本概念

线程的引入
  • 为了减少程序在并发执行时所付出的时空开销,更好的并发性
  1. 基本属性:
    1. 一个可拥有资源的独立空间
    2. 一个可独立调度和分派的基本单位
  2. 程序并发执行时所需付出的时空开销:
    1. 创建进程
    2. 撤销进程
    3. 进程切换
  3. 作为调度和分派的基本单位:拥有资源基本单位,,又不实施频繁的切换
线程与进程的比较
\ 进程 线程
调度的基本单位 传统OS中,作为调度与分派的基本单位,开销较大 引入后为调度与分派的基本单位,线程切换不引起进程切换(同一进程中)
并发性 允许进程间并发执行,并发性一般 允许进程间并发执行,允许一个进程多个线程并发,并发性更好
拥有资源 可以拥有资源,并作为系统中拥有资源的一个基本单位源 拥有自己少量资源外,允许多个线程共享该进程所拥有资源
独立性 拥有一个独立的地址空间和其他资源,独立性更好 共享进程的内存地址空间和资源,独立性一般
系统开销 创建/撤销进程,OS付出的开销大于线程创建/撤销所付开销
多处理机 只能运行在一个处理机上 一个进程多线程分配到多个处理机上,使并发执行加速完成
线程的状态和线程控制块
  1. 三个状态:
    1. 执行状态
    2. 就绪状态
    3. 阻塞状态
  2. 线程控制块TCB:
    1. 线程标识符
    2. 一组寄存器
    3. 线程运行状态
    4. 线程专有存储区
    5. 优先级
    6. 信号屏蔽
    7. 堆栈指针
  3. 多线程OS中进程属性:
    1. 进程是一个可拥有资源的基本单位
    2. 多个线程可并发执行
    3. 进程已不是可执行的实体

线程的实现

线程的实现方式
  1. 内核支持线程KST:
    • 优势:
      1. 多处理系统中,内核能同时调度同一进程中的多个线程并发执行
      2. 进程中一个线程被阻塞,可运行其他进程中线程
      3. 切换快,开销小
      4. 提高系统执行速度和效率
    • 劣势:
      1. 模式切换开销大
  2. 用户级线程ULT:
    • 优势:
      1. 线程切换不需要转换到内核空间
      2. 调度算法可以是进程专用的
      3. 实现与OS平台无关,可以在不支持多线程机制的操作系统上实现
    • 劣势:
      1. 系统调用的阻塞问题
      2. 进程中仅有一个线程能执行,其他线程只能等待
  3. 组合方式:
    1. 多对一:多个用户线程映射到一个内核控制线程
      • 优势:开销小,效率高
      • 劣势:一个线程阻塞,整个进程阻塞
    2. 一对一
      • 优势:更好的并发性,一个线程阻塞,允许调度另一个线程执行
      • 劣势:开销大
    3. 多对多:结合上述两种方式的优势
线程的实现
  1. 内核支持线程的实现与进程的调度与切换相似
  2. 用户级线程的实现:不能利用系统调用,分为运行时程序和内核控制线程
线程的创建与终止
  • 由创建而产生,由调度而执行,由终止而消亡
  1. 线程的创建:“初始化线程”在创建时,利用一个线程创建函数,提供相应的参数,返回一个线程标识符供以后使用
  2. 线程的终止:完成任务或出现异常而强行结束,用终止线程通过调用相应函数(或系统调用)对他执行终止操作

本章练习

  1. 在单处理机系统中,处于运行状态的进程(A)。
    A.最多只有一个
    B.可以有多个
    C.不能被挂起
    D.必须在执行完后才能被撤下

  2. 原语是(B)
    A.一条机器指令
    B.若干条机器指令组成,不可被中断
    C.一条特定指令
    D.中途能打断的指令

  3. 支持多道程序设计的操作系统在运行过程中,不断地选择新进程运行来实现CPU的共享,但其中(D) 不是引起操作系统选择新进程的直接原因
    A.运行进程的时间片用完
    B.运行进程出错
    C.运行进程要等待某—事件的发生
    D.有新进程进入就绪状态

  4. 在支持多线程的系统中,进程P创建的若干个线程不能共享的是(D)
    A.进程P的代码段
    B.进程P中打开的文件
    C.进程P的全局变量
    D.进程P中某线程的栈指针

  5. 在创建进程时,(A))不是创建进程所必须的步骤
    A.由调度程序为进程分配CPU
    B.建立一个PCB
    C.为进程分配内存
    D.将进程插入就绪队列

  6. 线程控制块TCB中不应拥有的内容是(A)
    A.内存地址空间
    B.指令计数器PC
    C.用户栈指针
    D.线程状态

  7. 我们把在一段时间内,只允许一个进程访问的资源,称为临界资源,因此,我们可以得出下列论述,正确的论述为(D)
    A.对临界资源是不能实现资源共享的。
    B.只要能使程序并发执行,这些并发执行的程序便可对临界资源实现共享。
    C.为临界资源配上相应的设备控制块后,便能被共享。
    D.对临界资源,应采取互斥访问方式,来实现共享。

  8. 有两个并发执行的进程 P1 和 P2,共享初值为 1 的变量 x。P1 对 x 加 1,P2 对 x 减 1,两个操作完成后,x 的值A.可能为©
    A.可能为-1 或 3
    B.只能为 1
    C.可能为 0、1 或 2
    D.可能为-1、0、1 或 2

  9. 关于wait()和signal()操作,下面哪个说法是对的(A)
    A.wait()申请一个资源,资源不够,则阻塞,signal()操作释放一个资源,若有进程等待在唤醒
    B.wait()申请一个资源,signal()操作释放一个资源,若有进程等待在唤醒
    C.wait()申请一个资源,资源不够,则阻塞,signal()操作释放一个资源
    D.wait()申请一个资源,signal()操作释放一个资源

  10. 关于读者和写者问题,下列说法错误的是(D)A.如果有一个读者在读,其他读者也可以读,因此读者与读者之间不需要互斥。
    B.如果有一个读者读,其他写者就不能写,因此,读者和写者之间需要互斥。
    C.只要有一个写者写,其他写者就不能写,因此,写者和写者之间需要互斥。
    D.如果有多个读者,需要设一个共享变量来计数,这个共享变量是临界区

  11. 关于生产者消费者问题,下列叙述错误的是(E)
    A.当只有一个生产者和一个消费者一个缓冲区时,他们之间只有同步关系,不需要互斥信号量。
    B.当有多个生产者多个消费者多个缓冲区时,因为可能存在对某个缓冲区的竞争访问,既需要同步也需要互斥。
    C.生产者消费者问题可以通过AND信号量解决
    D.进程同步关键在于什么时候停(缓冲区满,生产者要停;缓存区空,消费者停),什么时候走(发信号让消费者走;发信号让生产者走)。
    E.同步就是把临界区放在wait()和signal()之间,而互斥就是把wait()和signal()分别放在生产者和消费者两个进程/线程中。

  12. 关于哲学家就餐问题,下列叙述错误的是(D)
    A.5个叉子表示5个临界资源,因此设5个信号量,每个的初值为1
    B.每个哲学家必须获得两个资源才能吃通心粉,因此可以通过AND信号量解决同步问题。
    C.当每个哲学家拿到一个叉子时,谁也吃不到通心粉,这就出现死锁了。
    D.用AND信号量信号量解决哲学家就餐问题,依然会出现死锁。
    E.当5个哲学家线程同时运行时,可能出现死锁,也可能不出现死锁

  13. 关于消息通信,以下说法错误的是©
    A.当发送消息时,发送原语就陷入到内核态,然后申请消息缓冲区,消息被从用户态缓冲区拷贝到这个消息缓冲区中,接着要找到接收者进程的pcb,并将消息挂到该PCB消息队列队列的末尾。
    B.因为消息队列是临界资源,因此插入操作要用wait-signal的操作,确保它们互斥的进行。
    C.消息队列是临界资源,应该对其进行互斥的访问,不存在同步问题。D.当发送者进程把消息放到消息队列离开的时,要进行一个signal操作,就是要唤醒接收者进程,告诉接收者进程,队列中有消息了。

  14. 一般管道通信用于父子进程之间 ,当我们通过fork( )创建了父子进程,那么父子进程的管道都有两个文件描述符,必须关闭其中的一个读端和一个写端,建立一条“父进程写入子进程读取”的通道,或者“子进程写入父进程读取”的通道,这种说法(A)
    A.对
    B.错

  15. 管道通信有读端和写端,这个模型是(B)
    A.读者-写者
    B.生产者消费者
    C.哲学家就餐
    D.爸爸妈妈儿子女儿吃苹果和橘子

猜你喜欢

转载自blog.csdn.net/weixin_44321600/article/details/107471480