1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Linux系统编程学习笔记(九)进程间通信IPC

Linux系统编程学习笔记(九)进程间通信IPC

时间:2022-01-18 14:43:33

相关推荐

Linux系统编程学习笔记(九)进程间通信IPC

进程间通信IPC:

我们以前介绍过进程控制原语,看到怎么创建多个进程。但是进程之间交互信息的方式只介绍了通过fork或者exec继承父进程的打开文件或者通过文件系统。

经典的进程通信方式有:管道、FIFOs,消息队列,信号量和共享内存。

1、管道:

管道是Unix系统IPC最古老的形式,PIPE有以下限制:

1)是半双工的,一些系统提供了全双工的管道,但是为了可移植性,我们最好不要作此假设。

2)管道只能在有共同祖先的进程之间。一般一个进程创建一个管道,然后进程调用fork,然后管道在父进程和子进程之间使用。

FIFO摆脱了第二个限制,Unix domain socket和具名STREAMS-based管道可以摆脱这两者的限制。

尽管有以上限制,半双工的管道仍然是最常使用的IPC。

1)创建管道:

#include <unistd.h> int pipe(int filedes[2]);

调用成功之后,通过filedes返回了两个文件描述符:filedes[0]被打开可以读取,filedes[1]被打开可以写,filedes[1]的输出时filedes[0]的输入,fstat函数返回这两个文件描述符是FIFO,通过宏S_ISFIFO来判断是否是一个管道。

一个进程的管道几乎没有用处,通常一个进程调用pipe创建管道,然后调用fork,在父子进程之间创建IPC通道。

从父进程到子进程的管道,父进程关闭读端的管道(filedes[0]),子进程关闭写端的管道(filedes[1]);从子进程到父进程的管道,父进程关闭filedes[1],子进程关闭filedes[0].

当一端的管道关闭:

1)如果我们从一个写端已经关闭的管道读取,当所有的数据都已经读完,read返回0,指示文件结束。

2)如果我们从一个读段已经关闭的管道进行写操作,将产生SIGPIPE信号。当我们忽略或者处理它时,write返回-1,errno设置成EPIPE。

当我们往管道里面写数据的时候,常量PIPE_BUF指示了内核管道的buffer大小,当有多个进程向该管道写时,写小于等于PIPE_BUF大小的数据,不会交错,大于PIPE_BUF会有可能导致数据的交错。我们可以使用sysconf来查看PIPE_BUF的大小。

例子:

#include <unistd.h> #include <sys/types.h> #include <stdlib.h> #define MAXLINE 90 int main(void){int n; int fd[2]; pid_t pid; char line[MAXLINE]; if(pipe(fd) < 0){perror("pipe"); exit(1); } if((pid = fork()) < 0){perror("fork"); exit(1); }else if(pid > 0){close(fd[0]); write(fd[1],"hello,world\n",12); }else{close(fd[1]); n = read(fd[0],line,MAXLINE); write(STDOUT_FILENO,line,n); } exit(0);}

这个例子创建了一个从父进程到子进程的一个pipe,并发送 了一些数据。

我们复制pipe的文件描述符到标准输入输出,这个会比较有意思,我们经常从运行我们的程序,从标准输入读或者写到标准输出。

例子:

我们考虑写一个程序,显示一些输出,一次一页,我们可以使用系统自带的分页程序,我们避免把数据写到临时文件中或者使用system,我们想建立从标准输入到分页的管道来实现。

#include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <sys/wait.h> #include <stdio.h> #define DEF_PAGER "/bin/more" #define MAXLINE 80 int main(int argc,char *argv[]){int n; int fd[2]; pid_t pid; char *pager, *argv0; char line[MAXLINE]; FILE *fp; if(argc != 2){printf("Usage: a.out <pathname>"); exit(1); } if((fp = fopen(argv[1],"r")) == NULL){perror("fopen"); exit(1); }if(pipe(fd) < 0){perror("pipe"); exit(1); } if((pid = fork()) < 0){perror("fork"); exit(1); }else if(pid > 0){/* parent */ close(fd[0]);/* close read end */ while(fgets(line,MAXLINE,fp) != NULL){n = strlen(line); if(write(fd[1],line,n) != n){perror("write"); exit(1); } } if(ferror(fp)){perror("fgets"); exit(1); } close(fd[1]); /* close write end of the pipe for reader */ if(waitpid(pid,NULL,0) < 0){perror("waitpid"); exit(1); } exit(0); }else{/* child */ close(fd[1]); if(fd[0] != STDIN_FILENO){if(dup2(fd[0],STDIN_FILENO) != STDIN_FILENO){perror("dup2"); exit(1); } close(fd[0]);// don't need it after dup2 } /* get arguments for execl() */ if((pager = getenv("PAGER")) == NULL){pager = DEF_PAGER; } if((argv0 = strrchr(pager,'/')) != NULL) argv0++;/* step past rightmost slash */ else argv0 = pager; if(execl(pager,argv0,(char *)0) < 0){perror("execl"); exit(1); } } exit(1);}

