首页 > 系统相关 >Linux操作系统下的进程通信

Linux操作系统下的进程通信

时间:2024-12-02 19:29:56浏览次数:6  
标签:操作系统 映射 int 通信 管道 mq 内存 Linux 进程

目录

1.进程通信的概念

2.管道

1.匿名管道

pipe函数

2.命名管道

mkfifo函数

3.内存映射

mmap()函数

munmap()函数

4.消息队列

消息队列函数

5.总结

 管道:

内存映射

消息队列


1.进程通信的概念

        进程间通信简称为IPC(Inter process communication),进程间通信就是在不同进程之间传播或交换信息。

        由于各个运行的进程之间具有独立性,这个独立性主要体现在数据层面,而逻辑层面可以是私有的也可以是共有的(例如父子进程),因此各个进程之间要实现通信是非常困难的。

        各个进程之间要是想实现通信,一定要借助第三方资源,这些进程就可以向这个第三方资源写入或读取数据,进而实现进程之间的通信,这个第三方资源实际上就是操作系统提供的一段内存区域。

       

        因此,进程间通信的本质就是,让不同的进程看到同一份资源(内存、文件、内核缓存等)。由于这份资源可以由操作系统中不同的模块提供,因此出现了不同的进程间通信的方式。

进程间通信的目的:

        数据传输:一个进程需要将它的数据发送给另外一个进程

        资源共享:多个进行之间共享同样的资源

        通知事件:一个进程需要向另一个或一组进程发送消息,通知他们发生了某种事件,比如进程终止时需要通知其父进程。

        进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有的陷入和异常,并能够及时知道它的状态改变。

2.管道

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

1.匿名管道

        匿名管道用于进程间通信,且仅限于本地关联进程之间的通信。

        进程间通信的本质就是,让不同的进程看到同一份资源,使用匿名管道实现父子进程间通信的原理就是:让两个父子进程看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或者读取操作,进而实现父子间进程通信。

注意:

        这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进写时拷贝

        管道虽然用的是文件的方案,但是操作系统一定不会把进程进行通信的数据刷新到磁盘党总,因为这样做有IO参与会降低效率,而且也没有必要。这种文件是一批不会把数据写到磁盘党总的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存存在,而不会在磁盘当中存在。

pipe函数

        函数描述

                创建匿名管道

        函数原型

                int pipe(int pipefd[2]);

        函数参数:

                pipefd是一个传出参数,用于返回两个管道读端和写端的文件描述符。

                pipefd[0]:管道读端的文件描述符

                pipefd[1]:   管道写端的文件描述符

        函数返回值:

                成功:返回0

                失败:返回-1,并设置errno

        在创建匿名管道实现父子进程通信的过程中,需要pipe()函数和fork()函数搭配使用,步骤如下:

        1.父进程调用pipe函数创建管道

        2.父进程创建子进程

        3.父进程关闭写端,子进程关闭读端(根据情况而定)

注意:

        1.管道只能进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。

        2.从管道写端写入的数据会被存到内核缓冲区,直到管道的读端被读取。

int main(int argc, char* argv[])
  8 {  
  9     int pipefd[2];
 10     pipe(pipefd);//创建管道文件
 11     int pid=fork();
 12     if(pid==0){
 13         close(pipefd[0]);//关闭读端
 14         int write_count=write(pipefd[1],"hello word",5);
 15         printf("write_count=%d\n",write_count);
 16     }else{
 17         close(pipefd[1]);//关闭写端
 18         char buf[512];
 19         sleep(1);
 20         int read_count=read(pipefd[0],buf,sizeof(buf));
 21         printf("read_count=%d buf=%s\n",read_count,buf);
 22 
 23     }
 24     return 0;
 25 }  5

在这里会有以下几种情况:

        1.当管道中没有数据时:write返回成功写入的字节数,读端进程阻塞在read上()因为没有东西可以读到)

        2.当管道中的数据没有满:write放回成功写入的字节数,read返回成功读取的字节数

        3.管道已满:写端进行阻塞在write上(因为写不进去了),read返回成功读取的字节数

        4.写端全部关闭:read正常读,返回读取的字节数

        5.读端全部关闭:写端进程会异常结束。

