1. 管道
当数据从一个进程连接流入到另外一个进程的时候,两个进程之间连接的就是一个管道。通常把一个进程的输出通过管道连接到另外一个进程的输入。
比较简单的管道的用法在刚刚接触linux的时候是会经常使用到的,就是通过管道,来查询对应的输出。
管道的本质也是一个文件,但是它并不占用存储空间,占用的是内存空间,Linux上的管道就是一个内存缓冲区。
1.1 无名管道
无名管道在shell里面最常用的方式就是|
。无名管道只能在父子进程中使用,父进程产生子进程之前必须打开一个管道文件,然后fork产生一个一模一样的子进程,子进程中就包含有父进程当中的这个同样的管道。
1.1.1无名管道的特性
- 没有名字,无法使用open()函数,但是可以使用close()函数进行关闭
- 只提供半双工的通信方式,父子进程都能访问这个文件,只是不能同时进行读写。
- 只能用于1对1的通信
- 使用pipe函数来创建一个无名管道
1.1.2 无名管道的使用
pipe()函数用于创建一个匿名管道,可以用于进程间通信的单向数据传输。
int pipe(int pipefd[2]);
想要进行父子进程之间数据的交互,需要执行的操作有。
- 父进程调用pipe()函数创建匿名管道,得到两个文件描述符pipefd[0],pipefd[1],执行匿名管道的读取段和写入端。
- 父进程调用fork函数启动子进程,父子进程同时拥有前面管道的两个文件描述符。
- 父进程向子进程写入数据,父进程需要关闭读取端,子进程关闭写入端,然后夫进程将数据写入到管道中
- 子进程向夫进程传递数据,父进程需要关闭写入端,子进程关闭读取端。
- 不需要管道的时候,在进程中关闭管道即可。
1.2 匿名管道举例
还是例子来得实在:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <wait.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
int main(void)
{
pid_t pid;
int pipe_fd[2];/*管道的文件描述符*/
int read_bytes, write_bytes;
char buf[256];
const char data[] = "This is a test demo!!!";
printf("This is fork demo\n");
/*创建管道*/
if(pipe(pipe_fd) < 0){
printf("pipe create failed!!!\r\n");
exit(-1);
}
/*调用fork函数创建子进程*/
pid = fork();
if(pid == -1)
{
printf("fork error!!!\r\n");
}
/*子进程*/
else if(pid == 0)
{
/*子进程关闭写描述符*/
close(pipe_fd[1]);
sleep(3);
/*子进程来获取管道中的内容*/
if((read_bytes = read(pipe_fd[0],buf,256)) > 0)
{
printf("%d bytes from pipe is '%s'\n",read_bytes, buf);
}
/*子进程中关闭读取文件描述符*/
close(pipe_fd[0]);
exit(0);
}
/*父进程*/
else if(pid > 0 ){
/*父进程关闭读描述符*/
close(pipe_fd[0]);
sleep(1);
if((write_bytes = write(pipe_fd[1], data, strlen(data))) != -1){
printf("Parent write %d bytes : '%s'\n", write_bytes, data);
}
/*关闭父进程写描述符*/
close(pipe_fd[1]);
/*收集子进程退出状态*/
waitpid(pid, NULL, 0);
exit(0);
}
}
上面的代码实现的功能就是父进程像无名管道中写入数据,子进程从无名管道中读出数据。在父进程写入数据的时候,需要先关闭父进程对无名管道的读取,在子进程进行读取数据的时候,需要关闭子进程的写入使能。
1.3 有名管道
前面的无名管道,只能在父子进程之间进行数据的传输。如果想在两个不同的进程之间进行数据的交互,可以使用FIFO来完成这个工作。作为一个FPGAer,发现FIFO真的到处都是啊,真是个好东西。
在Linux当中可以使用mkfifo来创建一个有名管道。
int mkfifo(const char * pathname,mode_t mode);
需要传递的参数有:
- 一个具体的路径,mkfifo会根据这个路径来创建一个fifo文件。
- 模式: 像文件的权限:
- O_RDONLY:读管道
- O_WRONLY: 写管道
- O_RDWR: 读写管道
- O_NONBLOCK:非阻塞
- O_CREAT:该文件不存在就创建一个文件
返回值为0表示成功,其他表示错误。
1.3.1 FIFO读写过程当中的操作
对FIFO进行写操作
- 管道是阻塞类型的,且当前FIFO没有数据,那么这个进程将会一直等待,直到FIFO中有数据。
- 如该管道是非阻塞的,不论FIFO内是否有数据,这个读取的进程都会立即执行读取操作。如果FIFO中 没有数据,该函数将立刻返回0。
对FIFO进行写操作
- 管道是阻塞类型的,当FIFO被写满之后想要再往里面写入数据,需要等到FIFO中有足够的空间。
- 若该管道是非阻塞类型并且当前没有足够空间来进行全部数据的写入,则写操作进行部分或者调用失败。
1.4 有名管道的使用
还是用一个例子来记录下这个用法:
有名管道的具体的使用方法是:
第一个进程
- 进程使用mkfifo创建一个管道
- 使用open函数,打开这个管道。
- 调用read write函数来对管道进行操作
- 调用close来关闭这个管道
第二个进程
- 使用open函数打开这个管道
- 进行read,write操作
- 使用close函数,来关闭这个管道
读取FIFO进程
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#define MY_FIFO "/tmp/my_fifo"
int main(int argc, char * argv[])
{
char buf[512];
int fd;
int bytes_read;
if(access(MY_FIFO, F_OK) == -1){
/*create a fifo*/
if((mkfifo(MY_FIFO, 0666)<0) && (errno != EEXIST)){
printf("create fifo failed\n");
exit(-1);
}
}
/*open fifo, read only*/
fd = open(MY_FIFO, O_RDONLY);
if(fd == -1){
printf("open fifo failed\n");
exit(-1);
}
/*read data from fifo*/
while (1)
{
memset(buf, 0, 512);
if ((bytes_read = read(fd, buf, 512)) > 0)
{
printf("read %s from fifo\r\n", buf);
}
}
/*close the fifo*/
close(fd);
exit(0);
}
写入FIFO进程
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#define MY_FIFO "/tmp/my_fifo"
int main(int argc, char * argv[]){
char buf[512];
int fd;
int bytes_write;
fd = open(MY_FIFO, O_WRONLY);
if (fd == -1)
{
printf("open file failed\r\n");
}
sscanf(argv[1],"%s",buf);
if ((bytes_write = write(fd, buf, 512)) > 0)
{
printf("write %s to fifo\r\n",buf);
}
close(fd);
exit(0);
}
从一个进程发送,另外一个进程接收。