Linux下C++进程间通信机制

linux下进程间通信的几种主要手段简单简介:

  1. 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
  2. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数 sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal 函数);
  3. 进程信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  4. 消息队列(Message):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  5. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
  6. 套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

一.管道通信方式:

管道通信具有单向,无结构,先进先出的字节流特点;管道有2个端点,一个端点写入数据,一个端点读取数据,当数据从管道中读出时,这些数据将被移走。当进程从空管道中读取数据或向已满的管道写入数据时,进程将被挂起,直到有进程向管道中写入数据或从管道中读取数据,此时,进程将由等待状态变为就绪状态。对管道的操作和对文件的操作差不多。根据管道提供的应用接口的不同,管道可分为命名管道和无名管道。

1、无名管道:

#include <unistd.h>

int pipe(int fd[2])  ;      

 创建一个无名管道fd包含2个文件描述符的数组,fd[0]用于读,fd[1]用于写若成功返回0,否则反回-1

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

//一般某个进程用于读,另一个进程用于写,用于读的进程需要close(fd[1]),用于写的进程需要close(fd[0]);
int main(void)
{
    pid_t pid;
    int fd[2],i,n;
    char chr;
    pipe(fd);
    pid=fork();
    if(pid==0)          //子进程
    {
        close(fd[1]);
        for(i=0;i<10;i++)
        {
            read(fd[0],&chr,1);
            printf("%c\n",chr);
        }
        close(fd[0]);
        exit(0);
    }
    close(fd[0]);           //父进程
    for(i=0;i<10;i++)
    {
        chr='a'+i;
        write(fd[1],&chr,1);
        sleep(1);
    }
    close(fd[1]);
    return 0;
}

2、命名管道:

#include <sys/types.h>

#include <sys/stat.h>

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

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    mkfifo("fifo",0660);   //创建一个命令管道,属主和用户组具有读写权限
    pid_t pid;
    pid=fork();
    if(pid==0)
    {
        char buf[256];
        int fd=open("fifo",O_RDONLY);   //子进程读管道中的数据
        read(fd,buf,10);
        buf[10]=0;
        printf("%s",buf);
        close(fd);
        exit(0);
    }
    int fd=open("fifo",O_WRONLY);    //父进程向管道写入数据
    write(fd,"fifo test\n",10);
    close(fd);
    return 0;
}

 

二、信号量(用与多进程程序在存取共享资源时的同步控制)——System V

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

1、int semget(key_t key,int nsems,int semflg)   //获得或创建信号量,成功返回信号量标识,出错返回-1

key:根据key生成信号量标识;

nsems:创建的信号量集中的信号量的个数,该参数只在创建信号量集时有效;

semflg:存取权限或创建条件若为0则用于获得已存在的信号量,若为IPC_CREAT|perm perm为存取权限,则用于创建信号量;

2、int semop(int semid,struct sembuf* sops,unsigned nsops)  //获得或者释放信号量,成功返回0,否则返回-1

semid:信号量标识;

sops指向由sembuf组成的数组;

nsops信号量的个数;

struct sembuf{

ushort sem_num;   //在信号量数组中的索引

short sem_op;       //要执行的操作,若sem_op大于0那么操作为将sem_op加入到信号量的值中,并唤醒等待信号增加的进程;若sem_op等于0,当信号量的值也是0时, 函数返回,否则阻塞直到信号量的值为0;若sem_op小于0,则判断信号量的值加上sem_op的值,如果结果为0,唤醒等待信号量为0的进程,如果小于0,调用该函数的进程阻塞,如果大于0,那么从信号量里减去这个值并返回。

short sem_flg;         //操作标致,SEM_UNDO会阻塞,IPC_NOWAIT不会阻塞

};

3、int semctl(int semid,int semnum,int cmd,union semun arg); //在信号量集上的控制操作

semid信号量集的标识;

semnum信号量集的第几个信号量,撤销信号量集时,次参数可缺省;

cmd用于指定操作类别,值为GETVAL获得信号量的值,SETVAL设置信号量的值,GETPID获得最后一次操作信号量的进程,GETNCNT获得正在等待信号量的进程数,GETZCNT获得等待信号量值变为0的进程数,IPC_RMID 删除信号量或信号量数组

三、消息队列

消息队列是存在于内核中的消息列表,一个进程可以将消息发送到消息列表。另一个进程可以从消息列表接受消息。消息队列的操作方式为先进先出

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

1、int msgget(key_t key,int msgflg)

函数说明:功能:获取或者创建一个消息队列,key值同上,msgflg:存取或者创建条件值同上。成功返回消息队列标识,失败返回-1.

2、int msgsnd(int msgid,const void* msgp,size_t msgsz,int msgflg)

函数说明:功能:向消息队列中发送消息,msgid:消息队列标识,msgp消息结构体的地址,msgsz:消息结构体的字节,msgflg:操作标志,成功返回0,否则返回-1。在消息队列没有足够的空间容纳发送的消息时,该函数会阻塞,如果msgflg为IPC_NOWAIT ,则不管发送消息是否成功,该函数都不会阻塞。其中msgp必须指向这样一个结构体