2.命名管道

        匿名管道只能用于共同祖先的进程(具有亲缘关系的进程)之间的通信。如果想实现两个毫无关系进程之间的通信,可以使用命名管道来实现。命名管道是一种特殊类型的文件,两个进程通过命名管道的文件打开同一个管道文件,此时两个进程就看到了同一份资源,进而就可以进行通信了。

        命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘里有一个简单的映像,但是这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信的数据刷新到磁盘中。

mkfifo函数

        函数描述

                程序中创建命名管道

        头文件

                #include<sys/types.h>

                #include<sys/stat.h>

       函数原型

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

        函数参数

                pathname:表示要创建的命名管道

                mode:表示要创建命名管道文件的默认权限

        函数返回值

                成功:返回0

                失败:返回-1

       命名管道在父子进程间的通信

#include<sys/stat.h>
mkfifo("./fifo",0664);
int fd=open("./fifo",O_RDWR);
int pid=fork();
if(pid==0){
    write(fd,"hello fifo",10);
}
else if(pid>0){
    char buf[1024]={'\0'};
    int ret=read(fd,buf,sizeof(buf));
    printf("read count =%d,buf=%s\n",ret,buf);
}

命名管道在没有血缘关系的进程间通信

wfifo.c

mkfifo("./fifo",0664);
int fd=open("./fifo",O_WRONLY);
printf("fd=%d\n",fd);
write(fd,"hello fifo",10);

rfifo.c

int fd=open("./fifo",O_RDONLY);
char buf[1024];
int ret=read(fd,buf,sizeof(buf));
printf("read count=%d,buf=%s\n",ret,buf);

命名管道打开的规则:

        这里又涉及到打开文件的第二个参数,对于管道来说,默认是阻塞的,以O_NONBLOCK方式打开可以做非阻塞。

        读进程打开FIFO,并且没有写进程打开时

                没有O_NONBLOCK:阻塞直到有进程打开该FIFO

                有O_NONBLOCK:立刻返回成功

        写进程打开FIFO,并且没有读进程打开时:

                没有O_NONBLOCK:阻塞直到有读进程打开该FIFO

                有O_NONBLOCK:立刻返回失败

3.内存映射

        内存映射是将磁盘文件的数据映射到内存中,用户通过修改内存就能修改磁盘文件。

        文件映射:将文件的一部分映射到调用进程的虚拟内存中,对文件映射部分的访问转化为对应内存区域的字节操作。映射页面会按需自动从文件中加载。

        匿名映射:一个匿名映射没有对应的文件。其映射页面的内容会被初始化为0.

一个进程所映射的内存可能与其他进程的映射共享,共享的两种方式

        1.两个进程对同一文件的同一区域映射

        2.fork()创建的子进程继承其父进程的映射

mmap()函数

        函数描述

                在调用进程的虚拟地址空间创建一个新的内存映射

        头文件

                <sys/mman.h>

        函数原型

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

        函数参数

                addr:指向欲映射的内存起始地址,通常设置为NULL,由系统会自动选定地址

                length:映射的长度

                prot:映射区域的保护方式

                        PROT_READ:映射区域可读取

                        PROT_WRITE:映射区域可修改

                flags:影响映射区域的特性。必须指定MAP_SHARED或MAP_PRIVATE

                        MAP_SHARED:创建共享映射。对映射的写入会写入文件里,其他共享映射的进程可见。

                        MAP_ANONYMOUS:创建匿名映射。此时会忽略参数fd,设置为-1,不涉及文件,没有血缘关系的进程不能共享。

                fd:要映射的文件描述符,匿名映射设为-1

                offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小(4k)的整数倍。

        函数返回值

                若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1)报错原因存于errno中。