2)popen和pclose函数:

既然一些常见的操作:比如创建一个从当前进程到另一个进程的pipe,去读它的输出或者向它的输入发送数据,标准的I/O支持popen和pclose

这两个函数为我们帮我们做了很多的脏活累活:创建一个管道,fork一个子进程,关闭没有用的管道的端,执行shell去运行命令,最后等待命令的执行的终止。

#include <stdio.h> FILE *popen(const char *cmdstring, const char *type); int pclose(FILE *fp);

popen执行一个fork和exec去执行cmdstring,返回标准的I/O文件指针。如果type是"r",文件指针和cmdstring的标准输出相连。

如果type是"w",则和cmdstring的标准输入相连。

一个好记的方法是和fopen对比,"r"表示可读,"w"表示可写。

pclose关闭标准I/O,等待命令执行终止,返回shell的执行状态。

我们使用popen来重写上面的例子:

#include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <sys/wait.h> #include <stdio.h> #define MAXLINE 90 #define PAGER "${PAGER:-more}" int main(int argc, char** argv){char line[MAXLINE]; FILE *fpin, *fpout; if(argc != 2){printf("usage: a.out <pathname>\n"); exit(1); } if((fpin = fopen(argv[1],"r")) == NULL){perror("fopen"); exit(1); } if((fpout = popen(PAGER,"w")) == NULL){perror("popen"); exit(1); } while(fgets(line,MAXLINE,fpin) != NULL){if(fputs(line,fpout) == EOF){perror("fputs"); exit(1); } } if(ferror(fpin)){perror("fgets"); exit(1); } if(pclose(fpout)){perror("pclose"); exit(1); } exit(0); }

2、FIFOs:

FIFOs经常被称为具名管道。管道只能被有共同祖先的进程之间使用。FIFO可以在不相关的进程之间交换数据。

FIFO是一种文件类型,可以通过S_ISFIFO来测试它。

创建一个FIFO和创建一个文件很相似。

1)创建一个FIFO:

#include <sys/stat.h> int mkfifo(const char *path, mode_t mode;);

成功返回0,失败返回-1,并设置errno。

例子:

#define FIFO_PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S _IROTH) if(mkfifo("myfifo",FIFO_PERMS) == -1) perror("Failed to create myfifo");

删除FIFO和删除普通文件一样。

例子:父进程读取子进程写到具名管道的数据:

