-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathipcator.hpp
1760 lines (1678 loc) · 66.9 KB
/
ipcator.hpp
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Copyright (C) 2024-2025 谢骐
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, version 2.0. If a copy of the MPL was not distributed with
* this file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
/**
* @mainpage
* @brief 用于 基于共享内存 的 IPC 的基础设施.
* @note 有关 POSIX 共享内存的简单介绍: <br />
* 共享内存由临时文件映射而来. 若干进程将相同的目标文件映射到 RAM 中就能实现
* 内存共享. 一个目标文件可以被同一个进程映射多次, 被映射后的起始地址一般不同.
* <br />
* @details 自顶向下, 包含 3 个共享内存分配器 **适配器**: `Monotonic_ShM_Buffer`,
* `ShM_Pool<true>`, `ShM_Pool<false>`. <br />
* 它们将 2 个 POSIX shared memory 分配器 (即一次性分配若干连续的📄页面而
* 不对其作任何切分, 这些📄页面由 kernel 分配) —— `ShM_Resource<std::set>`
* 和 `ShM_Resource<std::unordered_set>` —— 的其中之一作为⬆️游, 实施特定的
* 分配算法. <br />
* `ShM_Resource` 拥有若干 `Shared_Memory<true>`, `Shared_Memory` 即是对 POSIX
* shared memory 的抽象. <br />
* 读取器有 `ShM_Reader`. 工具函数/类/概念有 `ceil_to_page_size(std::size_t)`,
* `generate_shm_UUName()`, [namespace literals](./namespaceliterals.html),
* [concepts](./concepts.html).
* @note 关于 POSIX shared memory 生命周期的介绍: <br />
* 我们使用 `Shared_Memory` 实例对 POSIX shared memory 进行引用计数, 这个计数是跨
* 进程的, 并且和 `Shared_Memory` 的生命周期相关联, 一个实例对应 **1** 个计数.
* **仅当** POSIX shared memory 的计数为 0 的时候, 它占用的物理内存才被真正释放.
* <br />
* `ShM_Resource`, `Monotonic_ShM_Buffer`, 和 `ShM_Pool` 都持有若干个 `Shared_Memory`
* 实例, 在这些分配器析构之前, 它们分配的共享内存块绝对能被访问; 在它们析构之后, 或
* 调用了 `release()` 方法 (如有) 之后, 那些共享内存块是否仍驻留在物理内存中 视情况
* 而定, 原因如上. <br />
* `Shared_Memory` \[*creat*=false\] 是对 POSIX shared memory 的读抽象. 读取消息
* 通常使用 `ShM_Reader` 实例, 它自动执行目标共享内存的映射以及读取, 并在读取后保留
* 该映射以备后续再次读取, 也就是说它维护一个 `Shared_Memory` \[*creat*=false\] cache.
* 因此 `ShM_Reader` 对 POSIX shared memory 的引用计数也有贡献, 且保证单个实例对同
* 一片 POSIX shared memory 最多增加 **1** 个引用计数. 当 `ShM_Reader` 析构时, 释放
* 所有资源 (所以也会将缓存过的 POSIX shared memory 的引用计数减一).
* @warning 要构建 release 版本, 请在文件范围内定义以下宏, 否则性能会非常差:
* - `NDEBUG`: 删除诸多非必要的校验措施;
* - `IPCATOR_OFAST`: 开启额外优化. 可能会导致观测到 API 的行为发生变化, 但此类
* 变化通常无关紧要 (例如, 不判断 allocation 的 alignment 参数是否能被满足, 因为
* 基本不可能不满足).
* @note 定义 `IPCATOR_LOG` 宏可以打开日志. 调试用.
* @note 定义 `IPCATOR_NAMESPACE` 宏可以将该文件内的所有 API 放到指定的命名空间.
*/
/* clang-format off */
#pragma once
#include <version>
#include <algorithm> // ranges::fold_left
# if __has_include(<experimental/algorithm>)
# include <experimental/algorithm> // experimental::sample
# endif
#include <atomic> // atomic_uint, memory_order_relaxed
#include <cassert>
#include <cerrno> // EPERM, errno
#include <chrono>
#include <climits> // NAME_MAX
#include <concepts> // {,unsigned_}integral, convertible_to, copy_constructible, same_as, movable
#include <cstddef> // size_t
# if __has_include(<format>)
# include <format> // format, formatter, format_error, vformat{_to,}, make_format_args
# elif __has_include(<experimental/format>)
# include <experimental/format>
namespace std {
using experimental::format,
experimental::formatter, experimental::format_error,
experimental::vformat, experimental::vformat_to,
experimental::make_format_args;
}
# elif __has_include("fmt/format.h")
# include "fmt/format.h"
# if FMT_VERSION < 10'00'00L
# error "你的 `libfmt' 版本太低了"
# else
namespace std {
using ::fmt::format,
::fmt::formatter, ::fmt::format_error,
::fmt::vformat, ::fmt::vformat_to,
::fmt::make_format_args;
}
# endif
# else
# error "你需要首先升级编译器和标准库以获得完整的 C++20 支持, 或安装 C++20 <format> 的替代品 <https://github.com/fmtlib/fmt>"
# endif
#include <cstdint> // uintptr_t
#include <filesystem> // filesystem::filesystem_error
#include <functional> // bind{_back,}, bit_or, plus
#include <future> // async, future_status::ready
#include <iostream> // clog
#include <iterator> // size, {,c}{begin,end}, data, empty, back_inserter
#include <memory> // shared_ptr
#include <memory_resource> // pmr::{memory_resource,monotonic_buffer_resource,{,un}synchronized_pool_resource,pool_options}
#include <new> // bad_alloc
#include <ostream> // ostream
#include <ranges> // ranges::find_if, views::{chunk,transform,join_with,iota}
#include <set>
# if __has_include(<source_location>)
# include <source_location> // source_location::current
# elif __has_include(<experimental/source_location>)
# include <experimental/source_location>
namespace std { using typename experimental::source_location; }
# else
# warning "连 <source_location> 都没有, 虽然我给你打了个补丁, 但还是建议你退群"
namespace std {
struct source_location {
static consteval auto current() noexcept {
return source_location{};
}
constexpr auto function_name() const noexcept {
return "某函数";
}
};
}
# endif
#include <span>
#include <stdexcept> // invalid_argument
#include <string>
#include <string_view>
#include <system_error> // make_error_code, errc::no_such_file_or_directory
#include <thread> // this_thread::{sleep_for,yield}
#include <tuple> // ignore
#include <type_traits> // conditional_t, is_const{_v,}, remove_reference{_t,}, is_same_v, decay_t, disjunction, is_lvalue_reference
#include <unordered_set>
# include <utility> // as_const, move, swap, unreachable, hash, exchange
# ifndef __cpp_lib_unreachable
namespace std {
[[noreturn]] inline void unreachable() {
# if defined _MSC_VER && !defined __clang__
__assume(false);
# else
__builtin_unreachable();
# endif
}
}
# endif
#include <variant> // monostate
#include <fcntl.h> // O_{CREAT,RDWR,RDONLY,EXCL}, open
#include <sys/mman.h> // m{,un}map, shm_{open,unlink}, PROT_{WRITE,READ,EXEC}, MAP_{SHARED,FAILED,NORESERVE}
#include <sys/stat.h> // fstat, struct stat, fchmod
#include <unistd.h> // close, ftruncate, getpagesize
#ifdef __clang__
# pragma clang diagnostic ignored "-Wc++2a-extensions"
# pragma clang diagnostic ignored "-Wc++2b-extensions"
# pragma clang diagnostic ignored "-Wc++2c-extensions"
# pragma clang diagnostic ignored "-Wc++23-attribute-extensions"
# pragma clang diagnostic ignored "-Wc++26-extensions"
# pragma clang diagnostic ignored "-Wunknown-attributes"
#elif defined __GNUG__
# pragma GCC diagnostic ignored "-Wc++23-extensions"
# pragma GCC diagnostic ignored "-Wc++26-extensions"
# if 12 <= __GNUC__
# pragma GCC diagnostic ignored_attributes "clang::"
# endif
#endif
#ifdef IPCATOR_NAMESPACE
# define IPCATOR_OPEN_NAMESPACE namespace IPCATOR_NAMESPACE {
# define IPCATOR_CLOSE_NAMESPACE }
#else
# define IPCATOR_OPEN_NAMESPACE
# define IPCATOR_CLOSE_NAMESPACE
#endif
IPCATOR_OPEN_NAMESPACE
using namespace std::literals;
#ifndef __cpp_size_t_suffix
# ifdef IPCATOR_USED_BY_SEER_RBK
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wuser-defined-literals"
# endif
consteval auto operator "" uz(unsigned long long integer) -> std::size_t {
return integer;
}
# ifdef IPCATOR_USED_BY_SEER_RBK
# pragma clang diagnostic pop
# endif
#endif
/* 对 POSIX API 的复刻, 但参数的类型更多样. */
namespace POSIX {
// 注意: POSIX API 不使用异常!
inline auto close(const decltype(::open("", {})) *const fd) noexcept {
#ifdef IPCATOR_LOG
std::clog << "调用了 `"s +
# if defined __GNUG__ || defined __clang__
__PRETTY_FUNCTION__
# else
__func__
# endif
+ "` (手写的 POSIX close 的重载版本).\n";
#endif
return ::close(*fd);
}
}
inline namespace utils {
/**
* @brief 将数字向上取整, 成为📄页面大小 (通常是 4096) 的整数倍.
* @details 用该返回值设置 shared memory 的大小, 可以提高内存空间♻️利用率.
* @note example:
* ```
* assert( ceil_to_page_size(0) == 0 );
* std::cout << ceil_to_page_size(1);
* ```
*/
inline auto ceil_to_page_size [[gnu::const]] (
const std::size_t min_length
) noexcept -> std::size_t {
const auto current_num_pages = min_length / ::getpagesize();
const bool need_one_more_page = min_length % ::getpagesize();
return (current_num_pages + need_one_more_page) * ::getpagesize();
}
}
/**
* @brief 对由目标文件映射而来的 POSIX shared memory 的抽象.
* @note 文档约定:
* 称 `Shared_Memory` **[*creat*=true]** 实例为 creator,
* `Shared_Memory` **[*creat*=false]** 实例为 accessor.
* @tparam creat 是否新建文件以供映射.
* @tparam writable 是否允许在映射的区域写数据.
*/
template <bool creat, auto writable=creat>
class Shared_Memory: public std::span<
std::conditional_t<
writable,
char, const char
>
> {
using span = std::span<
std::conditional_t<
writable,
char, const char
>
>;
std::string name;
public:
/**
* @brief 创建 shared memory 并映射, 可供其它进程打开以读写.
* @param name 这是目标文件名. POSIX 要求的格式是 `/path-to-shm`.
* 建议使用 `generate_shm_UUName()` 自动生成该名字.
* @param size 目标文件的大小, 亦即 shared memory 的长度. 建议使用
* `ceil_to_page_size(std::size_t)` 自动生成.
* @note Shared memory 的长度是固定的, 一旦创建, 无法再改变.
* @warning POSIX 规定 `size` 不可为 0.
* @details 根据 `name` 创建一个临时文件, 并将其映射到进程自身的
* RAM 中. 临时文件的文件描述符在构造函数返回前就会被删除.
* @warning `name` 不能和已有 POSIX shared memory 重复, 否则会崩溃.
* @note example (该 constructor 会推导类的模板实参):
* ```
* Shared_Memory shm{"/ipcator.Shared_Memory-creator", 1234};
* static_assert( std::is_same_v<decltype(shm), Shared_Memory<true, true>> );
* ```
*/
Shared_Memory(
const std::string
#ifdef IPCATOR_OFAST
&
#endif
name, const std::size_t size
) requires(creat): span{
Shared_Memory::map_shm(name, size),
size,
}, name{name} {
#ifdef IPCATOR_LOG
std::clog << std::format("创建了 Shared_Memory: \033[32m{}\033[0m", *this) + '\n';
#endif
}
/**
* @brief 打开📂目标文件, 将其映射到 RAM 中.
* @param name 目标文件的路径名. 这个路径通常是事先约定的, 或者
* 从其它实例的 `Shared_Memory::get_name()` 方法获取.
* @details 目标文件的描述符在构造函数返回前就会被删除.
* @note 若目标文件不存在, 则每隔 20ms 查询一次, 持续至多 1s.
* @exception 若 1s 后目标文件仍未创建, 抛出
* `std::filesystem::filesystem_error` (no such file
* or directory).
* @note example (该 constructor 会推导类的模板实参):
* ```
* Shared_Memory creator{"/ipcator.1", 1};
* Shared_Memory accessor{"/ipcator.1"};
* static_assert( std::is_same_v<decltype(accessor), Shared_Memory<false, false>> );
* assert( std::size(accessor) == 1 );
* ```
*/
Shared_Memory(
const std::string
#ifdef IPCATOR_OFAST
&
#endif
name
) noexcept(noexcept(Shared_Memory::map_shm(""s))) requires(!creat)
: span{
[&]() -> span {
const auto [addr, length] = Shared_Memory::map_shm(name);
return {addr, length};
}()
}, name{name} {
#ifdef IPCATOR_LOG
std::clog << std::format("创建了 Shared_Memory: \033[32m{}\033[0m\n", *this) + '\n';
#endif
}
/**
* @brief 实现移动语义.
* @note example:
* ```
* Shared_Memory a{"/ipcator.move", 1};
* auto b{std::move(a)};
* assert( std::data(a) == nullptr );
* assert( std::data(b) && std::size(b) == 1 );
* ```
*/
Shared_Memory(Shared_Memory&& other) noexcept
: span{
// Self 的 destructor 靠 `span` 是否为空来
// 判断是否持有所有权, 所以此处需要强制置空.
std::exchange<span>(other, {})
}, name{std::move(other.name)} {}
/**
* @brief 实现交换语义.
*/
friend void swap(Shared_Memory& a, decltype(a) b) noexcept {
std::swap<span>(a, b);
std::swap(a.name, b.name);
}
/**
* @brief 实现赋值语义.
* @note example:
* ```
* auto a = Shared_Memory{"/ipcator.assign-1", 3};
* a = {"/ipcator.assign-2", 5};
* assert(
* a.get_name() == "/ipcator.assign-2" && std::size(a) == 5
* );
* ```
*/
auto& operator=(Shared_Memory other) {
swap(*this, other);
return *this;
}
/**
* @brief 将 shared memory **unmap**. 对于 creator, 还会阻止对关联的目标
* 文件的新的映射.
* @details 如果是 creator 析构了, 其它 accessors 仍可访问对应的 POSIX
* shared memory, 但新的 accessor 的构造将导致进程 crash. 当余下
* 的 accessors 都析构掉之后, 目标文件的引用计数归零, 将被释放.
* @note example (creator 析构后仍然可读写):
* ```
* auto creator = new Shared_Memory{"/ipcator.1", 1};
* auto accessor = Shared_Memory<false, true>{creator->get_name()};
* auto reader = Shared_Memory{creator->get_name()};
* (*creator)[0] = 42;
* assert( accessor[0] == 42 && reader[0] == 42 );
* delete creator;
* accessor[0] = 77;
* assert( reader[0] == 77 );
* ```
*/
~Shared_Memory() noexcept {
if (std::data(*this) == nullptr)
return;
// 🚫 Writer 将要拒绝任何新的连接请求:
if constexpr (creat)
::shm_unlink(this->name.c_str());
// 此后的 ‘shm_open’ 调用都将失败.
// 当所有 shm 都被 ‘munmap’ed 后, 共享内存将被 deallocate.
::munmap(
const_cast<char *>(std::data(*this)),
std::size(*this)
);
#ifdef IPCATOR_LOG
std::clog << std::format("析构了 Shared_Memory: \033[31m{}\033[0m", *this) + '\n';
#endif
}
/**
* @brief 所关联的目标文件的路径名.
* @note example:
* ```
* auto a = Shared_Memory{"/ipcator.name", 1};
* assert( a.get_name() == "/ipcator.name" );
* ```
*/
auto& get_name() const { return this->name; }
#if __has_cpp_attribute(nodiscard)
[[nodiscard]]
#endif
static auto map_shm(const std::string& name, const std::unsigned_integral auto... size)
noexcept(false) // 创建时可能文件已存在; 打开时可能报 “no such file” 错误.
requires(sizeof...(size) == creat)
{
assert(
name.length() <= NAME_MAX
// 实际上 POSIX 没有规定 name 的长度上限, 但我认为需要一个保守值.
);
using POSIX::close;
const
#if 16 <= __clang_major__ && __clang_major__ <= 21 // <https://github.com/llvm/llvm-project/issues/129631>
decltype(::open("", {}))
#else
auto
#endif
fd [[gnu::cleanup(close)]] = [&](const auto do_open) {
std::future opening = std::async([&] {
while (true)
if (const auto fd = do_open(); fd != -1)
return fd;
else
std::this_thread::sleep_for(20ms);
});
if (opening.wait_for(1s) == std::future_status::ready)
[[likely]] return opening.get();
else
throw std::filesystem::filesystem_error{
// 不要加句号:
creat ? "重名的 共享内存对象 已存在, 等待它被删除... creator 等待超时"
: "共享内存对象 仍未被创建, 导致 accessor 等待超时",
std::format(
#ifdef __linux__
"/dev/shm/"
#elif defined __FreeBSD__ || defined __APPLE__
"/var/run/shm/"
#elif defined __NetBSD__
"/var/shm/"
#endif
"{}", name
),
std::make_error_code(
creat ? std::errc::file_exists
: std::errc::no_such_file_or_directory
)
};
}(std::bind(
::shm_open,
name.c_str(),
(creat ? O_CREAT|O_EXCL : 0) | (writable ? O_RDWR : O_RDONLY),
0777
));
#if __has_cpp_attribute(assume)
[[assume(fd != -1)]];
#endif
#ifdef IPCATOR_USED_BY_SEER_RBK
::fchmod(fd, 0777);
#endif
if constexpr (creat) {
// 设置 shm obj 的大小:
const auto result_resize [[maybe_unused]] = ::ftruncate(
fd,
size...
#ifdef __cpp_pack_indexing
[0]
#endif
);
assert(result_resize != -1);
}
return [
fd, size=[&] {
if constexpr (creat)
return
#ifdef __cpp_pack_indexing
size...[0]
#else
[](auto size, ...) { return size; }(size...)
#endif
;
else
// 等到 creator resize 完 shm obj:
for (struct ::stat shm; true; std::this_thread::yield())
if (::fstat(fd, &shm); shm.st_size)
[[likely]] return shm.st_size + 0uz;
}()
] {
assert(size);
#if __has_cpp_attribute(assume)
[[assume(size)]]; // POSIX mmap 要求.
#endif
const auto area_addr = [&] {
#ifdef IPCATOR_OFAST
static constinit auto failed_because_of_exec = false;
#endif
const auto mmap_executable = [&](bool use_prot_exec) {
return ::mmap(
nullptr, size,
PROT_READ | (writable ? PROT_WRITE : 0) | (use_prot_exec ? PROT_EXEC : 0),
MAP_SHARED | (!writable ? MAP_NORESERVE : 0),
fd, 0
);
};
auto addr = mmap_executable(
#ifndef IPCATOR_OFAST
true
#else
failed_because_of_exec ? false : true
#endif
);
if (addr == MAP_FAILED && errno == EPERM)
#ifdef IPCATOR_OFAST
[[unlikely]] // 因为只会设置这么一次:
failed_because_of_exec = true,
#endif
#ifdef IPCATOR_LOG
std::clog << "Failed to map shm as PROT_EXEC.\n",
#endif
addr = mmap_executable(false);
assert(addr != MAP_FAILED);
return (char *)addr;
}();
#if !__has_cpp_attribute(gnu::cleanup)
# ifdef IPCATOR_LOG
std::clog << "调用了 POSIX close.\n";
# endif
::close(fd); // 映射完立即关闭, 对后续操作🈚影响.
#endif
if constexpr (creat)
return area_addr;
else {
const struct {
std::conditional_t<
writable,
char, const char
> *const addr;
const std::size_t length;
} area{area_addr, size};
return area;
}
}();
}
/**
* @brief 🖨️打印内存布局到一个字符串. 调试用.
* @details 一个造型是多行多列的矩阵, 每个元素
* 用 16 进制表示对应的 byte.
* @param num_col 列数
* @param space 每个 byte 之间的填充字符串
*/
auto pretty_memory_view [[gnu::cold]] (
const std::size_t num_col = 16, const std::string_view space = " "
) const {
#if defined __cpp_lib_ranges_fold \
&& defined __cpp_lib_ranges_chunk \
&& defined __cpp_lib_ranges_join_with \
&& defined __cpp_lib_bind_back
return std::ranges::fold_left(
this->area
| std::views::chunk(num_col)
| std::views::transform(
std::bind_back(
std::bit_or<>{},
std::views::transform([](auto& B) static {
return std::format("{:02X}", B);
})
| std::views::join_with(space)
)
)
| std::views::join_with('\n'),
""s, std::plus<>{}
);
#else
std::vector<std::vector<std::string>> lines;
std::vector<std::string> line;
for (const auto& B : this->area) {
line.push_back(std::format("{:02X}", B));
line.push_back(std::string{space});
if (line.size() / 2 == num_col) {
line.back() = '\n';
lines.push_back(std::exchange(line, {}));
}
}
lines.push_back(line);
std::string view;
for (const auto& line : lines) {
for (const auto& e : line)
view += e;
}
view.pop_back();
return view;
#endif
}
/**
* @brief 将 self 以类似 JSON 的格式输出.
* @note 也可用 `std::println("{}", self)` 打印 (since C++23).
* @note example:
* ```
* std::cout << Shared_Memory{"/ipcator.print", 10} << '\n';
* ```
*/
friend auto operator<<[[gnu::cold]](std::ostream& out, const Shared_Memory& shm)
-> decltype(auto) {
return out << std::format("{}", shm);
}
};
Shared_Memory(
std::convertible_to<std::string> auto, std::integral auto
) -> Shared_Memory<true>;
Shared_Memory(
std::convertible_to<std::string> auto
) -> Shared_Memory<false>;
static_assert(
!std::copy_constructible<Shared_Memory<true>>
&& !std::copy_constructible<Shared_Memory<true, false>>
&& !std::copy_constructible<Shared_Memory<false>>
&& !std::copy_constructible<Shared_Memory<false, true>>
);
IPCATOR_CLOSE_NAMESPACE
template <auto creat, auto writable>
struct std::formatter<
#ifdef IPCATOR_NAMESPACE
IPCATOR_NAMESPACE::
#endif
Shared_Memory<creat, writable>
> {
constexpr auto parse(const auto& parser) {
if (const auto p = parser.begin(); p != parser.end() && *p != '}')
throw std::format_error("不支持任何格式化动词.");
else
return p;
}
auto format(const auto& shm, auto& context) const {
constexpr auto obj_constructor = []() consteval {
if (creat)
if (writable)
return "Shared_Memory<creat=true,writable=true>";
else
return "Shared_Memory<creat=true,writable=false>";
else
if (writable)
return "Shared_Memory<creat=false,writable=true>";
else
return "Shared_Memory<creat=false,writable=false>";
}();
const auto addr = (const void *)std::data(shm);
const auto length = std::size(shm);
const auto name = shm.get_name();
return std::vformat_to(
context.out(),
R":({{
"area": {{ "&addr": {}, "|length|": {} }},
"name": "{}",
"constructor()": "{}"
}}):",
std::make_format_args(
addr, length,
name,
obj_constructor
)
);
}
};
IPCATOR_OPEN_NAMESPACE
namespace literals {
/**
* @brief 创建 `Shared_Memory` 实例的快捷方式.
* @details
* - 创建指定大小的命名的共享内存, 以读写模式映射: `"/filename"_shm[size]`;
* - 不创建, 只将目标文件以读写模式映射至本地: `+"/filename"_shm`;
* - 不创建, 只将目标文件以只读模式映射至本地: `-"/filename"_shm`.
* @note example:
* ```
* using namespace literals;
* auto creator = "/ipcator.1"_shm[123];
* creator[5] = 5;
* auto accessor = +"/ipcator.1"_shm;
* assert( accessor[5] == 5 );
* auto reader = -"/ipcator.1"_shm;
* accessor[9] = 9;
* assert( reader[9] == 9 );
* ```
*/
inline auto operator""_shm[[gnu::cold]](const char *const name, [[maybe_unused]] std::size_t) {
struct ShM_Constructor_Proxy {
const char *const name;
auto operator[](const std::size_t size) const {
return Shared_Memory{name, size};
}
auto operator+() const {
return Shared_Memory<false, true>{name};
}
auto operator-() const {
return Shared_Memory{name};
}
};
return ShM_Constructor_Proxy{name};
}
}
inline namespace utils {
/**
* @brief 创建一个 **全局唯一** 的 POSIX shared memory
* 名字, 不知道该给共享内存起什么名字时就用它.
* @see Shared_Memory::Shared_Memory(std::string, std::size_t)
* @note 格式为 `/固定前缀-进程专属的标识符-原子自增的计数字段`.
* @details 返回的名字的长度为 (31-8=23). 你可以将它转换成包含
* NULL 字符的 c_str, 此时占用 24 bytes. 在传递消息时
* 需要告知接收方该消息所在的 POSIX shared memory 的
* 名字和消息在该 shared memory 中的偏移量, 偏移量通常
* 是 `std::size_t` 类型, 因此加起来刚好 32 bytes.
* @note example:
* ```
* auto name = generate_shm_UUName();
* assert( name.length() + 1 == 24 ); // 计算时包括 NULL 字符.
* assert( name.front() == '/' );
* std::cout << name << '\n';
* ```
*/
inline auto generate_shm_UUName() noexcept {
constexpr auto len_name = 31uz - sizeof(std::size_t);
constexpr auto prefix = "ipcator";
// 在 shm obj 的名字中包含一个顺序递增的计数字段:
constinit static std::atomic_uint cnt;
const auto suffix = std::format(
"{:06}",
1 + cnt.fetch_add(1, std::memory_order_relaxed)
);
constexpr auto available_chars = "0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"sv;
// 由于 (取名 + 构造 shm) 不是原子的, 可能在构造 shm obj 时
// 和已有的 shm 的名字重合, 或者同时多个进程同时创建了同名 shm.
// 所以生成的名字必须足够长 (取决于 `infix`), 📉降低碰撞率.
static const auto infix = [&] {
std::string infix;
std::experimental::sample(
std::cbegin(available_chars), std::cend(available_chars),
std::back_inserter(infix),
len_name - ("/"s + prefix + '.' + "" + '.' + suffix).length()
);
return infix;
}();
assert(infix.length() >= 7);
auto&& full_name = "/"s + prefix + '.' + infix + '.' + suffix;
assert(full_name.length() == len_name);
return full_name;
}
}
#ifndef IPCATOR_LOG
# define IPCATOR_LOG_ALLO_OR_DEALLOC(color) (void())
#else
# define IPCATOR_LOG_ALLO_OR_DEALLOC(color) ( \
std::clog << \
std::source_location::current().function_name() + "\n"s \
+ std::vformat( \
(color == "green"sv ? "\033[32m" : "\033[31m") \
+ "\tsize={}, &area={}, alignment={}\033[0m"s, \
std::make_format_args(size, (const void *const&)area, alignment) \
) + '\n' \
)
#endif
/**
* @brief Allocator: 给⬇️游分配 POSIX shared memory.
* 本质上是一系列 `Shared_Memory<true>` 的集合.
* @note 该类的实例持有 `Shared_Memory<true>` 的所有权.
* @tparam set_t 存储 `Shared_Memory<true>` 的集合类型.
* 不同的 set_t 决定了
* - allocation 的速度. 因为需要向 set_t 实例
* 中插入 `Shared_Memory<true>`.
* - 当给出任意指针时, 找出它指向的对象位于哪片
* POSIX shared memory 区间时的速度.
*/
template <template <typename... T> class set_t = std::set>
class ShM_Resource: public std::pmr::memory_resource {
public:
/**
* @cond
* 请 Doxygen 忽略该变量, 因为它总是显示初始值 (而我不想这样).
*/
static constexpr bool using_ordered_set = []() consteval {
if constexpr (requires {
requires std::same_as<set_t<int>, std::set<int>>;
})
return true;
else if constexpr (std::is_same_v<set_t<int>, std::unordered_set<int>>)
return false;
else {
#if !__GNUG__ || __GNUC__ >= 13 // P2593R1
static_assert(false, "只接受 ‘std::{,unordered_}set’ 作为注册表格式.");
#else
std::unreachable();
#endif
}
}(); /// @endcond
private:
struct ShM_As_Addr {
using is_transparent = int;
static auto get_addr(const auto& shm_or_ptr)
-> const void * {
if constexpr (std::is_same_v<
std::decay_t<decltype(shm_or_ptr)>,
const void *
>)
return shm_or_ptr;
else
return std::data(shm_or_ptr);
}
/* As A Comparator */
#ifdef __cpp_static_call_operator
static
#endif
bool operator()[[gnu::cold]](const auto& a, const auto& b)
#ifndef __cpp_static_call_operator
const
#endif
noexcept {
const auto pa = get_addr(a), pb = get_addr(b);
if constexpr (using_ordered_set)
return pa < pb;
else
return pa == pb;
}
/* As A Hasher */
#ifdef __cpp_static_call_operator
static
#endif
auto operator()(const auto& shm)
#ifndef __cpp_static_call_operator
const
#endif
noexcept -> std::size_t {
const auto addr = get_addr(shm);
return std::hash<std::decay_t<decltype(addr)>>{}(addr);
}
};
std::conditional_t<
using_ordered_set,
set_t<Shared_Memory<true>, ShM_As_Addr>,
set_t<Shared_Memory<true>, ShM_As_Addr, ShM_As_Addr>
> resources;
protected:
#ifdef IPCATOR_IS_BEING_DOXYGENING // stupid doxygen
/**
* @brief 分配 POSIX shared memory.
* @param alignment 对齐要求.
* @details 新建 `Shared_Memory<true>`, 不作任何切分,
* 因此这是粒度最粗的分配器.
* @return `Shared_Memory<true>` 的 `std::data` 值.
* @note example:
* ```
* auto allocator = ShM_Resource<std::set>{};
* auto _ = allocator.allocate(12); _ = allocator.allocate(34, 8);
* allocator = ShM_Resource<std::unordered_set>{};
* _ = allocator.allocate(56), _ = allocator.allocate(78, 16);
* ```
*/
void *allocate(
std::size_t size, std::size_t alignment = alignof(std::max_align_t)
);
/**
* @brief 析构对应的 `Shared_Memory<true>`.
* @param area 与 deallocation 对应的那次 allocation 的返回值.
* @param size 若定义了 `NDEBUG` 宏, 传入任意值即可; 否则, 表示
* POSIX shared memory 的大小, 必须与请求分配 (`allocate`)
* 时的大小 (`size`) 一致.
* @note example:
* ```
* auto allocator_1 = ShM_Resource<std::set>{};
* allocator_1.deallocate(
* allocator_1.allocate(111), 111
* );
* auto allocator_2 = ShM_Resource<std::unordered_set>{};
* allocator_2.deallocate(
* allocator_2.allocate(222), 222
* );
* ```
*/
void deallocate(void *area, std::size_t size);
#endif
#ifdef IPCATOR_OFAST
[[gnu::assume_aligned(4096)]]
#endif
void *do_allocate [[using gnu: returns_nonnull, alloc_size(2)]] (
const std::size_t size, const std::size_t alignment
) noexcept
#ifndef IPCATOR_OFAST
(false)
#endif
[[clang::lifetimebound]] override {
if (alignment > ::getpagesize() + 0u) [[unlikely]] {
struct TooLargeAlignment: std::bad_alloc {
const std::string message;
TooLargeAlignment(const std::size_t demanded_alignment)
: message{
std::format(
"请求分配的字节数组要求按 {} 对齐, 超出了页表大小 (即 {}).",
demanded_alignment,
::getpagesize()
)
} {}
const char *what() const noexcept override {
return this->message.c_str();
}
};
#ifndef IPCATOR_OFAST
throw TooLargeAlignment{alignment};
#endif
}
const auto [inserted, ok] = this->resources.emplace(
generate_shm_UUName(),
size
);
assert(ok);
#if __has_cpp_attribute(assume)
[[assume(ok)]];
#endif
if constexpr (!using_ordered_set)
this->last_inserted = std::to_address(
#if _GLIBCXX_RELEASE == 10 // GCC 的 bug, 见 ipcator#2.
&*
#endif
inserted
);
const auto area = std::data(*inserted);
IPCATOR_LOG_ALLO_OR_DEALLOC("green");
return area;
}
[[gnu::nonnull(2)]] // 不用 `nonnull_if_nonzero` 是因为 size 不可能为 0.
void do_deallocate(
void *const area [[clang::noescape]],
const std::size_t size [[maybe_unused]],
const std::size_t alignment [[maybe_unused]]
)
#ifdef IPCATOR_OFAST
noexcept
#endif
override {
IPCATOR_LOG_ALLO_OR_DEALLOC("red");
// 标准要求 allocation 与 deallocation 的 ‘alignment’ 要匹配, 否则是 undefined
// behavior. 我们没有记录 allocation 的 ‘alignment’ 值是多少, 但肯定不比📄页面大.
assert(alignment <= ::getpagesize() + 0u);
const auto whatcanisay_shm_out = std::move(
this->resources
#ifdef __cpp_lib_associative_heterogeneous_erasure
.template extract<const void *>(area)
#else
.extract(
std::find_if(
this->resources.cbegin(), this->resources.cend(),
[area=(const void *)area](const auto& shm) noexcept {
if constexpr (using_ordered_set)
return !ShM_As_Addr{}(shm, area) == !ShM_As_Addr{}(area, shm);
else
return ShM_As_Addr{}(shm) == ShM_As_Addr{}(area)
&& ShM_As_Addr{}(shm, area);
}
)
)
#endif
.value()
);
// 标准要求 allocation 与 deallocation 的 ‘size’ 要匹配, 否则是 undefined
// behavior. 我们没有记录 allocation 的 ‘size’ 值是多少, 但肯定在此范围.
assert(
size <= std::size(whatcanisay_shm_out)
&& std::size(whatcanisay_shm_out) <= ceil_to_page_size(size)
);
}
bool do_is_equal [[gnu::cold]] (
const std::pmr::memory_resource& other
) const noexcept override {