04.进程间通信

进程间通信基本概念

IPC(Inter Process Communication)

进程间通信

进程通信就是不同进程之间进行信息的交换或传播

为什么进程之间实现通信和困难

因为进程之间具有独立性,数据独立,程序可能独立也可能不独立(父子进程的程序时一样的)
所以要想进行进程间的数据交换,必须借助一个第三方的资源,进程一可以对这个资源进行读写,进程二也可以对这个资源进行读写,如图所示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个第三方的资源实际上就时OS提供的内存区域

进程通信的本质

进程通信的本质就时让不同进程访问同一份第三方资源,这个第三方资源可以由OS的不同模块提供,因此出现在不同的进程间的通信方式

进程通信的目的

1.数据传输:一个进程传输它的数据给另一个进程
2.资源共享:多个进程之间共享同样的资源
3.通知事件:一个进程需要向另一个或另一组进程发送消息,通知它们发生了某种事件,比如进程终止时需要通知其父进程(信号)
4.进程控制:有些进程希望完全控制另一个进程的执行(Bebug进程),此时控制的进程希望能够阻拦另一个进程所有陷入和异常,并且能够及时知道它的状态改变

管道

一个进程连接到另一个进程的数据流称为管道

管道符(|)

管道符就时利用这种管道的通信方式
进程1 | 进程2
ls / | wc -l

匿名管道

用于有血缘关系的进程间的通信

本质

进程通信的本质就是让不同的进程访问同一份第三方资源,而管道中第三方资源就是同一份打开的文件,父子进程都可以对这份文件进行读写,从而实现进程间的通信。
然后就是这个打开文件的位置,这个文件不可能放在磁盘中,因为磁盘的IO效率太慢了,所以它应该是在内存之中

pipe函数

创建管道的函数
int pipe(int pipefd[2])
pipefd[0] pipefd[1] 管道的读写端,传出参数
返回值:0 或 -1

因为管道是实现父子进程间的通信的,所以一般要配合fork函数使用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现子进程向父进程中写入hello

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
int main(int argc, char* argv[])
{
	int pipefd[2];
	pipe(pipefd);//创建匿名管道,pipefd[0]读端,pipefd[1]写端
	int pid = fork();
	if(pid == 0){//子进程写
		close(pipefd[0]);//关闭读端
		// close(pipefd[1]);
		int write_count = write(pipefd[1],"hello",6);
		printf("write_count = %d\n",write_count);
	}else{//父进程读
		close(pipefd[1]);//关闭写端
		char buf[1024]={'\0'};
		int read_count = read(pipefd[0],buf,sizeof(buf));
		//当写段开着但管道中没有数据的时候,不会直接返回-1,而是阻塞等待管道中数据的出现
		//如果写段没开,不会阻塞等待,直接然后0 
		printf("read_count = %d,buf = %s\n",read_count,buf);
	}
	while(1);
	return 0;
}

管道中状态的描述

初始时管道中没有数据:写端write返回成功读入的字节数,读端read阻塞等待管道中数据的产生
管道中有数据但没满:写端write返回成功读入的字节数,读端read返回成功读入的字节数
管道中数据满了:写端write阻塞等待,读端read返回成功读入的字节数
写端关闭:读端read返回成功读入的字节数,如果没有写入,总会有读完的时候,这个时候不会阻塞等待直接返回0
读端关闭:写端write会异常终止进程(被SIGPIPE信号杀死)

模拟ls / | wc -l

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
int main(int argc, char* argv[])
{
	int pipefd[2];
	pipe(pipefd);
	int pid = fork();
	if(pid > 0){//父进程
	//ls /内容由stdout转到管道的写端
		close(pipefd[0]);
		dup2(pipefd[1],1);
		execlp("ls","ls","/",NULL);
	}else{//子进程
	//wc -l读的由stdin到读端
		close(pipefd[1]);
		dup2(pipefd[0],0);
		execlp("wc","wc","-l",NULL);
	}
	while(1);
	return 0;
}

