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 : #ifndef PETERSON_LOCK_H
12 : #define PETERSON_LOCK_H
13 :
14 : #include <string>
15 : #include <thread>
16 : #include <atomic>
17 : #include <hccl/hccl_types.h>
18 : #include "hccl_common.h"
19 : #include "mem_device_pub.h"
20 :
21 : /**
22 : * 这里实现Peterson算法来实现host侧与device侧的互斥访问
23 : *
24 : * 背景:host侧与device可以通过H2D/D2H等API来进行共享内存访问,
25 : * 那么这两者之间无法对共享变量使用类似于CAS的机制,因此不能基于
26 : * 原子修改类API的方式实现锁,所以这里基于Peterson算法实现,
27 : * paper参考:https://zoo.cs.yale.edu/classes/cs323/doc/Peterson.pdf
28 : *
29 : * 依赖:Peterson算法虽然不依赖原子操作,但是依赖读写内存序,因此需要添加内存屏障
30 : * 来避免编译器与CPU的乱序执行
31 : *
32 : * 限制:目前实现只支持单个Host线程与单个Device线程互斥访问,不支持Host/Device侧多线程访问,
33 : * 如需单侧多线程访问请先使用std::mutex或者pthread_spinlock_t保证互斥,再使用该锁
34 : *
35 : * 内存布局:
36 : * [u32 turn] [u32 hostFlag] [u32 deviceFlag]
37 : */
38 : namespace hccl {
39 : class PetersonLock {
40 : public:
41 : static constexpr u64 DEFAULT_LOCK_TIMEOUT_SEC = 60; /* 默认的超时时间, 60s */
42 :
43 : /* Host侧对象构造函数,在Init()中会进行Device侧内存申请 */
44 : explicit PetersonLock(u64 timeoutSec);
45 : /* Device侧对象构造函数,devPtr是在Host申请的设备侧内存地址 */
46 : PetersonLock(void* devPtr, u64 timeoutSec);
47 : ~PetersonLock();
48 :
49 : HcclResult Init();
50 : HcclResult DeInit();
51 :
52 : /* 目前该接口只服务于传递地址给Device侧,所以直接返回u64而不是指针 */
53 : u64 GetDevMemAddr() const;
54 :
55 : HcclResult Lock();
56 : HcclResult Unlock();
57 :
58 : /* 显式禁用所有copy、move构造函数,因为锁不可复制,不可移动 */
59 : PetersonLock(const PetersonLock&) = delete;
60 : PetersonLock(PetersonLock&&) = delete;
61 : PetersonLock& operator=(const PetersonLock&) = delete;
62 : PetersonLock& operator=(PetersonLock&&) = delete;
63 :
64 : private:
65 : HcclResult AllocDeviceMem();
66 :
67 : HcclResult WriteSelfFlag(u32 selfFlag);
68 : HcclResult WriteTurn();
69 : HcclResult ReadPeerFlag(u32& peerFlag);
70 : HcclResult ReadTurn(u32& peerTurn);
71 :
72 : static constexpr size_t MIN_SHM_LEN = 32; /* 最小的共享内存大小 */
73 : enum class Type : int { HOST = 0, DEVICE = 1 };
74 :
75 : static constexpr u32 TURN_FOR_HOST = 1;
76 : static constexpr u32 TURN_FOR_DEVICE = 0;
77 : static constexpr u32 FLAG_LOCK = 1; /* 获取锁 */
78 : static constexpr u32 FLAG_UNLOCK = 0; /* 释放锁 */
79 :
80 1197 : void MemFence() const
81 : {
82 : /* 内存屏障,即阻止编译器重排变量读写,也阻止CPU重排变量读写 */
83 : std::atomic_thread_fence(std::memory_order_seq_cst);
84 1197 : }
85 :
86 1285 : void Wait() const
87 : {
88 : /* 无事可做暂时释放CPU */
89 1285 : sched_yield();
90 1285 : }
91 :
92 : size_t size_ = 0;
93 : Type type_ = Type::HOST;
94 : std::string typeName_;
95 : u64 timeout_;
96 : DeviceMem devMem_;
97 : u32 myTurn_;
98 :
99 : /*
100 : * 这里变量使用volatile修饰,是为了Device直接读写内存数据,而不是CPU cache
101 : */
102 : volatile u32* turn_ = nullptr;
103 : volatile u32* hostFlag_ = nullptr;
104 : volatile u32* deviceFlag_ = nullptr;
105 : };
106 : /**
107 : * 使用RAII特性使用PetersonLock,在该对象构造时加锁,析构时释放锁
108 : * 因为获取锁有可能失败,所以要调用IsLockFailed()去检查,只有加锁成功才能继续往下执行
109 : */
110 : class PetersonLockGuard {
111 : public:
112 : explicit PetersonLockGuard(PetersonLock* lock);
113 : ~PetersonLockGuard();
114 :
115 200 : bool IsLockFailed() const { return lockFailed_; }
116 :
117 : /* 显式禁用所有copy、move构造函数,因为锁不可复制,不可移动 */
118 : PetersonLockGuard(const PetersonLockGuard&) = delete;
119 : PetersonLockGuard(PetersonLockGuard&&) = delete;
120 : PetersonLockGuard& operator=(const PetersonLockGuard&) = delete;
121 : PetersonLockGuard& operator=(PetersonLockGuard&&) = delete;
122 :
123 : private:
124 : PetersonLock* lock_ = nullptr;
125 : bool lockFailed_ = false;
126 : };
127 : } // namespace hccl
128 :
129 : #endif
|