Linux学习笔记--进程间通信
《进程间通信》:
IPC : Interprocess Commounication 进程间通信方式的统称。
进程A<--[IPC(管道、FIFO、共享内存、信号量)]-->进程B
IPC类型:
半双工管道:
半双工管道 : 匿名半双工管道 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;
posted on 2012-09-05 21:37 xuejianhui 阅读(249) 评论(0) 编辑 收藏 举报