struct msgbuf{

long mtype;   //必须有且大于0

char mtext[1];  //这个可以自己定以,也可以定义其他成员

}

3、size_t msgrcv(int msgid,void *msgp,size_t msgsz,long msgtyp,int msgflg)

函数说明:获取指定消息队列中,msgtyp类型的消息,该值要根发送消息的结构体中msgp->mtype值一样,msgsz,消息结构体的大小,msgflg操作标志,值同上

成功返回收到的字节个数,失败返回-1

4、int msgctl(int msqid,int cmd,struct msqid_ds* buf)

函数说明:cmd操作类型,IPC_RMID删除消息队列,IPC_STAT获取消息队列的状态,IPC_SET改变消息队列的状态,buf用来存放消息队列的属性信息,其结构体如下

struct msqid_ms{

struct ipc_perm msg_perm;   //权限

struct msg *msg_first;   //消息队列的首

struct msg *msg_last;     //消息队列的尾

__kernel_time_t msg_stime;   //最后发送时间

__kernel_time_t msg_rtime;  //最后接受时间

__kernel_time_t msg_ctime; //最后修改时间

unsigned short msg_cbytes;    //当前消息队列的字节数

unsigned short msg_qnum;  //消息队列中的消息数

unsigned short msg_qbytes; //消息队列的最大字节数

__kernel_ipc_pid_t  msg_lspid; //最后发送消息的进程ID

__kernel_ipc_pid_t  msg_lrpid; //最后接受消息的进程ID

};

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#define MSG_KEY 111
#define BUFSIZE 4096
struct msgbuf
{
    long mtype;
    char mtext[BUFSIZE];
};
int main()
{
    int mspid;
    pid_t pid;

    mspid=msgget(MSG_KEY,IPC_CREAT|0666);
    if(mspid==-1)
    {
        perror("msgget");
        exit(-1);
    }

    pid=fork();
    if(pid<0)
    {
        perror("fork");
        exit(-1);
    }
    else if(pid==0)
    {
        sleep(10);
        int msqid=msgget(MSG_KEY,0);
        if(msqid==-1)
        {
            perror("msgget");
            exit(-1);
        }
        struct msgbuf buf;
        if(msgrcv(msqid,(void *)&buf,sizeof(struct msgbuf),1,0)==-1)
        {
            perror("msgrcv");
            exit(-1);
        }
        printf("child:rcv a msg is %s\n",buf.mtext);
        exit(0);
    }
    else
    {
        struct msgbuf buf;
        buf.mtype=1;
        strcpy(buf.mtext,"Hello World!");
        if(msgsnd(mspid,(const void *)&buf,sizeof(struct msgbuf),0)==-1)
        {
            perror("msgsnd");
            exit(-1);
        }
        printf("parent:snd a msg is %s\n",buf.mtext);
        sleep(10);
    }

   // struct msqid_ds cbuf;
    msgctl(mspid,IPC_RMID,NULL);
    return 0;
}

 

四、共享内存

共享内存是内核中的一块存储空间,这块内存被映射至多个进程的虚拟地址空间。共享内存在不同进程虚拟地址空间中映射地址未必相同。

#include <sys/ipc.h>

 #include <sys/shm.h>

1、int shmget(key_t key,int size,int shmflg)

函数说明:功能:创建或获得共享内存,key:作用同上,size:共享内存的大小,shmflg:存取权限或创建条件,若为IPC_CREAT|perm perm为存取权限,则表示创建共享内存,为0表示获得共享内存

2、void * shmat(int shmid,const void *shmaddr,int shmflg)

函数说明:功能:将创建的共享内存映射到进程虚拟地址空间的某个位置,shmid:共享内存标识,shmaddr要映射到的进程虚拟空间地址,若为NULL,则由系统决定对应的地址,shmflg:指定如何使用共享内存,若指定了SHM_RDONLY位则表示以只读的方式使用此段,否则以读写的方式使用此段.成功返回映射的地址失败返回-1

3、int shmdt(const void* shmaddr);

函数说明:解除共享内存的映射,shmaddr:共享内存的映射地址,成功返回0,否则返回-1

4、int shmctl(int shmid,int cmd,struct shmid_ds *buf)

函数说明:对以存在的共享内存进行操作,shmid:共享内存标识,cmd:操作类型:cmd 为IPC_STAT 获取共享内存的状态,IPC_/SET设置共享内存的权限,IPC_RMID删除共享内存,IPC_LOCK 锁定共享内存,使共享内存不被置换出去,IPC_UNLOCK解锁。

    struct shmid_ds{

    struct ipc_perm   shm_perm;   //存取权限

    int        shm_segsz; //共享内存大小

    __kernel_time_t       shm_atime;  //最后映射时间

      __kernel_time_t       shm_dtime;  //最后解除映射时间

    __kernel_time_t       shm_ctime;  //最后修改时间

    __kernel_ipc_pid_t  shm_cpid;    //创建进程ID

    __kernel_ipc_pid_t  shm_lpid;    //最近操作进程ID

    unsigned short         shm_nattch; //建立映射的进程数

    }