命名管道

创建管道文件有两种方式

mkfifo fifo文件
调用函数mkfifo(path,0644)

mkfifo函数

创建命名管道文件
int mkfifo(const char* pathname,mode_t mode)

wififo.c

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>

int main(int argc, char* argv[])
{
    // mkfifo("fifo",0644);
    int fd=open("./fifo",O_WRONLY);
    int write_count = write(fd,"hello",6);
    printf("write_count = %d\n",write_count);
    return 0;
}

riffo.c

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>

int main(int argc, char* argv[])
{
    int fd=open("./fifo",O_RDONLY);
    char buf[1024]={'\0'};
    int read_count = read(fd,buf,sizeof buf);
    printf("read_count = %d,buf = %s\n",read_count,buf);
    return 0;
}
如果写端没开,读端read会阻塞等待写端的打开(和匿名管道不同)
如果读端没开,写端write会阻塞等待读端的打开(和匿名管道不同)

命名管道的打开规则

在没有设置非阻塞属性的条件下,fifo读端和写端只要有一个没有打开,就会阻塞等待另一个的打开
在设置非阻塞属性的条件下,fifo的读端没有打开,立即返回失败,fifo写端没有打开,立即返回成功

user1和user2聊天实现

main.c
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
int main(int argc, char* argv[])
{
    mkfifo("./u1r_u2w_fifo",0644);
    mkfifo("./u1w_u2r_fifo",0644);   
    return 0;
}
u1.c
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>

int main(int argc, char* argv[])
{
    //首先发送和结束数据不应该有先后顺序,所以应该一个进程接收,一个进程发送
    int pid = fork();
    if(pid == 0){//子进程,发送数据
        //打开u1的写管道
        int fd = open("./u1w_u2r_fifo",O_WRONLY);
        while(1){
            char buf[1024];
            //从终端把输入读到buf里面
            int r_count = read(0,buf,sizeof(buf));
            //buf中的数据写到写管道里面
            write(fd,buf,r_count);
        }
        
    }else{//父进程接收数据
        //打开u1的读管道
        int fd = open("./u1r_u2w_fifo",O_RDONLY);
        while(1){
            char buf[1024];
            //从管道中把数据读到buf中
            int r_count = read(fd,buf,sizeof buf);
            //把buf中的数据写道终端中
            write(1,buf,r_count);
        }
    }
    return 0;
}
u2.c
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>

int main(int argc, char* argv[])
{
    //首先发送和结束数据不应该有先后顺序,所以应该一个进程接收,一个进程发送
    int pid = fork();
    if(pid == 0){//子进程,发送数据
        //打开u2的写管道
        int fd = open("./u1r_u2w_fifo",O_WRONLY);
        while(1){
            char buf[1024];
            //从终端把输入读到buf里面
            int r_count = read(0,buf,sizeof buf);
            //buf中的数据写到写管道里面
            write(fd,buf,r_count);
        }
    }else{//父进程接收数据
        //打开u2的读管道
        int fd = open("./u1w_u2r_fifo",O_RDONLY);
        while(1){
            char buf[1024];
            //从管道中把数据读到buf中
            int r_count = read(fd,buf,sizeof buf);
            //把buf中的数据写道终端中
            write(1,buf,r_count);
        }
    }
    return 0;
}

死锁问题

前提:我们都知道命名管道假设没有设置非阻塞属性的话,读写俩端必须都打开才能进行数据的传输,只要有一个没有打开,另外一个就会阻塞等待。比如写端没有打开读端就会阻塞等待写端的打开
如果想实现进程间互相通信,必须有俩个命名管道文件,假设叫fifo1和fifo2,现在假如在进程1中fifo1的写端打开了,进程2中fifo2的读端打开了,这种情况就会造成死锁问题,因为进程1在阻塞等待fifo1读端的打开,fifo2在阻塞等待fifo2写端的打开,双方互相等待,造成死锁问题

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决方法:让fifo1的读端打开,fifo1的写端打开,fifo2的读端打开,fifo2的写端打开,放在四个进程中,单独执行,及时阻塞,但并不影响执行

