上篇Linux的博客是有关管道的,今日就让我们继续康康进程间通信的另外一种方法:共享内存
完整代码详见我的gitee仓库 👇
https://gitee.com/musnow/raspberry-practice/tree/master/code/22-11-12_systemV
[TOC]
1.啥是共享内存? 进程间通信的基本方式,就是让两个进程看到同一份资源。
共享内存的方式,通过系统接口开辟一段内存,再让多个进程去访问这块内存,就能同时看到一份资源。
这里贴出之前动态库博客中的图,共享内存的方式和该图展示的方式类似。进程需要调用系统接口,将已经开辟好的共享内存映射到自己的页表中,以实现访问。
这里就出现了一个问题:
操作系统的接口怎么知道进程要的是那一块共享内存?即共享内存是怎么标识的? 要知道,之前我们打开文件、开辟管道等等,都是具有唯一的文件路径来标识文件的。如果按以前的想法:打开文件->系统返回文件的文件描述符
,共享内存则应该是开辟共享内存->系统返回共享内存的编号
假设进程A开辟了一段共享内存,系统返回了编号123,那么进程A要怎么让其他想使用这块共享内存进行通信的进程,知道它开辟的共享内存编号 是123呢?总不能开个管道告诉它吧?那岂不是多此一举😂
所以,共享内存的编号其实和命名管道一样,是由用户手动在代码中指定的 。只要进程使用这个编号去获取共享内存,他们就能获取到同一份!
2.相关接口 说完了基本概念,现在让我们来康康它的使用
2.1 ftok ftok - convert a pathname and a project identifier to a System V IPC key
1 2 3 #include <sys/types.h> #include <sys/ipc.h> key_t ftok (const char *pathname, int proj_id) ;
前面提到了,共享内存的key是我们自己指定的。Linux系统给定了ftok
接口,将用户提供的pathname
工作路径,以及proj_id
项目编号转换为一个共享内存的key(其实就是int类型)
只要我们的工作路径和项目编号 传的是一样的,那么它返回的key就是一样的!
2.2 shmget shmget - allocates a System V shared memory segment
1 2 3 4 #include <sys/ipc.h> #include <sys/shm.h> int shmget (key_t key, size_t size, int shmflg) ;
参数分别为key值,共享内存的大小,以及创建共享内存的方式。
key值需要通过ftok
函数获取;
其中共享内存的大小最好设置为4kb的整数倍,因为操作系统IO的基本单位是4KB。如果你申请了不是4的整数倍的字节,比如15个字节,其还是会申请16个字节(4个页)交给你,而其中有1kb的内存你是无法使用的,即造成了内存浪费😥
创建共享内存的shmflg
:
IPC_CREAT
:创建共享内存。如果存在则获取,如果不存在则创建后获取IPC_EXCL
:必须配合IPC_CREAT
使用,如果不存在指定的共享内存,就进行创建;如果该共享内存存在,则出错返回 (即保证获取到的共享内存一定是当前进程创建的,是一个新的共享内存)返回值是一个共享内存的标识符
1 2 RETURN VALUE On success, a valid shared memory identifier is returned. On errir, -1 is returned, and errno is set to indicate the error.
这些工作都是操作系统做的。其内核中有专门的管理单元来判断一个共享内存是否存在,以及何时被创建、被使用、被什么进程绑定等等…
命令行键入man shmctl
,可以看到下面的内核结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct shmid_ds { struct ipc_perm shm_perm ; size_t shm_segsz; time_t shm_atime; time_t shm_dtime; time_t shm_ctime; pid_t shm_cpid; pid_t shm_lpid; shmatt_t shm_nattch; ... }; struct ipc_perm { key_t __key; uid_t uid; gid_t gid; uid_t cuid; gid_t cgid; unsigned short mode; unsigned short __seq; };
共享内存要被管理,其内核结构中一定有一个唯一的key值来标识该共享内存,即和文件的inode
一样
关于key为何要让用户提供,已经在上面做出过解释👉 回顾一下
2.3 shmat/shmdt at其实是attach绑定的缩写,这个接口的作用是将一个共享内存和我们当前的进程绑定。
其实就是将这个共享内存映射到进程的页表中(堆栈之间)
shmat, shmdt - System V shared memory operations
1 2 3 4 5 #include <sys/types.h> #include <sys/shm.h> void *shmat (int shmid, const void *shmaddr, int shmflg) ;int shmdt (const void *shmaddr) ;
一共有两个函数,分别为at和dt,用于绑定/解绑共享内存
shmat
的三个参数如下
shmid:为shmget
的返回值 shmaddr:指定共享内存连接到当前进程中的地址位置。通常为空,表示让系统来选择共享内存的地址。 shmflg:如果指定了SHM_RDONLY
位,则以只读方式连接此段;否则以读写的方式连接此段;通常设置为0 调用成功的时候,返回指向共享内存第一个字节的指针;出错返回-1
以下是man手册中对这两个函数返回值的描述👇
1 2 3 4 5 RETURN VALUE On success shmat() returns the address of the attached shared memory segment; on error (void *) -1 is returned, and errno is set to indicate the cause of the error. On success shmdt() returns 0; on error -1 is returned, and errno is set to indicate the cause of the error.
2.4 shmctl 这个函数可以用于操作我们的共享内存
1 2 3 4 #include <sys/ipc.h> #include <sys/shm.h> int shmctl (int shmid, int cmd, struct shmid_ds *buf) ;
其中cmd的参数有下面几种
最后一个buf参数是一个指向shmid_ds
结构的指针,一般设为NULL
1 The buf argument is a pointer to a shmid_ds structure
shmid_ds
的基本结构如下
1 2 3 4 5 6 struct shmid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; };
以删除为例,其操作如下
1 shmctl(shmid, IPC_RMID, NULL );
2.5 ipcs命令 先来康康几个ipcs
命令的选项,其中我们要用到的是-m
查看共享内存
1 2 3 4 ipcs -c ipcs -s ipcs -q ipcs -m
执行了之后,会列出当前操作系统中开辟的共享内存,以及它们的基本信息
1 2 3 4 5 6 [muxue@bt-7274:~/git/linux/code/22-11-12_systemV]$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 1 0x20011ac8 1 muxue 0 1024 0
这里的key和我们使用ftok
获取到的key值是一样的,只不过我们打印的时候是十进制,操作系统列出来的为十六进制
我们可以使用ipcrm -m 共享内存的shmid
来删除共享内存
1 2 3 4 5 6 7 8 9 10 11 12 13 [muxue@bt-7274:~/git/linux/code/22-11-12_systemV]$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 1 0x20011ac8 1 muxue 0 1024 0 [muxue@bt-7274:~/git/linux/code/22-11-12_systemV]$ ipcrm -m 1 [muxue@bt-7274:~/git/linux/code/22-11-12_systemV]$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 1
可以看到我们自己创建的共享内存已经被删除了
消息队列/信号量的接口 消息队列和信号量的接口和共享内存很相似
消息队列用的不多,信号量的难度很高!😂
1 2 3 4 5 6 7 8 9 10 msgget msgctl msgsnd msgrcv semget semctl semop
ipcrm 这个命令可以用与删除ipc资源,包括共享内存
但是,当我们尝试用该命令删除一个正在被使用的共享内存时,它并不会被立即删除(立即删除会影响进程运行)
此时执行删除,在共享内存的status
列会出现dest
;观察结果,当进程结束 的时候,这个共享内存会被直接删除(进程内部并没有调用shmctl
接口)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [muxue@bt-7274:~/git]$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 1 0x20011ac8 21 muxue 666 1024 2 [muxue@bt-7274:~/git]$ ipcrm -m 21 [muxue@bt-7274:~/git]$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 1 0x00000000 21 muxue 666 1024 2 dest [muxue@bt-7274:~/git]$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 1
相比之下,如果不执行ipcrm
命令+进程内部不调用shmctl
接口,这个共享内存就会一直存在
1 2 3 4 5 6 [muxue@bt-7274:~/git]$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 1 0x20011ac8 22 muxue 666 1024 0
结论:使用ipcrm -m
命令删除共享内存之后,其共享内存不一定会立即释放。如果有进程关联了该共享内存,则会在进程去关联之后释放
3.使用 3.1 创建并获取 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #define NUM 1024 #define PROJ_ID 0x20 #define PATH_NAME "/home/muxue/git/linux/code/22-11-12_systemV" key_t CreateKey () { key_t key = ftok(PATH_NAME, PROJ_ID); if (key < 0 ) { cerr <<"ftok: " << strerror(errno) << endl ; exit (1 ); } return key; } int main () { key_t key = CreateKey(); int id = shmget(key, NUM, IPC_CREAT | IPC_EXCL); if (id<0 ) { cerr << "shmget err: " << strerror(errno) << endl ; return 1 ; } cout << "shmget success: " << id << endl ; return 0 ; }
File exists 这里会发现,第一次运行代码的时候,程序成功获取了共享内存;但是第二次运行的时候,却报错说File exists(文件存在)
1 2 3 4 [muxue@bt-7274:~/git/linux/code/22-11-12_systemV]$ ./test shmget: 1 [muxue@bt-7274:~/git/linux/code/22-11-12_systemV]$ ./test shmget err: File exists
这是因为共享内存的声明周期是随内核的。即只要这个共享内存不被删除,他就会一直存在,直到内核因为某种原因释放掉它,亦或者操作系统关机
通过上面提到的ipcrm -m shmid
命令删除共享内存,才能重新运行代码获取新的共享内存
为了避免这个问题,应该在进程结束后使用shmctl
接口删除共享内存
1 2 3 4 5 6 7 8 [muxue@bt-7274:~/git/linux/code/22-11-12_systemV]$ ./test shmget success: 2 [muxue@bt-7274:~/git/linux/code/22-11-12_systemV]$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 1 0x20011ac8 2 muxue 0 1024 0
设置权限值 默认情况下,我们创建的共享内存的perms
是0,代表没有用户能访问这个共享内存。所以在创建的时候,我们需要在flag里面直接或上这个共享内存的权限值
代码如下👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int main () { key_t key = CreateKey(); int id = shmget(key, NUM, IPC_CREAT | IPC_EXCL | 0666 ); if (id<0 ) { cerr << "shmget err: " << strerror(errno) << endl ; return 1 ; } cout << "shmget success: " << id << endl ; sleep(5 ); shmctl(id,IPC_RMID,nullptr); return 0 ; }
这时候创建的共享内存就有正确的权限值了
1 2 3 4 5 6 [muxue@bt-7274:~/git]$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 1 0x20011ac8 4 muxue 666 1024 0
3.2 挂接/取消挂接 1 2 char *str = (char *)shmat (id, nullptr , 0 );
因为shmat函数的返回值是一个void*
指针,我们可以以使用malloc
一样的方式使来挂接共享内存。随后对这个内存的操作就是正常的指针操作了!
同样的,另外一个进程也需要用同样的方式挂接共享内存,才能读取到相同的数据
1 2 3 4 5 6 [muxue@bt-7274:~/git]$ ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 1 0x20011ac8 4 muxue 666 1024 1
挂接成功后,可以发现nattch
的值从0变为1
取消/删除 取消挂接的方式很简单,直接把shmat
的返回值传入即可
如果是服务端,则还需要在取消挂接之后,删除共享内存。避免下次程序运行的时候,无法通过key获取到新的共享内存
1 shmctl (id,IPC_RMID,nullptr );
3.3 写入内容 因为共享内存本质就是一个内存,其和malloc出来的内存都是一样的,直接使用即可
这里还是用一个服务端和一个客户端来进行演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include "Mykey.hpp" int main () { key_t key = CreateKey (); int id = shmget (key, NUM, IPC_CREAT | IPC_EXCL | 0666 ); if (id<0 ) { cerr<< "shmget err: " << strerror (errno) << endl; return 1 ; } cout << "shmget success: " << id << endl; sleep (2 ); char *str = (char *)shmat (id, nullptr , 0 ); printf ("[server] shmat success\n" ); int i=0 ; while (i<=40 ) { printf ("[%03d] %s\n" ,i,str); i++; sleep (1 ); } shmdt (str); printf ("[server] shmdt(str)\n" ); shmctl (id,IPC_RMID,nullptr ); printf ("[server] exit\n" ); return 0 ; } #include "Mykey.hpp" int main () { key_t key = CreateKey (); int id = shmget (key, NUM, IPC_CREAT); if (id<0 ) { cerr<< "shmget err: " << strerror (errno) << endl; return 1 ; } cout << "shmget success: " << id << endl; sleep (2 ); char *str = (char *)shmat (id, nullptr , 0 ); printf ("[client] shmat success\n" ); int i=0 ; while (i<26 ) { char base = 'A' ; str[i] = base+i; str[i+1 ] = '\0' ; printf ("write times: %02d\n" ,i); i++; sleep (1 ); } shmdt (str); printf ("[client] shmdt & exit\n" ); return 0 ; }
跑起来之后,客户端向共享内存中写入数据(注意控制\0
)服务端进行读取。这便实现了我们进程之间的通信
不过我们发现,客户端已经停止写入之后,服务端还是在不停的读取。如果我们不控制while
循环的话,其会一直这么读取下去
这便牵扯出共享内存的一个特性了
共享内存没有访问控制 在管道的博客中提到,管道是有访问控制的进程通信方式,写端没有写入数据的时候,读端会在read
中进行等待
而共享内存因为我们是直接像操作一个malloc出来的空间一样访问,没有使用任何系统接口 (相比之下管道需要使用read/write
)所以操作系统没有办法帮我们进行访问控制
也正是因为没有等待,共享内存是进程中通信中最快 的一种方式
通过管道进行共享内存的控制 既然共享内存没有访问控制,那么我们可以利用管道来让控制共享内存的读写
写端写完后,将完成信号写入管道,由读端读取 读端从管道中获取到信号后,访问共享内存读出内容 如果写端没有写好,读端就会在管道read内部等待 你可能会说,那为何不直接用管道通信呢?
管道仅作访问控制,只需要一个int乃至一个char类型即可; 相比直接管道通信,内存的方式更好控制(毕竟使用内存的方式和使用指针一样,管道还需要文件操作) 读取很长一串数据的时候,共享内存的速度优势能体现出来 以下是完整代码👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 #pragma once #include <iostream> #include <cstdio> #include <cstring> #include <ctime> #include <cstdlib> #include <sys/wait.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ipc.h> #include <sys/shm.h> #include <fcntl.h> #include <unistd.h> #include <cassert> using namespace std;#define NUM 1024 #define PROJ_ID 0x20 #define PATH_NAME "/home/muxue/git/linux/code/22-11-12_systemV" #define FIFO_FILE "sc.pipe" key_t CreateKey () { key_t key = ftok (PATH_NAME, PROJ_ID); if (key < 0 ) { cerr <<"ftok: " << strerror (errno) << endl; exit (1 ); } return key; } void CreateFifo () { umask (0 ); if (mkfifo (FIFO_FILE, 0666 ) < 0 ) { cerr << "fifo: " << strerror (errno) << endl; exit (2 ); } } int Open (int flags) { return open (FIFO_FILE, flags); } ssize_t Wait (int fd) { char val = 0 ; ssize_t s = read (fd, &val, sizeof (val)); return s; } int Signal (int fd) { char sig = 'g' ; write (fd, &sig, sizeof (sig)); } #include "Mykey.hpp" int main () { CreateFifo (); key_t key = CreateKey (); int id = shmget (key, NUM, IPC_CREAT | IPC_EXCL | 0666 ); if (id<0 ) { cerr<< "shmget err: " << strerror (errno) << endl; return 1 ; } cout << "shmget success: " << id << endl; int fd = Open (O_RDONLY); cout << "open fifo success: " << fd << endl; sleep (2 ); char *str = (char *)shmat (id, nullptr , 0 ); printf ("[server] shmat success\n" ); int i=0 ; while (i<=40 ) { ssize_t ret = Wait (fd); if (ret!=0 ) { printf ("[%03d] %s\n" ,i,str); i++; sleep (1 ); } else { cout<<"[server] wait finish, break" << endl; break ; } } shmdt (str); printf ("[server] shmdt(str)\n" ); shmctl (id,IPC_RMID,nullptr ); close (fd); unlink (FIFO_FILE); printf ("[server] exit\n" ); return 0 ; } #include "Mykey.hpp" int main () { key_t key = CreateKey (); int id = shmget (key, NUM, IPC_CREAT); if (id<0 ) { cerr<< "shmget err: " << strerror (errno) << endl; return 1 ; } cout << "shmget success: " << id << endl; int fd = Open (O_WRONLY); cout << "open fifo success: " << fd << endl; sleep (2 ); char *str = (char *)shmat (id, nullptr , 0 ); printf ("[client] shmat success\n" ); int i=0 ; while (i<26 ) { char base = 'A' ; str[i] = base+i; str[i+1 ] = '\0' ; printf ("write times: %02d\n" ,i); i++; Signal (fd); sleep (1 ); } shmdt (str); printf ("[client] shmdt & exit\n" ); close (fd); printf ("[client] close fifo\n" ); return 0 ; }
运行结果 管道控制了之后,当客户端退出的时候,管道也不会继续读取,而是在read内等待
如果客户端最后关闭了管道的写段,服务器端就会直接退出。这样我们就实现了通过管道控制共享内存 的读写👍
4.相关概念 4.0 临界资源 能被多个进程看到的资源,被称为临界资源
如果不对临界资源进行访问控制,进程对该资源的访问就是乱序的 (比如父子进程向显示器打印内容)可能会因为数据交叉导致乱码、数据不可用等情况
以此可见,显示器、管道、共享内存都是临界资源
进程访问临界资源的代码,称为临界区
一个进程中,并不是所有的代码都在访问临界资源。如管道中,其实只有read/write
接口在访问临界资源 互斥 :任何时刻,只允许一个进程访问临界资源
原子性 :一件事情只有做完/没做
两种状态,没有中间状态
下面对信号量的概念进行讲解~只用基本理解即可
4.1 信号量 信号量是对临界资源
的控制方式之一,其本质是一个计数器
信号量保证不会有多余 的进程连接到这份临界资源 还需要保证每一个进程的能够访问到临界资源的不同位置(根据上层业务决定) 信号量根据情况的不同分为两种:
二元信号量(互斥 状态,当进程使用的时候为1,没有进程使用的时候为0) 多元信号量(常规) 如果一个进程想访问由信号量控制的临界资源,必须先申请信号量。申请成功,就一定能访问到这个临界资源中的一部分(或者全部)
原子性的说明 先来想想,我们对一个变量+1/-1
需要做什么工作:
将这个变量从内存中拿到CPU的寄存器中 在寄存器中完成加减操作 放回内存 这其中是有很多个中间状态的,设该变量初始值为100
假设一个进程A拿走了这个变量,放入CPU的寄存器 另外一个进程B也来拿走了这个变量 此时A和B拿到的都是100 A对该变量进行了循环--
操作,最终该变量变成了50,将其放回内存 B对该变量-1
,将其放回内存 最终导致A对变量的操作被B覆盖,出现了变量不统一的情况 而我们的信号量为了保证能够正确的控制进程的访问,其就必须维护自身的原子性 !不能有中间状态
说人话就是,如果进程A在访问信号量,进程B来了,信号量应该拒绝B的访问,直到A访问结束。不能让B中途插入访问,从而导致可能的数据不统一
共享内存同样可以通过信号量进行访问控制
改变信号量的值 1 int semop (int semid, struct sembuf *sops, size_t nops) ;
功能: 操作信号量,P V
操作
参数:
semid 为信号量集的标识符; sops 指向进行操作的结构体数组的首地址; nsops 指出将要进行操作的信号的个数; 返回值: 成功返回0,出错返回-1
1 2 RETURN VALUE If successful semop() and semtimedop() return 0; otherwise they return -1 with errno indicating the error.
4.2 扩展 mmap 这部分仅供参考,可能有错误😥部分资料参考
前面贴出过IPC
资源的内核结构,它们都有一个共同的特点:第一个成员都相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 struct shmid_ds { struct ipc_perm shm_perm ; size_t shm_segsz; time_t shm_atime; time_t shm_dtime; time_t shm_ctime; pid_t shm_cpid; pid_t shm_lpid; shmatt_t shm_nattch; ... }; struct semid_ds { struct ipc_perm sem_perm ; time_t sem_otime; time_t sem_ctime; unsigned long sem_nsems; }; struct msqid_ds { struct ipc_perm msg_perm ; time_t msg_stime; time_t msg_rtime; time_t msg_ctime; unsigned long __msg_cbytes; msgqnum_t msg_qnum; msglen_t msg_qbytes; pid_t msg_lspid; pid_t msg_lrpid; };
它们的第一个成员 都是一个struct ipc_perm
,其中包含了一个信号量的基本信息
1 2 3 4 5 6 7 8 9 10 struct ipc_perm { key_t __key; uid_t uid; gid_t gid; uid_t cuid; gid_t cgid; unsigned short mode; unsigned short __seq; };
而内核中对IPC
资源的管理,是通过一个数组 进行的。我们所获取的shmid
,和文件描述符一样,都是一个数组的下标
其中我在测试的时候,便发现了一点:我们每一次获取的新的共享内存,它的编号都会+1
,而不像文件描述符一样,提供第一个没有被使用的下标
1 2 3 4 5 6 7 8 9 struct ipc_ids { int in_use; int max_id; unsigned short seq; unsigned short seq_max; struct semaphore sem ; struct ipc_id_ary nullentry ; struct ipc_id_ary * entries ; };
在内核中,struct ipc_id_ary* entries
是一个指向所有ipc_perm
的指针数组 。其能够通过该数组找到我们对于id(下标)的资源,对其进行访问
1 2 3 4 5 struct ipc_id_ary { int size; struct kern_ipc_perm *p [0]; };
那你可能想问了,这里只是第一个元素啊?那如果我想访问shmid_ds
结构的其他成员,岂不是没有办法访问了?
要是这么想,就还是太年轻了😂
我们只需要对这个指针进行强转,就能直接访问其他成员!
这是因为:C语言中,结构体第一个元素的地址,和结构体整体的地址是一样的!
指针的类型会限制这个指针访问元素的能力,只要我们进行强转,其就能直接访问父结构体的其他成员!
这是一种切片 的思想
用这种办法,可以用统一的规则在内核中管理不同的IPC
资源,没有必要再为每一个IPC资源建立一个单独的数组来管理。
不得不说,linus
大佬是真的牛逼!
结语 关于共享内存的操作到这里就OVER了!
最后还了解了一些内核设计上的小妙招,不得不说,真的牛批~
如果本文有什么问题,欢迎在评论区提出