进程间通信(IPC)

本文介绍了Linux进程间通信的几种方式,包括匿名和命名管道、共享内存、消息队列以及信号量。管道提供了半双工的数据传输,而共享内存则是最快的通信方式,通过内核中的缓冲区实现数据共享。消息队列提供了带优先级的数据传输,而信号量用于进程间的同步和互斥控制。这些机制各自具有不同的特性和应用场景,如管道的生命周期随进程,共享内存的生命周期随内核等。

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


进程间通信(IPC):操作系统为用户提供的几种进程间通信方式。

管道–用于进程间的数据传输

本质:内核中的一块缓冲区–通过半双工(可以选择方向的单向通信)通信实现数据传输。
原理:通过让多个进程都能访问到同一块缓冲区,来实现进程间通信。
管道分类:匿名管道\命名管道

匿名管道

概念:这块内核中的缓冲区没有标识。
特性:只能用于具有亲缘关系的进程间通信。子进程通过复制父进程的方式,获取到管道的操作句柄进而实现访问一个管道通信。
创建管道时,操作系统会提供两个操作句柄(文件描述符),其中一个用于从管道读取数据,一个向管道写入数据。每次通信时只能有一个操作。
文件描述符:内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。相当于就是内核给这个文件的一个标记。

int pipe(int pipefd[2]);创建一个匿名管道,向用户通过参数pipefd返回管道的操作句柄。
pipefd[0]:用于从管道读取数据
pipefd[1]:用于从管道写入数据
返回值:0-成功,-1-失败

特性:若管道中没有数,则read会阻塞;若管道写满了,则write会阻塞;管道自带同步与互斥。

同步:对临界资源访问的合理性。一个进程使用完资源之后,若是还要再次使用,若有其他进程等待使用资源,该进程就必须排队等待。
互斥:通过保证同一时间只有一个进程能够访问临界资源,保证临界资源访问的安全性。对管道进行数据操作的大小不超过PIPE_BUF=4096的时候,则保证操作的原子性。

若管道所有的写端被关闭(表示当前没有进程继续写入数据了),read读完管道中的数据之后,就不会再阻塞而是返回0。
若管道所有读端被关闭(表示没有进程读取数据了),继续write会触发异常,程序退出。

管道的使用:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 int main(){
  6 
  7     int pipefd[2]={0};
  8     int ret=pipe(pipefd);
  9     if(ret<0){
 10         perror("pipe error");
 11         return -1;
 12     }
 13     pid_t pid=fork();
 14     if(pid<0){
 15         perror("fork error");
 16         return -1;
 17     }else if(pid==0){
 18         char buf[1024]={0};
 19         int ret=read(pipefd[0],buf,1023);
 20         printf("buf:[%s]-[%d]\n",buf,ret); 
 21 
 22     }else{
 23         char *ptr="超级开心";
 24         write(pipefd[1],ptr,strlen(ptr));
 25     }
 26     while(1){
 27         printf("---------------------%d\n",getpid());
 28 
 29     }
 30  
 31     return 0;
 32 }

命名管道

内核中的缓冲区具有标识符(标识符是一个可见于文件系统的管道文件),其他的进程可以通过这个标识符,找到这块缓冲区(通过打开同一个管道文件,进而访问到同一块缓冲区),进而实现通信。

命令操作:mkfifo filename
int mkfifo(const char* pathname,mode_t mode);—创建命名管道文件
pathname:管道文件名称
mode:文件权限

创建管道文件:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<sys/stat.h>
  5 #include<errno.h>
  6 
  7 int main(){
  8 
  9     umask(0);
 10     char* file="./test.fifo";
 11     int ret=mkfifo(file,0664);
 12     if(ret<0&&errno!=EEXIST){
 13         perror("mkfifo error");
 14         return -1;
 15     }
 16     return 0;
 17 }

运行结果:
在这里插入图片描述
打开特性

若管道文件以只读的方式打开,则会阻塞,直到这个管道文件以被以写的方式打开。
若管道以只写的方式发开,则会阻塞,直到这个管道文件被以读的方式打开。
若管道以读写的方式打开,则不会阻塞。

打开特性:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<sys/stat.h>
  5 #include<errno.h>
  6 #include<fcntl.h>
  7 int main(){
  8 
  9     umask(0);
 10     char* file="./test.fifo";
 11     int ret=mkfifo(file,0664);
 12     if(ret<0&&errno!=EEXIST){
 13         perror("mkfifo error");
 14         return -1;
 15     }
 16     printf("create fifo success\n");
 17     int fd=open(file,O_RDONLY);
 18     if(fd<0){
 19         perror("open error");
 20         return -1;
 21     }
 22     printf("open fifo success\n");
 23     return 0;
 24 }

运行这个程序,程序阻塞在读取管道这里
在这里插入图片描述
当我们向管道中写入数据后,程序运行结束。
在这里插入图片描述
在这里插入图片描述

管道特性

1.管道生命周期随进程。
2.半双工特性。
3.自带同步和互斥。
4.提供字节流服务–有序,链接,可靠的字节流传输。

共享内存–用于进程间的数据共享

最快的进程间通信方式。为什么呢?

