进程通信
进程是系统分配资源的最小单位, 不同进程之间是相互隔离的, Linux常用于进程通信的几种方式有
- 匿名管道及有名管道 : 匿名管道用于具有亲缘关系的进程通信, 有名管道除此之外还可用于一般进程之间.
- 信号 : 软件层对中断机制的一种模拟.
- 消息队列
- 共享内存 : 不同进程享同一块内存区域, 不同进程可以实时查看对方对共享内存的更新. 需要借助同步机制, 如互斥锁, 信号量.
- 信号量 : 主要用于不同进程以及同一进程不同线程的同步和互斥.
- 套接字 : 广泛用于网络间进程通信.
无名管道
管道是是基于文件描述符的通信方式, 无名管道只能用于具有亲缘关系之间的进程通信. 建立一个管道时 它会创建两个文件描述符, fd[0] 和 fd[1] , 其中 fd[0] 用于读取数据, fd[1] 用于写入数据, 请看下图 :
如果父进程需要向子进程发送数据通信, 那么可以在父进程上创建一个管道, 关闭父进程的fd[0]和子进程的fd[1], 子进程向父进程发送数据就与之相反.
下面演示了父进程向管道写入数据, 子进程从中读取. sleep()函数确保父进程已经关闭了相应的文件描述符.
#include<stdio.h> #include<sys/types.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<unistd.h> int main(void){ pid_t pid; int pipe_fd[2]; char buf[1024]; const char data[] = "管道测试"; int real_read, real_write; memset(buf, 0, sizeof(buf)); /* 创建管道 */ if(pipe(pipe_fd) < 0){ perror("pipe"); exit(1); } if((pid = fork()) < 0){ perror("fork"); exit(1); }else if(pid == 0){ /* 子进程关闭写描述符, 等待1秒让父进程关闭读描述符*/ close(pipe_fd[1]); sleep(2); if((real_read = read(pipe_fd[0], buf, 1024)) > 0){ printf("读取管道内容 : %s\n", buf); } close(pipe_fd[0]); exit(0); }else{ /* 父进程关闭读描述符, 等待1秒让子进程关闭写描述符 */ close(pipe_fd[0]); sleep(1); if((real_write = write(pipe_fd[1], data, strlen(data))) > 0){ printf("写入管道内容 : %s\n", data); } close(pipe_fd[1]); /* 收集子进程退出信息 */ waitpid(pid, NULL, 0); exit(0); } }
管道读写需要注意几点
- 向管道写入数据时, 管道读端必须存在, 否则写进程将收到内核传来SIGPIPE信号.
- 向管道写入数据, 不保证原子性, 如果读进程不读取管道缓冲区中的数据, 那么写进程会一直阻塞.
- 父子进程运行是, 并不能保证先后顺序, 这里简单用sleep()解决.
标准流管道
相当系统调用, 用于创建连接到另一个进程之间的管道, 这里的进程是指可进行一定操作的可执行文件, 标准流管道把一系列创建过程合并到popen() 中了.
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<fcntl.h> /* 标准管道流操作 */ int main(void){ FILE *fp; char *cmd = "ps -ef"; char buf[1024]; /* r 文件指针连接到command的标准输出*/ if((fp = popen(cmd, "r")) == NULL){ printf("popen error"); exit(1); } while((fgets(buf, 1024, fp)) != NULL){ printf("%s\n", buf); } pclose(fp); exit(0); }
有名管道
Linux中专门设立了一个专门的特殊文件系统--管道文件,以FIFO的文件形式存在于文件系统中,这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO不相关的进程也能交换数据.但在磁盘上只是一个节点,而文件的数据则只存在于内存缓冲页面中,与普通管道一样.
管道文件的读写可能有阻塞问题
对于读进程
- 如果管道是阻塞打开, 且当前FIFO没有数据, 则读进程一直阻塞.
- 如果非阻塞打开, 立即执行读操作.
对于写进程
- 如果管道是阻塞打开, 则一直阻塞到可以写入.
- 如果非阻塞打开而不能全部写入, 则写入部分或者写入失败.
下面包含两个程序, 一个用于读取管道, 并在该程序中创建管道, 另一个用于写管道. 首先要调用读程序, 创建一个管道.
读程序
#include<stdio.h> #include<string.h> #include<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<fcntl.h> #include<stdlib.h> #include<limits.h> /* 由读管道创建 */ #define MYFIFO "/tmp/myfifo" int main(int argc, char *argv[]){ int fd; char buf[PIPE_BUF]; int nread; /* 如果管道不存在则创建 */ if(access(MYFIFO, F_OK) == -1){ /* 0是管道文件, 666是权限 */ if((mkfifo(MYFIFO, 0666) < 0) && (errno != EEXIST)){ perror("create fifo"); exit(1); } } /* 只读阻塞方式打开管道 */ fd = open(MYFIFO, O_RDONLY); if(fd == -1){ perror("open"); exit(1); } /* 读取字符串 */ while(1){ memset(buf, 0, sizeof(buf)); if((nread = read(fd, buf, PIPE_BUF)) > 0){ printf("read %s\n", buf); } } close(fd); exit(0); }
写程序
#include<stdio.h> #include<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<fcntl.h> #include<stdlib.h> #include<limits.h> /* 由读管道创建 */ #define MYFIFO "/tmp/myfifo" int main(int argc, char *argv[]){ int fd; char buf[PIPE_BUF]; int nwrite; if(argc <= 1){ printf("Usage: ./fifo_write <strring>"); exit(1); } sscanf(argv[1], "%s", buf); /* 只写阻塞方式打开管道 */ fd = open(MYFIFO, O_WRONLY); if(fd == -1){ perror("open"); exit(1); } /* 写入字符串 */ if((nwrite = write(fd, buf, PIPE_BUF)) > 0){ printf("write : %s\n", buf); } close(fd); exit(0); }
编译运行
读程序 $ gcc fifo_read.c -o fifo_read $ ./fifo_read read 写入1 read 写入2 read 写入3 写程序 $ gcc fifo_write.c -o fifo_write $ ./fifo_write $ ./fifo_write 写入1 write : 写入1 $ ./fifo_write 写入2 write : 写入2 $ ./fifo_write 写入3 write : 写入3