1 / 35

实验三 使用 Linux 高级 IPC

实验三 使用 Linux 高级 IPC. 陈毅东. 结束. 提纲. 进程间通信概述 目标问题 —— 哲学家进餐问题 问题描述 错误与不好的解法 并行度较高的解法 Linux 高级 IPC 机制 概述 System V 信号灯 System V 共享内存区 实现的其他问题 实习题. 进程间通信概述( 1 ):引子.

kynan
Download Presentation

实验三 使用 Linux 高级 IPC

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 实验三使用Linux高级IPC 陈毅东

  2. 结束 提纲 • 进程间通信概述 • 目标问题——哲学家进餐问题 • 问题描述 • 错误与不好的解法 • 并行度较高的解法 • Linux高级IPC机制 • 概述 • System V信号灯 • System V共享内存区 • 实现的其他问题 • 实习题

  3. 进程间通信概述(1):引子 #include <unistd.h>#include <sys/types.h>int result;main(){ pid_t pid; result=0; pid=fork(); if(pid<0) exit(-1); else{ sleep(3); result=result+10; exit(0); } while(wait((int*)0)!=-1); printf("%d\n", result); exit(0);} if(pid){ pid=fork(); if(pid<0) exit(-1); if(pid==0){ sleep(3); result=result+20; exit(0); }} 输出结果是什么?

  4. 进程间通信概述(2) • 进程是相互独立的,进程间的通信需要专门的机制。 • 进程之间的通信可以经由文件系统,但实际使用较为复杂(例如,需要锁机制)。 • UNIX IPC (InterProcess Communication)机制是各种进程通信方式的统称。 • Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。

  5. 进程间通信概述(3) • 对于UNIX的发展,贝尔实验室和BSD在进程间通信方面的侧重点有所不同: • 贝尔实验室对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“System V IPC”,通信进程局限在单个计算机内; • BSD则主要考虑跨计算机的进程间通信,形成了基于套接口(socket)的进程间通信机制。

  6. 最初的 UNIX IPC System V IPC Linux IPC 基于Socket的IPC POSIX IPC 返回 进程间通信概述(4) • 最初的Unix IPC:信号、管道、FIFO; • System V IPC:消息队列、信号量、共享内存区; • POSIX IPC:消息队列、信号量、共享内存区。

  7. 返回 哲学家进餐问题的描述 • 五个哲学家围坐在一张圆桌周围,每个哲学家面前都有一碗米饭,相邻的两碗之间有一支筷子(如图)。 • 哲学家的生活包含两种活动:即吃饭和思考。当一个哲学家觉得饿时,他就试图分两次去取他左边和右边的筷子,每次拿起一支,但不分次序。如果成功地获得了一双筷子,他就开始吃饭,吃完以后放下筷子继续思考。这样,问题就是,为每个哲学家写一段程序来描述其行为,要求不死锁。

  8. 若每个哲学家进程都运行到此句后发生进程切换,则进入死锁。若每个哲学家进程都运行到此句后发生进程切换,则进入死锁。 错误与不好的解法(1) • 解法一:可能进入“死锁”状态 #define N 5 void philosopher(int i) { while(TRUE){ think(); take-chopstick(i); take-chopstick((i+1)%N); eat(); put-chopstick(i); put-chopstick((i+1)%N); } }

  9. 不妨假设此函数能做到“测试且设置”。 错误与不好的解法(2) • 解法二:可能进入“饥饿”状态   这种解法可能会造成下面情况:哲学家们不断地重复“拿起各自左边的筷子又放下”的动作,谁也不能进餐。注意:这时和解法一的状态不同,这时进程都没有阻塞。 #define N 5 void philosopher(int i) { while(TRUE){ think(); do{ take-chopstick(i); if(can-take-chopstick((i+1)%N)) break; else put-chopstick(i); }while(TRUE); eat(); put-chopstick(i);put-chopstick((i+1)%N); } }

  10. 返回 错误与不好的解法(3) • 解法三:可行但效率低下 #define N 5 typedef int semaphore; semaphore mutex=1; void philosopher(int i) { while(TRUE){ think(); down(mutex); take-chopstick(i); take-chopstick((i+1)%N); eat(); put-chopstick(i); put-chopstick((i+1)%N); up(mutex); } } 本解法从理论上可行,但从实际角来看,有一局限性:同一时刻只能有一位哲学家进餐。而这里有五支筷子,实际上应能允许两位哲学家同时进餐。

  11. 并行度较高的解法(1) #define N 5 #define LEFT (i+N-1)%N #define RIGHT (i+1)%N #define THINKING 0 #define HUNGRY 1 #define EATING 2 typedef int semaphore; int state[N]; semaphore mutex=1; semaphore s[N]; void philosopher(int i){ while(TRUE){ think(); take-chopsticks(i); eat(); put-chopsticks(i); }}

  12. 返回 并行度较高的解法(2) void put-chopsticks(int i) { down(&mutex); state[i]=THINKING; test(LEFT); test(RIGHT); up(&mutex); } void take-chopsticks(int i) { down(&mutex); state[i]=HUNGRY; test(i); up(&mutex); down(&s[i]); } void test(i) { if(state[i]==HUNGRY &&state[LEFT]!=EATING&&state[RIGHT]!=EATING){ state[i]=EATING;up(&s[i]); } }

  13. 返回 概述 • System V IPC包含了三种机制,在实现“哲学家进餐问题”时,我们只使用信号灯机制和共享存储区机制。主要的函数如下: 信号灯 共享内存区  头文件 <sys/sem.h> <sys/shm.h> 创建或打开IPC semget shmget  控制IPC操作 semctl shmctl IPC操作函数semop shmat, shmdt • System V IPC对象以key_t类型的值作为其名字。 • System V IPC对象以一定的存取权限来控制其访问。

  14. 返回 System V IPC的名字 • System V IPC是有名的,这样可以支持无亲缘关系的进程访问同一的IPC对象。其名字的类型为key_t,可以由ftok函数赋予或直接取值IPC_PRIVATE。 • ftok函数 • 原型:#include <sys/types.h>#include <sys/ipc.h>key_t ftok(const char * pathname, int id); • 功能:把已存在的路径名和一整数标识符转换成一个key_t值,称为IPC键。 • 返回值:成功时返回IPC键,出错返回-1。 • 说明:1、ftok产生的键值不会是IPC_PRIVATE;2、不能保证ftok生成的键值唯一;3、用于产生键的文件不能在该IPC对象存活其  内删除。

  15. 返回 System V IPC对象的存取权限 • 为防止共享的IPC对象被非法访问,必须为IPC对象设置存取权限。 • System V IPC对象的存取权限和文件系统中文件的存取权限类似,也用9位分3组表示,三组分别代表属主、组成员和其他用户对该IPC对象的存取权限;每组中三位,只用其中的两位表示是否可读和是否可写。一般为了安全,在创建IPC对象时应该设置存取权限制为0600,表示仅仅对属主是可读,可写的。

  16. 返回 System V信号灯 • semget • semctl • semop • down和up的实现

  17. semget(1)——函数说明 • 原型:#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semget(key_t key, int nsems, int oflag); • 功能:创建或打开信号灯集合。 • 返回值:成功返回非负信号灯标识符,出错返回-1。 • 说明:1、key是欲创建或打开的信号灯集合的名字; 2、nsems指明信号灯集合中包含的信号灯数; 3、oflag是一个位信息标志,含两部分信息,即存    取权限和控制字段。低9位表示存取权限,控    制字段中IPC_CREAT位和IPC_EXCL位的设    置情况与参数key共同决定了本调用的操作。

  18. 返回 semget(2)——工作流程 if (key==IPC_PRIVATE)创建新信号灯集并返回其id;else if (与key相关的信号灯集合存在) if ((oflag&IPC_CREAT)&&(oflag&IPC_EXCL))返回-1; else if (访问权限允许)返回与key相关的信号灯集id; else返回-1; else if (oflag&IPC_CREAT)创建新信号灯集并返回其id; else返回-1;

  19. semctl(1)——函数说明 • 原型:#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semctl(int semid, int semnum, int cmd,union semun arg); • 功能:对semid标识的信号灯集合进行控制。 • 返回值:成功返回非负值,出错返回-1。 • 说明:1、cmd是对信号灯集的控制命令,有GETVAL,SETVAL,GETALL,SETALL,IPC_RMID等,我们只用最后的两个。2、semnum标识该信号灯集中某成员(以0为第     一个),仅用于GETVAL,SETVAL等命令。3、arg是可选的,取决于参数cmd,GETALL或SETALL等命令就要用到参数arg。

  20. 返回 semctl(2)——union semun • union semun的定义为:union semun{ int val; //仅用于SETVAL命令struct semid_ds * buf; //用于IPC_SET等命令ushort * array; //用于SETALL等命令} • 注意,本联合未在出现在任何系统头文件中,因此必须由应用程序声明。

  21. semop(1)——函数说明 • 原型:#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semop(int semid,struct sembuf * opsptr,size_t nops); • 功能:对semid标识的信号灯集合中信号灯进行操作。 • 返回值:成功返回0,出错返回-1。 • 说明:1、opsptr是指向结构struct sembuf的指针,可     以是这种类型的结构数组的头指针。数组的每     个元素包含对信号灯集合中一个信号的的操作     的信息,从而可实现同时对多信号灯的操作。2、nops指出opsptr指向的结构数组中元素数。

  22. 返回 semop(2)——struct sembuf • struct sembuf的定义为:struct sembuf{ short sem_num; //信号灯号:0,1,…,nsems-1 short sem_op; //信号灯操作:<0, 0, >0 short sem_flg; //操作标识:这里我们只要置0} • sem_op不同值对应的操作(设信号灯当前值为sv): • sem-op<0:start: if (sv>=abs (sem_op)) sv=sv-abs(sem_op); else {sleep; goto start;} • sem_op>0:sv=sv+sem_op; 唤醒所有阻塞于该信号量的进程; • sem_op==0:测试sv是否为0。

  23. 返回 down和up的实现 void down(int sid, int sn) { struct sembuf op; op.sem_num=sn; op.sem_op=-1; op.sem_flg=0; semop(sid, &op, 1); } void up(int sid, int sn) { struct sembuf op; op.sem_num=sn; op.sem_op=1; op.sem_flg=0; semop(sid, &op, 1); }

  24. 返回 System V共享内存区 • shmget • shmctl • shmat和shmdt • 使用共享内存区的一般流程

  25. 返回 shmget • 原型:#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>int shmget(key_t key, size_t size, int oflag); • 功能:创建或打开共享内存区。 • 返回值:成功返回非负内存区标识符,出错返回-1。 • 说明:1、size指明共享内存区大小(字节为单位);2、key和oflag的说明基本和semget相同;3、打开或创建一个共享内存区,并没提供调用进     程访问该内存区的手段。必须调用shmat。

  26. 返回 shmctl • 原型:#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>int shmctl(int shmid, int cmd,struct shmid_ds *buff); • 功能:对shmid标识的共享内存区进行控制。 • 返回值:成功返回0,出错返回-1。 • 说明:1、参数cmd是对共享内存区的控制命令,可以是IPC_RMID,IPC_SET或IPC_STAT,我们仅     用第一个;2、buff主要用于命令IPC_SET和IPC_STAT,命     令为IPC_RMID时可直接提供NULL;

  27. 返回 shmat和shmdt • 原型:#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>void * shmat(int shmid,const void * shmaddr,int flag);int shmdt(const void * shmaddr); • 功能:shmat用于将一个打开的共享内存区附接到调用进程的地址空间。shmdt用于切断这个内存区域。 • 返回值:出错返回-1,shmat成功返回映射区的起始地址而shmdt成功时候返回0。 • 说明:shmat中shmaddr和flag可用来影响映射区起始地址的选取。一般应该分别提供NULL和0。

  28. 返回 使用共享内存区的一般流程 • 使用System V共享内存区时的一般流程: • 用shmget创建或打开一个共享内存区 • 用shmat将打开的共享内存区附接到进程地址空间 • 对共享内存区进行操作 • 用shmdt切断共享内存区与本进程地址空间的联系 • 用shmctl拆除共享内存区(用命令IPC_RMID)

  29. 返回 实现的其他问题 • 如何创建多个进程 • 创建一个进程链 • 创建一个进程扇 • 创建一个进程树 • 程序的结束 • 程序失败后如何释放资源

  30. 1 2 3 4 返回 创建一个进程链 #define N 4 #include <stdio.h> #include <sys/types.h> #include <unistd.h> main() { int i; for (i=1;i<N;++i) if (fork()) break; fprintf(stderr, "#%d is process %ld with parent %ld\n", i, (long)getpid(), (long)getppid()); } 注意:本程序未考虑fork失败的情况。

  31. 4 1 2 3 返回 创建一个进程扇 #define N 4 #include <stdio.h> #include <sys/types.h> #include <unistd.h> main() { int i; for (i=1;i<N;++i) if (fork()==0) break; fprintf(stderr, "#%d is process %ld with parent %ld\n", i, (long)getpid(), (long)getppid()); } 注意:本程序未考虑fork失败的情况。

  32. 0 1 2a 2b 3a 3b 3c 3d 返回 创建一个进程树 #define N 4 #include <stdio.h> #include <sys/types.h> #include <unistd.h> main() { int i, id=0; for (i=1;i<N;++i) if (fork()==0) id=i; fprintf(stderr, "#%d is process %ld with parent %ld\n", id, (long)getpid(), (long)getppid()); } 注意:本程序未考虑fork失败的情况。

  33. 返回 程序的结束 • 在什么时机结束程序 • 以某个外部文件的存在与否来决定:每个哲学家在“吃”完后都检测某个特定的文件(例如:~/quit)是否已经被创建。若是则结束程序。 • 利用信号(signal)截取键盘Ctrl-C信号。 • 程序结束时候要记得回收IPC资源

  34. 返回 程序失败后如何释放资源 • 杀掉垃圾进程 • 使用ps –ax查看所有进程 • 使用killall name进行删除 • 删除垃圾IPC资源 • 使用ipcs [-a]查看所有IPC资源 • 使用ipcrm [shm|sem] id进行删除

  35. 返回 实习题 • 理解实现进程通信的难点 • 练习创建多个进程的方法 • 解决哲学家进餐问题 • 实现不好的或有错误的算法,观察运行情况 • 实现正确的算法(需要检查) • 也实现其他方法

More Related