本章主要介绍几种进程间通信的方式。管道,FIFO(也叫命名管道), 消息队列,信号量,共享存储。
其他的不在本章内容中的进程间通信方式有:流管道,命令流管道(下章介绍),套接字,流(后两种支持在不同主机间的进程通信)。
1. 管道
管道只能在拥有公共祖先间进程通信使用,并且管道是半双工的。
#include
int pipe(int fields[2])
field[0]为读,field[1]为写。通常父子进程一个关闭读端,一个关闭写端。
当一个管道读端被关闭时,再向写端写数据将会产生SIGPIPE。当一个管道写端杯关闭时,所有数据被读完后,read 返回0.以指示到达了文件末尾。
简单的一个例子,子进程发送一条消息给主进程。没有数据时候,主进程的read将会一直block.
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int fd[2];
if(pipe(fd)<0)
{
printf("pipe error\n");
return -1;
}
if((pid=fork())<0)
{
printf("fork error\n");
}
else if (pid==0)
{
close(fd[0]);
char* buf = "message write to parent";
sleep(5);
if(write(fd[1],buf, strlen(buf))!=strlen(buf))
{
printf("Write to pipe error\n");
return -1;
}
}
else
{
close(fd[1]);
char bufp[1024];
int n;
printf("Begin to read\n");
if((n= read(fd[0],bufp, 1024))==-1)
{
printf("read error\n");
}
//bufp[n] = "\0";
printf("Read content from child is %s\n", bufp);
}
}
利用管道,父进程读文件,然后子进程输出的例子:
#include
#include
#include
#include
#include
int main(int argc,char* argv[])
{
pid_t pid;
int fd[2];
FILE* fp;
if (argc<2)
{
printf("No args\n");
return -1;
}
if(pipe(fd)<0)
{
printf("pipe error\n");
return -1;
}
if((pid=fork())<0)
{
printf("fork error\n");
return -1;
}
else if (pid == 0) //child
{
close(fd[1]);
if (STDIN_FILENO!=fd[0])
{
if (dup2(fd[0],STDIN_FILENO)!= STDIN_FILENO)
{
printf("dup error\n");
return -1;
}
close(fd[0]);
}
if (execlp("more","more",(char*)0)<0)
{
printf("execlp error\n");
}
else
{
printf("execlp succeed\n");
}
}
else
{
close(fd[0]);
char line[4096];
int n;
fp = fopen(argv[1],"r");
if(fp==NULL)
{
printf("fopen error\n");
return -1;
}
while(fgets(line,4096,fp)!=NULL)
{
n = strlen(line);
if(write(fd[1],line,n)!=n)
{
printf("write pipe error\n");
return -1;
}
}
if (ferror(fp))
{
printf("fgets error");
return -1;
}
close(fd[1]);
if((waitpid(pid,NULL,0))<0)
{
printf("wait error");
return -1;
}
exit(0);
}
}
管道版进程同步函数,作者有些牵强,只是为了写管道的用法而写,一般人估计不会用这种方式同步。
#include
#include
int fd1[2];
int fd2[2];
void TELL_WAIT()
{
if(pipe(fd1)<0 || pipe(fd2)<0)
{
printf("pipe error\n");
}
}
void TELL_CHILD()
{
if(write(fd1[1],"p",1)!=1)
printf("write error\n");
}
void WAIT_CHILD()
{
char c;
if(read(fd2[0],&c,1)!=1)
printf("write error\n");
if(c!='c')
printf("get wrong char\n");
}
void TELL_PARENT()
{
if(write(fd2[1],"c",1)!=1)
printf("write error\n");
}
void WAIT_PARENT()
{
char c ;
if(read(fd1[0],&c,1)!=1)
printf("write error\n");
if(c!='p')
printf("get wrong char\n");
}
int main()
{
TELL_WAIT();
pid_t pid;
if ((pid=fork())<0)
{
printf("fork error\n");
return -1;
}
else if(pid==0)
{
WAIT_PARENT();
printf("message from child\n");
TELL_PARENT();
}
else
{
printf("message from parent\n");
TELL_CHILD();
WAIT_CHILD();
printf("message from parent2\n");
}
}
popen 的demo
#include
#include
int main()
{
FILE* fp = popen("ls -lt /home","r");
char buf[2048];
while((fgets(buf,2048,fp)!=NULL))
{
int n = strlen(buf);
buf[n] = '\0';
printf("%s", buf);
}
pclose(fp);
}
没有制定,O_NONBLOCK选项时,读进程会阻塞到某一个写进程打开fifo. 指定 O_NONBLOCK时, 如果没有写进程打开FIFO, 则读进程立即返回。如果没有为读而打开一个FIFO,那么只写操作将返回错误,errno 是ENXIO.
和管道一样,如果没有读进程,那写操作将产生SIGPIPE,如果最后一个写进程关闭,则为该FIFO产生一个文件结束符标识。
使用FIFO的时候需要注意使用UNLINK 销毁fifo,不然即使进程结束FIFO 也会存在系统中。如下代码,没有unlink时候,第二次执行将出错。
#include
#include
#include
#include
#define fifo_name "/tmp/tf1.fifo"
#define FILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
int main()
{
char errorbuf[2048];
if(mkfifo(fifo_name,FILEMODE )==-1)
{
snprintf(errorbuf,sizeof(errorbuf), "can not create %s\n",fifo_name);
perror(errorbuf);
}
unlink(fifo_name);
}
FIFO 的服务器客户端版本
#include
#include
#include
#include
#include
#include
#include
#define fifo_server "/tmp/fifo_server"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
int main()
{
char buferror[1024];
char buf[1024];
int fd;
if((mkfifo(fifo_server,FILE_MODE )==-1) && errno!= EEXIST)
{
snprintf(buferror,sizeof(buferror), "create %s error\n",fifo_server);
return -1;
}
if((fd=open(fifo_server, O_RDONLY ))==-1)
{
snprintf(buferror,sizeof(buferror), "open %s error\n",fifo_server);
return -1;
}
int n;
while(1)
{
n =read(fd, buf,sizeof(buf));
if (n<1)
continue;
if (buf[strlen(buf)-1]=='\n')
buf[strlen(buf)-1] = '\0';
int pid = atoi(buf);
printf("Get message from %d\n", pid);
}
}
client
#include
#include
#include
#define fifo_server "/tmp/fifo_server"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
int main()
{
pid_t pid = getpid();
char buf[1024];
snprintf(buf,sizeof(buf), "%d\n",pid);
int fd;
if((fd=open(fifo_server, O_WRONLY))<0)
{
printf("open error\n");
return -1;
}
if(write(fd, buf, strlen(buf))!= strlen(buf))
{
printf("write error\n");
return -1;
}
}
三种内核IPC结构都使用非负整数作为唯一标识符。到达最大整数后又从0开始计数。
//ftok创建唯一IPC key
#include
#include
key_t ftok(const char* fname,int id)
创建IPC 的方法有两种:a) 使用shmget, semget,msgget 的key 为IPC_PRIVATE 或者 b) 使用shmget,semget,msgget的flag为IPC_CREAT 并且key 唯一。
IPC的权限结构:
struct ipc_perm{
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode; /*access modes*/
ulong seq; /* slot usage sequence number*/
key_t key; /*key*/
}
消息队列的优缺点:1. 和其他进程通信不同,IPC会有残留 2. 增加了其他很多API函数
4. 消息队列demo
Server 部分,创建消息队列,一直等待client传递消息,直到遇到end消息后,删除消息队列并返回。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MSG_LEN 512
struct mymsg
{
long mbytes;
char text[MSG_LEN];
};
int main()
{
char buf[2048];
//get or create message id
key_t key = ftok("/tmp/abc/",1);
int id;
id=msgget(key, 0666| IPC_CREAT);
if(id==-1)
{
snprintf(buf,2048, "Creat key msg error:");
perror(buf);
return -1;
}
//wait for the message
while(1)
{
struct mymsg msg;
if((msgrcv(id, (void*) &msg,MSG_LEN ,0,0))==-1)
{
printf("receive message error\n");
return -1;
}
printf("Received message:%s\n", msg.text);
if(strcmp(msg.text,"end")==0)
break;
}
if(msgctl(id, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
#include
#include
#include
#include
#include
#include
#define MSG_LEN 512
struct mymsg
{
long mbytes;
char text[MSG_LEN];
};
int main()
{
char buf[2048];
//create get the msg queue
key_t key = ftok("/tmp/abc/",1);
printf("key is %d\n", key);
int id;
if((id=msgget(key, 0600| IPC_CREAT))==-1)
{
snprintf(buf,2048, "Creat key msg error\n");
perror(buf);
}
//send first message
struct mymsg msg;
const char sendmsg[] = "fist msg to send\n";
snprintf(msg.text,MSG_LEN,sendmsg );
msg.mbytes = sizeof(sendmsg);
if(msgsnd(id,(void*)&msg,sizeof(sendmsg),0)==-1)
{
memset(buf,sizeof(buf),0);
snprintf(buf,2048, "send first msg error\n");
perror(buf);
}
//send second message
struct mymsg msg2;
const char sendmsg2[] = "end";
snprintf(msg2.text,MSG_LEN,sendmsg2 );
msg2.mbytes = sizeof(sendmsg2);
if(msgsnd(id,(void*)&msg2,sizeof(sendmsg2),0)==-1)
{
memset(buf,sizeof(buf),0);
snprintf(buf,2048, "send second msg error\n");
perror(buf);
}
}
5. 信号量
信号量一般用作进程间同步,或进程锁。一般只要0,1开关信号量。系统实现的信号量有些复杂。
信号量的demo. 主要使用IPC_UNDO, 这个标识可以使得即使程序异常终止,也不会因为以前的改动造成程序一直死锁。
运行方法如下: ./a.out new OOO & ; /a.out anything XXX. 则可看到屏幕上OXOX交叉打印了。
最后可运行. ./a.out del anything 来删除系统中的信号量。
#include
#include
#include
#include
union semun
{
int val;
struct semid_ds *buf;
ushort *array;
};
static int ctlSemaphore(int sem_id, int op_number)
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = op_number;
buf.sem_flg = SEM_UNDO;
if(semop(sem_id,&buf,1)==-1)
{
fprintf(stderr,"operate semaphore failed\n");
return 0;
}
return 1;
}
static int iniSemaphore(int sem_id)
{
union semun sem_union;
sem_union.val =1;
if(semctl(sem_id,0,SETVAL,sem_union)==-1)
{
fprintf(stderr,"initial semaphore failed\n");
return 0;
}
return 1;
}
static void del_semvalue(int sem_id)
{
union semun sem_union;
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
{
fprintf(stderr, "Failed to delete semaphore\n");
}
}
int main(int argc, char* argv[])
{
if (argc<3)
{
printf("At least two arguments\n");
return -1;
}
//create the semophore key
key_t key = ftok(".",5);
int semid = semget(key,1,0666|IPC_CREAT);
if(semid==-1)
{
printf("Create or get semophore failed. errno = %d, error is :%s\n", errno,strerror(errno));
return -1;
}
//if del , we delete the semaphore
if(strcmp(argv[1],"del")==0)
{
del_semvalue(semid);
return -1;
}
//if new, we need to initial the semaphore
if(strcmp(argv[1],"new")==0)
{
if(0==iniSemaphore(semid))
return -1;
}
int i;
for (i=0;i<10;i++)
{
ctlSemaphore(semid,-1);
printf("%c",argv[2][0]);
fflush(stdout);
sleep(2);
ctlSemaphore(semid,1);
sleep(2);
}
}
最快的一种进程间通信方式。常用的父子进程间通信的demo, 父进程得到子进程的消息并打印。
#include
#include
#include
#include
#include
#include
#define SIZE 1024
int main()
{
int shmid;
char* shmaddr;
struct shmid_ds buf;
int flag =0;
int pid;
//0 get share memory
shmid = shmget(IPC_PRIVATE,SIZE,IPC_CREAT| 0600);
if(shmid<0)
{
perror("get shm ipc_di error");
return -1;
}
shmaddr = (char*) shmat(shmid,NULL,0);
if((int)shmaddr==-1)
{
perror("shmat addr error");
return -1;
}
pid = fork();
if(pid==0)
{
strcpy(shmaddr,"Message from child\n");
shmdt(shmaddr);
return 0;
}
else if (pid>0)
{
sleep(3);
printf("%s",shmaddr);
shmdt(shmaddr);
if(shmctl(shmid,IPC_RMID,NULL)==-1)
printf("remove shared memory failed\n");
}
}
mmap.
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);int munmap(void* start,size_t length) 出错返回(void*) -1
start 为映射去的开始地址。0 表示自动分配。 length为映射区长度。prot, 期望的内存保护标识。PROT_READ,PROT_WRITE分别为读写。
flags为映射对象类型,常用为MAP_SHARED, MAP_PRIVATE 等。
fd为映射文件标识符。 offset提示映射从那个位置开始。
通过内存映射改写文件内容demo:
#include
#include
#include
#include
int main()
{
int fd = open("test.log",O_RDWR);
if(fd<0)
{
printf("Open log error\n");
return -1;
}
int size;
struct stat statbuf;
if(fstat(fd,&statbuf)==-1)
{
printf("stat error\n");
return -1;
}
char* mapped;
mapped= mmap(NULL,statbuf.st_size,PROT_READ| PROT_WRITE,MAP_SHARED,fd,0);
if(mapped==(void*)-1)
{
printf("map memory error\n");
return -1;
}
close(fd);
printf("%s", mapped);
mapped[1] = 'b';
if ((msync((void *)mapped, statbuf.st_size, MS_SYNC)) == -1) {
perror("msync");
return -1;
}
if ((munmap((void *)mapped, statbuf.st_size)) == -1) {
perror("munmap");
}
return 0;
}
mmap父子进程通信
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 100
int main()
{
char* mapped;
mapped= mmap(NULL,BUF_SIZE ,PROT_READ| PROT_WRITE , MAP_SHARED | MAP_ANON ,-
1,0);
if(mapped==(void*)-1)
{
printf("map memory error\n");
return -1;
}
pid_t pid;
pid = fork();
if(pid<0)
{
printf("fork error\n");
return -1;
}
if(pid==0)
{
sprintf(mapped,"%s","message from child");
sleep(5);
exit(0);
}
sleep(2);
printf("%s\n",mapped);
if ((munmap((void *)mapped,BUF_SIZE )) == -1) {
perror("munmap");
}
return 0;
}