[APUE]再读之进程间通信
来源:互联网 发布:与av淘宝一样的网站 编辑:程序博客网 时间:2024/06/13 12:05
本章主要介绍几种进程间通信的方式。管道,FIFO(也叫命名管道), 消息队列,信号量,共享存储。
其他的不在本章内容中的进程间通信方式有:流管道,命令流管道(下章介绍),套接字,流(后两种支持在不同主机间的进程通信)。
1. 管道
管道只能在拥有公共祖先间进程通信使用,并且管道是半双工的。
#include <unistd.h>int pipe(int fields[2])field[0]为读,field[1]为写。通常父子进程一个关闭读端,一个关闭写端。
当一个管道读端被关闭时,再向写端写数据将会产生SIGPIPE。当一个管道写端杯关闭时,所有数据被读完后,read 返回0.以指示到达了文件末尾。
简单的一个例子,子进程发送一条消息给主进程。没有数据时候,主进程的read将会一直block.
#include <stdio.h>#include <sys/wait.h>#include <stdlib.h>#include <unistd.h>#include <string.h>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 <stdio.h>#include <sys/types.h>#include <stdlib.h>#include <unistd.h>#include <string.h>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 <unistd.h>#include <stdio.h>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 <stdio.h>#include <string.h>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);}
2. fifo.
没有制定,O_NONBLOCK选项时,读进程会阻塞到某一个写进程打开fifo. 指定 O_NONBLOCK时, 如果没有写进程打开FIFO, 则读进程立即返回。如果没有为读而打开一个FIFO,那么只写操作将返回错误,errno 是ENXIO.
和管道一样,如果没有读进程,那写操作将产生SIGPIPE,如果最后一个写进程关闭,则为该FIFO产生一个文件结束符标识。
使用FIFO的时候需要注意使用UNLINK 销毁fifo,不然即使进程结束FIFO 也会存在系统中。如下代码,没有unlink时候,第二次执行将出错。
#include <sys/stat.h>#include <sys/types.h>#include <sys/errno.h>#include <stdio.h>#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 <errno.h>#include <fcntl.h>#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <stdio.h>#include <string.h>#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 <stdio.h>#include <fcntl.h>#include <string.h>#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; }}
3. 信号量,消息队列和共享存储
三种内核IPC结构都使用非负整数作为唯一标识符。到达最大整数后又从0开始计数。
//ftok创建唯一IPC key#include <sys/types.h>#include <sys/ipc.h>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 <sys/ipc.h>#include <sys/msg.h>#include <sys/types.h>#include <stdio.h>#include <string.h>#include <fcntl.h>#include <stdlib.h>#include <string.h>#include <errno.h>#define MSG_LEN 512struct 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); }
client 部分,发送两个消息,第一个为正常发送消息,第二个为结束消息。
#include <sys/ipc.h>#include <sys/msg.h>#include <sys/types.h>#include <stdio.h>#include <string.h>#include <fcntl.h>#define MSG_LEN 512struct 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 <sys/sem.h>#include <stdio.h>#include <string.h>#include <errno.h>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); }}
6. 共享内存
最快的一种进程间通信方式。常用的父子进程间通信的demo, 父进程得到子进程的消息并打印。
#include <stdio.h>#include <unistd.h>#include <string.h>#include <sys/ipc.h>#include <sys/shm.h>#include <error.h>#define SIZE 1024int 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"); }}
7. 内存映射
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 <stdio.h>#include <unistd.h>#include <fcntl.h>#include <sys/mman.h>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 <stdio.h>#include <unistd.h>#include <fcntl.h>#include <sys/mman.h>#include <unistd.h>#include <stdlib.h>#define BUF_SIZE 100int 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;}
- [APUE]再读之进程间通信
- [APUE] 再读之进程环境
- [APUE] 再读之进程控制
- [APUE] 再读之进程关系
- [APUE] 再读之信号
- APUE进程间通信
- APUE------进程间通信
- [APUE]再读之高级IO
- [APUE]再读之 unix 基础知识
- APUE笔记 进程间通信
- APUE读书笔记---进程间通信之POSIX共享内存区
- [APUE]再读之文件和目录
- [APUE]再读之 标准IO库
- APUE函数笔记十三: 进程间通信
- APUE笔记 高级进程间通信
- apue 第十五章 进程间通信
- apue 第十七章 高级进程间通信
- apue 第15章 进程间通信
- 1024. 科学计数法 (20)
- Android使用第三方或者自制字体库(Typespace)
- 等价类划分--三角形测试用例设计
- Linux重启网卡的3种方式
- 1025. 反转链表 (25)
- [APUE]再读之进程间通信
- Maven 发布jar包到远程仓库
- STL字符串常用方法扩展
- Java语言程序设计 学习作业2.6**分离个十百位数求和
- 题目1388:跳台阶 -- 简单的动归 注意数据范围
- Linux 配置收集2 - Some Configuration In Ubuntu14.04.02 And EOS
- JAVA上机——2.6
- LeetCode 278--First Bad Versionx先判断
- C++ Primer Plus 第6版 中文版 第5章编程练习