学习系统编程No.16进程间通信

Posted 今天还要努力

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习系统编程No.16进程间通信相关的知识,希望对你有一定的参考价值。

引言:

北京时间:2023/4/9/20:44,昨天,也就是这个周末的星期六,就是传说中的蓝桥杯,哈哈哈!摆烂,做题方面真不怎么行,可惜,当初可能是年少轻狂或者说是没什么经验阅历,希望在有了这次的经历之后,明年的今天,能够更加从容吧!谁让我们平时不怎么做题呢?准确的来说是没什么做题的习惯,也可以说是没什么时间做题,虽然我从小就知道时间像海绵里的水,挤一挤总是有的,但一个懒字了的,例如,蓝桥杯回来,本可以直接开始写博客,但是直接摆烂到了现在,算了,主要是没什么太大的压力(这个可能就是当代大一学生的现状,没目标,没压力),这种情况下,一个人有100中说服自己的理由摆烂,我也不例外,哈哈哈!但,还是那句话,摆烂归摆烂,不放弃就行,有小目标就好,哈哈哈!当然,像我其实压力还是很大的,最直接的一点就是CSDN上各路大佬坚持更新,我们自然而然也不甘屈居人后啦!所以导致我还是比较容易上道!所以今天我们就来学习一下有关进程间通信的知识和上篇博客有关动静态库的知识吧!

回顾动静态库

上篇博客我们了解到了,动态库由于使用的时候,不是把库中的所有数据拷贝到目标程序中,而是通过头文件的形式被调用,所以只有编译器可以识别到动态库路径,而操作系统不能识别,这样就会导致动态库在使用时,也就是可执行程序要被执行时,操作系统出现链接错误,并且了解到,为了解决这个问题,有三种解决方式,如下:

  1. 将库路径导入到环境变量中(临时)
  2. 软连接(永久)
  3. 配置文件方案

并且上篇博客,我们已经了解了前面两种,导入环境变量和建立软链接,所以接下来就让我们学习一下第三种,配置文件方案的方式吧!并且复习一下前两种,导入环境变量和创建软链接:

导入环境变量
也就是将动态库所在的路径导入到操作系统可以默认识别到的环境变量之中,因为本质就是为了让动态库所处的路径可以被操作系统找到,此时这个环境变量为:&LD_LIBRARY_PATH所以此时想要让操作系统找到动态库路径,只要把动态库路径导入到这个环境变量中,就行,指令:export LD_LIBRARY_PATH=&LD_LIBRARY_PATH:"动态库所在的路径",所以当我们把动态库所处的路径导入到了这个环境变量中之后,操作系统此时就可以找到对应的路径了,如下图所示:

建立软链接
还是明白,本质上就是为了让操作系统可以找到动态库所处路径,所以建立软链接的本质就是给动态库所处的路径建立一个在操作系统默认可以识别到的路径下的映射文件,指令:sudo ln -s /home/vimtest/其他人的动静态库/我的动态库/lib/libmymath.so /lib64/libmymath.so,如下图:


表示的就是,把动态库所处的路径在操作系统默认可以识别的lib64路径下建一个libmymath.so的软链接映射文件
如下图:此时操作系统默认就可以找到动态库所处的路径了

配置文件方案
首先还是明白,不管是那种方式,它的本质都是为了可以让操作系统找到动态库所在的文件路径,所以配置文件的方式,同理,如下图所示,并且注意:在Linux系统下,默认存在一个配置文件路径

并且明白,我们是可以在该配置文件路径下,创建自己的文件,指令:sudo touch /etc/ld.so.conf.d/bit_107.conf


如上图,我们就可以发现,在系统的配置文件路径下,此时就有了bit_107.conf这个文件,所以此时我们只需要把动态库所在的路径写入该文件中,如下图:

并且最后将这个配置文件的路径重新激活,指令:sudo ldconfig,此时就可以让动态库所在的路径被操作系统识别到了,如下图所示:

总:通过上述三种方式,此时就可以明白,操作系统在执行一个可执行程序的时候,有三种方式去寻找相应的动态库路径


深入动静态库内部

首先明白,上述的知识和上篇博客的知识,本质上只是动静态库在操作方面的知识,所以此时就让我们深入动静态库的内部,具体的看一看动静态库吧!(理论)

