Line data Source code
1 : /**
2 : * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3 : * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4 : * CANN Open Software License Agreement Version 2.0 (the "License").
5 : * Please refer to the License for details. You may not use this file except in compliance with the License.
6 : * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7 : * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8 : * See LICENSE in the root of the software repository for the full text of the License.
9 : */
10 :
11 : #include <securec.h>
12 : #include "adapter_rts_common.h"
13 : #include "peterson_lock.h"
14 :
15 : namespace hccl {
16 525 : PetersonLock::PetersonLock(u64 timeoutSec)
17 525 : : size_(MIN_SHM_LEN),
18 525 : type_(Type::HOST),
19 1050 : typeName_("Host"),
20 525 : timeout_(timeoutSec),
21 525 : myTurn_(TURN_FOR_HOST)
22 525 : {}
23 :
24 13 : PetersonLock::PetersonLock(void* devPtr, u64 timeoutSec)
25 13 : : size_(MIN_SHM_LEN),
26 13 : type_(Type::DEVICE),
27 26 : typeName_("Device"),
28 13 : timeout_(timeoutSec),
29 13 : devMem_(devPtr, size_, false),
30 13 : myTurn_(TURN_FOR_DEVICE)
31 13 : {}
32 :
33 538 : PetersonLock::~PetersonLock() { DeInit(); }
34 :
35 538 : HcclResult PetersonLock::Init()
36 : {
37 538 : if (type_ == Type::HOST) {
38 525 : if (AllocDeviceMem() != HCCL_SUCCESS) {
39 0 : return HCCL_E_INTERNAL;
40 : }
41 : }
42 :
43 538 : auto buffer = reinterpret_cast<u8*>(devMem_.ptr());
44 538 : size_t offset = 0;
45 538 : turn_ = reinterpret_cast<volatile u32*>(buffer + offset);
46 538 : offset += sizeof(u32);
47 :
48 538 : hostFlag_ = reinterpret_cast<volatile u32*>(buffer + offset);
49 538 : offset += sizeof(u32);
50 :
51 538 : deviceFlag_ = reinterpret_cast<volatile u32*>(buffer + offset);
52 538 : offset += sizeof(u32);
53 :
54 538 : HCCL_INFO(
55 : "[PetersonLock][Init] type [%s] init success, memSize [%lu Byte] timeout[%lu s]", typeName_.c_str(),
56 : devMem_.size(), timeout_);
57 538 : return HCCL_SUCCESS;
58 : }
59 :
60 525 : HcclResult PetersonLock::AllocDeviceMem()
61 : {
62 525 : if (devMem_.ptr() != nullptr || devMem_.size() != 0) {
63 0 : HCCL_ERROR("[PetersonLock][AllocDeviceMem] init failed, maybe it's already inited");
64 0 : return HCCL_E_INTERNAL;
65 : }
66 :
67 525 : CHK_RET(DeviceMem::alloc(devMem_, size_));
68 :
69 525 : if (hrtMemSet(devMem_.ptr(), devMem_.size(), devMem_.size()) != HCCL_SUCCESS) {
70 0 : HCCL_ERROR("[PetersonLock][AllocDeviceMem] memset device memory failed");
71 0 : return HCCL_E_INTERNAL;
72 : }
73 :
74 525 : HCCL_INFO("[PetersonLock][AllocDeviceMem] Type[%s] alloc memSize[%lu Byte]", typeName_.c_str(), size_);
75 525 : return HCCL_SUCCESS;
76 : }
77 :
78 538 : HcclResult PetersonLock::DeInit()
79 : {
80 538 : size_ = 0;
81 :
82 538 : turn_ = nullptr;
83 538 : hostFlag_ = nullptr;
84 538 : deviceFlag_ = nullptr;
85 538 : return HCCL_SUCCESS;
86 : }
87 :
88 2 : u64 PetersonLock::GetDevMemAddr() const { return reinterpret_cast<u64>(devMem_.ptr()); }
89 :
90 400 : HcclResult PetersonLock::Lock()
91 : {
92 400 : if (turn_ == nullptr || hostFlag_ == nullptr || deviceFlag_ == nullptr) {
93 0 : HCCL_ERROR("[PetersonLock][lock] ptr is nullptr, maybe not call Init()");
94 0 : return HCCL_E_INTERNAL;
95 : }
96 :
97 400 : HCCL_DEBUG("[PetersonLock][Lock] type [%s] before require the lock", typeName_.c_str());
98 400 : auto startTime = std::chrono::steady_clock::now();
99 400 : auto timeout = std::chrono::seconds(timeout_);
100 :
101 : /* 更新flag表明自己准备获取锁 */
102 400 : CHK_RET(WriteSelfFlag(FLAG_LOCK));
103 :
104 : /* 更新TURN值 */
105 400 : CHK_RET(WriteTurn());
106 :
107 : /*
108 : * 这里判断为真只有一种场景会进入等待,需满足以下两个条件
109 : * - 条件1:peer也想要获取锁
110 : * - 条件2:self更新的turn等于自己设置的值
111 : * 通常条件2意味着peer执行的更快,使得自己是覆盖写turn,那么自己就需要等待
112 : *
113 : * (PS:当然这里存在并发场景,即self刚写完turn就又读turn,peer也是刚写完turn就又读turn,
114 : * 此时两端都满足上述为真,就都进入等待,但是因为turn最终只会有一个值,
115 : * 所以再循环一遍后最后turn的值就只能是一个了,此时谁最后更新谁就去等待)
116 : *
117 : * Q:为什么读取peer为FLAG_UNLOCK时本端一定可以安全获取锁?
118 : * A:当自己读到peer是释放锁状态时,peer侧有三种场景:
119 : * - 场景1:peer不准备获取锁,此时self可以安全获取锁
120 : * - 场景2:peer已经释放了锁,此时self可以安全获取锁
121 : * - 场景3:peer侧也想获取锁,但是由于并发(self读flag peer写flag)导致peer更新的flag未被self读到
122 : * 对于场景3,由于严格内存序那么写self与读peer操作一定是串行,此时self虽未及时读到peer的flag,
123 : * 但是因为自己的flag已经被更新,不会与peer的读有并发,因此一定能被peer读到,那么peer就会获取到
124 : * self要获取锁,就会进入前面描述的状态,即等待self释放锁
125 : * 时序图描述大致如下:
126 : * self peer
127 : * write flag (LOCK)
128 : * write turn (HOST)
129 : * read peer flag (UNLOCK) write flag (LOCK) # 这里并发导致未及时读到最新值
130 : * 成功获取锁 write turn (DEVICE) # 这里自己一定是最后更新的turn
131 : * read peer flag (LOCK) # 读到的一定是获取锁,因为没有并发问题
132 : * peer==LOCK && turn==DEVICE 为真,所以等待
133 : */
134 : u32 peerFlag;
135 : u32 turn;
136 : while (true) {
137 1684 : if ((std::chrono::steady_clock::now() - startTime) > timeout) {
138 0 : HCCL_ERROR("[PetersonLock][Lock] type [%s] get lock timeout [%lu s]", typeName_.c_str(), timeout_);
139 :
140 : /* 重置flag */
141 0 : CHK_RET(WriteSelfFlag(FLAG_UNLOCK));
142 0 : return HCCL_E_TIMEOUT;
143 : }
144 :
145 1678 : CHK_RET(ReadPeerFlag(peerFlag));
146 1685 : CHK_RET(ReadTurn(turn));
147 :
148 1685 : if (peerFlag == FLAG_LOCK && turn == myTurn_) {
149 1285 : Wait();
150 : } else {
151 400 : HCCL_DEBUG("[PetersonLock][Lock] type [%s] got the lock", typeName_.c_str());
152 400 : break;
153 : }
154 1285 : }
155 :
156 400 : HCCL_DEBUG("[PetersonLock][Lock] type [%s] after require the lock", typeName_.c_str());
157 400 : return HCCL_SUCCESS;
158 : }
159 :
160 400 : HcclResult PetersonLock::Unlock()
161 : {
162 400 : if (deviceFlag_ == nullptr) {
163 0 : HCCL_ERROR("[PetersonLock][Unlock] ptr is nullptr, maybe not call Init()");
164 0 : return HCCL_E_INTERNAL;
165 : }
166 :
167 : /* 释放锁 */
168 400 : WriteSelfFlag(FLAG_UNLOCK);
169 :
170 400 : HCCL_DEBUG("[PetersonLock][Unlock] type [%s] release the lock", typeName_.c_str());
171 400 : return HCCL_SUCCESS;
172 : }
173 :
174 799 : HcclResult PetersonLock::WriteSelfFlag(u32 selfFlag)
175 : {
176 799 : if (type_ == Type::DEVICE) {
177 400 : *deviceFlag_ = selfFlag;
178 : } else {
179 399 : u32 hostFlag = selfFlag;
180 799 : if (hrtMemSyncCopy(
181 399 : const_cast<u32*>(hostFlag_), sizeof(u32), &hostFlag, sizeof(u32),
182 : HcclRtMemcpyKind::HCCL_RT_MEMCPY_KIND_HOST_TO_DEVICE)
183 400 : != HCCL_SUCCESS) {
184 0 : HCCL_INFO("[PetersonLock][WriteSelfFlag] H2D write hostFlag not correct");
185 0 : return HCCL_E_INTERNAL;
186 : }
187 : }
188 :
189 800 : MemFence();
190 800 : return HCCL_SUCCESS;
191 : }
192 :
193 400 : HcclResult PetersonLock::WriteTurn()
194 : {
195 400 : if (type_ == Type::DEVICE) {
196 200 : *turn_ = TURN_FOR_DEVICE;
197 : } else {
198 200 : u32 turn = TURN_FOR_HOST;
199 400 : if (hrtMemSyncCopy(
200 200 : const_cast<u32*>(turn_), sizeof(u32), &turn, sizeof(u32),
201 : HcclRtMemcpyKind::HCCL_RT_MEMCPY_KIND_HOST_TO_DEVICE)
202 200 : != HCCL_SUCCESS) {
203 0 : HCCL_INFO("[PetersonLock][WriteSelfFlag] H2D write turn not correct");
204 0 : return HCCL_E_INTERNAL;
205 : }
206 : }
207 :
208 400 : MemFence();
209 399 : return HCCL_SUCCESS;
210 : }
211 :
212 1678 : HcclResult PetersonLock::ReadPeerFlag(u32& peerFlag)
213 : {
214 1678 : if (type_ == Type::DEVICE) {
215 1485 : peerFlag = *hostFlag_;
216 : } else {
217 393 : if (hrtMemSyncCopy(
218 193 : &peerFlag, sizeof(u32), const_cast<u32*>(deviceFlag_), sizeof(u32),
219 : HcclRtMemcpyKind::HCCL_RT_MEMCPY_KIND_DEVICE_TO_HOST)
220 200 : != HCCL_SUCCESS) {
221 0 : HCCL_INFO("[PetersonLock][ReadPeerFlag] D2H read device flag not correct");
222 0 : return HCCL_E_INTERNAL;
223 : }
224 : }
225 1685 : return HCCL_SUCCESS;
226 : }
227 :
228 1685 : HcclResult PetersonLock::ReadTurn(u32& turn)
229 : {
230 1685 : if (type_ == Type::DEVICE) {
231 1485 : turn = *turn_;
232 : } else {
233 400 : if (hrtMemSyncCopy(
234 200 : &turn, sizeof(u32), const_cast<u32*>(turn_), sizeof(u32),
235 : HcclRtMemcpyKind::HCCL_RT_MEMCPY_KIND_DEVICE_TO_HOST)
236 200 : != HCCL_SUCCESS) {
237 0 : HCCL_INFO("[PetersonLock][ReadPeerFlag] D2H read turn not correct");
238 0 : return HCCL_E_INTERNAL;
239 : }
240 : }
241 1685 : return HCCL_SUCCESS;
242 : }
243 :
244 200 : PetersonLockGuard::PetersonLockGuard(PetersonLock* lock) : lock_(lock), lockFailed_(false)
245 : {
246 200 : if (lock_ == nullptr) {
247 0 : HCCL_ERROR("[PetersonLockGuard] invalid lock");
248 0 : lockFailed_ = true;
249 0 : return;
250 : }
251 :
252 200 : if (lock_->Lock() != HCCL_SUCCESS) {
253 0 : HCCL_ERROR("[PetersonLockGuard] lock failed");
254 0 : lockFailed_ = true;
255 0 : lock_ = nullptr;
256 : }
257 : }
258 :
259 200 : PetersonLockGuard::~PetersonLockGuard()
260 : {
261 200 : if (lock_) {
262 200 : lock_->Unlock();
263 : }
264 200 : }
265 : } // namespace hccl
|