#include <errno.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <sys/wait.h> #define BUFSIZE 256 #define FIFO_PERM (S_IRUSR | S_IWUSR) int dofifochild(const char *fifoname, const char *idstring); int dofifoparent(const char *fifoname); int main(int argc,char *argv[]){pid_t childpid; if(argc != 2){fprintf(stderr,"Usage: %s pipename\n",argv[0]); return 1; } if(mkfifo(argv[1],FIFO_PERM) == -1){if(errno != EEXIST){fprintf(stderr,"[%ld]: failed to create name pipe %s: %s\n", (long)getpid(),argv[1],strerror(errno)); return 1; } } if((childpid = fork()) == -1){perror("Failed to fork"); return 1; } if(childpid == 0){return dofifochild(argv[1],"this was written by the child"); }else{return dofifoparent(argv[1]); } } int dofifochild(const char *fifoname, const char *idstring){char buf[BUFSIZE]; int fd; int rval; ssize_t strsize; fprintf(stderr,"[%ld]:(child) about to open FIFO %s...\n",(long)getpid(),fifoname); if(fd = open(fifoname,O_WRONLY) == -1){fprintf(stderr,"[%ld]:failed to open name pipe %s for write: %s\n", (long)getpid(),fifoname,strerror(errno)); return 1; } rval = snprintf(buf,BUFSIZE,"[%ld]:%s\n",(long)getpid(),idstring); if(rval < 0){fprintf(stderr,"[%ld]: failed to make the string:\n",(long)getpid()); return 1; } strsize = strlen(buf)+1; fprintf(stderr,"[%ld]:about to write...\n",(long)getpid()); rval = write(fd,buf,strsize); if(rval != strsize){fprintf(stderr,"[%ld]:failed to write to pipe: %s\n",(long)getpid(),strerror(errno)); return 1; } fprintf(stderr,"[%ld]:finishing...\n",(long)getpid()); return 0; } int dofifoparent(const char *fifoname) {char buf[BUFSIZE]; int fd; int rval; fprintf(stderr, "[%ld]:(parent) about to open FIFO %s...\n", (long)getpid(), fifoname); if ((fd = open(fifoname, FIFO_PERM)) == -1) {fprintf(stderr, "[%ld]:failed to open named pipe %s for read: %s\n", (long)getpid(), fifoname, strerror(errno)); return 1; } fprintf(stderr, "[%ld]:about to read...\n", (long)getpid()); rval = read(fd, buf, BUFSIZE); if (rval == -1) {fprintf(stderr, "[%ld]:failed to read from pipe: %s\n", (long)getpid(), strerror(errno)); return 1; } fprintf(stderr, "[%ld]:read %.*s\n", (long)getpid(), rval, buf); return 0; }

gcc编译之后,运行,其中fifo1就是我们给这个具名管道起的名字

m@ubuntu:~/test$ ./a.out fifo1[2721]:(parent) about to open FIFO fifo...[2722]:(child) about to open FIFO fifo...[2722]:about to write...[2722]:this was written by the child[2722]:finishing...[2721]:about to read...[2721]:read m@ubuntu:~/test$ file fifo1fifo1: fifo (named pipe)

2)使用FIFO客户端服务端通信:

我们使用简单的协议进行来进行客户端服务端通信,客户端将log信息写到命名管道中,服务端从命名管道中读取然后写入文件中:

服务端:

#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <errno.h> #define FIFOARG 1 #define BLKSIZE 1024 #define FIFO_PERMS (S_IRWXU | S_IWGRP | S_IWOTH) int main(int argc, char *argv[]){int requestfd; if(argc != 2){fprintf(stderr,"Usage: %s fifoname > logfile\n",argv[0]); return 1; } if((mkfifo(argv[FIFOARG],FIFO_PERMS) == -1) && (errno != EEXIST)){perror("Server failed to create FIFO"); return 1; } if((requestfd = open(argv[FIFOARG],O_RDWR)) == -1){perror("Server failed to open its FIFO"); return 1; } copyfile(requestfd,STDOUT_FILENO); return 1;} int copyfile(int fromfd, int tofd) {char *bp; char buf[BLKSIZE]; int bytesread, byteswritten; int totalbytes = 0; for ( ; ; ) {while (((bytesread = read(fromfd, buf, BLKSIZE)) == -1) && (errno == EINTR)) ; /* handle interruption by signal */ if (bytesread <= 0)/* real error or end-of-file on fromfd */ break; bp = buf; while (bytesread > 0) {while(((byteswritten = write(tofd, bp, bytesread)) == -1 ) && (errno == EINTR)) ; /* handle interruption by signal */ if (byteswritten <= 0) /* real error on tofd */ break; totalbytes += byteswritten; bytesread -= byteswritten; bp += byteswritten; } if (byteswritten == -1) /* real error on tofd */ break; } return totalbytes; }

客户端:

#include <errno.h> #include <fcntl.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <time.h> #include <unistd.h> #include <sys/stat.h> #define FIFOARG 1 int main(int argc, char *argv[]){time_t curtime; int len; char requestbuf[PIPE_BUF]; int requestfd; if(argc != 2){fprintf(stderr,"Usage %s fifoname", argv[0]); return 1; } if((requestfd = open(argv[FIFOARG],O_WRONLY)) == -1){perror("Client failed to open log fifo for writing"); return 1; } curtime = time(NULL); snprintf(requestbuf,PIPE_BUF,"%d:%s",(int)getpid(),ctime(&curtime)); len = strlen(requestbuf); if(write(requestfd,requestbuf,len) != len){perror("Client failed to write"); return 1; } close(requestfd); return 0; }

参考:

《Unix system programming》《Advanced Programming in the Unix Environment》

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。