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

发布于:2024-04-19 ⋅ 阅读:(34) ⋅ 点赞:(0)

一、进程间通信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。假定父进程实现ls,子进程实现wc

要求:(要用到 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到管道写端 
                execl("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  
                execl("ls","ls",NULL);
                sys_err("execlp ls error");     
        }
        else if(i == 0)//弟弟  
        {
                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

管道的优劣:

优点:简单,相比信号,套接字实现进程间通信,简单很多。

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

            2. 只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决。

三、FIFO

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据。

无血缘关系进程间通信:

读端,open fifo O_RDONLY

写端,open fifo O_WRONLY

创建方式:

1. 命令:mkfifo 管道名
2. 库函数:int mkfifo(const char *pathname,  mode_t mode);  成功:0; 失败:-1

利用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,操作起来就像文件一样的:

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;
}