进程的通信——管道和共享内存

进程间的通信有很多种
管道

匿名管道pipe

命名管道

System V IPC

System V 消息队列

System V 共享内存

System V 信号量

POSIX IPC

消息队列

共享内存

信号量

互斥量

条件变量

读写锁
这篇文章主要介绍管道和共享内存

管道

管道内核数据结构:在Linux2.6中 struct file里有 const struct cred *f_cred;cred里面有struct inode,inode里有union(联合体),union里就一个字段是struct pipe_inode_info,这个就代表管道文件

struct pipe_inode_info {
	wait_queue_head_t wait;
	unsigned int nrbufs, curbuf;
	struct page *tmp_page;
	unsigned int readers;
	unsigned int writers;
	unsigned int waiting_writers;
	unsigned int r_counter;
	unsigned int w_counter;
	struct fasync_struct *fasync_readers;
	struct fasync_struct *fasync_writers;
	struct inode *inode;
	struct pipe_buffer bufs[PIPE_BUFFERS];
};

什么是管道

管道是Linux中很重要的一种进程间通信方式,是把一个进程的输出直接连接到另一个进程的输入

管道分为:

  • 匿名管道

  • 命名管道

管道特点

  1. 都是单向的。(进程间的通信管道,单向的,传输数据的)
  2. 管道传输资源的
    现在不理解没关系,等下看图就能理解原理了

匿名管道

匿名管道接口

#include 

功能:创建一无名管道
原型

int pipe(int fd[2]);

参数

fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

匿名管道原理
管道是进程间通信的一种重要的方式,那么进程通信其实就是让进程看到同一份资源,匿名管道的方式,就是让一个进程打开文件,称为管道文件吧,用结构体struct file来描述,然后用分配两个文件描述符指向这个文件,这个代表读端,一个代表写端;然后让这个进程创建子进程,子进程会继承这个文件描述符,所以子进程也有两个文件描述符指向这个管道文件,然后根据业务需求关闭一方的读端,一方的写端(因为管道是单向的);这样两个进程就可以通过这个管道文件进行通信了
进程的通信——管道和共享内存_第1张图片
匿名管道只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创 建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
具体父进程是怎么创建管道和子进程来产生联系的,我们来看图
进程的通信——管道和共享内存_第2张图片
进程的通信——管道和共享内存_第3张图片
进程的通信——管道和共享内存_第4张图片

匿名管道接口的使用

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

// 匿名管道通信的基本流程
int main()
{
    // 1.创建管道
    int pipefd[2] = {0};
    if (pipe(pipefd) != 0)
    {
        cout << "pipe error" << endl;
        return 1;
    }
    cout << "pipefd[0]:" << pipefd[0] << endl;
    cout << "pipefd[1]:" << pipefd[1] << endl;

    // 2.创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        cout << "fork error" << endl;
        return 2;
    }
    else if (id == 0)
    {
        // child
        // 我们约定让子进程进行读取,父进程进行写
        close(pipefd[1]);
        char buffer[1024];
        while (true)
        {
            memset(buffer, 0, sizeof(buffer));
            ssize_t s = read(pipefd[0], buffer, sizeof(buffer)-1);
            if (s > 0)
            {
                buffer[s] = '\0';
                cout << "子进程收到消息:" << buffer << endl;
            }
            else if (s == 0)
            {
                // 父进程关闭写端
                cout << "父进程关闭写端" << endl;
                break;
            }
            else
            {
                // do nothing
            }
        }
        close(pipefd[0]);
        exit(0);
    }
    else
    {
        // parent
        // 关闭读端
        close(pipefd[0]);
        // 发送5次消息
        int cnt = 0;
        string msg = "I am your parent";
        while (cnt < 5)
        {
            write(pipefd[1], msg.c_str(), msg.size());
            sleep(1);
            cnt++;
        }
        close(pipefd[1]);
        cout << "父进程写完了" << endl;
    }
    
    pid_t res = waitpid(id, nullptr, 0);
    if (res > 0)
    {
        cout << "等待子进程成功" << endl;
    }
    return 0;
}

匿名管道特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创 建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  • 管道提供流式服务 ,没有格式边界,需要用户来定义区分内容的边界
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程 (管道是文件 —— 进程退出了,曾经打开的文件会怎么办? —— 打开的文件就会退出 —— 所以管道的生命周期是随进程)
  • 一般而言,内核会对管道操作进行同步与互斥
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

命名管道

刚才讲的是有血缘关系进程才能用匿名管道通信,但是我想让两个没有关系的进程通信呢?这个时候,就要用到命名管道了。
命名管道的原理跟匿名管道类型,区别就是会在磁盘创建实体文件.fifo;但是命名管道文件创建完,打开后,不会往磁盘写数据,只是内存级别;磁盘上的.fifo文件相当于一个符号,让多个进程可以找到它,然后用直接的文件描述符表指向struct_file(.fifo),这样就能构成通信了

命名管道接口函数

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

函数的使用

#include 
#include 
#include 
#include 
#include 
#include 
#define ICP_PATH "./.fifo"

using namespace std;

