Linux系统编程---进程间通信IPC(一)

一、进程间通信IPC(InterProcess Communication)

进程间通信的常用方式,特征:

1. 管道:简单

2. 信号:开销小

3. 共享存储映射(mmap):非血缘关系进程间

4. socket(本地套接字):最稳定

二、管道

真文件:普通文件、目录文件、软链接--->需要占用磁盘空间

伪文件:字符设备文件、块设备文件、管道文件、套接字--->不占用磁盘空间,只占用内存(缓冲区)

实现原理: 内核借助环形队列机制,使用内核缓冲区实现。

特质:

1. 伪文件
2. 管道中的数据只能一次读取。
3. 数据在管道中,只能单向流动。

局限性:

1. 自己写,不能自己读。
2. 数据不可以反复读。
3. 半双工通信。
4. 血缘关系进程间可用

管道的基本用法:

int pipe(int fd[2]);

创建,并打开管道。

参数:

        fd[0]: 读端。

        fd[1]: 写端。

返回值:

        成功: 0

        失败: -1 errno

管道通信原理:

1.父进程调用了pipe(),相当于创建了一个管道并打开了读端和写端,pipefd[0]是读端,pipefd[1]是写端

2.父进程fork出一个子进程,此时子进程也掌握着父进程管道的读端和写端

3.父进程写入数据(读端关闭),子进程读取数据(写端关闭)

pipe demo:父进程往管道里写,子进程从管道读,然后打印读取的内容到屏幕上

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void sys_err(const char *str)
{
        perror(str);
        exit(1);
}

int main(int argc,char *argv[])
{
        int ret;
        int fd[2];
        pid_t pid;

        char *str = "hello pipe\n";
        char buf[1024];

        ret = pipe(fd);
        if (ret == -1){
        sys_err("pipe error");}

        pid = fork();
        if (pid > 0){
                close(fd[0]);   //父进程关闭读端
                write(fd[1],str,strlen(str));//写入数据
                sleep(1);    //让终端提示和输出不混杂在一块
                close(fd[1]);//写后关闭写端
        }else if(pid == 0){
                close(fd[1]);   //子进程关闭写端
                ret = read(fd[0],buf,sizeof(buf));//读取数据到buf中
                write(STDOUT_FILENO,buf,ret);//将buf的数据打印到屏幕上,ret是字节数
                close(fd[0]);//关闭读端
        }

        return 0;
}

要是不想让终端提示和输出混杂在一起,就在父进程写入内容之后sleep一秒钟

管道的读写行为:

读管道:

1. 管道有数据,read返回实际读到的字节数。
2. 管道无数据:
        1)无写端,read返回0 (类似读到文件尾)
        2)有写端,read阻塞等待。

写管道:

1. 无读端(读端全关闭), 进程异常终止(SIGPIPE导致的)
2. 有读端:
        1) 管道已满, 阻塞等待
        2) 管道未满, 返回写出的字节个数。

练习:使用管道实现父子进程间通信,完成:ls | wc –l。假定父进程实现wc,子进程实现ls

要求:(要用到 pipe dup2 exec)

1. 假定父进程实现wc,子进程实现ls

2. ls命令正常会将结果集写到stdout,但现在会写入管道写端

3. wc -l命令正常应该从stdin读取数据,但此时会从管道的读端读。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void sys_err(const char *str)
{
        perror(str); 
        exit(1);
}

int main(int argc,char *argv[])
{
        int fd[2];
        int ret;
        pid_t pid;

        ret =  pipe(fd);    //父进程先创建一个管道,持有管道的读端和写端
        if (ret == -1){
                sys_err("pipe error");
        }

        pid = fork();    //子进程同样持有管道的读和写端 
        if (pid == -1){
                sys_err("fork error");
        }else if (pid > 0){      // 父进程读,关闭写端
                close(fd[1]);
                dup2(fd[0],STDIN_FILENO);     //重定向 stdin到管道的 读端 
                execlp("wc","wc","-l",NULL);    // 执行 wc -l 程序  
                sys_err("execlp wc error");         
        }else if(pid == 0){
                close(fd[0]);
                dup2(fd[1],STDOUT_FILENO);    //重定向stdout到管道写端 
                execlp("ls","ls",NULL);    //子进程执行ls命令
                sys_err("execlp ls error");   
        }
        
        return 0;
}

兄弟间进程通信:

使用管道实现兄弟进程间通信。 兄:ls  弟: wc -l  父:等待回收子进程。 

