Linux开发(十一):多进程通信与同步---管道通信

管道是Linux环境中历史最悠久的进程间通信方式,本质上,管道就是一个操作方式为文件的内存缓冲区。Linux上的管道分两种类型,分别为匿名管道(无名管道)和有名管道(命名管道)。

目录

一、匿名管道

1、创建匿名管道

2、注意事项

3、代码示例

 二、有名管道

1、管道创建 

(1) 命令行创建

(2) 函数创建

2、删除命名管道

3、代码示例


一、匿名管道

匿名管道是一种特殊类型的文件,完全由操作系统管理和维护,因为其存储位置只有亲缘关系的进程知道,所以只能用于亲缘关系的进程之间通信,且其内核资源会在两个通信进程退出后才自动释放。

使用管道进行父子进程间通信的步骤:

  1. 创建管道:父进程调用pipe()函数创建一个管道,此时,管道的读端和写端都在一个进程之中,没有多大用。
  2. 父进程通过fork()函数创建一子进程,此时子进程会继承父进程所创建的管道
  3. 确定管道的传输方向:在父、子进程中根据需要的传输方向关闭无关的读端或写端文件描述符
  4. 通信:在写进程中调用write()函数,在读进程中调用read()函数
  5. 关闭管道:调用close()关闭管道相关的文件描述符

1、创建匿名管道

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

函数参数

pipefd[2]:创建出两个文件描述符,保存在pipefd中,其中pipefd[0]是读方式打开,作为管道的读描述符;pipefd[1]是写方式打开,作为管道的写描述符。

返回值

成功返回0,失败返回-1

2、注意事项

  1. 以阻塞方式读管道时:
    1. 有读进程,无写进程:
      1. 管道内无数据时,立即返回
      2. 管道内数据不足,读出所有数据
      3. 管道内数据充足,读出期望数据
    2. 有读进程,有写进程:
      1. 管道内无数据时,读进程阻塞
      2. 管道内数据不足,读出所有数据
      3. 管道内数据充足,读出期望数据
  2. 以阻塞方式写管道时:
    1. 有写进程,无读进程:写进程将收到SIGPIPE信号,wirte函数返回-1
    2. 有写进程,有读进程,且管道内有写空间:写入成功

3、代码示例

#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define MAX_DATA_LEN   256
#define DELAY_TIME 1
 
