进程间通信(管道、消息队列、共享内存)

本文详细介绍了进程间通信(IPC)的几种主要方式,包括管道(无名管道和命名管道FIFO)、消息队列和共享内存。每种方式都提供了详细的解释、特点、函数原型和示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



前言

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

IPC的方式通常有管道(包括无名管道和命名管道)、消息队列信号量共享存储SocketStreams等。其中 Socket和Streams支持不同主机上的两个进程IPC。


一、管道

Ⅰ、无名管道

1、简介:
管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。

2、特点:
①它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
②它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
③它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

3、函数原型:

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

管道存在于内核当中。fd[2]为两个文件描述符,fd[0]为只读文件描述符,fd[1]为只写文件描述符。要关闭管道只需将这两个文件描述符关闭即可。

4、返回值:
若成功返回0,失败返回-1

5、例程:

pipe.c
#include <stdio.h>
#include <unistd.h>
//int pipe(int pipefd[2]);

int main()
{
    int fd[2];
    pid_t pid;
    char readBuf[20];

    if(pipe(fd) == -1){
        printf("Creat pipe error.\n");
    }

    if((pid = fork()) < 0){
        printf("Creat process error.\n");
    }
    else if(pid > 0){  //父进程
        sleep(3);
        printf("This is father process.\n");
        close(fd[0]);  //关闭读
        write(fd[1],"Hello world!\n",12);  //往管道里写
    }
    else if(pid == 0){  //子进程
        printf("This is child process.\n");
        close(fd[1]);  //关闭写
        read(fd[0],readBuf,20);  //读管道内容读到readBuf,如果管道内没东西,子进程会阻塞在这
        printf("read from father process is %s\n",readBuf);
    }
    close(fd[0]);
    close(fd[1]);
    return 0;
}

6、运行结果:

$ ./pipe
This is child process.
This is father process.
read from father process is Hello world!

程序先执行子进程,因为设置了父进程先休眠3s,子进程阻塞在read()函数,3s过后执行父进程打印和写入内容至管道,之后子进程读取内容,并打印。

Ⅱ、命名管道 FIFO

1、简介:
FIFO,也称为命名管道,它是一种文件类型。

2、特点:
①FIFO可以在无关的进程之间的亲缘关系交换数据,与无名管道不同。
②FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

3、函数原型:

#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。

当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

  • 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open要阻塞到某个其他进程为读而打开它。
  • 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该FIFO,其errno置ENXIO。

4、返回值:
返回值:成功返回0,出错返回-1。

5、例程:

//FIFO_write.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
//int mkfifo(const char *pathname, mode_t mode);

int main()
{
    int fd;
    char *str = "message from fifo";
    if(mkfifo("./file.txt",0600) == -1 && errno!=EEXIST){  //EEXIST表示文件存在的宏
        printf("Creat fifo failed\n");
        perror("why");
    }
    fd = open("./file.txt",O_WRONLY);  //只写打开管道文件
    write(fd,str,strlen(str));
    printf("write success\n");
    close(fd);
    return 0;
}

//FIFO_read.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
//int mkfifo(const char *pathname, mode_t mode);

int main()
{
    int fd;
    char readBuf[30] = {0};

    if(mkfifo("./file.txt",0600) == -1 && errno!=EEXIST){ //创建命名管道
        printf("Creat fifo failed\n");
        perror("why");
    }

    fd = open("./file.txt",O_RDONLY);  //只读打开管道文件
    int nread = read(fd,readBuf,30);  //读到readBuf
    printf("read %d byte data,contex is %s\n",nread,readBuf);
    close(fd);
    return 0;
}

6、运行结果:
读进程:

$ ./read
//阻塞,等到执行了write进程
read 17 byte data,contex is message from fifo

写进程:

$ ./write
write success

二、消息队列

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

2、特点:
①消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
②消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
③消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

3、函数原型:

#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数说明:
key:两个通信进程的key要相同,可通过ftok()得到。
flag:消息队列的一些宏,后面或上权限。
msqid:msgget()执行成功返回的ID,添加消息和读取消息进程的msqid要一致。
ptr:一个结构,struct msgbuf {
long mtype; /* message type, must be > 0 /
char mtext[199]; /
message data */
};
size:写入队列的长度。
type:一般写0。
cmd:一些控制消息队列操作的宏。
buf:一般写NULL。