本质上还是动静态库在使用特征上的不同:使用静态库时,目标程序需要把整个静态库的实现给拷贝到程序中,而动态库则是通过头文件中函数接口的声明,间接使用动态库中函数接口的实现,并且动态库是可以通过条件判断来区分被使用了的函数接口和没被使用的函数接口,达到精准使用的能力,不需要盲目拷贝

当明白了上述知识,此时轻而易举就可以知道 ,当静态库加载到了程序之中,那么此时就会非常的占用资源,无论时空间资源,还是网络资源,而动态库在使用的时候,由于只是将头文件加载到了目标程序,而整个动态库的实现是在程序被执行的时候才加载到内存之中(因为本质上是二进制文件而已),所以此时动态库的使用将不会那么的占用资源,如下图所示:

静态库

动态库

动态库链接到目标程序,此时并没有拷贝动态库中的具体内容,而只是将动态库所对应的接口实现的地址通过头文件和条件判断替换到相应目标程序所对应的外部符号而已,具体过程如下图所示:

总的来说,就是在使用动态库的时候,不仅需要将目标程序加载到内存,还需要将动态库也加载到内存
6TFDD
并且还要 注意 ,根据上图,我们可以知道,物理内存中已经存放着相应的动态库了,如果此时别的程序,也就是别的进程,也同样需要用到该动态库,那么此时就当操作系统识别到内存中已经有相应的库,此时就不需要重新从磁盘中加载,而是直接将动态库通过页表映射到自己地址空间的共享区中就行,达到一库多用的作用,这样就可以大大的节省资源,无论是操作系统效率方面还是时空方面

如何理解动态库地址(fPIC

搞定了上述有关,动静态库在内存中的是如何调用的之后,此时我们就再深入了解一下动静态库具体的寻址方式,目的就是为了理解fPIC与位置无关码)的相关知识,如下述所说:

首先第一点要明白,一个程序是允许同时存在多个地址的,就类比为,一个人是允许有多个名字或者多个编号,学号、宿舍号、身份证号,明白了这点之后,显然就可以知道,一个程序肯定是存在不同的地址的,例,磁盘中的逻辑地址或者物理内存中的物理地址或者是虚拟地址空间中的虚拟地址,也就是表明,一个客观存在的物体或者人,在不同的场景之下,就会存在不同的属性,这个属性可能是形态上的,也可能是逻辑上的,明白了这点,就让我们继续深入地址吧!

明白了上述的知识,知道一个程序可以有不同的地址,逻辑地址,物理地址,虚拟地址,并且明白,这些不同的地址之所有有不同的名字,只是因为程序所处的场景不同而已,所以总的来说程序有很多地址,并且这写地址会因为场景不同而不同,但是总的来说,地址本质上只有两类,一类是绝对地址(静态库),一类是相对地址(动态库),并且明白虚拟地址空间中的编址属于绝对地址

理解绝对地址和相对地址
相信我们以前都了解过什么是绝对路径,什么是相对路径,同理,在编址的时候,地址也是一样的,绝对地址就是像物理内存中的物理地址一样,内存中的每一个字节都有属于自己的编号(地址),这个就叫绝对地址,而相对地址,就像是磁盘中的逻辑地址一样,以一个固定的点或者值为参照,然后通过偏移量的形式来进行编址,这个就是相对地址

为什么动态库使用相对编址
无论是从上篇博客知识,还是上述知识,此时我们都明白,一个目标程序在链接静态库的时候,是直接将静态库中所有的接口实现给拷贝到目标程序中,所以此时进程虚拟地址空间通过页表映射物理内存的时候,虚拟地址空间上存放的就是通过页表映射的物理内存上目标程序完整代码,使用绝对地址存放毫无关系,只要操作系统能找到就行;而动态库由于不同的进程,运行程度不同或者是运行的程序不同,需要使用的动态库接口数量上或者接口实现上不同,或者是需要使用的第三方库不同,所以注定每一个目标程序的虚拟地址空间上存储的动态库不同,每一个进程的地址空间不同,每一个进程的共享区间上存储的接口实现代码不同,所以动态库不允许使用绝对地址,而要使用相对地址,具体理由如下:

