进程间通信(interprocess communiction)
进程间通信方式:
①、信号
②、管道(命名 匿名)
③、消息队列
④、信号量
⑤、共享内存
⑥、socket
⑦、流
⑧、磁盘文件
⑨、环境变量
1、管道
管道是Unix系统ipc最古老的形式
#define PIPE_BUF 4096
管道每次可以写最大的数据为 PIPE_BUF(为原子操作4096个字符,并不是管道的buf就只有4096),用pathconf或fpathconf可以修改PIPE_BUF限制。
1.1、pipe(匿名管道)
a、用于有公共祖先的进程间通信(即由一个父进程创建后,之后父子进程之间的通讯),为半双工通信管道,某些实现成全双工管道,但是不可移植的。
b、通一条管道,不能多个读。但可以多个写一个读(但对于pipe一般只有一个读,一个写。多写的一读的一般用FIFO)。
c、当读一个写端已闭的管道时,在所有数据被读完后返回0 以显式达到文件尾结束;
当写一个读端被关闭的管道时,产生SIGPIPE的信号,write返回-1。
int pipe(int fd[2]);
成功返回0 ,失败返回-1
read fd[0]
,write:fd[1]
1.2、FIFO (命名管道)
a、因为有fifo文件的存在,不相关的进程之间也可以通讯,已XSI IPC的一样。
b、打一个FIFO时,没有设置O_NONBLOCK产生下列影响。
c、读打开FIFO要阻塞到其他进程为写而打开,同样,写打开要阻塞到某个进程为读打开。
d、如果指定了O_NONBLOCK,则只读open立即返回,那么只写open将出错返回-1,errno==ENXIO。
int mkfifo(char *path,mode_t mode);
1.3、popen
popen的常见操作是:创建一个管道连接到另一个进程,然后读取其输出或向其输入端发送数据。
FILE *popen(char *cmd,const char *mode)
先执行fork然后调用exec以执行cmd,
如果type为 "r" 则返回文件指连接到cmd的stdout,
如果为"w"连接到cmd的stdin
int pclose(FILE *fp)
1.4、协同进程
unix系统过滤程序从标准输入读取数据,对其进适当处理后写到标准输出.当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出时,则该过滤程序就成为协同进程.
2、XSI IPC
XSI的缺点:
a、IPC结构是在系统范围起作用的,没有访问计数。例如,一个进程创建了一个消息队列,在队列放了几则消息后退出。但退出后,消息队列和内容并没有被删除,残留在系统,直到某个进程调用了msgrcv或msgctrl或删除该消息队列。与管道不一样。
b、由于IPC结构是没有名字的,我们不能用open,read,fopen,fread等函数去访问和修改他们的特性。为此,系统不得不增加十几条全新的系统调用(msgget,semop,shmat等等)。而且,我们还是不能使用ls查看IPC对象,不能使用rm删除,也不能使用chmod来更改访问权限。于是不得不增加 新命令ipcs和ipcrm。
c、因为不能使用文件描述符,所以不能对他们使用多路转接I/O函数:select和poll。这就使得不能一次使用多个IPC结构,以及在文件或设备I/O中使用IPC结构例如:没有某种形式的忙-等待循环,就不能使一个服务器进程等待要放在两个消息队列任一个中的消息。
优点:
a、可靠
b、流受控制
c、面向记录
d、可以用非先进先出方式处理
标识与键
a、这个键就是使不同进程的一个回合,一个外部一致的名称来确定共享的内容。
b、每个内核中的 XSI IPC结构都用一个非负的整数的标识符加以引用,每一个对ipc对象都与一个键关联
常用的回合方法有三种:
a、服务进程指定IPC_PRIVATE创建一个新的IPC结构,然后把返回的标识符存放在文件,然后客户进程去文件读取。(个人的另一个想法,用环境变量来共享标识符,但一定不能用全局变量,进程间内存是隔离的)
b、公共头文件定义一个客户进程和服务进程大家都认可的键(如0x555)。但如果此键已存在,服务进程必须处理这情况:使用msgget,semget,shmget来出错返回,然后删除已存在的,再去创建。
c、使用创建一个键 key_t ftok(char *path,int id);
path必须引用的是一个现存的文件 产生键时只使用id参数的低8位
注意:ftok用的不多,因为当目标文件被操作过(如打开过),其获得的key将有所不同
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
int main(void)
{
key_t key;
key=ftok("/etc/services",88);
printf("====>key:%d\n",key);
return 0;
}
权限结构
XSI IPC为每一个IPC结构设置了一个ipc_perm结构 它至少实现以下几项
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid; //有效用户id
__kernel_gid_t cgid;
__kernel_mode_t mode; //access mode
};
2.1、消息队列
消息队列 :消息队列是消息的链接表,存入在内核中并由消息队列标识符标识
每一个消息队列有一个msqid_ds结构
struct msqid_ds { //linux 下的实现 每个平台互少实现前几项
struct ipc_perm msg_perm;
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
};
msgget:打开一个现有队列或创建一个新的队列
int msgget(key_t key,int flag); 成功返回消息队列ID 失败返回-1
当key为 IPC_PRIVATE 或 key是一个并不存在对象 并且flag为IPC_CREAT | 0644 创建一个新的队列
msgctl:控制操作一个消息队列
int msgctl(int msgpid,int cmd,struct msqid_ds *buf);
cmd决定执行的操作:
a、IPC_STAT 取此队列的msqid_ds结构存入buf中
b、IPC_SET 按buf中的值设置msg_per.uid,msg_qbytes(只有超级用户能改),msg_perm.gid, msg_perm.mode,因为要修改信息,所以能执行此命令的只有root user和有效用户ID等于msg_per.uid或msg_per.cuid
c、IPC_RMID 删除该消息队列以及仍在队列中的所有数据,该删除立即生效
msgsnd:将数据发送到消息队列
int msgsnd(int msgqid,const void *ptr,size_t nbytes,int flag);
每一个消由三部分组成:正长整型类型字段,非负长度以及实际数据字节,消息总是放在队尾
msgrcv:获取一个消息
ssize_t msgrcv(int msqid,void *ptr,size_t nbytes,long type,int flag);
nbytes说明数缓冲区的长度,如果消息大于nbytes,而且flag设置MSG_NOERROR,则截短并不通知我们,如是没有 MSG_NOERROR,则出错返回 E2BIG
参数type指定我们要哪种消息
type==0 返回队列第一个消息
type >0 返回消息类型为type的第一个信息
type <0 返回消息类型小于或等于type绝对值的消息,如果有多个,返回类型值最小的
2.2、信号量
semget:创建信号量 或 获取现在存的信号量
int semget(key_t key,int nsems,int flag)
nsems:表示信号量的数量
flag :同msgget
semctl:操作一个现有的信号量
int semctl(int semid,int semnum,int cmd,.../* union semun arg */)
semnum: [0] -- [nsems-1]
semop:
int semop(int semid,struct sembuf semoparray[],size_t nops) //nops 操作几个信号量
2.3、共享存储
void *shmget(key_t key,size_t size,int flag);
int shmctl(int shmid,int cmd,.../* */);
void *shmat(int shmid,void *addr,int flag);
int shmdt(const void *shmaddr);