进程间通信(Linux)

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

IPC方式包括管道(匿名管道和命名管道),消息队列,共享内存,信号量等。

一、管道

管道是Unix中最古老的进程间通信方式,我们把从一个进程连接到另一个进程的一个数据流称为“管道”。

管道(匿名管道)的特点:

  1. 只能用于具有共同祖先的进程(具有血缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  2. 管道提供流式服务。
  3. 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  4. 一般而言,内核会对管道操作进行同步和互斥
  5. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

管道分为匿名管道和命名管道,他们的区别如下:

  1. 匿名管道由pipe函数创建并打开。
  2. 命名管道由mkfifo函数创建,打开用open
  3. FIFO(命名管道)与pipe(匿名管道),之间唯一的区别在他们创建和打开的方式不同,一旦这些工作完成,他们具有相同的语义。
  4. 命名管道可以在无关的进程间通信,而匿名管道只能在拥有共同祖先的进程间通信

pipe(匿名管道)原型:

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

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

实例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define ERR_EXIT(m)\
    do \
    { \
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0)

int main(){                                                                                                                                                                                    
    int pipefd[2];
    if(pipe(pipefd) == -1){
        ERR_EXIT("pipe error");
    }   

    pid_t pid;
    pid = fork();
    if(pid == -1){
        ERR_EXIT("fork error");
    }   

    if(pid == 0){ 
        close(pipefd[0]);
        write(pipefd[1], "hello", 5); 
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    }   

    close(pipefd[1]);
    char buf[10];
    read(pipefd[0], buf, 10);
    printf("buf = %s\n", buf);

    return 0;
}

FIFO(命名管道)

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

       int mkfifo(const char *pathname, mode_t mode);
//两个参数,第一个pathname是文件名路径,第二个参数是权限

实例:
server.c

#include <stdio.h>                                                                                                                                                                             
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
    if(mkfifo("./fifo", 0644) < 0){ 
        printf("mkfifo error!\n");
        return 1;
    }   

    int fd = open("./fifo", O_RDONLY);
    if(fd < 0){ 
        perror("open");
        return 2;
    }   

    char buf[64];
    while(1){
        ssize_t s = read(fd, buf, sizeof(buf) - 1); 
        if(s > 0){ 
            buf[s] = 0;
            printf("server# %s\n", buf);
        }else if(s == 0){ 
            printf("clident quit\n");
            break;
        }   
    }   

    close(fd);
    return 0;
}

client.c

#include <stdio.h>                                                                                                                                                                             
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(){
    int fd = open("./fifo", O_WRONLY);
    if(fd < 0){ 
        perror("open");
        return 2;
    }   

    char buf[64];
    while(1){
        printf("Pleans Enter:");
        scanf("%s", buf);
        if(strcmp(buf, "quit") == 0){ 
            break;
        }   
        write(fd, buf, strlen(buf));
    }   


    close(fd);
    return 0;
}

二、消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

特点:

  1. 消息队列提供一个从一个进程向另外一个进程发送一块数据的方法。
  2. 每个数据都被认为是有一个类型,接受者进程接收的数据块可以有不同的类型值。
  3. 消息队列也有管道一样的不足,就是每个消息的最大长度有上限,每个消息队列的总的字节数也有上限,系统上消息队列的总数也有上限。
  4. 消息队列是由操作系统提供,因此它不随进程的消失而消失,它是随内核的。

消息队列结构和IPC数据结构:


           struct msqid_ds {
               struct ipc_perm msg_perm;     /* Ownership and permissions */
               time_t          msg_stime;    /* Time of last msgsnd(2) */
               time_t          msg_rtime;    /* Time of last msgrcv(2) */
               time_t          msg_ctime;    /* Time of last change */
               unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
               msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
               msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
               pid_t           msg_lspid;    /* PID of last msgsnd(2) */
               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
           };

       The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):

           struct ipc_perm {
               key_t          __key;       /* Key supplied to msgget(2) */
               uid_t          uid;         /* Effective UID of owner */
               gid_t          gid;         /* Effective GID of owner */
               uid_t          cuid;        /* Effective UID of creator */
               gid_t          cgid;        /* Effective GID of creator */
               unsigned short mode;        /* Permissions */
               unsigned short __seq;       /* Sequence number */
           };

消息队列函数

msgget:用于创建和访问一个消息队列

#include <sys/types.h>
#include <sys/ipc.h>
include <sys/msg.h>

int msgget(key_t key, int msgflg);

//key:某个消息队列的名字,key值有ftok函数创建
//msgflg:由九个权限标志构成,他们的用法和创建文件时使用的mode模式标志是一样的
//返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1。

msgctl:消息队列的控制函数

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

       int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//maqid:消息队列标识符