因为共享内存直接通过虚拟地址映射访问物理内存,而其他方式因为都是在内核中的缓冲区,因此通信时会涉及到用户态与内核态之间的两次数据拷贝。但是共享内存通信方式不会,所以通信速度最快。

共享内存的实现:

1.创建共享内存–在物理内存上开辟一块内存空间(具有标识符)。
2.将共享内存映射到各个进程的虚拟地址空间。
3.进程就可以通过虚拟地址直接访问到共享内存–多个进程要是映射同一块物理内存,就可以通过这块内存实现数据共享。
4.解除映射关系。
5.删除共享内存。

查看&删除进程间通信资源命令:

ipcs:查看进程间通信资源 ipcrm:删除进程间通信资源
-m:查看共享内存
-q:查看消息队列
-s:查看信号量

int shmget(key_t key,int size,int flag);–创建共享内存

key:共享内存的标识符,多个进程通过相同的标识符可以打开同一块共享内存
size:共享内存大小

flag:IPC_CREAT(存在打开,不存在创建)|IPC_EXCL(报错,不报错) |权限
返回值:成功返回一个操作句柄,失败返回-1

void shmat(int shmid,void addr,int flag);–映射共享内存

shmid:共享内存操作句柄
addr:映射到虚拟地址空间的首地址,通常置NULL
flag:通常0-可读可写 SHM_RDONLY-只读
返回值:
成功,返回映射的虚拟空间首地址,通过这个地址对共享内存进行操作
失败,返回-1

int shmdt(void shmstart);–解除映射*

shmstart:映射到虚拟地址空间的首地址
成功返回0,失败返回-1

int shmctl(int shmid,int cmd,struct shmid_ds buf);–删除共享内存*

shmid:共享内存操作句柄
cmd:具体对共享内存要进行的操作—IPC_RMID(删除共享内存)
成功返回0,失败返回-1

修改共享内存中的值:

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

#define IPC_KEY 0x12345678
int main(){

    //创建共享内存
    int shmid=shmget(IPC_KEY,32,IPC_CREAT|0664);
    if(shmid<0){
        perror("create error");
        return -1;
    }

    //映射共享内存
    void* shmstart=shmat(shmid,NULL,0);
    if(shmstart==(void*)-1){
        perror("shmat errror");
        return -1;
    }

    //修改共享内存中的值
    int i=0;
    while(1){
        //格式化数据写入标准输出
        //覆盖式写入
        sprintf(shmstart,"%s-%d\n","share memory",i++);
        sleep(1);
    }

    //解除映射
    shmdt(shmstart);
    //删除映射
    shmctl(shmid,IPC_RMID,NULL);

    return 0;
}

读取共享内存中的值:

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

#define IPC_KEY 0x12345678
int main(){

    //创建共享内存
    int shmid=shmget(IPC_KEY,32,IPC_CREAT|0664);
    if(shmid<0){
        perror("create error");
        return -1;
    }

    //映射共享内存
    void* shmstart=shmat(shmid,NULL,0);
    if(shmstart==(void*)-1){
        perror("shmat errror");
        return -1;
    }

    //读取共享内存中的值
    int i=0;
    while(1){
        printf("%s\n",shmstart);
        sleep(1);
    }

    //解除映射
    shmdt(shmstart);
    //删除映射
    shmctl(shmid,IPC_RMID,NULL);

    return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述
当删除共享内存的时候,共享内存不会立即被删除(因为有可能会造成正在访问的进程崩溃),而是将key修改为0,表示这块共享内存将不再继续接收映射链接,当这块共享内存的映射链接数为0的时候则自动被释放。

共享内存特性:

1.最快的进程间通信方式。
2.生命周期随内核。
注意:共享内存的操作时不安全的(并不会自动具备同步与互斥关系,需要操作用户进行控制)

消息队列–用于进程间的数据传输

本质:内核中的一个队列,多个进程通过向同一个队列中添加节点和获取节点实现通信。传输一个有类型(优先级)的数据块。
特性:

1.自带同步与互斥。
2.生命周期随内核。
3.数据传输自带优先级。

信号量–用于实现进程间的控制

作用:用于实现进程间的同步和互斥。
本质:内核中的一个计数器+pcb等待队列(对资源进行计数)。
互斥的实现:

通过只有0/1的计数器,实现对临界资源访问状态的标记;在访问临界资源之前先获取信号量,计数-1;若计数<0则使进程等待(将进程pcb加入队列);否则可以对临界资源进行访问(并且在访问期间,已经将临界资源的状态置为不可访问状态,因此可以保证其他进程不会再访问临界资源),当前进程访问完毕之后,则对计数器进行+1,并且唤醒一个进程(将一个pcb出队,置为运行状态)。

同步的实现:

信号量是一个对资源的计数,可以通过计数判断是否能够获取一个资源进行处理;若计数<0,则表示不能获取(并且计数进行-1),则需要等待(加入pcb队列)。这时候若其他进程产生一个资源,则会对计数进行+1,若计数<=0,则唤醒一个进程。假如说开始有100个停车位,每进来一辆车就会-1,当停车位数为0的时候,说明停车场已经停满了车,这时候再进来车时计数器还是会-1,但是车不能进入,只能在外面排队等着,当停车场有车开走了,计数器+1(此时计数器的数不一定>0),并且唤醒等待队列中的一个车,告诉他有停车为了可以去停车了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值