下面是一个经典的生产者消费者代码实例,通过信号量和共享内存实现:

//semshm.h 封装了创建信号量删除信号量,P操作,V操作

#ifndef SEMSHM_H
#define SEMSHM_H

#define SHM_KEY 9494
#define SEM_KEY_1 9399
#define SEM_KEY_2 9595
#define BUF_SIZE 1024
#include <string.h>
union semnum
{
    int     val;
    struct semid_ds *buf;
    unsigned short  *array;
};

int sem_create(key_t key,int val)   //创建一个信号量并置处值为val
{
    int semid;
    semid=semget(key,1,0666|IPC_CREAT);
    if(semid==-1)
    {
        perror("semget");
        exit(-1);
    }
    union semnum arg;  //联合类型的变量初始化必须用{},赋值使用 arg.val=0
    arg.val=val;         //设信号量的初始值
    if(semctl(semid,0,SETVAL,arg)==-1)
    {
        perror("semctl");
        exit(-1);
    }
    return semid;
} 
void sem_del(int semid)   //删除信号量
{
    union semnum arg;
    arg.val=0;
    if(semctl(semid,0,IPC_RMID,arg)==-1)
    {
        perror("semctl");
        exit(-1);
    }
}

int P(int semid)  //P操作,使信号量的值减1
{
    struct sembuf sops={0,-1,SEM_UNDO};//第三个参数设置为SEM_UNDO时当信号量小于0时会阻塞,设置为IPC_NOWAIT则不会阻塞
    return (semop(semid,&sops,1));
    
}

int V(int semid)  //V操作,使信号量的值加一
{
    struct sembuf sops={0,+1,SEM_UNDO};
    return (semop(semid,&sops,1));
}

struct msg_data         //定义一个共享内存存储的消息结构体
{
    char data[BUF_SIZE];
};

void Productor(struct msg_data *msg,int i)  //生产者
{
    const char * str="productid:";
    char *array[]={"1","2","3","4","5"};
    strcpy(msg->data,str);
    strcat(msg->data,array[i]);
    printf("Productor:%s\n",msg->data);
}

void Customer(struct msg_data *msg)     //消费者
{
    printf("Customer:%s\n",msg->data);
}
#endif

 

//allsemshm.c   创建了2个信号量用于实现生产者和消费者之间的同步问题 ,并创建了一个共享内存作为共享资源

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include "semshm.h"
int main()
{
    int empty,full,shmid;
    empty=sem_create(SEM_KEY_1,1);//设置一个信号量置初值为1
    full=sem_create(SEM_KEY_2,0); //设置一个信号量置初值为0
    shmid=shmget(SHM_KEY,sizeof(struct msg_data),0666|IPC_CREAT);
    if(shmid==-1)
    {
        perror("shmget");
        exit(-1);
    }

    printf("empty=%d\tfull=%d\tshmid=%d\n",empty,full,shmid);
    return 0;
}

 

//semshm_s.c  生产者代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include "semshm.h"

int main()
{
    int empty,full,shmid;
    empty=semget(SEM_KEY_1,1,0);    //获得信号量
    full=semget(SEM_KEY_2,1,0);
    shmid=shmget(SHM_KEY,0,0);      //获得共享内存
    if(empty==-1||full==-1||shmid==-1)
    {
        perror("get");
        exit(-1);
    }

    struct msg_data *buf;
    void * tmp=shmat(shmid,NULL,0);  //映射共享内存
    buf=(struct msg_data*)tmp;
    int i=0;
    for(i=0;i<5;i++)
    {
        sleep(15);
        P(empty);      
        Productor(buf,i);    //生产者向共享内存写入数据
        V(full);
    }
    return 0;
}
//semshm_c.c  消费者代码 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include "semshm.h"


int main()
{
    int empty,full,shmid;
    empty=semget(SEM_KEY_1,1,0);   //获取信号量
    full=semget(SEM_KEY_2,1,0);
    shmid=shmget(SHM_KEY,0,0);     //获取共享内粗
    if(empty==-1||full==-1||shmid==-1)
    {
        perror("get");
        exit(-1);
    }

    struct msg_data *buf;
    buf=(struct msg_data*)shmat(shmid,NULL,0);   //映射共享内存地址
    int i=0;
    for(i=0;i<5;i++)
    {
        P(full);
        Customer(buf);           //消费者从共享内存取数据
        V(empty);
    }

    
    return 0;
}
//delsemshm.c  //删除所建的信号量和共享内存    

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include "semshm.h"
int main()
{
    int empty,full,shmid;
    empty=semget(SEM_KEY_1,1,0);
    full=semget(SEM_KEY_2,1,0);
    shmid=shmget(SHM_KEY,0,0);
    if(empty==-1||full==-1||shmid==-1)
    {
        perror("del get");
        exit(-1);
    }
    sem_del(empty);
    sem_del(full);
    struct shmid_ds *ds=(struct shmid_ds*)malloc(sizeof(struct shmid_ds));
    shmctl(shmid,IPC_RMID,ds);
    printf("del success !\n");
    return 0;
}

参考链接:https://blog.csdn.net/hamlee67/article/details/84777440

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值