//cmd:将要采取的动作(IPC_STAT,IPC_SET,IPC_RMID(删除消息队列))
//返回值:成功返回0,失败返回-1

msgsnd/msgrcv:把一条消息添加到消息队列中/从消息队列中取出一条消息

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
       //msgid:消息队列标识符
       //msgp:指针,指向准备发送的消息
       //msgsz:是msgp指向消息队列的长度,这个长度不含保存消息类型的那个long int长整型
       //msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
       //返回值:成功返回0,失败返回-1


       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
       //其参数只比msgsnd多了一个msgtype,msgid和msgsz含义相同
       //msgp:指针,指向准备接受的消息
       //msgtype:实现接受优先级的简单形式
       //msgflg:控制着队列中没有相应类型的消息可供接受将要发生的事情
       //函数msgrcv在读取消息队列时,type参数有下面几种情况:
        //type == 0,返回队列中的第一个消息;
        //type > 0,返回队列中消息类型为 type 的第一个消息;
        //type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。

查看和删除消息队列命令:
ipcs -q :查看IPC消息队列资源
ipcrm -q:手动删除IPC消息队列资源
实例:
cpmm.h

#ifndef _COMM_H_
  #define _COMM_H_

  #include <stdio.h>
  #include <sys/types.h>
  #include <sys/ipc.h>
  #include <sys/msg.h>
  #include <string.h>
  #define PATH_NAME "./"
  #define PROJ_ID 0x21351261

  #define SERVER_TYPE 1
  #define CLIENT_TYPE 2

  struct msgbuf{                                                                                                                                                                               
      long mtype;
      char mtext[128];
  };

  int createMsgQueue();

  int getMsgQueue();


  int sendMsg(int msgid, char *msg, int t);

  int recvMsg(int msgid, int t, char *msg);

  void destroyMsgQueue(int);



  #endif

comm.c

#include "comm.h"                                                                                                                                                                              



static int commMsgQueue(int flag){
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if(k < 0){ 
        printf("ftok error!\n");
        return -1; 
    }   

    int msgid = msgget(k, flag);
    if(msgid < 0){ 
        printf("msgget error!\n");
        return -2; 
    }   
    return msgid;
}
int createMsgQueue(){
    return commMsgQueue(IPC_CREAT|IPC_EXCL|0644);
}
int getMsgQueue(){
    return commMsgQueue(IPC_CREAT);
}
int sendMsg(int msgid, char *msg, int t){ 
    struct msgbuf buf;
    buf.mtype = t;
    strcpy(buf.mtext, msg);

    if(msgsnd(msgid, &buf, sizeof(buf.mtext), 0) < 0){ 
        printf("msgsnd error\n");
        return -1; 
    }   
    return 0;
}
int recvMsg(int msgid, int t, char *msg){
    struct msgbuf buf;
    if(msgrcv(msgid, &buf, sizeof(buf.mtext), t, 0) < 0){
        printf("msgrcv error !\n");
        return -1;
    }
    strcpy(msg, buf.mtext);
    return 0;

}

void destroyMsgQueue(int msgid){
    if(msgctl(msgid, IPC_RMID, NULL) < 0){
        printf("msgctl error!\n");
    }
}               

srever.c

#include "comm.h"


int main(){
    char buf[256];
    int msgid = createMsgQueue();
    while(1){
        //recv
        recvMsg(msgid, CLIENT_TYPE, buf);
        if(strcmp(buf, "quit") == 0){ 
            printf("client is quit,me too!\n");
            break;
        }   
        printf("client# %s\n", buf);
        //send
        printf("Please Entr:");
        scanf("%s", buf);
        sendMsg(msgid, buf, SERVER_TYPE);
    }   

    destroyMsgQueue(msgid);
    return 0;
}

clinet.c

#include "comm.h"


int main(){
    char buf[256];
    int msgid = getMsgQueue();
    while(1){
        //send
        printf("Please Entr:");
        scanf("%s", buf);
        sendMsg(msgid, buf, CLIENT_TYPE);
        if(strcmp(buf, "quit") == 0){ 
            printf("client quit\n");
            break;
        }   
        //recv
        recvMsg(msgid, SERVER_TYPE, buf);
        printf("server# %s\n", buf);
    }   


    return 0;
}

三、共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

特点:

  1. 生命周期随内核
  2. 其分配是按页(一页4K)分配
  3. 共享内存是最快的IPC方式,它省去传数据所要经历的两次拷贝
  4. 它没有提供任何保护机制

共享内存函数

#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr); 
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。

当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。

shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。

shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

实例:
server.c

#include <stdio.h>                                                                                                                                                                             
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#define PATH_NAME "."
#define PROJ_ID 0x7777

