最全Linux应用开发八股文(三)——进程(下)

你好,我是拉依达。

这是我的Linux应用开发八股文详细解析系列

本系列最开始是我在csdn上更新的文章全文总字数超3w字,现重新对内容进行整理,希望可以帮助到更多学习嵌入式的同学。

【下面是拉依达推荐学习相关专栏:】
一、Linux驱动学习专栏:拉依达的Linux驱动八股文 - 牛客网
二、Linux应用学习专栏:拉依达的Linux应用八股文 - 牛客网
【我的嵌入式学习和校招经验】 拉依达的嵌入式学习和秋招经验-CSDN博客
嵌入式学习规划/就业经验指导,可私信咨询

———————————————————————————————————————————————————

2.3 内存映射(mmap)

如果想要实现进程间通信,可以通过函数创建一块内存映射区,和管道不同的是管道对应的内存空间在内核中,而内存映射区对应的内存空间在进程的用户区(用于加载动态库的那个区域),也就是说进程间通信使用的内存映射区不是一块,而是在每个进程内部都有一块。

由于每个进程的地址空间是独立的,各个进程之间也不能直接访问对方的内存映射区,需要通信的进程需要将各自的内存映射区和同一个磁盘文件进行映射,这样进程之间就可以通过磁盘文件这个唯一的桥梁完成数据的交互了。

使用内存映射区既可以进程有血缘关系的进程间通信也可以进程没有血缘关系的进程间通信。

创建内存映射区函数

创建内存映射区的函数原型如下

#include <sys/mman.h>
// 创建内存映射区
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

参数:

  • addr: 从动态库加载区的什么位置开始创建内存映射区,一般指定为 NULL, 委托内核分配

  • length: 创建的内存映射区的大小(单位:字节),实际上这个大小是按照 4k 的整数倍去分配的

  • prot: 对内存映射区的操作权限

    • PROT_READ: 读内存映射区

    • PROT_WRITE: 写内存映射区

    • 如果要对映射区有读写权限: PROT_READ | PROT_WRITE

  • flags:

    • MAP_SHARED: 多个进程可以共享数据,进行映射区数据同步
    • MAP_PRIVATE: 映射区数据是私有的,不能同步给其他进程
  • fd: 文件描述符,对应一个打开的磁盘文件,内存映射区通过这个文件描述符和磁盘文件建立关联

  • offset: 磁盘文件的偏移量,文件从偏移到的位置开始进行数据映射,使用这个参数需要注意两个问题:

    • 偏移量必须是 4k 的整数倍,写 0 代表不偏移
    • 这个参数必须是大于 0 的

返回值:

  • 成功:返回一个内存映射区的起始地址

  • 失败: MAP_FAILED (that is, (void *) -1)

释放内存映射区函数

释放内存映射区函数如下

int munmap(void *addr, size_t length);

参数:

  • addr: mmap () 的返回值,创建的内存映射区的起始地址
  • length: 和 mmap () 第二个参数相同即可
  • 返回值:函数调用成功返回 0,失败返回 -1

内存映射区进程间通信(利用同一个文件映射到两个内存,内存和文件数据同步)

需要在每个进程中分别创建内存映射区,但是这些进程的内存映射区必须要关联相同的磁盘文件,这样才能实现进程间的数据同步。

  1. 分别创建内存映射区,关联同一个文件, 得到一个起始地址, 假设使用ptr指针保存这个地址 2. 进程A往自己的内存映射区写数据, 数据同步到了磁盘文件中, 磁盘文件数据又同步到子进程的映射区中 3. 进程B从自己的映射区往外读数据, 这个数据就是进程A写的

内存映射拷贝文件

使用内存映射区拷贝文件思路:

  1. 打开被拷贝文件,得到文件描述符 fd1,并计算出这个文件的大小 size
  2. 创建内存映射区 A 并且和被拷贝文件关联,也就是和 fd1 关联起来,得到映射区地址 ptrA
  3. 创建新文件,得到文件描述符 fd2,用于存储被拷贝的数据,并且将这个文件大小拓展为 size
  4. 创建内存映射区 B 并且和新创建的文件关联,也就是和 fd2 关联起来,得到映射区地址 ptrB
  5. 进程地址空间之间的数据拷贝,memcpy(ptrB, ptrA,size),数据自动同步到新建文件中
  6. 关闭内存映射区