munmap()函数

        函数描述

                解除映射区域

        函数原型:

                int munmap(void *addr,size_t length);

        函数参数

                addr:指要解除映射的内存起始地址

                length:解除映射的长度

例如,在父子进程之间的通信

char* ptr=(char*)mmap(NULL,1024,PORT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
int pid=fork();
if(pid==0){
    strcpy(ptr,"hello world");
    printf("child ptr=%s\n",ptr);
}else if(pid>0){
    sleep(1);
    printf("parent ptr=%s\n",ptr);
}

在没有血缘关系进程通信

wmmap.c

int fd=open("./mmap.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
ftryncate(fd,1024);//扩展文件大小,空文件内存映射会报错
char* ptr=(char*)mmap(NULL,1024,PORT_WRITE,MAP_SHEARED,fd,0);
if(ptr==MAP_FAILED){
    perror("mmap error");
    exit(1);
}
int i=0;
while(1){
    sprintf(ptr,"----%d---\n",i++);
    sleep(1);
}

rmmap.c

int fd=open("./mmap.txt",O_RDONLY);
char* ptr=(char*)mmap(NULL,1024,PORT_READ,MAP_SHARED,fd,0);
if(ptr==MAP_FAILED){
    perror("mmap error");
    exit(1);
}
int i=0;
while(1){
    printf("ptr=%s\n",ptr);
    sleep(1);
}

4.消息队列

        消息队列就是保存在内核的消息链表,消息队列是面向消息进行通信的,一次读取一条完整的消息,每条消息中还包含一个整数表示优先级,可以根据优先级读取消息。进程A可以往队列中写入消息,进程B读取信息。并且,进程A写入消息后就可以终止,进程B在需要的时候再去读取。

每条信息通常具有以下属性:

                1.一个表示优先级的整数

                2.消息数据部分的长度

                3.消息数据本身

消息队列函数

        头文件

                #include<fcntl.h>

                #include<sys/stat.h>

                #include<mqueue.h>

        打开和关闭消息队列

                mqd_t mq_open(const char* name,int oflag);

                mqd_t mq_open(const char* name,int oflag,mode_t mode,struct mq_attr *attr);

                int mq_close(mqd_t mqdes);

        获取和设置消息队列属性

                int mq_getattr(mqd_t mqdes,struct mq_attr *attr);

                int mq_setattr(mqd_t mqdes,const struct mq_attr *newattr,struct mq_attr *oldattr);

        在队列中写入和读取一条消息

                int mq_send(mqd_t mqdes,const char *msg_ptr,size_t msg_len,unsigned intmsg_prio);

                ssize_t mq_receive(mqd_t mqdes,char *msg_ptr,size_t msg_len,unsignde int *msg_prio);

        删除消息队列名

                int mq_unlink(const char* name);

        函数参数和返回值

                name:消息队列名

                oflag:打开方式,类似open函数

                        必选项:O_RDONLY,O_WRONLY,O_RDWR

                        可选项:O_NONBLOCK,O_CREAT,O_EXCL

                mode:访问权限,oflag中含有O_CREAT且消息队列不存在时提供该参数

                attr:队列属性,open时传NULL表示默认属性

                mqdes:表示消息队列描述符

                msg_ptr:指向缓冲区的指针

                msg_len:缓冲区的大小

                msg_prio:消息优先级

        返回值:

                成功返回0,open返回消息队列的描述符,mq——receive返回成功写入的字节数

                失败返回-1

               

        写程序wmsg.c

mqd_t mqd=mq_open("/mymsg",O_RDWR|O_CREAT,0664,NULL);
if(mqd==-1){
    perror("mq_open error");
    exit(1);
}
int ret=mq_send(mqd,argv[1],strlen(argv[1])+1,atoi(argv[2]));
if(ret==-1){
    perror("mq_send error");
    exit(1);
}

        读程序rmsg.c

mqd_t mqd=mq_open("/mymsg",O_RDONLY);
if(mqd==-1){
    perror("mq_open error");
    exit(1);
}
char bud[8192];
unsigned int prio;
int ret=mq_receive(mqd,buf,size(buf),&prio);
if(ret==-1){
    perror("mq_send error");
    exit(1);
}
printf("buf=%s,prio=%d\n",buf,prio);

        查看消息队列的程序msg.c

mqd_t mqd=msg_open("/mymsg",O_RDONLY);
if(mqd==-1){
    perror("mq_open error");
    exit(1);
}
struct mq_attr attr;
mq_getattr(mqd,&attr);
printf("mq_maxmsg=%lu\n",attr.mq_maxmsg);
printf("mq_msgsize=%lu\n",attr.mq_msgsize);
printf("mq_curmsg=%lu\n",attr.mq_curmsgs);

5.总结

 管道:

        优点:

                1.简单易用:对于具有亲缘关系的进程通信,管道是一种非常简单直接的方式。它的实现机制相对比较容易理解,只需要通过系统调用pipe()或mkfifo()就可以建立通信管道,然后使用read()和write()函数进行数据读写。

                2. 高效性:管道的通信是基于内存缓冲区的,数据的传输速度相对较快,在数据量不是特别大的情况下,能够很好地满足进程间通信的需求。

        缺点:

                1.半双工限制:管道只能单向传输数据。如果要实现双向通信,需要创建两个管道,增加了系统的复杂性。

                2.数据量限制:管道的缓冲区大小是有限的,如果写入管道的数据量超过了缓冲区的大小,写入操作可能会被阻塞,直到有足够的空间来写入数据。

                3. 生命周期限制:匿名管道随着创建它的进程结束而结束,命名管道虽然可以在不相关的进程之间通信,但是也需要正确地管理其生命周期,否则可能会出现问题。

内存映射

        优点:

                1.高效的数据访问:内存映射避免了传统的通过系统调用进行数据读写的方式,减少了数据在用户空间和内核空间之间的复制次数。进程可以像访问内存一样访问文件内容,对于大数据量的读写操作效率更高。

                2.共享方便:多个进程可以方便地共享同一个内存映射区域,无论是共享一个文件还是共享一段内存,都可以通过简单的内存操作来实现数据的共享和传递。

        缺点:

                1.复杂性:内存映射的实现相对复杂,需要对虚拟内存管理、文件系统等有一定的了解。在使用内存映射时,需要正确地处理映射区域的大小、偏移量等参数,否则可能会出现错误。

                2.同步问题:当多个进程同时访问内存映射区域时,需要考虑同步问题。如果没有合适的同步机制,可能会导致数据不一致或者竞争条件的出现。例如,两个进程同时对同一个内存映射区域进行写操作,可能会导致数据混乱。

消息队列

        优点:

                1.异步通信:消息队列允许进程以异步的方式进行通信。发送进程可以将消息发送到消息队列后继续执行其他任务,接收进程可以在自己方便的时候从消息队列中接收消息,这样可以提高系统的整体效率。

                2.解耦性好:进程之间通过消息队列进行通信,彼此之间的依赖关系比较松散。发送进程不需要知道接收进程的具体细节,只要按照约定的消息格式发送消息即可。而且消息队列可以在多个不相关的进程之间传递消息,增强了系统的灵活性。

                3. 消息选择:接收进程可以根据消息类型有选择地接收消息,这样可以对消息进行分类处理,提高了通信的灵活性。

        缺点:

                1.额外开销:消息队列的实现需要内核的支持,包括消息的存储、管理等,这会带来一定的系统开销。相比管道等简单的通信方式,消息队列的创建和维护成本更高。

                2.可能的消息丢失:在一些情况下,如消息队列已满或者系统出现故障时,可能会出现消息丢失的情况。虽然可以通过一些机制来尽量避免消息丢失,如持久化消息等,但这也会增加系统的复杂性。

标签:操作系统,映射,int,通信,管道,mq,内存,Linux,进程
From: https://blog.csdn.net/gemluoye/article/details/144168420

相关文章

  • Linux history 命令详解
    简介history 命令显示当前 shell 会话中以前执行过的命令列表。这对于无需重新输入命令即可重新调用或重新执行命令特别有用。示例用法显示命令历史列表history#示例输出如下:1ls-l2cd/var/log3catsyslog执行历史记录中的命令!<number>!2#number表......
  • 【Nginx学习】5大绝招揭秘:Nginx进程间通信机制之互斥锁——文件锁实现的ngx_shmtx_t锁
    ......
  • linux-11 关于shell(十)执行入口、模数
      比如说在系统刚刚启动的时候,这里要求我们进行登录,我输入用户名,然后输入密码,就可以正常登录,正常登录以后,会显示什么,叫命令提示符对吧?叫做prompt,我们把它称为叫命令提示符。如下图,那就意味着我们在底下可以输入命令了,对吧?既然叫命令提示符,那么所输入的一定得是命令,要注......
  • ssh连接linux服务器中断后,如何让命令继续在服务器运行
    ssh连接linux服务器中断后,如何让命令继续在服务器运行这个问题也许是我们这些小白比较头疼的问题,尤其对于做机器学习需要花很久的时间才能训练出一个结果。然而就在这时,因为各种不可抗力我们使用ssh连接服务器时,ssh的窗口突然断开了连接,那么在服务器上跑的程序就也跟着断掉了,之前......
  • 不同源H5页面消息通信(web-view 内的H5向父级页面发消息并回传结果给子页面)
    文章目录父级H5页面子级H5页面应用场景是两个H5不同源的情况下实现消息互通。本示例使用uniapp开发H5,父级H5通过<web-view>组件加载子H5页面;子H5页面向父级H5发送消息调起父级H5页面的微信扫一扫功能,再将扫一扫结果回传给子H5页面。父级H5链......
  • 操作系统之程序的链接与装入
    一、链接链接是将各个目标模块以及它们所需的库函数装配成一个完整的可执行文件的过程。根据链接的时间不同,链接方式可分为:静态链接:在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件,之后不再拆开。装入时动态链接:将各目标模块装入内存时,边装入......
  • 将Linux内核参数ip_forward设置为0后,以下那些功能可能受影响:Nginx、LVS、Keepalived、
    将Linux内核参数ip_forward设置为0后,以下那些功能可能受影响:Nginx、LVS、Keepalived、iptables、firewalld、HAProxy将Linux内核参数ip_forward设置为0后,影响的是系统的IP数据包转发功能。它决定了Linux是否能够作为路由器转发非本机目标的数据包。当其值为0时,系......
  • Qt/C++实现帧同步播放器/硬解码GPU绘制/超低资源占用/支持8K16K/支持win/linux/mac/嵌
    一、前言首先泼一盆冷水,在不同的电脑上实现完完全全的帧同步理论上是不可能的,市面上所有号称帧同步的播放器,同一台电脑不同拼接视频可以通过合并成一张图片来绘制实现完完全全的帧同步,不同电脑,受限于网络的延迟,命令交互的时间占用,不同硬件之间的主频偏差等,肯定会有些许的误差,只要......
  • 渗透测试之Web基础之Linux病毒编写——泷羽sec
    声明:        学习视频来自B站UP主泷羽sec,如涉及侵权马上删除文章。本文只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负泷羽sec的个人空间-泷羽sec个人主页-哔哩哔哩视频(bilibili.com)https://space.bilibili.com/350329294导读:时刻保持谦逊......
  • Linux C/C++编程之静态库
    【图书推荐】《LinuxC与C++一线开发实践(第2版)》_linuxc与c++一线开发实践pdf-CSDN博客《LinuxC与C++一线开发实践(第2版)(Linux技术丛书)》(朱文伟,李建英)【摘要书评试读】-京东图书(jd.com)10.3.1 静态库的基本概念静态库文件的后缀为.a,在Linux下一般命名为libxxx.a。当......