《现代操作系统》第二章进程概念整理

进程

  • 进程模型(是什么?)
    进程是什么呢?操作系统(例如linux)通过维护一张名叫task_struct的表来调度、维护进程的执行。进程是cpu分配资源的单位,也就是说,计算机上的软件在被运行时都是以进程的形式运行于内存中的,当某个进程被执行的时候,一个cpu就只允许这个进程进入它的身体,也就是说,cpu同时只能执行一个进程,一般来说,我们使用的交互式系统(例如windows)根据相应的调度算法来调整对各个进程的执行顺序。
    对于一个进程来说,它由程序计数器(pc)、寄存器(诸如%eax、%cr3等)、进程id、用户堆栈等组件构成的。
  • 进程的创建
    • 系统初始化(开机了,os进程会启动)
    • 正在运行的程序执行了创建进程的系统调用 (例如在linux中,使用fork()函数创建子进程)
    • 用户请求创建一个新进程(例如使用shell、cnd运行一个新程序,又或者在交互式系统中用户点击了一个可执行文件的图像)
    • 一个批处理作业被初始化(例如写个.bat,然后执行它)
  • 进程的终止
    • 正常退出(比如自己调用exit()函数)
    • 进程出错了退出
    • 严重错误
    • 被其他进程杀死(比如被父进程)
  • 进程的状态
    1. 运行态(占用cpu时)
    2. 就绪态(准备好占用cpu了)
    3. 阻塞态(除非被信号唤醒进入就绪台,否则一直被挂起,无法占用cpu)

线程

  • 线程是什么?为什么要用线程?
    对于一个进程来说,首先它要被分配在内存中的一部分中,然后再让cpu来执行它;这是2个过程,也就是资源分配和执行。

    将两种过程结合到一个进程会导致一个问题,也就是cpu同时只能操作一个进程,对于多个进程共享的资源来说,如果我想切换到另一个进程来读写这些共享的资源,那么对于cpu来说,切换进程是需要耗费时间、资源的(这个过程就不详述了)

    于是,就有聪明的B提出了线程的概念。

    进程是资源分配的单位,那么线程就是资源执行的单位。

    一个进程可以有多个线程;也就是说,进程负责分配资源,而线程负责读写进程的资源。进程的资源对于它的所有线程来说是共享的。

  • 线程的内容

    程序计数器、寄存器、堆栈、状态

  • posix线程
    IEEE定义了线程的标准,其中有一个叫pthread的线程包,其中定义了一些函数,例如:pthread_create(创建一个新线程)、pthread_exit(结束调用的线程)、pthread_join(等待一个特定的线程退出)、pthread_yield(释放cpu来运行另一个线程)、pthread_attr_init(创建并初始化一个线程的属性结构)、pthread_attr_destroy(删除一个线程的属性结构)等

  • 使用线程的优点
    除了上面提到的便于对共享的进程资源操作时避免进行进程切换,而选用线程切换来减少修改内存映射等额外的开销来节约时间、资源。线程的存在可以允许用户自己来定义调度算法,也就是说,对于一个进程,当被cpu执行的时候,用户可以自己指定线程的先后顺序

  • 弹出式线程
    当一个消息到达时,系统就为这个消息创建一个专门处理消息的线程。
    比如说,我想用java写一个聊天室,实现多个用户终端可以在同一个聊天室中聊天,如何实现呢?我们可以通过一个服务器终端来转发每一个用户的消息给所有用户看,如果用套接字socket的话,也就是服务器检测一个端口,只要有客户端socket访问这个端口,那服务器就为这个socket创建一个专门用于处理收发信息的线程,这样,一旦有某个用户向这个服务器发送消息,那么这个服务器用于处理这个用户的线程就会接收到消息,然后将消息转发到所有用户。 我自己也根据这个思想用java写了一个聊天室:https://github.com/rookiiiie/MyChatRoom(缺点是服务器用于接受socket和读写线程都是忙等待的,很耗费cpu资源,暂时未解决)