2.4 共享内存

共享内存不同于内存映射区,它不属于任何进程并且不受进程生命周期的影响。 通过调用 Linux 提供的系统函数就可得到这块共享内存。使用之前需要让进程和共享内存进行关联,得到共享内存的起始地址之后就可以直接进行读写操作了,进程也可以和这块共享内存解除关联,解除关联之后就不能操作这块共享内存了。在所有进程间通信的方式中共享内存的效率是最高的。

共享内存操作默认不阻塞,如果多个进程同时读写共享内存,可能出现数据混乱,共享内存需要借助其他机制来保证进程间的数据同步,比如:信号量,共享内存内部没有提供这种机制。

创建/打开共享内存函数——shmget

在使用共享内存之前必须要先做一些准备工作,如果共享内存不存在就需要先创建出来,如果已经存在了就需要先打开这块共享内存。不管是创建还是打开共享内存使用的函数是同一个,函数原型如下:

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

参数:

  • key: 类型 key_t 是个整形数,通过这个key可以创建或者打开一块共享内存,该参数的值一定要大于0
  • size: 创建共享内存的时候,指定共享内存的大小,如果是打开一块存在的共享内存,size 是没有意义的
  • shmflg:创建共享内存的时候指定的属性
    • IPC_CREAT: 创建新的共享内存,如果创建共享内存,需要指定对共享内存的操作权限,比如:IPC_CREAT | 0664
    • IPC_EXCL: 检测共享内存是否已经存在了,必须和 IPC_CREAT 一起使用

返回值:共享内存创建或者打开成功返回标识共享内存的唯一的 ID,失败返回 - 1

生成shmget函数key值的函数——ftok

shmget () 函数的第一个参数是一个大于 0 的正整数,如果不想自己指定可以通过 ftok () 函数直接生成这个 key 值。该函数的函数原型如下:

// ftok函数原型
#include <sys/types.h>
#include <sys/ipc.h>

// 将两个参数作为种子, 生成一个 key_t 类型的数值
key_t ftok(const char *pathname, int proj_id);

参数:

  • pathname: 当前操作系统中一个存在的路径

  • proj_id: 这个参数只用到了 int 中的一个字节,传参的时候要将其作为 char 进行操作,取值范围: 1-255

返回值:函数调用成功返回一个可用于创建、打开共享内存的 key 值,调用失败返回 - 1

shmget函数和ftok函数使用例子

// 根据路径生成一个key_t
key_t key = ftok("/home/robin", 'a');
// 创建或打开共享内存
shmget(key, 4096, IPC_CREATE|0664);

关联共享内存函数——shamt(把共享内存和进程的虚拟内存关联上,通过唯一的共享内存号)

创建 / 打开共享内存之后还必须和共享内存进行关联,这样才能得到共享内存的起始地址,通过得到的内存地址进行数据的读写操作,关联函数的原型如下:

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:

  • shmid: 要操作的共享内存的 ID, 是 shmget () 函数的返回值
  • shmaddr: 共享内存的起始地址,用户不知道,需要让内核指定,写 NULL
  • shmflg: 和共享内存关联的对共享内存的操作权限
    • SHM_RDONLY: 读权限,只能读共享内存中的数据

    • 0: 读写权限,可以读写共享内存数据

返回值:关联成功,返回值共享内存的起始地址,关联失败返回 (void *) -1

解除关联共享内存函数——shmdt

当进程不需要再操作共享内存,可以让进程和共享内存解除关联,另外如果没有执行该操作,进程退出之后,结束的进程和共享内存的关联也就自动解除了。

int shmdt(const void *shmaddr);
  • 参数:shmat () 函数的返回值,共享内存的起始地址
  • 返回值:关联解除成功返回 0,失败返回 - 1

