至于linux进程间通信(IPC)(http://www.ibm.com/developerworks/cn/linux/l-ipc/)
1 管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信
2 信号:信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持unix早期信号语义函数sigal外,还支持语义符号Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现signal函数)
3 报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列System V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点
4 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率低而设计的。往往与其他通信机制,如信号量结合使用,来达到进程间的同步及互斥
5信号量(semaphore):主要作为进程间及同一进程不同线程之间的同步方式
6套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信
下面对上述6方式,用代码实现:
管道:其实一个文件fd,fd[1]写端,fd[0]读端。和文件的区别:a 管道是固定大小的缓冲区。在linux中,缓冲区的大小为1页,即4*k字节,使得它的大小不像文件那样不加检验的增长。b 使用单个固定缓冲区也会带来问题, 比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认的调用将被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。当为空的时候,随后的read()将被阻塞,等待某些数据被写入 c 从管道读数据时一次性操作,数据一旦被读,它就从管道中被抛弃,释放空间以便写更多的数据
代码:
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd[2],n;
char line[100];
pid_t pid;
if(pipe(fd)<0)
{
printf("pipe create error \n");
}
if((pid=fork())==0){
close(fd[1]);//关闭管道的写端
n=read(fd[0],line,100);
write(STDOUT_FILENO,line,n);
}else if(pid>0)
{
close(fd[0]);//关闭管道的读端
char str[]="hello the world";
write(fd[1],str,sizeof(str)/sizeof(char));
}
exit(0);
}
命名管道 特点:1 FIFO是作为一个特殊的设备文件存在 2 不同祖先进程的进程之间可以共享数据; 3使用完后FIFO将继续存在
读端
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#define FIFO "/tmp/myfifo"
int main(int argc,char **argv)
{
char buf_r[100];
int fd;
int nread;
if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))//创建并执行
printf("cannot create fifoserver \n");
printf("preparing for reading bytes............\n");
memset(buf_r,0,sizeof(buf_r));
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);//readonly 不阻塞
if(fd==-1)
{
perror("open");
exit(1);
}
while(1)
{
memset(buf_r,0,sizeof(buf_r));
if(nread=read(fd,buf_r,sizeof(buf_r))==-1){//读取管道
if(errno==EAGAIN)
printf("no data yet \n");
}
printf("read %s from FIFO \n",buf_r);
sleep(1);
}
pause();
unlink(FIFO);
}
写端:
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FIFO "/tmp/myfifo"
int main(int argc,char **argv)
{
int fd;
char w_buf[100];
int nwrite;
if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
printf("cannot create fifoserver");
printf("preparing for writing bytes ......\n");
fd=open(FIFO,O_WRONLY|O_NONBLOCK,0);
if(argc==1)
printf("please send somethig \n");
strcpy(w_buf,argv[1]);
if((nwrite=write(fd,w_buf,100))==-1)
{
if(errno==EAGAIN)
printf("the fifo has not been read yet, please try later\n");
}else
printf("write %s to the FIFO \n",w_buf);
}
通常先执行读 后写
信号:
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>
//信号处理函数
void sig_handler(int signum)
{
printf("at handler \n");
sleep(10);
printf("leave handler \n");
}
int main(int argc,char **argv){
char buf[100];
int ret;
struct sigaction action,old_action;
action.sa_handler=sig_handler;
sigemptyset(&action.sa_mask);
action.sa_flags=0;
action.sa_flags|=SA_RESTART;//如果不设置,从标准输入数据的时候,如果按下ctrl+c,则read系统调用被中断,返回-1
//如设置,信号处理函数返回后,恢复read系统调用,但信号处理返回之前,read是无法读取数据的
//注册信号处理函数
sigaction(SIGINT,NULL,&old_action);
if(old_action.sa_handler!=SIG_IGN){
sigaction(SIGINT,&action,NULL);
}
bzero(buf,100);
ret=read(0,buf,100);
if(ret==-1)
perror("read");
printf("read %d bytes : \n",ret);
printf("%s \n",buf);
return 0;
}
#include <signal.h>
#include <stdio.h>
#include <error.h>
#include <string.h>
void sig_handler(int signum)
{
printf("catch sigint \n");
}
int main(int argc,char **argv)
{
sigset_t block;
struct sigaction action,old_action;
action.sa_handler=sig_handler;
sigemptyset(&action.sa_mask);
action.sa_handler=sig_handler;
sigaction(SIGINT,NULL,&old_action);
if(old_action.sa_handler!=SIG_IGN)
sigaction(SIGINT,&action,NULL);
sigemptyset(&block);
sigaddset(&block,SIGINT);
printf("block sigint\n");
sigprocmask(SIG_BLOCK,&block,NULL);
//这里的屏蔽,并不是禁止递送信号,而是暂时阻塞信号的递送
//解除屏蔽后,信号将被递送,不会丢失
sleep(5);
/*unblock信号后,之前触发的信号将被递送,如果之前被触发多次,只会递送一次*/
sigprocmask(SIG_UNBLOCK,&block,NULL);
printf("unblock sigint \n");
sleep(5);
return 0;
}
消息队列:http://www.cnblogs.com/lpshou/archive/2013/06/20/3145651.html
消息队列就是一个消息的链表。可以把消息看着一个记录,具有特定的格式以及特定的格式和特定的优先级。对消息队列有写权限的进程可以向其中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息
函数:
1 创建新消息队列或获得已存在消息队列
原型:int msgget(key_t key,int msgflg)
参数:key:可以认为一个端口号,也可以由函数ftok生成
msgflg:IPC_CREAT值,如果没有改队列,则创建一个并返回新标识符;如已存在,则返回原标识符
IPC_EXCL值,若没有改队列,则返回-1;若已存在,则返回0
2 想队列读/写消息
原型:
msgrcv从队列中取用消息:ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
msgsnd将数据放到消息队列中:int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
msgtyp:从消息队列内读取的消息形态。如果值为0,则表示消息队列中的所有消息都会被读取
msgflg:用来指明核心程序在队列没有数据的情况下所应采用的行动。如果msgflg和常数IPC_NOWAIT合用,则在msgsnd()执行时若消息队列已满
,则msgsnd()将不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列空时候,不做等待马上返回-1,并设错误码ENOMSG.
当msgflg为0时,msgsnd()及msgrcv()在队列满或空的时候,采用阻塞等待的处理模式
3设置消息队列属性:
原型:int msgctl(int msgid,int cmd,struct msqid_ds *buf);
有3种cmd IPC_STAT,IPC_SET,IPC_RMID
IPC_STAT:该命令用来获取消息队列对应的msqid_ds数据结构,并将其保存到buf指定的地址空间
IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf中
IPC_RMID:从内核中删除msqid标识的消息队列
代码如下:
receive.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#define MSGKEY 9999
struct msgstru
{
long msgtype;
char msgtext[2046];
};
void childproc()
{
struct msgstru msgs;
int msgid,ret_value;
char str[512];
while(1)
{
msgid=msgget(MSGKEY,IPC_EXCL);
if(msgid<0)
{
printf("msq not existed ! errno=%d [%s]\n",errno,strerror(errno));
sleep(2);
continue;
}
//msgid=msgget(MSGKEY,IPC_CREAT|0666);
//cout<<"msgid ="<<msgid<<endl;
printf("msgid = %d ip =%d\n ",msgid,getpid());
ret_value=msgrcv(msgid,&msgs,sizeof(struct msgstru),0,0);
printf("ret_value = %d \n",ret_value);
//if(ret_value>0)
printf("text=[%s] pid=[%d]\n",msgs.msgtext,getpid());
//return;
}
return ;
}
int main()
{
int i,cpid;
for(i=0;i<2;i++)
{
cpid=fork();
if(cpid<0)
{
printf("fork failed \n");
}
else if(cpid==0)
childproc();
}
// return 0;
}
send.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <string.h>
#define MSGKEY 9999
struct msgstru
{
long msgtype;
char msgtext[2048];
};
int main()
{
struct msgstru msgs;
int msg_type;
char str[256];
int ret_value;
int msqid;
msqid=msgget(MSGKEY,IPC_EXCL);
if(msqid<0)
msqid=msgget(MSGKEY,IPC_CREAT|0666);
if(msqid<0)
{
printf("failed to create msq | errno= %d [%s]",errno,strerror(errno));
exit(-1);
}
while(1)
{
printf("input message type (end:0):");
scanf("%d",&msg_type);
if(msg_type==0)
break;
printf("input message to be sent:");
scanf("%s",str);
//gets(str);
msgs.msgtype=msg_type;
strcpy(msgs.msgtext,str);
ret_value=msgsnd(msqid,&msgs,strlen(msgs.msgtext)+1,0);
if(ret_value<0)
{
printf("msgsnd() write msg failed ,errno=%d[%s]",errno,strerror(errno));
exit(-1);
}
}
msgctl(msqid,IPC_RMID,0);
}
共享内存:
与内存映射的区别: 内存映射实际上是把文件映射到一块内存上,在进程中返回映射的地址,以后对该文件的操作就像操作内存一样,加快了设备、文件的访问速度
A.B进程共享内存的意思是将共享内存映射到A、B映射到各自的进程地址空间中去,以后无论进程A或者进程B对共享内存的读写,彼此都知道。
从功能上区分:内存映射是为了加快文件/设备的读写速度,而共享内存是加快多个进程间通信
共享内存案例可以看 http://blog.csdn.net/wc7620awjh/article/details/7721331
函数:
int shmget(key_t key,int size,int shmflg);//创建共享内存
int shmat(int shmid,char *shmaddr,int flag);//映射共享内存 flag决定以什么样的方式来确定映射的地址(通常为0) 成功返回地址,失败返回-1
int shmdt(char *shmaddr)//共享内存解除映射