要求,使用“循环创建N个子进程”模型创建兄弟进程,使用循环因子i标示。注意管道读写行为

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void sys_err(const char *str)
{
        perror(str); 
        exit(1);
}

int main(int argc,char *argv[])
{
        int fd[2];
        int ret,i;
        pid_t pid;

        ret =  pipe(fd);
        if (ret == -1){
            
                sys_err("pipe error");
        }

        for (i = 0;i < 2;i++) // 表达式2 出口,仅限父进程使用  
        {
                pid = fork();
                if (pid == -1 )
                {
                        sys_err("fork error");
                }
                if (pid == 0) // 子进程,出口
                        break;
        }

        if (i == 2)  // 父进程 . 不参与管道使用.   
        {
                close(fd[0]);// 关闭管道的 读端/写端
                close(fd[1]);
                wait(NULL); // 回收子进程  
                wait(NULL);
        }

        else if (i == 0)//兄
        {
                close(fd[0]);
                dup2(fd[1],STDOUT_FILENO);// 重定向stdout  
                execlp("ls","ls",NULL);
                sys_err("execlp ls error");     
        }
        else if(i == 1)//弟弟  
        {
                close(fd[1]);
                dup2(fd[0],STDIN_FILENO);// 重定向 stdin 
                execlp("wc","wc","-l",NULL);
                sys_err("execlp wc error");     
        }
        
        return 0;
}

注意:父进程不使用管道,所以一定要关闭父进程的管道,保证数据单向流动

允许一个pipe有一个写端多个读端        也允许一个pipe有多个写端一个读端 

管道缓冲区大小:

可以使用ulimit –a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小,通常为:

pipe size            (512 bytes, -p) 8

管道pipe的优缺点: 

优点:

        简单,相比信号,套接字实现进程通信,简单很多

缺点:

        1. 只能单向通信,双向通信需建立两个管道

        2. 只能用于有血缘关系的进程间通信。该问题后来使用fifo命名管道解决。

三、FIFO

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间,但通过FIFO,没有血缘关系的进程也能交换数据fifo操作起来像文件

fifo原理图: 

创建方式:

  1. 命令:mkfifo 管道名
  2. 库函数:int mkfifo(const char *pathname,mode_t mode);成功0,失败-1
  3. 参数解析:参数1是管道名,参数二是权限0664;

无血缘关系进程间通信:

读端,open fifo O_RDONLY

写端,open fifo O_WRONLY

利用mkfifo函数创建一个FIFO: 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<pthread.h>

int sys_err(const char *str)
{
        perror(str);
        exit(1);
}

int main(int argc,char *argv[])
{
        int ret = mkfifo("mytestfifo",0664);
        if(ret == -1)
        {
                sys_err("mkfifo error");
        }

        return 0;
}

fifo实现非血缘关系进程间通信:

非血缘关系进程,一个写fifo,一个读fifo,操作起来就像文件一样的:

writefifo.c

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

int main(int argc, char *argv[])
{
    int fd,i;
    char buf[4096];

    if(argc < 2)
    {
        printf("Enter like this: ./a.out fifoname\n");
        return -1;
    }

    fd = open(argv[1],O_WRONLY);//打开管道文件
    if(fd < 0)
    {
        perror("open");
        exit(-1);
    }

    i = 0;
    while(1)
    {
        sprintf(buf,"hello %d\n",i++);

        write(fd,buf,strlen(buf));//向管道写入数据
        sleep(1);
    }
    close(fd);

    return 0;
}

readfifo.c

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

int main(int argc,char *argv[])
{
    int fd,i;
    char buf[4096];

    if(argc < 2)
    {
        printf("./a.out fifonema\n");
        return -1;
    }
    //int fd = mkfifo("tetsfifo",0664);
    //open(fd,...);

    fd = open(argv[1],O_RDONLY);//打开管道文件
    if(fd<0)
    {
        perror("open");
        exit(-1);
       }

    while(1)
    {
        int len = read(fd,buf,sizeof(buf));//从管道的读端读取数据
        write(STDOUT_FILENO,buf,len);
        sleep(1);//多个读端时应增加睡眠秒数,放大效果
    }

    close(fd);

    return 0;
}

四、文件用于进程间通信(有没有血缘关系都行)

打开的文件是内核中的一块缓冲区。多个无血缘关系的进程,可以同时访问该文件。

  • 只是有血缘关系的进程对于同一个文件,使用的同一个文件描述符,
  • 没有血缘关系的进程,对同一个文件使用的文件描述符可能不同。
  • 这些都不是问题,打开的是同一个文件就行。

文件实现进程间通信原理图:

  • 36
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值