管道通信总结

进程间的通信就如它表面上一样,即不同进程之间进行信息的交流,而本质上是因为进程与进程之间是独立的,也就是说修改进程1中的数据对进程2不会产生影响,所以为了实现进程之间的交流必须引入一个第三方资源,不同进程之间都可以对第三方资源进程读写操作,这样就完成了进程之间的通信。因为第三方资源可以由操作系统的不同模块提供,也就产生了各种各样的通信方式,管道就是其中之一,管理利用的第三方资源是内存中的文件(也就虚拟内存)。管道又分为匿名管道和命名管道。匿名管道只能用于有血缘关系进程间的通信,因为匿名管道相当于一个没有文件名的虚拟文件,只有在父子进程中,它打开该虚拟文件的fd才是一样的,这样就可以对同一个打开的虚拟文件进行读写操作,从而实现进程间的通信。而命名管道则可以实现任意两个进程间的通信,因为它是有名字的,不同进程中通过open它的路径就是找到该虚拟文件。
以上就是我对管道通信的一些理解。

内存映射

共享内存

俩个进程共享同一块虚拟内存

什么是内存映射?

内存映射指的是将磁盘文件中的数据映射到内存之中,这样修改内存就相当于修改了磁盘
image-20240421152206887

mmap函数

void char* mmap(void *addr, size_t length,int prot, int flags, int fd, off_t offset)

参数描述:
addr:磁盘文件映射到内存中的地址,如果不特殊指定,为NULL即可
length:要映射多少字节,一般为1024
prot:映射区域的保护方式是什么
PROT_READ//可读
PROT_WRITE//可写
flags:映射这段区域的属性是什么
MMAP_SHARED//创建共享映射,内存的写入即磁盘文件的写入
MMAP_PRIVATE//创建私有映射
MMAP_ANONYMOUS//创建匿名映射
fd:映射磁盘文件的文件描述符
offset:文件偏移量

munmap函数

解除映射区域
int munmap(void *addr, size_t length);

匿名映射

匿名映射相当于没有磁盘文件映射到内存之中,这段映射内存会被初始化为0
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/mman.h>
int main(int argc, char* argv[])
{
    char *ptr = (char*)mmap(NULL,1024,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);
    int pid = fork();
    if(pid == 0 ){//子进程
        strcpy(ptr,"hello world");
        printf("child ps ptr = %s\n",ptr);
    }else{//父进程
        sleep(1);
        printf("father ps ptr = %s\n",ptr);
    }
    return 0;
}

文件映射

磁盘中文件的数据映射到内存中,对内存的修改就相当于对磁盘文件的修改

wmmap.c

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<stdlib.h>
int main(int argc, char* argv[])
{
    int fd = open("./mmap.txt",O_RDWR | O_CREAT,0644);
    ftruncate(fd,1024);
    char *ptr=(char*)mmap(NULL,1024,PROT_WRITE | PROT_READ,MAP_SHARED,fd,0);
    if(ptr == MAP_FAILED){
        perror("mmap error:");
        exit(1);
    }
    int i = 0;
    while(1){
        sprintf(ptr,"-------------%d-----------\n",i++);
        sleep(1);
    }
    return 0;
}

rmmap.c