动态库不能使用绝对编址是因为动态库在被加载时,会被映射到进程的地址空间中,并且动态库是被多个程序共享的,而每个程序的内存地址空间都是不同的,所以动态库不能使用绝对编址是因为动态库是在被加载到进程的内存空间中时确定其最终的内存位置的,而不同进程的内存地址空间不同(可执行程序不同),因此,如果动态库使用绝对地址进行编址,那么在加载该库的不同进程中,其代码和数据可能会被映射到不同的内存地址上。这会导致代码无法在多个进程之间共享,因为其中一个进程中的地址无法被其他进程正确解释。因此,动态库必须采用相对地址,以确保其代码和数据可以在所有进程中使用相同的偏移量进行访问

如下图所示:

如上图,就是为什么要使用相对路径,而不是绝对路径,并且注意:动态库中的所有函数接口实现的地址都是偏移量,并且默认从0开始并
究极理解:

所以本质上动态库不允许使用绝对地址,就是因为地址空间和内存地址的映射关系是对应的,而地址空间上的动态库由于程序代码的不同,导致动态库不同,但又因为进程之间是共用一份动态库,所以导致地址空间上动态库的接口也必须相同,否则就不能通过对应的映射关系找到内存上的那份唯一的动态库数据,所以此时地址空间上就不能使用绝对地址,从而使用相对地址,因为使用相对地址,此时就可以通过动态库的起始位置加偏移量(只要各个进程使用的接口实现是相同的,偏移量就相同),所以调用时,调用的动态库的接口就相同,最后再根据映射关系,访问到的内存上的动态库的接口实现就相同

所以得出结论:

总:这就是为什么要使用fPIC的原因

反正终极目的就是理解动态库如何实现局部加载,相对地址的概念

测试动静态库的具体使用场景

明白了上述的知识,此时动静态库有关操作和理论方面的知识我们就搞定了,此时我们就再深入几个场景看看,动静态库具体还有什么特点,首先要明白,一个庞大的程序,是拥有非常多的代码和数据的,此时就不仅需要有动态库的实现,此时还要有静态库的实现,所以大部分的程序都是静态库和动态库同时使用来完成的,但是一般一些小的程序 ,仅仅使用动态库或者静态库就能完成,所以假如此时有一个动态库和一个静态库,并且这两个库中的函数接口实现都是目标程序需要的,那么此时系统是链接静态库还是链接动态库呢?如下图所示:


所以此时我们知道,当一个库中同时有静态库和动态库时,此时程序优先调用的是动态库,那么我相信,大家此时肯定有一个疑问,那就是那么如何优先调用静态库呢?如下图所示:

所以我们就知道了,当一个库中同时拥有动态库和静态库时,虽然默认的是动态库,但是我们依然可以优先调用静态库,如下图通过查看文件的属性,此时我们也可以看出程序链接的是动态库,还是静态库,具体指令:
gcc -o mytest-d main.c -I include -L lib -l mymath
gcc -o mytest-s main.c -I include -L lib -l mymath -static


总的来说就是有什么用什么,但是默认动态库优先,没有动态库才链接静态库

进程间通信

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

进程间通信发展

1.管道 2.System V进程间通信 3.POSIX进程间通信

进程间通信分类

1.管道 2.匿名管道pipe 3.命名管道

详细知识下篇博客见

总结:动静态库的知识真的非常多,终于全部搞定了,难搞呀!接下来,就让我们再谈进程吧!进程间通行详解,下篇博客见啦!

23Linux系统编程进程间通信的集中方式

一、进程间通信概述

1、进程间通信的目的

传输数据
  一个进程须要将它的数据发送给还有一个进程。发送的数据量在一个字节到几M字节之间

共享数据
  多个进程想要操作共享数据,一个进程对共享数据

通知事件
  一个进程须要向还有一个或一组进程发送消息。通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

资源共享
  多个进程之间共享相同的资源。为了作到这一点,须要内核提供锁和同步机制。

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

2、Linux 进程间通信(IPC)的发展

  linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件公布中心)在进程间通信方面的側重点有所不同。前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”。通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。