进程通信

  • 临界区(Critical region)
    我们把对共享内存进行访问的程序片段叫做临界区
  • 如何使进程互斥访问临界区?
    • 忙等待的互斥锁(重点1)
      例如使用锁变量,有2个进程想访问共享内存,设立一个flag变量,初始值为0,当一个进场想访问内存之前,先测试这个flag,若为0则表示可以访问,并把flag令为1,若为1,则表示资源已经在使用中,则不访问。
      这种算法容易理解,但是这种算法是有问题的!假如一个进程发现flag为0可以使用,但是在这个进程将flag设为1之前,cpu突然调度另一个进程访问共享区,另一个进程也发现flag为0,这就会出现2个进程共同访问同一个共享资源的情况,并且可能发生不可预知、难以排查的错误。 这种错误的根本原因在于判断资源和使用资源这两步是非原子性的[no-atomic](我们知道原子是化学反应不可再分割的,所以用来比喻这里的2个过程是可以分割的),也就是这两个过程是可能被cpu调度打断的!
      书上也介绍了解决这种问题的办法,例如严格轮换算法,实现了2个进程互斥访问共享区,还是通过一个flag,但是这两个进程并不是都像上面的锁变量一样判断flag=0来获取访问共享资源的权限的,而是分别通过flag=0或flag=1来访问,并且在访问完之后再设置成~flag,这么看来确实是解决了非原子性的问题,因为一个flag在被修改之前只能是某一个值,而只有一个进程可以通过这个值访问资源,在被修改之前,另一个进程确实不能够访问资源,只能等资源被释放。但是,这么做是有代价的,如果一个进程已经在访问共享资源了,即便这样,临界区外的进程也可能被cpu调度,其他进程即便没有权限访问临界区,但是也可能会被调度来阻塞正在访问临界区的进程,因此cpu会空空白转 除了严格轮换法,书上也提到了类似的Peterson解法、TSL指令、XCHG解法,他们也都是能够实现线程互斥访问临界资源但是有着忙等待的缺点。

    • 信号量(重点2)
      为了解决以上问题,聪明的Dijkstra提出了信号量的概念,信号量也就是一个整形变量,用来累计唤醒次数,书上这么解释可能难以理解,换个通俗的说法,也就是共享资源在某个时刻可供进程访问的进程数。
      Dijkstra建议设立两种操作:down和up(分别为一般话后的sleep和wakeup),在他原来的论文中,他分别用了名称P和V而不是down和up,
      在荷兰语中,Proberen的意思是尝试,Verhogen的含义是增加。
      下面分别介绍信号量semaphore、p、v操作的定义
      typedef struct semaphore{
      int value; //表示可用的资源数
      struct pcb* list; //表示代等待访问的进程队列(这里采用了先进先出的原则)
      }
      void P(semaphore s){
      s.value- -;
      if(s.value<0) //这一步判断是因为当s.value<=0之后再减1的话,那么资源数就不够用了,那当前这个进程就得排进等待队列中去
      sleep(s.list);
      }
      void V(semaphore s){
      s.value++;
      if(s.value<=0) //这一步判断是因为当s.value<=-1的时候,释放资源后s.value+1之后,就会有一个资源空出来,这个资源就用于等待队列的第一个进程。
      wakeup(s.list)
      }
      当然了,对于p、v操作来说,他们是原子性的(atomic)!也就是说p、v操作要么不做,要做就会一直做到底,不会被中途打断!

      利用p、v操作信号量,可以实现生产者消费者对产品的互斥、条件访问。详细见书本p74,除了限制同一时间只允许同一个进程访问产品资源,还可以为消费者和生产者设立full、empty信号量,用于判断产品数量是否为空或产品是否已经满了,根据自定义条件来对进程进行调度。

  • 线程同步访问共享资源
    和多个进程访问共享资源一样,线程作为进程的执行单位,并且共享进程的资源,也需要解决互斥访问进程资源的问题。类似生产者和消费者进程共享临界资源,生产者、消费者线程共享进程资源也可以通过设立信号量+条件变量来建立起相应的机制,详见书本p77,由于思路跟上面的很相似,就不多说了。
  • 管程的概念
    管程其实并不是操作系统提供的什么函数,它只是一种封装的思想,它的意思就是类似封装p、v操作一样,它可以封装对临界区访问的线程代码的操作,并且保证操作的原子性(atomic),如果在java中,可以用sychronize标示符标示一个函数,例如insert()或remove(),见书本p80,这个sychronize的意思是什么呢?引用百度百科的说法:“synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。”。也就是给方法上了个二元信号量,保证了同一时刻只能有一个线程调用这个方法,将方法体作为临界资源并上锁。关于管程,光从理论上解释是很欠缺的,还是要依据实例来分析,才能理解透彻。
  • 消息传递、屏障(见书)

