zoukankan      html  css  js  c++  java
  • 进程通信、同步与调度

    1. 进程通信(Process Communication)
    进程协作有其根本性的需求,主要是出于四点:信息共享,通过并行提升计算速度,利于模块化以及方便性。因此也衍生出了进程通信(IPC)的需求。下面将概述IPC的相关内容。
    进程通信用于进程之间的数据交换,主要有两种抽象模型:其一共享内存模型(Shared memory),其二消息传递模型(Message passing)。
    顾名思义,采用共享内存的进程间通信需要建立共享内存区域,它们通过在共享内存区域读或写来交换信息。我们通过生产者—消费者模型对共享内存模型稍作研究。这个模型里,生产者进程产生信息以供消费者进程“消费”。为了能使这两种进程并发执行,必须要有一个缓冲来被生产者填充并为消费者所使用。缓冲可以看作一个队列,生产和消费可以分别看作入队和出队。这里缓冲分为两类,有限缓冲和无限缓冲,二者区别在于后者情况下,生产者无需等待,总是有缓冲区域存放其生产的消息。
    对于消息传递模型,则不需要共享地址空间。消息传递工具需要至少提供两种操作:发送消息和接收消息。而这两种操作的关键在于通信线路(Communication Link)。这里我们只分析其逻辑实现,关注以下几个问题。命名:在直接通信中,需要通信的每个进程必须明确的命名通信的接收者或者发送者。这种方案会在二者之间自动建立线路。但是这样限制了进程定义的模块化。而在间接通信中,通过邮箱或者端口来传递消息。同步:消息传递可以是阻塞非阻塞的,也称为同步或者异步。很容易理解,即进程是否会因为send()或者receive()而阻塞。缓冲:通信交换的信息驻留在临时队列里,有三种实现方法:零容量,有限容量,无限容量。关键区别在于队列的大小不同,对发送者的阻塞情况不同,零容量必须阻塞发送,有限容量会在队列满了的时候阻塞发送,无限容量不会阻塞。
    通过网络通信的进程需要使用一对sockets作为端点,由一个iP地址和一个端口号组成。Socket一般采用客户端-服务器结构,服务器通过监听指定端口来等待客户请求,收到请求后会自动连接。一般,1024以下的端口用于标准服务。

    除此之外,进程通信的方式还有管道(pipe)。管道分为普通管道和命名管道;两者都是半双工的。普通管道只能用于父子进程或兄弟进程间的通信,因为普通管道通过fork调用来拷贝文件描述符的,在文件系统中,普通管道并不对应物理文件。命名管道在文件系统中有物理文件存在,因此可以用于非亲属的进程间通信。

    2. 进程同步(Process Synchronization)
    系统的不同部分操作资源,自然需要这些变化不相互影响,这是就需要进程同步。
    总体来说,进程同步通过临界区来实现,临界区问题必须确保:互斥,前进,有限等待。临界区前后分别由进入区和退出区,其它为剩余区。
    临界区问题有两类处理方法:抢占内核和非抢占内核。容易理解,非抢占内核数据从根本上不会导致竞争条件,而抢占内核情况下则复杂得多。但是考虑到后者在响应时间和实时编程的优势,这种复杂值得花费力气解决。这里讨论一些solution proposals。(a)使临界区不可被打断,与非抢占内核类似,进程在临界区内时不允许上下文切换,我们可以通过一个系统调用来实现这个需求。(b)严格轮换,但是忙等会浪费CPU资源。而且轮换如此严格,使连续多次执行某个进程的临界区成为不可能。(c)对严格轮换进行改进得到Peterson’s solution,使用了两个共享数据项int turn和boolean flag。但是同样会导致忙等,而且可能会使进程的优先权错位(d)在硬件上实现互斥锁,同样忙等。
    实际最终我们选择的方案是——信号量。信号量是一种数据类型,只能通过两个标准原子操作访问wait()和signal()。信号量通常分为计数信号量和二进制信号量,后者有时称为互斥锁。可以使用二进制信号量处理多进程的临界区问题,而计数信号量可以用来控制访问具有若干实例的某种资源,此时信号量表示可用资源的数量。
    当一个进程位于临界区时,其他试图进入临界区的进程必须在进入代码中连续地循环,这种称为自旋锁,会导致忙等。为了克服这一点可以修改wait()和signal()地定义——当一个进程执行wait()需要等待的时候,改为阻塞自己而不是忙等。阻塞操作将此进程放入到与信号量相关的等待队列中,状态改为等待,然后会选择另一个进程来执行。
    考虑进程同步时,很重要的一点是避免死锁。死锁的特征包括:互斥、占有并等待、非抢占、循环等待。当死锁发生时,进程永远不能完成,所以必须解决。有三种方法:(1)使用协议确保死锁不发生(2)允许死锁然后检测恢复(3)认为死锁不存在。

    此外,进程同步中有三个经典问题,用来检验新的同步方案——生产者消费者问题、读者-写者问题、哲学家进餐问题,可以用来分析中的各种情况包括死锁问题,这里不作具体分析。

    3. 进程调度
    首先明确,进程执行由CPU执行和I/O等待周期组成,进程在这两种状态之间且切换。因为运算资源CPU是有限的,所以为了提高利用率,在CPU空闲时,必须从就绪队列中选择一个进程执行,具体到如何选择,这就产生了调度。
    调度应当满足以下准则:CPU使用率和吞吐量最大化,周转时间、等待时间和响应时间最小化。
    稍微概述几种调度算法。
    a) 先到先服务算法:即先请求cpu的进程先分配到进程,实现简单,但平均等待时间通常较长。考虑FCFS调度在动态情况下,会产生护航效果,会导致cpu和设备使用率变得很低。
    b) 最短作业优先调度:cpu空闲时,它会赋给具有最短cpu区间的进程,SJF算法可证为最佳,其平均等待时间最小。但是困难在于如何知道下一个cpu区间的长度。一种方法是近似SJF调度,可以用以前cpu长度的指数平均来预测下一个区间长度。
    c) 优先级调度:每个进程都会有一个优先级,具有最高优先级的进程会被分配到cpu。这种算法的主要问题是无穷阻塞或者饥饿,可以使用老化的方法来处理。
    d) 轮转法调度:专门为分时系统设计,与FCFS类似,但是增加了抢占。这里定义了一个时间片,就绪队列作为循环队列,每个进程分配不超过一个时间片的cpu。
    e) 多级队列调度:将就绪队列分为多个独立队列,根据进程属性每个队列有自己的调度算法。而且队列之间必须有调度,通常采用固定优先级抢占调度。例如,前台队列可以比后台队列具有绝对的优先级,这样也符合交互的要求。
    f) 多级队列反馈调度:与多级队列相比,差异在于允许进程在队列之间移动。
    如果在多个处理器的情况下,负载分配成为可能。一种方法是让一个处理器处理所有调度决定,成为非对称多处理。这种方法,数据共享的需要较小。另一种是对称多处理(SMP),每个处理器自我调度。
    此外,进程在不同处理器转移时,第一个处理器缓存必须无效,第二个处理器缓存需要重构,这样做代价太高,所以SMP系统尽量避免这样做,试图提高处理器亲和性,努力使一个进程在用同一个处理器上运行。
    为了更好的利用多处理器,更重要的一点是负载平衡,负载平衡通常有两种方法:Push migration和Pull migration,此不赘述。


    参考资料:
    [1] Abraham Silberschatz. 操作系统概念. 高等教育出版社, 2007.3.

  • 相关阅读:
    Maven 集成Tomcat插件
    dubbo 序列化 问题 属性值 丢失 ArrayList 解决
    docker 中安装 FastDFS 总结
    docker 从容器中拷文件到宿主机器中
    db2 相关命令
    Webphere WAS 启动
    CKEDITOR 4.6.X 版本 插件 弹出对话框 Dialog中 表格 Table 自定义样式Style 问题
    SpringMVC JSONP JSON支持
    CKEDITOR 3.4.2中 按钮事件中 动态改变图标和title 获取按钮
    git回退到远程某个版本
  • 原文地址:https://www.cnblogs.com/JK-Z/p/12262061.html
Copyright © 2011-2022 走看看