4、返回值:
①msgget():成功返回队列ID,失败返回-1
②msgsnd():成功返回0,失败返回-1
③msgrcv():成功返回消息数据的长度,失败返回-1
④msgctl():成功返回0,失败返回-1

5、例程:

//msgSend.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[199];    /* message data */
};

int main()
{
    struct msgbuf buf = {999,"This is from que message"};
    key_t key = ftok(".",'z');
    printf("key = %x\n",key);  //用ftok()生成key
    int msgID = msgget(key,IPC_CREAT|0777);
    if(msgID == -1){
        printf("get que failuer\n");
    }

    msgsnd(msgID,&buf,strlen(buf.mtext),0);
    printf("Send end\n");
    //msgctl(msgID,IPC_RMID,NULL);
    return 0;
}

//msgGet.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[30];    /* message data */
};

int main()
{
    struct msgbuf readBuf;
    int cnt = 0;
    key_t key = ftok(".",'z');  //用ftok()生成key
    printf("key = %x\n",key);
    int msgID = msgget(key,IPC_CREAT|0777);
    if(msgID == -1){
        printf("get que failuer\n");
    }

    msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),999,0);
    while(cnt < 3){
    printf("readBuf is %s\n",readBuf.mtext);
    cnt++;
    }
    //msgctl(msgID,IPC_RMID,NULL);
    return 0;
}

6、运行结果:

Get进程:

$ gcc msgGet.c -o get
$ ./get
//阻塞在msgrcv(),执行完send进程,get往下执行
readBuf is This is from que message
readBuf is This is from que message
readBuf is This is from que message

Send进程:

$ gcc msgSend.c -o send
$ ./send
Send end

三、共享内存

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

2、特点
①共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
②因为多个进程可以同时操作,所以需要进行同步。
③信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

3、原型

#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);

参数说明:
key:两个通信进程的key要相同,可通过ftok()得到。
size:开辟共享内存的大小,以MB为单位。
flag:一些宏,后面可或上权限。
shm_id:shmget()成功执行返回的ID。
shmat的addr:一般写NULL,随系统默认分配。
shmdt的addr:shmat()成功执行返回指向共享内存的指针。
cmd:控制共享内存操作的一些宏。
buf:一般写0。

注意事项:

  • 当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。
  • 当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
  • shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
  • shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

4、返回值:
①shmget():成功返回共享内存ID,失败返回-1
②shmat():成功返回指向共享内存的指针,失败返回-1
③shmdt():成功返回0,失败返回-1
④shmctl():成功返回0,失败返回-1

5、例程:

//shmw.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);

int main()
{
    int shmid;
    key_t key;
    char *shmaddr;

    key = ftok(".",1);
    shmid = shmget(key,1024*4,IPC_CREAT|0666);  
    //新建共享内存,大小以MB为单位,开辟4MB,可读可写方式新建
    if(shmid == -1){
        printf("Shmget failuer\n");
        exit(-1);
    }
    shmaddr = shmat(shmid,0,0);  //将共享内存映射到本进程中
    strcpy(shmaddr,"Hello World!");  //写内容到共享内存

    sleep(5);
    //shmdt(shmaddr);  //释放映射
    //shmctl(shmid,IPC_RMID,0);  //释放共享内存
    return 0;
}

//shmr.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//int shmdt(const void *shmaddr);
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);

int main()
{
    int shmid;
    key_t key;
    char *shmaddr;

    key = ftok(".",1);
    shmid = shmget(key,1024*4,0);  //打开共享内存
    if(shmid == -1){
        printf("Shmget failuer\n");
        exit(-1);
    }
    shmaddr = shmat(shmid,0,0);  //将共享内存映射到本进程中
    printf("data is %s\n",shmaddr);  //读取共享内存的内容
    //shmdt(shmaddr);  
    printf("quit\n");
    return 0;
}

6、运行结果:

read进程:

$ gcc shmr.c -o read
$ ./read
//没有内容 阻塞
data is Hello World!

write进程:

$ gcc shmw.c -o write
$ ./write 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值