#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<stdlib.h>
int main(int argc, char* argv[])
{
    int fd=open("./mmap.txt",O_RDWR);
    char *ptr=(char*)mmap(NULL,1024,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(ptr == MAP_FAILED){
        perror("mmap error:");
        exit(1);
    }
    while(1){
        printf("ptr = %s\n",ptr);
        sleep(1);
    }
    return 0;
}

网络问题

sudo nmcli network off

sudo nmcli network on

消息队列

message queue

消息队列的优点

如果要实现一条一条消息发送的这种通信方式,管道和内存映射没有办法满足,因为它俩本质就是文件,没办法区分这是第一条消息,那是第二条,除非你加上某种特定的方式

基本概念

消息队列是内核中维护的消息链表,是面向消息进行通信的,每次读取一条完整的消息。每条消息具有的属性为:
1.消息本身内容
2.消息数据部分长度
3.表示消息优先级的整数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

头文件

#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>

mq_open函数

打开消息队列
mqd_t mq_open(const char* name,int flags)
mqd_t mq_open(const char* name,int flags,mode_t mode,struct mq_ *attr)
1.返回值是消息队列的fd,mqd
2.name消息队列的名字
3.flags,mq的访问模式,O_RDONLY,O_WRONLY,O_RDWR,O_NONBLOCK,O_CREAT,O_EXCL
4.mode,mq的访问权限,0644
5.attr,mq的属性

mq_close函数

关闭消息队列

int mq_close(mqd_t mqd);

mq_getattr函数

获取消息队列属性

int mq_getattr(mode_t mqd,struct mq_attr *attr);

mq_setattr函数

设置消息队列属性

int mq_setattr(mode_t mqd,struct mq_attr *newattr,struct mq_attr *oldattr);

mq_send函数

向消息队列中发送消息

int mq_send(mqd_t mqd,const char *buf,size_t msg_len,unsigned int msg_prio)

mq_receive函数

接收消息队列中传来的消息

int mq_receive(mqd_t mqd,const char *buf,size_t msg_len,unsigned int * msg_prio)

mq_unlink函数

删除消息队列队列名

int mq_unlink(const char* name);

消息队列的四个属性

struct mq_attr{
	long mq_flags;//是否阻塞
	long mq_maxmsg;//最大消息数
	long mq_msgsize;//最大消息的大小,1024*9
	long mq_curmsgs;//当前消息的个数
}
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<mqueue.h>
#include<stdlib.h>

int main(int argc, char* argv[])
{
    mqd_t mqd=mq_open("/msgqueue",O_RDWR | O_CREAT,0644,NULL);
    int w_count = mq_send(mqd,argv[1],strlen(argv[1]),atoi(argv[2]));
    if(w_count == -1){
        perror("w_count:");
        exit(1);
    }
    return 0;
}
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<mqueue.h>
#include<stdlib.h>

int main(int argc, char* argv[])
{
    mqd_t mqd=mq_open("/msgqueue",O_RDWR,0644,NULL);
    char buf[8192];
    int flags;
    int r_count = mq_receive(mqd,buf,sizeof buf,&flags);
    if(r_count == -1){
        perror("w_count:");
        exit(1);
    }
    printf("buf = %s\n",buf);
    return 0;
}
#include<stdio.h>
#include<string.h>                                                              
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<mqueue.h>
#include<stdlib.h>
#include<sys/stat.h>
int main(int argc, char* argv[])
{
    int mqd = mq_open("/msgqueue",O_RDONLY);
    if(mqd == -1){
        perror("open mq error:");
        exit(1);
    }
    struct mq_attr attr;
    int ret = mq_getattr(mqd,&attr);
    struct mq_attr attr_new;
    attr.mq_maxmsg=20;
    attr_new=attr;
    mq_setattr(mqd,&attr_new,&attr);
    if(ret == -1){
        perror("getattr error:");
        exit(1);
    }
    printf("mq_flags = %lu\n",attr_new.mq_flags);
    printf("mq_maxmsg = %lu\n",attr_new.mq_maxmsg);
    printf("mq_msgsize = %lu\n",attr_new.mq_msgsize);
    printf("mq_curmsgs = %lu\n",attr_new.mq_curmsgs);

    return 0;
}
  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值