Skip to content

Commit 9fffeb2

Browse files
authored
Add a common interface for DMA-like one-sided data communication Introduce a unified API for performing direct memory access (DMA)-style operations with support for one-sided read/write semantics. The initial implementation utilizes shared memory as the underlying transport mechanism.
1 parent 1c846ef commit 9fffeb2

8 files changed

Lines changed: 695 additions & 2 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ file(GLOB PHOTON_SRC RELATIVE "${PROJECT_SOURCE_DIR}"
202202
net/*.cpp
203203
net/http/*.cpp
204204
net/security-context/tls-stream.cpp
205+
net/vdma/*.cpp
205206
rpc/*.cpp
206207
thread/*.cpp
207208
)

examples/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ target_link_libraries(rpc-example-server PRIVATE photon_static)
2727
add_executable(sync-primitive sync-primitive/sync-primitive.cpp)
2828
target_link_libraries(sync-primitive PRIVATE photon_static)
2929

30+
add_executable(vdma-example vdma-transfer/vdma-example.cpp)
31+
target_link_libraries(vdma-example PRIVATE photon_static)
32+
3033
if (PHOTON_ENABLE_FSTACK_DPDK)
3134
add_executable(fstack-dpdk-demo fstack-dpdk/fstack-dpdk-demo.cpp)
3235
target_link_libraries(fstack-dpdk-demo PRIVATE ${FSTACK_LIBRARIES} photon_static)
33-
endif ()
36+
endif ()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include <photon/photon.h>
2+
#include <photon/common/alog-stdstring.h>
3+
#include <photon/net/vdma.h>
4+
5+
#include <string>
6+
#include <iomanip>
7+
#include <sstream>
8+
9+
10+
int main() {
11+
photon::init();
12+
DEFER(photon::fini());
13+
14+
// transfer procedure simple simulation
15+
std::string shm_name = "foo";
16+
size_t shm_size = 65536; // 64KB
17+
size_t unit = 4096; // 4KB
18+
// (1) create a target (want data)
19+
// map shm to local addr space, and new a allocator to manage it
20+
auto target = photon::new_shm_vdma_target(shm_name.c_str(), shm_size, unit);
21+
// (2) create a initiator (has data)
22+
auto initiator = photon::new_shm_vdma_initiator(shm_name.c_str(), shm_size);
23+
// (3) target alloc a shared memory buffer
24+
auto t_buffer = target->alloc(unit);
25+
auto id = t_buffer->id();
26+
LOG_DEBUG("step3: shared memory buffer real address is ", HEX((uint64_t)t_buffer->address()));
27+
size_t want_data_size = 4096;
28+
off_t want_data_offset = 0;
29+
// (4) target send a request msg to initiator, tell initiator: logical addr + want which data
30+
// (5) initiator use the logical addr map shm to its local addr space
31+
auto i_buffer = initiator->map(id);
32+
// (6) initiator copy corresponding data
33+
char thedata[4096];
34+
memset(thedata, 0x22, 4096);
35+
memcpy((char*)i_buffer->address() + want_data_offset, thedata, want_data_size);
36+
// (7) initiator single-side write
37+
initiator->write(i_buffer, want_data_size, want_data_offset);
38+
// (8) initiator unmap the shm buffer
39+
initiator->unmap(i_buffer);
40+
// (9) initiator send a response msg to target, tell target: transfer done
41+
// (10) target check data
42+
uint8_t* ptr = (uint8_t*)((char*)t_buffer->address() + want_data_offset);
43+
for (size_t i=0; i<want_data_size; i++) {
44+
if (ptr[i] != 0x22) {
45+
target->dealloc(t_buffer);
46+
LOG_ERROR_RETURN(0, -1, "failed, not same at ", i, "want ", 0x22, ", now ", ptr[i]);
47+
}
48+
}
49+
// (11) target dealloc the shm buffer
50+
target->dealloc(t_buffer);
51+
LOG_INFO("success");
52+
}

include/photon/net/vdma.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../net/vdma.h

net/test/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,8 @@ target_link_libraries(test-client PRIVATE photon_shared)
2424

2525
add_executable(test-ipv6 test-ipv6.cpp)
2626
target_link_libraries(test-ipv6 PRIVATE photon_shared)
27-
add_test(NAME test-ipv6 COMMAND $<TARGET_FILE:test-ipv6>)
27+
add_test(NAME test-ipv6 COMMAND $<TARGET_FILE:test-ipv6>)
28+
29+
add_executable(test-vdma test-vdma.cpp)
30+
target_link_libraries(test-vdma PRIVATE photon_shared)
31+
add_test(NAME test-vdma COMMAND $<TARGET_FILE:test-vdma>)

net/test/test-vdma.cpp

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
#include "../../test/gtest.h"
2+
#include "../vdma/shm.cpp"
3+
#include <photon/photon.h>
4+
#include <photon/common/alog.h>
5+
#include <photon/thread/thread11.h>
6+
#include <photon/thread/workerpool.h>
7+
8+
#include <vector>
9+
10+
TEST(Buffer, encode_decode) {
11+
std::string str;
12+
photon::SharedMemoryBuffer::encode_to(str, 111, 222);
13+
uint64_t idx = 0;
14+
size_t bufsz = 0;
15+
photon::SharedMemoryBuffer::decode_from(str, &idx, &bufsz);
16+
EXPECT_EQ(111, idx);
17+
EXPECT_EQ(222, bufsz);
18+
}
19+
20+
TEST(Target, basic) {
21+
photon::vDMATarget* target = photon::new_shm_vdma_target("foo", 65536, 4096);
22+
DEFER(delete target);
23+
photon::vDMAInitiator* initiator= photon::new_shm_vdma_initiator("foo", 65536);
24+
DEFER(delete initiator);
25+
26+
std::string id_0_4096, id_1_4096, id_2_4096, id_3_4096;
27+
photon::SharedMemoryBuffer::encode_to(id_0_4096, 0, 4096);
28+
photon::SharedMemoryBuffer::encode_to(id_1_4096, 1, 4096);
29+
photon::SharedMemoryBuffer::encode_to(id_2_4096, 2, 4096);
30+
photon::SharedMemoryBuffer::encode_to(id_3_4096, 3, 4096);
31+
32+
// in this version, alloc size must same as unit
33+
photon::vDMABuffer* buffer = target->alloc(512);
34+
EXPECT_EQ(buffer, nullptr);
35+
36+
// target alloc
37+
photon::vDMABuffer* t_buffer0 = target->alloc(4096);
38+
EXPECT_EQ(reinterpret_cast<photon::SharedMemoryBuffer*>(t_buffer0)->idx(), 0);
39+
EXPECT_EQ(t_buffer0->buf_size(), 4096);
40+
EXPECT_EQ(t_buffer0->id().size(), id_0_4096.size());
41+
EXPECT_EQ(memcmp(t_buffer0->id().data(), id_0_4096.data(), id_0_4096.size()), 0);
42+
43+
photon::vDMABuffer* t_buffer1 = target->alloc(4096);
44+
EXPECT_EQ(t_buffer1->id().size(), id_1_4096.size());
45+
EXPECT_EQ(memcmp(t_buffer1->id().data(), id_1_4096.data(), id_1_4096.size()), 0);
46+
EXPECT_EQ(t_buffer1->buf_size(), 4096);
47+
EXPECT_EQ(t_buffer1->is_valid(), true);
48+
EXPECT_EQ(t_buffer1->is_registered(), true);
49+
EXPECT_EQ(t_buffer1->address(), (char*)t_buffer0->address()+4096);
50+
51+
photon::vDMABuffer* t_buffer2 = target->alloc(4096);
52+
EXPECT_EQ(t_buffer2->id().size(), id_2_4096.size());
53+
EXPECT_EQ(memcmp(t_buffer2->id().data(), id_2_4096.data(), id_2_4096.size()), 0);
54+
EXPECT_EQ(t_buffer2->address(), (char*)t_buffer1->address()+4096);
55+
56+
// initiator map
57+
photon::vDMABuffer* i_buffer0 = initiator->map(t_buffer0->id());
58+
EXPECT_EQ(i_buffer0->buf_size(), t_buffer0->buf_size());
59+
EXPECT_EQ(i_buffer0->id().size(), t_buffer0->id().size());
60+
EXPECT_EQ(memcmp(i_buffer0->id().data(), t_buffer0->id().data(), 16), 0);
61+
62+
char tmp[4096];
63+
memset(tmp, 0x5F, 4096);
64+
memcpy(t_buffer0->address(), tmp, 4096);
65+
EXPECT_EQ(memcmp(i_buffer0->address(), tmp, 4096), 0);
66+
memset(tmp, 0x22, 4096);
67+
memcpy(i_buffer0->address(), tmp, 4096);
68+
EXPECT_EQ(memcmp(t_buffer0->address(), tmp, 4096), 0);
69+
70+
initiator->unmap(i_buffer0);
71+
photon::vDMABuffer* t_buffer3 = target->alloc(4096);
72+
EXPECT_EQ(t_buffer3->id(), id_3_4096);
73+
74+
i_buffer0 = initiator->map(t_buffer0->id());
75+
photon::vDMABuffer* i_buffer1 = initiator->map(t_buffer1->id());
76+
77+
initiator->unmap(i_buffer0);
78+
initiator->unmap(i_buffer1);
79+
80+
target->dealloc(t_buffer0);
81+
target->dealloc(t_buffer1);
82+
target->dealloc(t_buffer2);
83+
target->dealloc(t_buffer3);
84+
}
85+
86+
void func1(photon::vDMATarget* target, int id) {
87+
auto buf = target->alloc(4096);
88+
LOG_INFO("thread ", id, " get buf ", reinterpret_cast<photon::SharedMemoryBuffer*>(buf)->idx());
89+
photon::thread_sleep(1);
90+
target->dealloc(buf);
91+
LOG_INFO("thread ", id, " release buf");
92+
}
93+
94+
TEST(Target, single_vcpu_multi_thread_alloc_1) {
95+
// 16 buffer, 16 thread(alloc - sleep 1s - free)
96+
// The result should be that each thread is allocated a different buffer
97+
auto target = photon::new_shm_vdma_target("foo", 65536, 4096);
98+
std::vector<photon::join_handle*> threads;
99+
for (int i=0; i<16; i++) {
100+
threads.push_back(photon::thread_enable_join(photon::thread_create11(func1, target, i)));
101+
}
102+
LOG_INFO("all create success");
103+
for (int i=0; i<16; i++) {
104+
photon::thread_join(threads[i]);
105+
}
106+
}
107+
108+
void func2(photon::vDMATarget* target, int id) {
109+
std::vector<int> stats(16, 0);
110+
for (int i=0; i<1000000; i++) {
111+
auto buf = target->alloc(4096);
112+
if (!buf) {
113+
LOG_INFO("some wrong ", id, " not get buffer in the middle");
114+
}
115+
for (int j=0; j<10; j++) {
116+
photon::thread_yield();
117+
}
118+
stats[reinterpret_cast<photon::SharedMemoryBuffer*>(buf)->idx()] += 1;
119+
target->dealloc(buf);
120+
}
121+
std::string output;
122+
for (const auto& t : stats) output.append(std::to_string(t) + " ");
123+
LOG_INFO(id, " res ", output.c_str());
124+
}
125+
126+
TEST(Target, single_vcpu_multi_thread_alloc_2) {
127+
// 16 buffer, 16 thread (repeat "alloc-free" 1000000 times)
128+
// alloc should be all success, test hold-release lock
129+
auto target = photon::new_shm_vdma_target("foo", 65536, 4096);
130+
std::vector<photon::join_handle*> threads;
131+
for (int i=0; i<16; i++) {
132+
threads.push_back(photon::thread_enable_join(photon::thread_create11(func2, target, i)));
133+
}
134+
LOG_INFO("all create success");
135+
for (int i=0; i<16; i++) {
136+
photon::thread_join(threads[i]);
137+
}
138+
}
139+
140+
void func3(photon::vDMATarget* target, int id) {
141+
auto buf = target->alloc(4096);
142+
if (buf) {
143+
LOG_INFO("thread ", id, " get buf ", reinterpret_cast<photon::SharedMemoryBuffer*>(buf)->idx());
144+
photon::thread_sleep(10);
145+
target->dealloc(buf);
146+
LOG_INFO("thread ", id, " release buf");
147+
}
148+
else {
149+
LOG_INFO("thread ", id, " not get buf");
150+
}
151+
}
152+
153+
TEST(Target, single_vcpu_multi_thread_alloc_3) {
154+
// 16 buffer, 20 thread (alloc - sleep 10s - dealloc)
155+
// only 16 thread can get buffer, other 4 thread will retry.
156+
// now, sleep time is very long
157+
// so, other 4 will get a nullptr in the end.
158+
// After a while, 16 thread release their buffer.
159+
auto target = photon::new_shm_vdma_target("foo", 65536, 4096);
160+
std::vector<photon::join_handle*> threads;
161+
for (int i=0; i<20; i++) {
162+
threads.push_back(photon::thread_enable_join(photon::thread_create11(func3, target, i)));
163+
}
164+
LOG_INFO("all create success");
165+
for (int i=0; i<20; i++) {
166+
photon::thread_join(threads[i]);
167+
}
168+
}
169+
170+
void func4(photon::vDMATarget* target, int id) {
171+
auto buf = target->alloc(4096);
172+
if (buf) {
173+
LOG_INFO("thread ", id, " get buf ", reinterpret_cast<photon::SharedMemoryBuffer*>(buf)->idx());
174+
photon::thread_usleep(1);
175+
target->dealloc(buf);
176+
LOG_INFO("thread ", id, " release buf");
177+
}
178+
else {
179+
LOG_INFO("thread ", id, " not get buf");
180+
}
181+
}
182+
183+
TEST(Target, single_vcpu_multi_thread_alloc_4) {
184+
// 16 buffer, 20 thread (alloc - sleep 1s - dealloc)
185+
// only 16 thread can get buffer at first, other thread will retry
186+
// sleep time is short, other 4 thread may get a buffer
187+
auto target = photon::new_shm_vdma_target("foo", 65536, 4096);
188+
std::vector<photon::join_handle*> threads;
189+
for (int i=0; i<20; i++) {
190+
threads.push_back(photon::thread_enable_join(photon::thread_create11(func4, target, i)));
191+
}
192+
LOG_INFO("all create success");
193+
for (int i=0; i<20; i++) {
194+
photon::thread_join(threads[i]);
195+
}
196+
}
197+
198+
TEST(Target, multi_vcpu_multi_thread) {
199+
// 16 buffer, 4 vcpu, each 16 thread (total 64 thread), 10000 task
200+
auto target = photon::new_shm_vdma_target("foo", 65536, 4096);
201+
photon::WorkPool wp(4, photon::INIT_EVENT_DEFAULT, photon::INIT_IO_NONE, 32);
202+
photon::semaphore sem(0);
203+
// rough statstic
204+
std::vector<int> stats(16, 0);
205+
int n_notget = 0;
206+
for (int i=0; i<10000; i++) {
207+
wp.async_call(new auto ([&]{
208+
auto buf = target->alloc(4096);
209+
if (buf) {
210+
int buf_idx = reinterpret_cast<photon::SharedMemoryBuffer*>(buf)->idx();
211+
LOG_INFO("task ", i, " get buf ", buf_idx);
212+
photon::thread_usleep(1);
213+
target->dealloc(buf);
214+
LOG_INFO("task ", i, " release buf ", buf_idx);
215+
stats[buf_idx] += 1;
216+
}
217+
else {
218+
LOG_INFO("task ", i, " not get buf");
219+
n_notget++;
220+
}
221+
sem.signal(1);
222+
}));
223+
}
224+
225+
sem.wait(10000); // wait most async task to complete
226+
227+
std::string output;
228+
for (int i=0; i<16; i++) output.append(std::to_string(stats[i]) + " ");
229+
LOG_INFO(output.c_str());
230+
LOG_INFO("get failed: ", n_notget);
231+
}
232+
233+
int main(int argc, char **argv){
234+
srand(time(nullptr));
235+
::testing::InitGoogleTest(&argc, argv);
236+
if (photon::init(photon::INIT_EVENT_DEFAULT, photon::INIT_IO_NONE))
237+
return -1;
238+
DEFER(photon::fini());
239+
return RUN_ALL_TESTS();
240+
}

0 commit comments

Comments
 (0)