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 : #include <securec.h>
11 : #include "adapter_rts_common.h"
12 : #include "peterson_lock.h"
13 :
14 : namespace hccl {
15 522 : PetersonLock::PetersonLock(u64 timeoutSec)
16 1044 : : size_(MIN_SHM_LEN), type_(Type::HOST), typeName_("Host"), timeout_(timeoutSec),
17 522 : myTurn_(TURN_FOR_HOST)
18 : {
19 522 : }
20 :
21 13 : PetersonLock::PetersonLock(void *devPtr, u64 timeoutSec)
22 26 : : size_(MIN_SHM_LEN), type_(Type::DEVICE), typeName_("Device"), timeout_(timeoutSec),
23 13 : devMem_(devPtr, size_, false), myTurn_(TURN_FOR_DEVICE)
24 : {
25 13 : }
26 :
27 :
28 532 : PetersonLock::~PetersonLock()
29 : {
30 532 : DeInit();
31 534 : }
32 :
33 535 : HcclResult PetersonLock::Init()
34 : {
35 535 : if (type_ == Type::HOST) {
36 522 : if (AllocDeviceMem() != HCCL_SUCCESS) {
37 0 : return HCCL_E_INTERNAL;
38 : }
39 : }
40 :
41 535 : auto buffer = reinterpret_cast<u8 *>(devMem_.ptr());
42 535 : size_t offset = 0;
43 535 : turn_ = reinterpret_cast<volatile u32 *>(buffer + offset);
44 535 : offset += sizeof(u32);
45 :
46 535 : hostFlag_ = reinterpret_cast<volatile u32 *>(buffer + offset);
47 535 : offset += sizeof(u32);
48 :
49 535 : deviceFlag_ = reinterpret_cast<volatile u32 *>(buffer + offset);
50 535 : offset += sizeof(u32);
51 :
52 535 : HCCL_INFO("[PetersonLock][Init] type [%s] init success, memSize [%lu Byte] timeout[%lu s]", typeName_.c_str(),
53 : devMem_.size(), timeout_);
54 535 : return HCCL_SUCCESS;
55 : }
56 :
57 522 : HcclResult PetersonLock::AllocDeviceMem()
58 : {
59 522 : if (devMem_.ptr() != nullptr || devMem_.size() != 0) {
60 0 : HCCL_ERROR("[PetersonLock][AllocDeviceMem] init failed, maybe it's already inited");
61 0 : return HCCL_E_INTERNAL;
62 : }
63 :
64 522 : CHK_RET(DeviceMem::alloc(devMem_, size_));
65 :
66 522 : if (hrtMemSet(devMem_.ptr(), devMem_.size(), devMem_.size()) != HCCL_SUCCESS) {
67 0 : HCCL_ERROR("[PetersonLock][AllocDeviceMem] memset device memory failed");
68 0 : return HCCL_E_INTERNAL;
69 : }
70 :
71 522 : HCCL_INFO("[PetersonLock][AllocDeviceMem] Type[%s] alloc memSize[%lu Byte]", typeName_.c_str(), size_);
72 522 : return HCCL_SUCCESS;
73 : }
74 :
75 534 : HcclResult PetersonLock::DeInit()
76 : {
77 534 : size_ = 0;
78 :
79 534 : turn_ = nullptr;
80 534 : hostFlag_ = nullptr;
81 534 : deviceFlag_ = nullptr;
82 534 : return HCCL_SUCCESS;
83 : }
84 :
85 2 : u64 PetersonLock::GetDevMemAddr() const
86 : {
87 2 : return reinterpret_cast<u64>(devMem_.ptr());
88 : }
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 398 : auto startTime = std::chrono::steady_clock::now();
99 400 : auto timeout = std::chrono::seconds(timeout_);
100 :
101 : /* 更新flag表明自己准备获取锁 */
102 398 : 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 1705 : 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 1696 : CHK_RET(ReadPeerFlag(peerFlag));
146 1705 : CHK_RET(ReadTurn(turn));
147 :
148 1705 : if (peerFlag == FLAG_LOCK && turn == myTurn_) {
149 1305 : Wait();
150 : } else {
151 400 : HCCL_DEBUG("[PetersonLock][Lock] type [%s] got the lock", typeName_.c_str());
152 400 : break;
153 : }
154 1305 : }
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 796 : HcclResult PetersonLock::WriteSelfFlag(u32 selfFlag)
175 : {
176 796 : if (type_ == Type::DEVICE) {
177 400 : *deviceFlag_ = selfFlag;
178 : } else {
179 396 : u32 hostFlag = selfFlag;
180 396 : if (hrtMemSyncCopy(const_cast<u32 *>(hostFlag_), sizeof(u32), &hostFlag,
181 400 : sizeof(u32), HcclRtMemcpyKind::HCCL_RT_MEMCPY_KIND_HOST_TO_DEVICE) != HCCL_SUCCESS) {
182 0 : HCCL_INFO("[PetersonLock][WriteSelfFlag] H2D write hostFlag not correct");
183 0 : return HCCL_E_INTERNAL;
184 : }
185 : }
186 :
187 800 : MemFence();
188 800 : return HCCL_SUCCESS;
189 : }
190 :
191 400 : HcclResult PetersonLock::WriteTurn()
192 : {
193 400 : if (type_ == Type::DEVICE) {
194 200 : *turn_ = TURN_FOR_DEVICE;
195 : } else {
196 200 : u32 turn = TURN_FOR_HOST;
197 200 : if (hrtMemSyncCopy(const_cast<u32 *>(turn_), sizeof(u32), &turn,
198 200 : sizeof(u32), HcclRtMemcpyKind::HCCL_RT_MEMCPY_KIND_HOST_TO_DEVICE) != HCCL_SUCCESS) {
199 0 : HCCL_INFO("[PetersonLock][WriteSelfFlag] H2D write turn not correct");
200 0 : return HCCL_E_INTERNAL;
201 : }
202 : }
203 :
204 400 : MemFence();
205 400 : return HCCL_SUCCESS;
206 : }
207 :
208 1696 : HcclResult PetersonLock::ReadPeerFlag(u32 &peerFlag)
209 : {
210 1696 : if (type_ == Type::DEVICE) {
211 1505 : peerFlag = *hostFlag_;
212 : } else {
213 191 : if (hrtMemSyncCopy(&peerFlag, sizeof(u32), const_cast<u32 *>(deviceFlag_),
214 200 : sizeof(u32), HcclRtMemcpyKind::HCCL_RT_MEMCPY_KIND_DEVICE_TO_HOST) != HCCL_SUCCESS) {
215 0 : HCCL_INFO("[PetersonLock][ReadPeerFlag] D2H read device flag not correct");
216 0 : return HCCL_E_INTERNAL;
217 : }
218 : }
219 1705 : return HCCL_SUCCESS;
220 : }
221 :
222 1705 : HcclResult PetersonLock::ReadTurn(u32 &turn)
223 : {
224 1705 : if (type_ == Type::DEVICE) {
225 1505 : turn = *turn_;
226 : } else {
227 200 : if (hrtMemSyncCopy(&turn, sizeof(u32), const_cast<u32 *>(turn_),
228 200 : sizeof(u32), HcclRtMemcpyKind::HCCL_RT_MEMCPY_KIND_DEVICE_TO_HOST) != HCCL_SUCCESS) {
229 0 : HCCL_INFO("[PetersonLock][ReadPeerFlag] D2H read turn not correct");
230 0 : return HCCL_E_INTERNAL;
231 : }
232 : }
233 1705 : return HCCL_SUCCESS;
234 : }
235 :
236 200 : PetersonLockGuard::PetersonLockGuard(PetersonLock *lock) : lock_(lock), lockFailed_(false)
237 : {
238 200 : if (lock_ == nullptr) {
239 0 : HCCL_ERROR("[PetersonLockGuard] invalid lock");
240 0 : lockFailed_ = true;
241 0 : return;
242 : }
243 :
244 200 : if (lock_->Lock() != HCCL_SUCCESS) {
245 0 : HCCL_ERROR("[PetersonLockGuard] lock failed");
246 0 : lockFailed_ = true;
247 0 : lock_ = nullptr;
248 : }
249 : }
250 :
251 200 : PetersonLockGuard::~PetersonLockGuard()
252 : {
253 200 : if (lock_) {
254 200 : lock_->Unlock();
255 : }
256 200 : }
257 : }
|