进程间通信 IPC

什么是进程间通信

进程间通信 InterProcess Communication, 简称IPC,的问题可以归纳为3类:

  1. 进程间相互传递信息
    比如linux里的pipeline,前面进程的output是后面进程的input
  2. 进程间不会妨碍彼此的运行
    订票系统的两个进程为不同用户抢同一张票
  3. 进程按特定的顺序执行
    pipeline的例子也可以应用到这里

上述3类问题对线程通信也是一样的。

Race Conditions 和 Critical Regions

在一些操作系统里,进程需要读写公共资源,比如共享内存,文件。但是当多个进程同时读写一个资源时,就会发生竞争。一个进程不会一直去访问共享资源,我们把访问共享资源的那部分程序叫做critical region 临界区。避免竞争的关键在于在同一时刻只能由同一个进程读写共享数据,也叫做临界区的互斥访问。

设计原则

  1. 同一时刻只有一个进程能够进入临界区
  2. 不能对CPU的数量和执行速度做预设
  3. 处在临界区之外的进程不能block其他进程
  4. 任何一个进程都应该有机会进入它的临界区

如何实现进程间通信

禁止中断

在单处理器系统中,最简单的办法是在一个进程进入临界区之前关闭中断,离开之后再打开中断。缺点在于给进程的权限过大,如果进程不将中断恢复,OS就什么也做不了了;此外关闭中断只适用于单CPU的计算机,在多核或多处理器系统中起不到任何作用,违反了原则2.

Mutual Exclusion with Busy Waiting

忙等待就是程序不停地检查某个共享变量值,直到符合条件进入临界区,也叫自旋锁 spin lock。等待期间程序只是在空转,非常浪费CPU时间,适用于等待时间很短的情况。被检查的变量称作锁变量Lock. 接下来介绍利用忙等待实现互斥访问的几种方法。

Strict Alternation

// 放在共享内存区域
int turn= 0; 
// turn为0时运行
process1 {
  while(true) {
    while (turn !=0);
    critical_region();
    turn = 1;
    noncritical_region();
  }
}
// lock 为1时运行
process2 {
  while(true) {
    while (turn !=1);
    critical_region();
    turn = 0;
    noncritical_region();
  }
}

两个进程严格交替运行,lock的写操作必须是原子性的以避免读写冲突。调度顺序固定,浪费CPU时间。如果process1的noncritical_region执行时间非常长,足够process2执行完两次以上全部的程序,process2也只能等待。违反了原则3.

Peterson's Solution

Peterson算法是由软件实现的双线程互斥访问算法,引入了随机性,核心在于使用两个变量:

  1. turn 表示轮到哪个进程进入临界区
  2. interested数组 表示哪些进程已经准备好/想进入临界区

不同的教材中中对Peterson算法有不同的实现,此处举两个例子,分别来自于现代操作系统和操作系统概念。
现代操作系统版本

    int turn;
    int[] interested = {0, 0};

    public void enter_region(int process){
        int other = 1 - process;
        interested[process] = 0;
        turn = process;
        while(turn == process && interested[other] == 1);
    }

    public void leave_region(int process) {
        interested[process] = 0;
    }

这种实现属于拼手速,谁先抢到turn谁先执行。即使给turn赋值之后被另一个进程抢走CPU使用权,对方也不能进入临界区,只能等待。但有可能会出现一个进程等两次CPU切换才能进入临界区的情况(process1设置完interrested, 轮到process0执行设置完turn,进入while循环等待,process1设置turn也进入while等待,process0执行while条件false进入临界区)

turn == process turn != process
interested[other] == 0 对方没准备好,可以直接进入 对方没准备好,可以直接进入
interested[other] == 1 对方先设置了turn, 对方先进 自己先抢到turn,被打断以后重新获得CPU可以进入

操作系统概念版本
操作系统概念与现代操作系统中的实现差别仅在于对turn的赋值和进入临界区的条件判断

    public void enter_region(int process){
        int other = 1 - process;
        interested[process] = 0;
        turn = other;
        while(turn == other && interested[other] == 1);
    }

这种实现也被人叫谦让算法,只要对方准备好就将turn让给对方。

TSL Instruction

TSL(Test and Set Lock)依赖于硬件的实现。
TSL RX, LOCK
TSL指令将LOCK的内容读入寄存器RX,并将LOCK设置为一个非负数。指令执行期间,CPU锁定内存总线,防止其他CPU访存从而实现原子性。

enter_region:
  TSL REGISTER, LOCK  // copy lock to register and set lock to 1
  CMP REGISTER, #0  // was lock 0 ?
  JNE enter_region  // if it was not 0, lock was set, so loop 
  RET  // return to caller, critical region entered

leave_region:
  MOVE LOCK,#0  // store 0 in lock
  RET  // return to caller

硬件实现的另一种方式是使用XCHG指令,交换两个变量的值。Inter x86使用XCHG实现

enter_region:
  MOVE REGISTER, #1  // put 1 in register
  XCHG REGISTER, LOCK  // swap the contents of the register and lock variable
  CMP REGISTER, #0  // was lock 0 ?
  JNE enter_region  // if it was not 0, lock was set, so loop 
  RET  // return to caller, critical region entered

leave_region:
  MOVE LOCK,#0  // store 0 in lock
  RET  // return to caller