Linux则把两者继承了下来

  • 早期UNIX进程间通信

  • 基于System V进程间通信

  • 基于Socket进程间通信

  • POSIX进程间通信。

UNIX进程间通信方式包含:管道、FIFO、信号。

System V进程间通信方式包含:System V消息队列、System V信号灯、System V共享内存

POSIX进程间通信包含:posix消息队列、posix信号灯、posix共享内存。

因为Unix版本号的多样性,电子电气project协会(IEEE)开发了一个独立的Unix标准,这个新的ANSI Unix标准被称为计算机环境的可移植性操作系统界面(PSOIX)。

现有大部分Unix和流行版本号都是遵循POSIX标准的。而Linux从一開始就遵循POSIX标准;

二、linux使用的进程间通信方式

  1. 管道(pipe):流管道(s_pipe)和有名管道(FIFO)

  2. 信号(signal)

  3. 消息队列

  4. 共享内存

  5. 信号量

  6. 套接字(socket)

1、管道(pipe)

管道这样的通讯方式有两种限制,一是半双工的通信,数据仅仅能单向流动,二是仅仅能在具有亲缘关系的进程间使用。进程的亲缘关系一般是指父子进程关系。

流管道s_pipe: 去除了第一种限制,能够双向传输.

管道可用于具有亲缘关系进程间的通信

有名管道(FIFO):name_pipe克服了管道没有名字的限制,因此,除具有管道所具有的功能外。它还同意无亲缘关系进程间的通信;

2、信号量(semophore)

信号量是一个计数器,能够用来控制多个进程对共享资源的訪问。

它常作为一种锁机制。防止某进程正在訪问共享资源时。其它进程也訪问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

信号是比較复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还能够发送信号给进程本身。linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制。又能够统一对外接口,用sigaction函数又一次实现了signal函数);

3、消息队列(message queue)

消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。

消息队列克服了信号传递信息少、管道仅仅能承载无格式字节流以及缓冲区大小受限等缺点。

消息队列是消息的链接表。包含Posix消息队列system V消息队列。有足够权限的进程能够向队列中加入消息,被赋予读权限的进程则能够读走队列中的消息。

4、信号(singal)

信号是一种比較复杂的通信方式,用于通知接收进程某个事件已经发生。

主要作为进程间以及同一进程不同线程之间的同步手段。

5、共享内存(shared memory)

共享内存就是映射一段能被其它进程所訪问的内存。这段共享内存由一个进程创建。但多个进程都能够訪问。共享内存是最快的 IPC 方式,它是针对其它进程间通信方式执行效率低而专门设计的。

它往往与其它通信机制。如信号量,配合使用。来实现进程间的同步和通信。

使得多个进程能够訪问同一块内存空间。是最快的可用IPC形式。

是针对其它通信机制执行效率较低而设计的。往往与其它通信机制,如信号量结合使用。来达到进程间的同步及相互排斥。

6、套接字(socket)

套解口也是一种进程间通信机制,与其它通信机制不同的是。它可用于不同机器间的进程通信

起初是由Unix系统的BSD分支开发出来的,但如今一般能够移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

 

各种通信方式的比較和优缺点

  1. 管道:速度慢。容量有限,仅仅有父子进程能通讯

  2. FIFO:不论什么进程间都能通讯,但速度慢

  3. 消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

  4. 信号量:不能传递复杂消息,仅仅能用来同步

  5. 共享内存区:能够非常easy控制容量,速度快,但要保持同步,比方一个进程在写的时候。还有一个进程要注意读写的问题,相当于线程中的线程安全。当然。共享内存区相同能够用作线程间通讯,只是没这个必要,线程间本来就已经共享了同一进程内的一块内存

更多详细内容参见:https://blog.csdn.net/godleading/article/details/78391159#%E8%BF%9B%E7%A8%8B%E9%80%9A%E4%BF%A1%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F

 






以上是关于学习系统编程No.16进程间通信的主要内容,如果未能解决你的问题,请参考以下文章

Linux系统编程--进程间通信 ---管道篇

Linux C语言高级编程之使用消息队列实现进程间通信!重点内容!!!

请教一个Linux下C语言的进程间的信号问题

云原生技术

Linux系统编程之进程间通信

Linux系统编程-进程间通信(共享内存)