int main()
{
    pid_t pid;
    int pipe_fd[2];
    char buf[MAX_DATA_LEN];
    const char data[] = "Pipe Test Program";
    int real_read, real_write;
    memset((void*)buf, 0, sizeof(buf));
    /* 创建管道 */
    if (pipe(pipe_fd) < 0)
    {
        printf("pipe create error\n");
        exit(1);
    }
    if ((pid = fork()) == 0)
    {
        /* 子进程关闭写描述符,并通过使子进程暂停3s等待父进程已关闭相应的读描述符 */
        close(pipe_fd[1]);
        sleep(DELAY_TIME * 3);        
        /* 子进程读取管道内容 */
        if ((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0)
	{
		printf("%d bytes read from the pipe is '%s'\n", real_read, buf);
	}
        	
       /* 关闭子进程读描述符 */
        close(pipe_fd[0]);
        exit(0);
     }
    else if (pid > 0)
    {
        /* 父进程关闭读描述符,并通过使父进程暂停1s等待子进程已关闭相应的写描述符 */
      close(pipe_fd[0]);
      sleep(DELAY_TIME);
 
      if((real_write = write(pipe_fd[1], data, strlen(data))) !=  -1)
	{
		printf("Parent wrote %d bytes : '%s'\n", real_write, data);
	}
		
      close(pipe_fd[1]);     /*关闭父进程写描述符*/
      waitpid(pid, NULL, 0); /*收集子进程退出信息*/
      exit(0);
     }   
    return 0;
 }

运行结果为:

 二、有名管道

有名管道可以在系统中任意两个进程之间进行通信,且创建的管道文件存储在硬盘上,不会随着进程结束而消失。和匿名管道一样,有名管道也只能用于数据的单向传输。但有以下几点需要注意:

  1. 当进程以写或读的方式打开管道文件,必须有另一个进程以相对应的读或写方式也打开该文件,否则该进程将阻塞在open()位置。
  2. 若两个进程都已打开,但中途某进程退出,则:
    1. 读进程退出,返回SIGPIPE信号
    2. 写进程退出,读进程将不再阻塞,直接返回 0

1、管道创建 

有名管道的创建有两种方式,一种是通过命令行创建,另一种是使用函数创建。

(1) 命令行创建

可以通过命令行命令 mkfifo 或 mknod 创建命名管道:

$ mkfifo /tmp/testp
$ mknod /tmp/testp p

然后可以通过 ls 命令查看命名管道的文件属性:

输出中的第一个字符为 p,表示这个文件的类型为管道。最后的 | 符号是有 ls 命令的 -F 选项添加的,也表示这个一个管道。

(2) 函数创建

在程序中创建命名管道,可以使用 mkfifo 函数,其签名如下:

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

 函数参数

pathname:用于存放命名管道的文件路径,文件必须不存在

mode:表示指定所创建文件的权限

返回值

成功返回0,失败返回-1

mknod ()函数也可以创建命名管道,它的前两个参数和 mkfifo 函数相同,第三个参数为0。

#include <sys/types.h>
#include <sys/stat.h>

int mknod(char *pathname, mode_t mode, dev_t dev);

 函数参数

pathname:用于存放命名管道的文件路径

mode:表示指定所创建文件的权限

dev:在创建命名管道时,此参数必为0

返回值

成功返回0,失败返回-1

2、删除命名管道

刚刚说了,创建的管道文件存储在硬盘上,所以使用完后,要像普通文件一下将其删除:

 rm /tmp/testp

3、代码示例

生产者代码,pipedemo.c:

#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/testp"
#define BUFFER_SIZE 4096
#define TEN_MEG (1024 * 1024 * 10)

int main(void)
{
    int pipe_fd;
    int res;
    int open_mode = O_WRONLY;
    int bytes_sent = 0;
    char buffer[BUFFER_SIZE + 1];

    if(access(FIFO_NAME, F_OK) == -1)
    {
        res = mkfifo(FIFO_NAME, 0777);
        if(res != 0)
        {
            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }

    printf("Process %d opening FIFO O_WRONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d opened fd %d\n", getpid(), pipe_fd);

    if(pipe_fd != -1)
    {
        while(bytes_sent < TEN_MEG)
        {
            res = write(pipe_fd, buffer, BUFFER_SIZE);
            if(res == -1)
            {
                fprintf(stderr, "Write error on pipe\n");
                exit(EXIT_FAILURE);
            }
            bytes_sent += res;
        }
        (void)close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }
    printf("Process %d finished\n", getpid());
    exit(EXIT_SUCCESS);
}

消费者代码,pipedemo2.c: 

#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "/tmp/testp"
#define BUFFER_SIZE 4096

int main(void)
{
    int pipe_fd;
    int res;
    int open_mode = O_RDONLY;
    int bytes_read = 0;
    char buffer[BUFFER_SIZE + 1];

    memset(buffer, '\0', sizeof(buffer));

    printf("Process %d opening FIFO O_RDONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d opened fd %d\n", getpid(), pipe_fd);

    if(pipe_fd != -1)
    {
        do
        {
            res = read(pipe_fd, buffer, BUFFER_SIZE);
            bytes_read += res;
        } while (res > 0);
        (void)close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }
    printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
    exit(EXIT_SUCCESS);
}

把上面的代码保存到文件 namedpipedemo2.c 中。并分别编译这两个程序:

$ gcc -Wall pipedemo.c -o pipe1
$ gcc -Wall pipedemo2.c -o pipe2

先在一个终端中执行生产者:

然后在另一个终端中执行消费者:

最后结果是二者完成数据传输后都返回了: 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,实验六主要涉及到Linux下多进程编程和进程间通信的知识。常见的进程间通信方式有管道、共享内存、消息队列、信号量等,其中最常用的是管道和共享内存。 1. 管道 管道是一种半双工的通信方式,可以用于在父进程和子进程之间传递数据。在Linux中,管道又分为匿名管道命名管道两种。 匿名管道只能用于有亲缘关系的进程间通信,也就是说,只能用于父子进程之间的通信。在创建管道时,需要调用pipe()函数,它会返回两个文件描述符,一个是读端,一个是写端。父进程可以通过写端向管道中写入数据,子进程则可以通过读端从管道中读取数据。 命名管道可以用于任意两个进程间的通信,不需要有亲缘关系。在创建命名管道时,需要调用mkfifo()函数,它会返回一个文件描述符。父子进程都可以通过这个文件描述符来进行读写。 2. 共享内存 共享内存是一种高效的进程间通信方式,可以用于多个进程之间共享同一块物理内存。在Linux中,共享内存的使用需要以下几个步骤: (1)创建共享内存区域,使用shmget()函数,它会返回一个共享内存标识符。 (2)将共享内存区域映射到进程的虚拟地址空间中,使用shmat()函数。 (3)进程间通过读写共享内存区域来进行通信。 (4)使用shmdt()函数将共享内存区域从进程的虚拟地址空间中解除映射。 (5)使用shmctl()函数删除共享内存区域。 需要注意的是,共享内存的使用需要保证多个进程之间对共享内存的读写操作的同步性,否则会出现数据不一致的问题。 除了上述两种方式,还有消息队列和信号量等方式可以进行进程间通信。不同的通信方式有各自的优缺点,需要根据实际情况选择合适的方式。 希望这些能对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chiang木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值