int main(){

    key_t k = ftok(PATH_NAME, PROJ_ID);
    if(k < 0){ 
        printf("ftok error!\n");
        return -1; 
    }   
    int shmid = shmget(k, 4096, IPC_CREAT|IPC_EXCL);
    if(shmid < 0){ 
        printf("shmget error!\n");
        return -2; 
    }   

    char *mem = (char *)shmat(shmid, NULL, 0); 

    while(1){
        printf("%s\r",mem);
        fflush(stdout);
        sleep(1);
    }   
    shmdt(mem);
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

client.c

#include <stdio.h>                                                                                                                                                                             
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#define PATH_NAME "."
#define PROJ_ID 0x7777

int main(){

    key_t k = ftok(PATH_NAME, PROJ_ID);
    if(k < 0){ 
        printf("ftok error!\n");
        return -1; 
    }   
    int shmid = shmget(k, 4096, IPC_CREAT);
    if(shmid < 0){ 
        printf("shmget error!\n");
        return -2; 
    }   

    sleep(5);
    char *mem = (char *)shmat(shmid, NULL, 0); 

    int x = 'A';
    for(; x <= 'X'; x++){
        mem[x -'A'] = x;
        mem[x -'A' + 1] = '\0';
        sleep(1);
    }   

    shmdt(mem);

    return 0;
}

查看共享内存命令:ipcs -m
删除共享内存命令:ipcrm -m

四、信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据

进程互斥:

  1. 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
  2. 系统中某些资源一次只允许一个进程使用,这样的资源被称为临界资源。
  3. 在进程中涉及到临界资源的程序段为临界区

进程同步:
指多个进程需要相互配合共同完成一项任务

特点:

  1. 信号量主要用于进程间的同步和互斥。
  2. 信号量的生命周期随内核。
  3. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
  4. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

信号量函数:

#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);  
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。

在semop函数中,sembuf结构的定义如下:

这里写图片描述

实例:
comm.h

#ifndef _COMM_H_                                                                                                                                                                               
#define _COMM_H_

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>

#define PATHNAME "."
#define PROJ_ID 0x6666

union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *_buf;
};

int creatsemset(int nums);
int initsem(int semid, int nums, int intval);
int getsem(int nums);
int P(int semid, int who);
int V(int semid, int who);
int destroysemset(int semid);


#endif

comm.c

#include "comm.h"                                                                                                                                                                              

int commsemset(int nums, int flags){
    key_t k = ftok(PATHNAME, PROJ_ID);
    if(k < 0){
        perror("ftok");
        return -1;
    }
    int semid = semget(k, nums, flags);
    if(semid < 0){
        perror("semget");
        return -2;
    }
    return semid;
}   

int creatsemset(int nums){
    return commsemset(nums, IPC_CREAT|IPC_EXCL|0666);
}

int getsemset(int nums){
    return commsemset(nums, IPC_CREAT);
}

int initsem(int semid, int nums, int intval){
    union semun un;
    un.val = intval;
    if(semctl(semid, nums, SETVAL, un) < 0){
        perror("semctl");
        return -1;
    }
    return 0;
}

static int commPV(int semid, int who, int op){
    struct sembuf sf;
    sf.sem_num = who;
    sf.sem_op = op;
    sf.sem_flg = 0;
    if(semop(semid, &sf, 1) < 0){
        perror("semop");
        return -1;
    }
    return 0;
}

int P(int semid, int who){
    return commPV(semid, who, -1);
}

int V(int semid, int who){
    return commPV(semid, who, 1);
}

int destroysemset(int semid){
    if(semctl(semid , 0, IPC_RMID) < 0){
        perror("semctl");
        return -1;
    }
    return 0;
}

test.c

#include <unistd.h>                                                                                                                                                                            
#include <sys/wait.h>          
#include "comm.h"              

int main(){                    
    int semid = creatsemset(1);
    initsem(semid, 0, 1);      
    pid_t id = fork();
    if(id == 0){//child
        int _semid = getsem(0);
        while(1){
            P(_semid, 0);
            printf("A");
            fflush(stdout);
            usleep(123456);
            printf("A ");
            fflush(stdout);
            usleep(321456);
            V(_semid, 0);
        }
    }else{
        while(1){
            P(semid, 0);
            printf("B");
            fflush(stdout);
            usleep(32452345);
            printf("B ");
            fflush(stdout);
            usleep(1234213);
            V(semid, 0);
        }
        wait(NULL);
    }
    destroysemset(semid);
    return 0;
}

makefile

test_sem:comm.c test_sem.c                                                                                                                                                                     
    gcc -o $@ $^               

.PHONY:clean
clean:
    rm -f test_sem             

以上就是进程间通信的几种方式,如有错误,请大佬谅解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值