上述的严格交替算法,Peterson算法,TSL指令都采用忙等待的方式,不只浪费CPU时间,而且可能导致优先级反转 priority inversion problem 的问题。如两个进程H和L,H有高优先级,L是低优先级。如果此时L已经在临界区内,H刚准备好进入临界区,CPU将使用权交给H,H开始忙等待,而L由于获取不到CPU无法释放锁,导致低优先级的进程block了高优先级进程的运行。

Sleep and Wakeup

sleep和wakeup是一组system call, sleep导致进程block, wakeup唤醒进程。
生产者消费者问题
producer-consumer问题也叫有界缓存bounded-buffer问题。两个进程共享一个固定大小的缓冲区,producer向buffer放入item,consumer从buffer取item。buffer满的时候producer应该停止工作直到consumer从走取走一个item,buffer空时consumer停止工作直到producer放入一个item。

    int N = 100;
    int count = 0;

    producer(){
        while(true){
            item = produceItem();
            if(count == N)
                sleep();
            insertItem(item);
            count = count + 1;
            if(count== 1)
                wakeup(consumer);
        }
    }

    consumer(){
        while(true){
            if(count ==0)
                sleep();
            item = removeItem();
            count = count - 1;
            if(count == N-1)
                wakeup(producer);
            consumeItem();
        }
    }

这个实现的问题是可能会造成死锁。如producer刚判断完count==N,在sleep之前CPU调度consumer开始执行,消费一个完一个item后,count编程N-1,调用了wakeup,但此时producer还没有sleep, 这个wakeup信号丢失,导致producer再也接收不到wakeup信号而sleep forever。解决方案是加一个wakeup waiting 位,如果此时没有sleep的进程,将wakeup信号保存起来,到producer要sleep的时候去检查wakeup waiting位,如果有就不sleep了。第二种方案是要count的条件判断与sleep/wakeup在原子操作内完成。

Semaphores

semaphore是一个变量类型,其实就是一个integer变量,用来记录可供未来使用的wakeup的数量。semaphore支持两个操作down(P)和up(V), 如果semaphore为0,进程在完成down操作之前sleep。整个down/up操作的检查变量值,修改,potential sleep是在原子操作内完成的。up操作要么使semaphore值加1,要么使sleep在该semaphore上的进程数减1. PV操作的原子性是通过TSL/XCHG指令保护的锁变量实现的。
用semaphore解决producer-consumer问题


    #define N 100  
    typedef int semaphoer;
    semaphore mutex = 1;  // controls access to critical region
    semaphore full = 0;  // counts full buffer slots
    semaphore empty = N;  // counts empty buffer slots

    producer(){
        while(true){
            item = produceItem();
            down(&empty);
            down(&mutex);
            insertItem(item);
            up(&mutex);
            up(&full);
        }
    }

    consumer(){
        while(true){
            down(&full);
            down(&mutex);
            item = removeItem();
            up(&mutex);
            up(&empty);
            consumeItem();
        }
    }

mutex用来实现互斥访问,empty和full用来实现同步操作。
Semaphore是在内核态实现的,进程结束后仍然存在。内核态和用户态的切换耗时导致semaphore的使用开销较大。

Mutexes

如果不需要semaphore的同步功能,我们可以使用mutex实现互斥操作。mutex是一个只有两种状态的变量,1 locked或0 unlocked. 进程(线程)访问临界区之前,调用mutex_lock,如果此时mutex unlocked,调用成功,否则进程block直到另一个线程退出临界区调用mutex_unlock. mutex很容易在用户态使用TSL和XCHG实现,经常在线程种使用。具体实现如下:

mutex_lock:
  TSL REGISTER, MUTEX
  CMP REGISTER, #0
  JZE ok
  CALL thread_yield
  JMP mutex_lock
ok:
  RET

mutex_unlock:
  MOVE MUTEX, #0
  RET

Pthread提供了很多mutex的方法

Thread call description
Pthread_mutex_init 创建一个mutex
Pthread_mutex_destroy 销毁一个mutex
Pthread_mutex_lock 获取一个lock或者block
Pthread_mutex_trylock 获取一个lock或者fail
Pthread_mutex_unlock 释放锁

Condition Variables
Pthread提供的同步机制叫做condition variable,处理线程由于某些条件无法满足而不能继续执行的情况。

Thread call description
Pthread_cond_init 创建一个condition variable
Pthread_cond_destroy 销毁一个condition variable
Pthread_cond_wait block且释放锁,等待一个signal
Pthread_cond_signal 给另一个线程发signal并唤醒它
Pthread_cond_broadcast 给多个线程发signal并唤醒它们

Mutex和Semaphore的区别

Mutex Semaphore
用户态 内核态
只有0 1 两个值 值可以大于1

Monitors

Monitor是对OS PV操作的封装,是在编程语言层面实现的,编译器会实现对monitor的互斥访问,一般使用mutex或两位的semaphore。在同一时刻只有一个进程可以执行monitor程序。java种的synchronized就是monitor的一种实现。monitor的一般结构如下:

monitor example
  integer i;
  condition c;

  procedure producer();
  ...
  end;

  procedure consumer();
  ...
  end;
end monitor;

Message Passing

message passing主要用来解决两个机器上的进程通信速度不匹配的问题。为了保证信息传递的完整性,准确性和安全性,采用编号,确认和认证机制。message passing使用两个system call.
send(destination, &message)
receive(source, &message)
message passing 用在Socket编程和message queue里。

Barriers

用来处理多个进程要同时去执行的情况,特别是并行计算中控制不同阶段多任务的同步性。

Avoiding Locks: Read-Copy-Update

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容