调度

  • 调度简介
    进程是cpu分配资源的单位,因此,一种调度算法来解决一种cpu分配资源的策略。一般进程都是I/O请求和计算交替突发的,cpu运行了一段时间可能会发出一个系统调用读写文件,然后继续计算。当今计算机的cpu计算速度很快,而受限制于向存储设备读写文件,所以现在的计算器都是I/O密集型的。

  • 何时调度
    1.在创建了一个新进程之后,要决定是父进程还是子进程先运行。
    2.当一个进程退出时
    3.当一个进程阻塞在I/O进程时或其他原因阻塞时,必须选择运行另一个进程。
    4.当一个进程发生I/O中断时

  • 抢占式与非抢占式调度
    根据如何处理时钟中断,可以把调度算法分为两类:非抢占式和抢占式调度算法。非抢占式算法挑选一个进程运行直至被阻塞或者运行结束。抢占式算法挑选一个进程运行到某个固定时段的最大值,然后强行切换到其他进程。

  • 对于不同系统的调度衡量准则

    • 批处理系统
      对于批处理系统来说,衡量调度算法的标准是 吞吐量(单位时间最大作业数)、周转时间(从提交到终止间的最小时间)、cpu利用力(保持cpu始终在工作)。 一般采用
      1.非抢占式的先来先服务算法。cpu按照进程的请求顺序来一个一个顺序处理进程任务
      2.最短作业优先算法。按照最短处理时间优先的条件对进程列表排序并执行
      3.最短剩余时间算法。按照进程列表中进程剩余运行的时间最短的条件进行排序并执行

    • 交互式系统
      对于交互式系统,快速响应用于当下的需求是最重要的,一般采用
      1.轮转调度算法。每个进程被分配一个时间片,表示这个进程运行时能占用的最大时间段,用完就会被替换掉。 注意,从一个进程切换到另一个进程是需要时间(保存和装入寄存器值及内存映像、更新各种表格、清楚和重新掉入内存高速缓存),这是一种额外的开销
      2.优先级调度算法。对于不同的进程,例如高低军官,他们的权限是不同的,越高的军官应该拥有更高的执行优先水平。因此,优先调度算法为此而生,为每个进程设立相应的优先级。
      3.多级队列
      为了解决兼容分时系统的进程切换速度过慢的问题,ctss的设计者想到了为cpu密集型进程设计较长的时间片来提高cpu利用率(通过减少进程切换时间),但是这么做又会损失响应时间,其解决办法是为进程设立优先级,优先级最高的进程运行一个时间片,优先级次低的进程运行2个时间片,再次一级的类推,当一个进程运行玩之后,优先级就下降到下一级。这样就可以兼顾优先级的同时保证一定的相应速度。
      4.还有几种调度算法就不详述了。

    • 实时系统的调度

    • 线程调度
      系统的线程切换需要切换进程,而用户级线程就不需要了,so,更加节约时间,资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值