《进程间通信》:
IPC : Interprocess Commounication 进程间通信方式的统称。进程A<--[IPC(管道、FIFO、共享内存、信号量)]-->进程BIPC类型:
半双工管道: 半双工管道 : 匿名半双工管道 FIFO(First In First Out); 全双工管道 : 匿名全双工管道; System V IPC / POSIX IPC : 消息队列、信号量、共享存储; 网络进程间通信 : Socket、Streams。详解:
1》匿名半双工管道简介:没有名字,使用的文件描述符没有路径名,只是在内存中跟索引节点相关联的两个文件描述符。
特性:
1)数据只在一个方向移动; 2)只适用在父子/兄弟间通信。2》有名管道 :
。。。。。。##################################################################################################----匿名管道--------------------------------------------------------------------
#include <unistd.h>int pipe(int fd[2]);// 创建匿名半双工管道 fd[0] : 读出端; fd[1] : 写入端.#include <stdio.h>
// popen =略= pipe + fork;FILE * popen(const char * command, const char * mode); command : 函数将调用/bin/sh -c来执行command字符串的命令; mode : "r",读取; "w", 写入.// pclose =略= fork + close;
int pclose(FILE * stream);//关闭流----有名管道--------------------------------------------------------------------
#include <sys/stat.h>#include <sys/types.h>int mkfifo(const char * filename, mode_t mode);
filename : 新创建的FIFO文件名称 mode : FIFO读写权限一般的I/O(open,close,read,write,unlink)函数都可用于FIFO文件。
注意open函数的参数flag标志位的 O_NONBLOCK 标志,它关系到返回状态。 O_NONBLOCK的状态: 置位 : 只读 open : 立即返回; 只写 open : 如没进程为读打开FIFO,则返回-1,并置errno值为ENXIO; 不置位: 只读 open : 阻塞到有进程为写打开FIFO; 只写 open : 阻塞到有进程为读打开FIFO;下面附录open函数:
#include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);--------------------------------------------------------------------------------
##################################################################################################3》System V IPC / POSIX IPC:System V IPC 已被 POSIX IPC取代。
注意:管道 和 FIFO 是基于文件系统的;System V IPC 是基于系统内核的。 1)IPC对象简介: 1)文件描述符 : 1>是唯一描述其的标志,非负整数,最大值为:65535(2^16 -1)。 2>IPC对象的创建/删除时,fd的值也随之增大到最大,归零循环使用。 3>fd只解决了内部访问一个IPC对象的问题;外部键(Key)解决了多进程同时访问某一个IPC对象的问题。 2)键值 : 1>创建IPC对象需要指定一个键值,类型为Key_t,在<sys/types.h>中定义为一个长整形。 键值--系统内核--> 标识符 2>如何让多进程知道这个键值: 1.使用文件作中间通道,创建IPC对象进程,使用键IPC_PRIVATE成功建立后,返回标识符存储在文件中,其他进程读取这个标识符来引用IPC对象通信。 2.定义一个多进程都认可的键,通过这个键来引用IPC对象。 3.使用ftok()解决:使用2方法时,如该键已被某IPC对象结合,必须先删除这个IPC对象,再创一新的。 #include <sys/ipc.h> key_t ftok(const char *path, int id); // 创建一个键值 //path是一个文件名,函数使用该文件的stat.st_dev 和stat.st_ino的部分值,与id的第八位结 合生成的一个键值。所以不排除两不同文件使用同一id得到同一键值的情况。 3>ipc_perm结构体: 系统为每个IPC对象保存一个ipc_perm结构体,其说明了IPC对象的权限和所有者。 该结构体在<sys/ipc.h>中,详情如下: struct ipc_perm { key_t key; uid_t uid; gid_t gid; uid_t cuid; gid_t cgid; unsigned short mode; unsigned short seq; } 每一版的 ipc_perm 至少要包含上述几个域。 赋值:调用IPC对象创建函数(semget, msgget, shmget)时,会对ipc_perm的每个域赋值; 修改:后续要修改这几个域时,可调用相应控制函数(semclt, msgctl, shmctl)。 注 : 只有root用户/创建本IPC的进程才有权修改ipc_perm的域值。 3)IPC对象问题: 1> 编程接口繁杂; 2> IPC不使用通用的文件系统; 1.不能使用标准I/0函数读写IPC对象-->必须新增函数(如 msgget)-->对不同IPC对象有不同的函数。 2.不能使用多路I/O监控函数select 和 poll操作IPC对象。 3> 缺少资源回收机制; 由于IPC对象使用过程中不保存使用引用计数,所以当创建IPC对像再退出时,只有如下情况会被释放: 1.某个进程读出消息; 2.root/所有者删除该对象。 4)Shell命令: 1> ipcs // 显示IPC状态; 2> ipcrm // 删除不用的IPC; 2)IPC对象类别: 1)共享内存 : 多进程讲一段内存映射到自己的进程空间,以实现数据的共享和传输,这是最快的通信方式。 [注意] : 只提供数据传送,不提供如何控制服务器和客户端的读写操作互斥。 在/proc目录下有对齐描述的相应文件。 1> 概念 : 内核--为进程分配--> 内存地址 --分页机制--> 物理地址不连续 --映射分配--> 不同进程; 对每个共享内存段,内核会维护一个shmid_ds结构体(sys/shm.h中),结构体如下: struct shmid_ds { struct ipc_perm shm_perm; pid_t shm_segsz; //初始化值为 :shmget函数的参数 size pid_t shm_cpid; pid_t shm_lpid; //初始化值为 : 0 shmatt_t shm_nattch; //初始化值为 : 0 time_t shm_atime; //初始化值为 : 0 time_t shm_dtime; //初始化值为 : 0 time_t shm_ctime; //初始化值为 : 系统当前值 ...... ...... } // 不同内核版本略有不同,存储段大小要查具体手册。2> 创建 : 内核以页为单位分配内存,它会给出满足参数size的最小内存页数,最后一页多余部分不可用;
函数如下:// 创建/打开一段共享内存区。 #include <sys/shm.h> int shmget(key_t key, size_t size, int flag); //失败,返回值 < 0; // key : 每个IPC对象对应一个key值; // size : 要请求的内存长度,以字节为单位; // flag : 权限位(ipc_perm.mode);函数的行为参数,指定当函数遇到阻塞/其他情况时的反应。 代码演示: #include <sys/types.h> <sys/ipc.h> <sys/shm.h> <stdlib.h> <stdio.h> // 为节省空间 #define BUFSZ 4096 int main() { int shm_id; // 共享内存标识符 shm_id = shmget(IPC_PRIVATE, BUFSZ, 0666); if (shm_id < 0) { printf("创建失败n"); exit(1); } printf("Successfully created segment : %dn", shm_id); system("ipcs -m"); //查看IPC exit(0); }3> 操作 : 共享内存段(segment)特殊--> 使用专有的操作函数,函数如下:#include <sys/shm.h>
1.// shmctl-- 多种操作。 int shmctl (int shm_id, int cmd, struct shmid_ds *buf); // shm_id : 所要操作的内存段标识符; // cmd : buf 与 cmd 的值相关; // IPC_STAT : 取 shm_id 指向内容 =赋值给=> buf; // IPC_SET : 取 buf 指向内容 =赋值给=> shm_id; // 使用条件 : 1.进程用户ID == (shm_perm.cuid / shm_perm.uid); 2.root权限; // 赋值内容 : 只对 shm_perm, uid shm_perm.gid及shm_perm.mode进行赋值; // IPC_RMID : 删掉shm_id指向的共享内存,只有 shmid_ds.shm_nattch == 0 时,才删; // 使用条件 : 与 IPC_SET 相同; // IPC_LOCK : 只许root用户使用,锁定共享内存; // IPC_UNLOCK : 只许root用户使用,解锁定共享内存。2.// shmat -- 将存在的内存段--连接到-->本进程空间。
void *shmat (int shm_id, const void *addr, int flag); // shm_id : 所要操作的内存段标识符; // addr : 与flag组合说明要引入的地址值; // 用法 : 1.(addr == 0), 内核决定第1个可以引入的位置; 2.(addr != 0) && (flag == SHM_RND), 引入addr指向的位置(依赖硬件,不推荐); // 返回值 : 成功,引入的实际地址( shm_id段的shmid_ds.shm_nattch++ ); 失败, -1。3.// shmdt -- 指定共享内存脱离当前进程空间
int shmdt (void *addr); // 返回值 : 成功, 0( shm_id段的shmid_ds.shm_nattch-- ); 失败, -1。 4> 注意 : 优势 : 数据--控制更方便、读写更透明、可被当前用户随时访问; 缺点 : 数据写入/读出进程中,需附加数据结构控制,也需在同步/互斥上附加代码辅助共享; 数据完整性 : 内存段都以'�'为一条信息的结尾,遵循此规则就不会破坏数据的完整性。 3)消息队列 : 链式结构,内核维护,最具数据操作性,可随意根据数据类型值来检索消息,需更多的内存资源。 命令 : ipcs -q可查看器状态; 描述 : 每个队列都有一个msqid_ds结构体描述队列当前状态。 创建 : #include <sys/msg.h> int msgget (key_t key, int flags); //key : //flags : 标明函数行为。 IPC_CREAT|0666,说明新建权限为0666的消息队列,其组用户,当前用户,其他用户拥有读写权限。 操作 : #include <sys/msg.h> int msgctl (int msqid, int cmd, struct msqid_ds *buf); //msqid : //cmd : // IPC_STAT : 取队列中msqid_ds结构,将其存放在buf所指向的结构中。 // IPC_SET : 使用buf指向结构的值对当前队列进行相关结构成员赋值 //结构成员 : msg_perm.uid、msg_perm.gid、msg_perm.mode、msg_perm.cuid; //触发条件 : 进程有效ID == ( msg_perm.cuid || msg_perm.uid ) 或 超级用户进程; // 有超级用户的值才可以增加队列的msg_qbytes的值 // IPC_RMID : 删除队列,并清除队列中所有消息。 //说明 : 此操作会影响后续进程对这个队列的相关操作; //条件 : 进程有效ID == ( msg_perm.cuid || msg_perm.uid ) 或 超级用户进程。 2)信号量 : 数据操作锁,无数据交换功能,只是外部资源标志,通过控制其他通信资源来实现进程间通信; 1> 概念 : 1.当请求一个使用信号量表示的资源时 : 请求-->取量值-判断-> (nsems == 0 ) ? 进入睡眠状态直到(nsems > 0): 可以; 2.当进程不再使用一个信号量控制的共享内存时 : [量值+1] --> 3.信号量的[自增]和[自减]都是原子操作,作用是维护资源的互斥/多进程的同步访问。 [注意] : 信号量的创建&初始化时,不能保证操作为原子操作。 4.shmid_ds结构 : 内核对每个信号量集都设置一个shmid_ds结构,同时用一个无名结构体来标识。 struct { unsigned short semval; pid_t sempid; unsigned short semcent; unsigned short semzcnt; ...... ...... }2> 创建 : ;
1.函数原型如下 : #include <sys/shm.h> int semget (key_t key, int nsems, int flag); // nsems : ① >= 0,指明该信号量集中可用资源数。 // ② 如打开一已存在的信号量时该参数为0。 // 其他参数与shmget大概一致。 // 返回值 : 成功,返回信号量标识符( >=0 ); 失败, -1。2.初始化 : 对相应的信号量集的 shmid_ds 初始化
shmid_ds.Sem_otime = 0; shmid_ds.Sem_ctime = 系统当前值; shmid_ds.Sem_ntime = 参数nsems( >=0 );3> 操作 :
1.// semop #include <sys/sem.h> int semop (int semid, struct sembuf semoparray[], size_t nops); // nops : semoparray 数组元素的个数 //semoparray : 指向 sembuf 结构体 //sembuf结构体如下 : struct sembuf { unsigned short sem_num;//相应信号量集的某一资源,其值是0到资源总数之间的整数 short sem_op; //所要执行的操作 short sem_flg;//说明函数sem_op的行为 } //资源总数 : ipc_perm.sem_nsems //sem_op详解: // == 0: 释放相应资源数,将sem_op加到信号量的值上; // > 0 : 进程阻塞知道信号量的相应值==0,到时函数立即返回; // < 0 : 求sem_op的绝对值。2.// semctl
#include <sys/sem.h> int semctl (int sem_id, int semnu, int cmd[, union semun arg]); //sem_id : //semnu : //cmd : GETVAL : 返回成员semnum的semval的值; SETVAL : 赋值 : semnum.sempid = arg.val; GETPID : 返回成员semnum的sempid的值; GETNCNT : 返回成员semnum的semncnt的值; GETZCNT : 返回成员semnum的semzcnt的值; GETALL : 信号量集所有值赋给arg.array; SETALL : 使用arg.array的值对信号量集赋值; SPC_STAT : (需要参数arg); 以下只允许root用户/信号量集拥有者使用: IPC_RMID : 删除信号量集; IPC_SET : 设置信号量集的 sem_perm.uid、sem_perm.gid、sem_perm.mode的值。 //arg : union semun { int val; struct semid_ds *buf; unsigned short *array; } //返回值: 成功 : GET操作返回响应值,其他操作返回0; 失败 : -1,并设置错误变量errno;