int main()
{
    if(mkfifo(ICP_PATH,0600) != 0)
    {
        cout << "mkfifo error" << endl;
        return 1;
    }
    cout << "服务器启动" << endl;
    int fd = open(ICP_PATH,O_RDONLY);
    if(fd < 0)
    {
        cout << "open error" << endl;
        return 2;
    }
    char msg[1024];
    while(true)
    {
        memset(msg,0,sizeof(msg));
        ssize_t s = read(fd,msg,sizeof(msg)-1);  
        if(s > 0)
        {
            cout << "客户端发来消息:" << msg << endl;
        }
        else if(s == 0)
        {
            cout << "客户端退出" << endl;
            break;
        }
        else
        {
            //do nothing
        }
    }
    close(fd);
    //服务器退出删除管道文件,不然下一次运行,会出错
    unlink(ICP_PATH);
    return 0;
}
#include 
#include 
#include 
#include 
#include 
#include 
#define ICP_PATH "./.fifo"



using namespace std;

int main()
{
    int fd = open(ICP_PATH,O_WRONLY);
    if(fd < 0)
    {
        cout << "open error" << endl;
        return 2;
    }
    char line[1024];
    while(true)
    {
        cout << "请输入你的消息: ";
        fflush(stdout);
      if(fgets(line,sizeof(line),stdin) != nullptr)
       {
            //line[strlen(line) - 1] = '\0';
            //这里strlen(line) 为什么不加1 ,因为这是管道文件,'\0'是c语言的东西,不需要这个
            write(fd,line,strlen(line));
       }
       else
       {
            break;
       }
    }

    close(fd);
    return 0;
}

共享内存

什么是共享内存

共享内存也是进程间通信的一种方式,它让多个进程的虚拟地址空间映射到同一个块内存,实现通信。
共享内存是最快的IPC形式,因为一旦这样的内存映射到共享它的进程的地址空间,这样进程间数据传递不再说是进程不再通过执行进入内核的系统调用来传递彼此的数据
进程的通信——管道和共享内存_第5张图片
内核中共享内存的数据结构

struct shmid_ds {
 struct ipc_perm shm_perm; /* operation perms */
 int shm_segsz; /* size of segment (bytes) */
 __kernel_time_t shm_atime; /* last attach time */
 __kernel_time_t shm_dtime; /* last detach time */
 __kernel_time_t shm_ctime; /* last change time */
 __kernel_ipc_pid_t shm_cpid; /* pid of creator */
 __kernel_ipc_pid_t shm_lpid; /* pid of last operator */
 unsigned short shm_nattch; /* no. of current attaches */
 unsigned short shm_unused; /* compatibility */
 void *shm_unused2; /* ditto - used by DIPC */
 void *shm_unused3; /* unused */
};



共享内存函数

shmget函数

功能:用来创建共享内存
原型

 int shmget(key_t key, size_t size, int shmflg);

参数

 key:这个共享内存段名字

 size:共享内存大小

 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat函数

功能:将共享内存段连接到进程地址空间
原型

 void *shmat(int shmid, const void *shmaddr, int shmflg);

参数

 shmid: 共享内存标识

 shmaddr:指定连接的地址

 shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

shmdt

功能:将共享内存段与当前进程脱离
原型

 int shmdt(const void *shmaddr);

参数

 shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存
原型

 int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数

 shmid:由shmget返回的共享内存标识码

 cmd:将要采取的动作(有三个可取值)

 buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

shmget

//函数
int shmget(key_t key,size_t size,int shmflg);
 key:这个共享内存段名字

 size:共享内存大小

 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

IPC_CREAT:创建共享内存,如果已经存在,就获取之;不存在,就创建之
IPC_EXCL:不单独使用,必须和IPC_CREAT 配合,如果不存在指定的共享内存,创建之,如果存在,出错返回(这样可以保证,如果当使用IPC_EXCL和IPC_CREXAT标记位时,如果shmget函数调用成功,一定是一个全新的share memory)

共享内存的基本编写

#include 
#include 
#include 
using namespace std;

#define PATH_NAME "/home/XHBIN/_linux_code/cpp/systemV"

#define PROJ_ID 0X14

#define MEM_SIZE 1024

const int flags = IPC_CREAT | IPC_EXCL;

key_t CreateKey()
{
    key_t key = ftok(PATH_NAME,PROJ_ID);//形成key值
    if(key < 0)
    {
        cerr << "ftok: " <<  strerror(errno) << endl;
        exit(1);
    }
}

int main()
{
    key_t key = CreateKey();
    cout << "key: " << key << endl;
    //创建共享内存
    int shmid = shmget(key,MEM_SIZE,flags | 0666);
    if(shmid < 0)
    {
        cout << "shmget: " << strerror(errno) << endl;
        return 2;
    }
    cout << "create shm success shmid: " << shmid << endl;
    //将共享内存和自己的进程产生关联attach
    char *str = (char*) shmat(shmid,nullptr,0);
    int cnt = 0;
    while(cnt <= 26)
    {
        str[cnt] = 'A' + cnt;
        ++cnt;
        str[cnt] = '\0';
        cout << str << endl;
        sleep(1);
    }
    cout  << str << endl;
    //2.去关联
    shmdt(str);
    cout << "detach shm: " << shmid << "success" << endl;
    //删除
    shmctl(shmid,IPC_RMID,nullptr);
    cout << "delete shm: " << shmid << "success" << endl;
    return 0;

}

但是共享内存不推荐使用,因为它没有提供互斥同步机制。

你可能感兴趣的:(linux)