删除共享内存——shmctl

shmctl () 函数是一个多功能函数,可以设置、获取共享内存的状态也可以将共享内存标记为删除状态

当共享内存被标记为删除状态之后,并不会马上被删除,直到所有的进程全部和共享内存解除关联,共享内存才会被删除。因为通过 shmctl () 函数只是能够标记删除共享内存,所以在程序中多次调用该操作是没有关系的。

// 共享内存控制函数
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

// 参数 struct shmid_ds 结构体原型          
struct shmid_ds {
	struct ipc_perm shm_perm;    /* Ownership and permissions */
	size_t          shm_segsz;   /* Size of segment (bytes) */
	time_t          shm_atime;   /* Last attach time */
	time_t          shm_dtime;   /* Last detach time */
	time_t          shm_ctime;   /* Last change time */
	pid_t           shm_cpid;    /* PID of creator */
	pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    // 引用计数, 多少个进程和共享内存进行了关联
	shmatt_t        shm_nattch;  /* 记录了有多少个进程和当前共享内存进行了管联 */
	...
};

参数:

  • shmid: 要操作的共享内存的 ID, 是 shmget () 函数的返回值
  • cmd: 要做的操作
    • IPC_STAT: 得到当前共享内存的状态
    • IPC_SET: 设置共享内存的状态
    • IPC_RMID: 标记共享内存要被删除了
  • buf:
    • cmd==IPC_STAT, 作为传出参数,会得到共享内存的相关属性信息
    • cmd==IPC_SET, 作为传入参,将用户的自定义属性设置到共享内存中
    • cmd==IPC_RMID, buf 就没意义了,这时候 buf 指定为 NULL 即可

返回值:函数调用成功返回值大于等于 0,调用失败返回 - 1

共享内存进程间通信

  1. 调用linux的系统API(shmget)创建一块共享内存

    • 这块内存不属于任何进程, 默认进程不能对其进行操作
  2. 准备好进程A, 和进程B, 这两个进程需要和创建的共享内存进行关联

    • 关联操作: 调用linux的 api(shmat)
    • 关联成功之后, 得到了这块共享内存的起始地址
  3. 在进程A或者进程B中对共享内存进行读写操作

    • 读内存: printf() 等;
    • 写内存: memcpy() 等;
  4. 通信完成, 可以让进程A和B和共享内存解除关联

    • 解除成功, 进程A和B不能再操作共享内存了
    • 共享内存不受进程生命周期的影响的
  5. 共享内存不在使用之后, 将其删除

    • 调用linux的api函数, 删除之后这块内存被内核回收了

共享内存(shm)和内存映射区(mmap)的的区别

  1. 实现进程间通信的方式
  • 共享内存: 多个进程只需要一块共享内存就够了,共享内存不属于进程,需要和进程关联才能使用
  • 内存映射区:位于每个进程的虚拟地址空间中,并且需要关联同一个磁盘文件才能实现进程间数据通信
  1. 效率:
  • 共享内存: 直接对内存操作,效率高
  • 内存映射区:需要内存和文件之间的数据同步,效率低
  1. 生命周期
  • 内存映射区:进程退出,内存映射区也就没有了
  • 共享内存:进程退出对共享内存没有影响,调用相关函数 / 命令 / 关机才能删除共享内存
  1. 数据的完整性 -> 突发状态下数据能不能被保存下来(比如:突然断电)
  • 内存映射区:可以完整的保存数据,内存映射区数据会同步到磁盘文件
  • 共享内存:数据存储在物理内存中,断电之后系统关闭,内存数据也就丢失了
#嵌入式##校招##八股文##Linux##Linux应用开发#
拉依达的Linux应用八股文 文章被收录于专栏

你好,我是拉依达。 这是我的Linux应用开发八股文详细解析系列。 本系列最开始是我在csdn上更新的文章全文总字数超3w字,现重新对内容进行整理,希望可以帮助到更多学习嵌入式的同学。

全部评论

相关推荐

点赞 评论 收藏
分享
1 1 评论
分享
牛客网
牛客企业服务