From 290450bfbc8c4af9f28d64181da5bab08b28a641 Mon Sep 17 00:00:00 2001 From: kuangyufei Date: Tue, 15 Feb 2022 18:41:32 +0800 Subject: [PATCH] =?UTF-8?q?=20=E6=B3=A8=E8=A7=A3Futex=E5=86=85=E6=A0=B8?= =?UTF-8?q?=E6=80=81=E5=AE=9E=E7=8E=B0=20=20=20=20=20=E7=99=BE=E5=9B=BE?= =?UTF-8?q?=E7=94=BB=E9=B8=BF=E8=92=99=20+=20=E7=99=BE=E6=96=87=E8=AF=B4?= =?UTF-8?q?=E5=86=85=E6=A0=B8=20+=20=E7=99=BE=E4=B8=87=E6=B3=A8=E6=BA=90?= =?UTF-8?q?=E7=A0=81=20=20=3D>=20=E6=8C=96=E9=80=8F=E9=B8=BF=E8=92=99?= =?UTF-8?q?=E5=86=85=E6=A0=B8=E6=BA=90=E7=A0=81=20=20=20=20=20=E9=B8=BF?= =?UTF-8?q?=E8=92=99=E7=A0=94=E7=A9=B6=E7=AB=99=20|=20http://weharmonyos.c?= =?UTF-8?q?om=20(=E5=9B=BD=E5=86=85)=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20|=20https://weharmony.github.io=20(=E5=9B=BD=E5=A4=96)?= =?UTF-8?q?=20=20=20=20=20oschina=20|=20https://my.oschina.net/weharmony?= =?UTF-8?q?=20=20=20=20=20=E5=8D=9A=E5=AE=A2=E5=9B=AD=20|=20https://www.cn?= =?UTF-8?q?blogs.com/weharmony/=20=20=20=20=20=E7=9F=A5=E4=B9=8E=20|=20htt?= =?UTF-8?q?ps://www.zhihu.com/people/weharmonyos=20=20=20=20=20csdn=20|=20?= =?UTF-8?q?https://blog.csdn.net/kuangyufei=20=20=20=20=2051cto=20|=20http?= =?UTF-8?q?s://harmonyos.51cto.com/column/34=20=20=20=20=20=E6=8E=98?= =?UTF-8?q?=E9=87=91=20|=20https://juejin.cn/user/756888642000808=20=20=20?= =?UTF-8?q?=20=20=E5=85=AC=E4=BC=97=E5=8F=B7=20|=20=E9=B8=BF=E8=92=99?= =?UTF-8?q?=E7=A0=94=E7=A9=B6=E7=AB=99=20(weharmonyos)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- kernel/base/include/los_futex_pri.h | 6 +- kernel/base/ipc/los_futex.c | 51 +++++----- kernel/include/los_hash.h | 29 ++++++ syscall/process_syscall.c | 13 +-- zzz/demo/futex_demo.c | 144 ++++++++++++++++++++++++++++ zzz/git/push.sh | 2 +- 7 files changed, 216 insertions(+), 35 deletions(-) create mode 100644 zzz/demo/futex_demo.c diff --git a/README.md b/README.md index 8bf6f636..041bde73 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ * [v24.03 鸿蒙内核源码分析(进程概念) | 如何更好的理解进程](https://my.oschina.net/weharmony/blog/4937521) * [v25.05 鸿蒙内核源码分析(并发并行) | 听过无数遍的两个概念](https://my.oschina.net/weharmony/blog/4940329) * [v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志](https://my.oschina.net/weharmony/blog/4944129) -* [v27.05 鸿蒙内核源码分析(互斥锁) | 同样是锁它确更丰满](https://my.oschina.net/weharmony/blog/4945465) +* [v27.05 鸿蒙内核源码分析(互斥锁) | 同样是锁它却更丰满](https://my.oschina.net/weharmony/blog/4945465) * [v28.04 鸿蒙内核源码分析(进程通讯) | 九种进程间通讯方式速揽](https://my.oschina.net/weharmony/blog/4947398) * [v29.05 鸿蒙内核源码分析(信号量) | 谁在解决任务间的同步](https://my.oschina.net/weharmony/blog/4949720) * [v30.07 鸿蒙内核源码分析(事件控制) | 多对多任务如何同步](https://my.oschina.net/weharmony/blog/4950956) @@ -115,7 +115,9 @@ * [v74.01 鸿蒙内核源码分析(控制台) | 一个让很多人模糊的概念](https://my.oschina.net/weharmony/blog/5356308) * [v75.01 鸿蒙内核源码分析(远程登录) | 内核如何接待远方的客人](https://my.oschina.net/weharmony/blog/5375838) * [v76.01 鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式](https://my.oschina.net/weharmony/blog/5412148) -* [v77.01 鸿蒙内核源码分析(消息封装) | 剖析LiteIpc进程通讯内容](https://my.oschina.net/weharmony/blog/5421867) +* [v77.02 鸿蒙内核源码分析(消息封装) | 剖析LiteIpc(上)进程通讯内容](https://my.oschina.net/weharmony/blog/5421867) +* [v78.01 鸿蒙内核源码分析(消息映射) | 剖析LiteIpc(下)进程通讯机制](https://my.oschina.net/weharmony/blog/5436744) +* [v79.01 鸿蒙内核源码分析(用户态锁) | 如何使用快锁Futex(上)](https://my.oschina.net/weharmony/blog/5446656) #### 三: 百万注内核 | 处处扣细节 | 细胞血管 * 百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。 diff --git a/kernel/base/include/los_futex_pri.h b/kernel/base/include/los_futex_pri.h index fbcff412..196b46c9 100644 --- a/kernel/base/include/los_futex_pri.h +++ b/kernel/base/include/los_futex_pri.h @@ -77,14 +77,14 @@ #define FUTEX_TRYLOCK_PI 8 #define FUTEX_WAIT_BITSET 9 -#define FUTEX_PRIVATE 128 +#define FUTEX_PRIVATE 128 //私有快锁(以虚拟地址进行哈希) #define FUTEX_MASK 0x3U typedef struct { UINTPTR key; /* private:uvaddr | 私有锁,用虚拟地址 shared:paddr | 共享锁,用物理地址 */ - UINT32 index; /* hash bucket index | 哈希桶索引 */ + UINT32 index; /* hash bucket index | 哈希桶索引 OsFutexKeyToIndex */ UINT32 pid; /* private:process id shared:OS_INVALID(-1) | 私有锁:进程ID , 共享锁为 -1 */ - LOS_DL_LIST pendList; /* point to pendList in TCB struct | 挂到任务阻塞链表上*/ + LOS_DL_LIST pendList; /* point to pendList in TCB struct | 挂到任务阻塞链表上,挂起任务*/ LOS_DL_LIST queueList; /* thread list blocked by this lock | 挂等待这把锁的任务*/ LOS_DL_LIST futexList; /* point to the next FutexNode | 下一把Futex锁*/ } FutexNode; diff --git a/kernel/base/ipc/los_futex.c b/kernel/base/ipc/los_futex.c index 1cf12a0d..294e1919 100644 --- a/kernel/base/ipc/los_futex.c +++ b/kernel/base/ipc/los_futex.c @@ -4,6 +4,11 @@ * @link mutex http://weharmonyos.com/openharmony/zh-cn/device-dev/kernel/kernel-small-basic-trans-user-mutex.html @endlink * @link d17a6152740c https://www.jianshu.com/p/d17a6152740c @endlink @verbatim + Futex 由一块能够被多个进程共享的内存空间(一个对齐后的整型变量)组成;这个整型变量的值能够通过汇编语言调用CPU提供的原子操作指令来增加或减少, + 并且一个进程可以等待直到那个值变成正数。Futex 的操作几乎全部在用户空间完成;只有当操作结果不一致从而需要仲裁时,才需要进入操作系统内核空间执行。 + 这种机制允许使用 futex 的锁定原语有非常高的执行效率:由于绝大多数的操作并不需要在多个进程之间进行仲裁,所以绝大多数操作都可以在应用程序空间执行, + 而不需要使用(相对高代价的)内核系统调用。 + 基本概念 Futex(Fast userspace mutex,用户态快速互斥锁)是内核提供的一种系统调用能力,通常作为基础组件与用户态的相关 锁逻辑结合组成用户态锁,是一种用户态与内核态共同作用的锁,例如用户态mutex锁、barrier与cond同步锁、读写锁。 @@ -107,10 +112,10 @@ #define FUTEX_INDEX_SHARED_POS FUTEX_INDEX_PRIVATE_MAX ///< 共享锁开始位置 #define FUTEX_HASH_PRIVATE_MASK (FUTEX_INDEX_PRIVATE_MAX - 1) #define FUTEX_HASH_SHARED_MASK (FUTEX_INDEX_SHARED_MAX - 1) -/// 单独哈希桶 +/// 单独哈希桶,上面挂了一个个 FutexNode typedef struct { LosMux listLock;///< 内核操作lockList的互斥锁 - LOS_DL_LIST lockList;///< 用于挂载Futex(Fast userspace mutex,用户态快速互斥锁) + LOS_DL_LIST lockList;///< 用于挂载 FutexNode (Fast userspace mutex,用户态快速互斥锁) } FutexHash; FutexHash g_futexHash[FUTEX_INDEX_MAX];///< 80个哈希桶 @@ -203,39 +208,39 @@ VOID OsFutexHashShow(VOID) } } #endif - +/// 通过用户空间地址获取哈希key STATIC INLINE UINTPTR OsFutexFlagsToKey(const UINT32 *userVaddr, const UINT32 flags) { UINTPTR futexKey; if (flags & FUTEX_PRIVATE) { - futexKey = (UINTPTR)userVaddr; + futexKey = (UINTPTR)userVaddr;//私有锁(以虚拟地址进行哈希) } else { - futexKey = (UINTPTR)LOS_PaddrQuery((UINT32 *)userVaddr); + futexKey = (UINTPTR)LOS_PaddrQuery((UINT32 *)userVaddr);//共享锁(以物理地址进行哈希) } return futexKey; } - +/// 通过哈希key获取索引 STATIC INLINE UINT32 OsFutexKeyToIndex(const UINTPTR futexKey, const UINT32 flags) { - UINT32 index = LOS_HashFNV32aBuf(&futexKey, sizeof(UINTPTR), FNV1_32A_INIT); + UINT32 index = LOS_HashFNV32aBuf(&futexKey, sizeof(UINTPTR), FNV1_32A_INIT);//获取哈希桶索引 if (flags & FUTEX_PRIVATE) { index &= FUTEX_HASH_PRIVATE_MASK; } else { index &= FUTEX_HASH_SHARED_MASK; - index += FUTEX_INDEX_SHARED_POS; + index += FUTEX_INDEX_SHARED_POS;//共享锁索引 } return index; } - +/// 设置快锁哈希key STATIC INLINE VOID OsFutexSetKey(UINTPTR futexKey, UINT32 flags, FutexNode *node) { - node->key = futexKey; - node->index = OsFutexKeyToIndex(futexKey, flags); - node->pid = (flags & FUTEX_PRIVATE) ? LOS_GetCurrProcessID() : OS_INVALID; + node->key = futexKey;//哈希key + node->index = OsFutexKeyToIndex(futexKey, flags);//哈希桶索引 + node->pid = (flags & FUTEX_PRIVATE) ? LOS_GetCurrProcessID() : OS_INVALID;//获取进程ID,共享快锁时 快锁节点没有进程ID } STATIC INLINE VOID OsFutexDeinitFutexNode(FutexNode *node) @@ -492,7 +497,7 @@ STATIC INT32 OsFutexInsertTasktoPendList(FutexNode **firstNode, FutexNode *node, return OsFutexInsertFindFromFrontToBack(queueList, run, node); } -/// 由参数快锁找到对应哈希桶 +/// 由指定快锁找到对应哈希桶 STATIC FutexNode *OsFindFutexNode(const FutexNode *node) { FutexHash *hashNode = &g_futexHash[node->index];//先找到所在哈希桶 @@ -519,7 +524,7 @@ STATIC INT32 OsFindAndInsertToHash(FutexNode *node) INT32 ret; headNode = OsFindFutexNode(node); - if (headNode == NULL) { + if (headNode == NULL) {//没有找到 OsFutexInsertNewFutexKeyToHash(node); LOS_ListInit(&(node->queueList)); return LOS_OK; @@ -538,14 +543,14 @@ STATIC INT32 OsFindAndInsertToHash(FutexNode *node) return ret; } - +/// 共享内存检查 STATIC INT32 OsFutexKeyShmPermCheck(const UINT32 *userVaddr, const UINT32 flags) { PADDR_T paddr; /* Check whether the futexKey is a shared lock */ - if (!(flags & FUTEX_PRIVATE)) { - paddr = (UINTPTR)LOS_PaddrQuery((UINT32 *)userVaddr); + if (!(flags & FUTEX_PRIVATE)) {//非私有快锁 + paddr = (UINTPTR)LOS_PaddrQuery((UINT32 *)userVaddr);//能否查询到物理地址 if (paddr == 0) return LOS_NOK; } @@ -693,15 +698,15 @@ INT32 OsFutexWait(const UINT32 *userVaddr, UINT32 flags, UINT32 val, UINT32 absT INT32 ret; UINT32 timeOut = LOS_WAIT_FOREVER; - ret = OsFutexWaitParamCheck(userVaddr, flags, absTime); + ret = OsFutexWaitParamCheck(userVaddr, flags, absTime);//参数检查 if (ret) { return ret; } - if (absTime != LOS_WAIT_FOREVER) { - timeOut = OsNS2Tick((UINT64)absTime * OS_SYS_NS_PER_US); + if (absTime != LOS_WAIT_FOREVER) {//转换时间 , 内核的时间单位是 tick + timeOut = OsNS2Tick((UINT64)absTime * OS_SYS_NS_PER_US); //转成 tick } - return OsFutexWaitTask(userVaddr, flags, val, timeOut); + return OsFutexWaitTask(userVaddr, flags, val, timeOut);//将任务挂起 timeOut 时长 } STATIC INT32 OsFutexWakeParamCheck(const UINT32 *userVaddr, UINT32 flags) @@ -712,12 +717,12 @@ STATIC INT32 OsFutexWakeParamCheck(const UINT32 *userVaddr, UINT32 flags) PRINT_ERR("Futex wake param check failed! error flags: 0x%x\n", flags); return LOS_EINVAL; } - + //地址必须在用户空间 if ((vaddr % sizeof(INT32)) || (vaddr < OS_FUTEX_KEY_BASE) || (vaddr >= OS_FUTEX_KEY_MAX)) { PRINT_ERR("Futex wake param check failed! error userVaddr: 0x%x\n", userVaddr); return LOS_EINVAL; } - + //必须得是个共享内存地址 if (flags && (OsFutexKeyShmPermCheck(userVaddr, flags) != LOS_OK)) { PRINT_ERR("Futex wake param check failed! error shared memory perm userVaddr: 0x%x\n", userVaddr); return LOS_EINVAL; diff --git a/kernel/include/los_hash.h b/kernel/include/los_hash.h index 7cbf257b..a34ccc20 100644 --- a/kernel/include/los_hash.h +++ b/kernel/include/los_hash.h @@ -40,6 +40,35 @@ extern "C" { #endif /* __cplusplus */ #endif /* __cplusplus */ +/* +http://www.isthe.com/chongo/tech/comp/fnv/ +FNV算法简介 +FNV算法属于非密码学哈希函数,它最初由Glenn Fowler和Kiem-Phong Vo于1991年在IEEE POSIX P1003.2上首先提出, +最后由Landon Curt Noll 完善,故该算法以三人姓的首字母命名。 + +FNV算法目前有三种,分别是FNV-1,FNV-1a和FNV-0,但是FNV-0算法已经被丢弃了。FNV算法的哈希结果有32、64、128、256、512和1024位等长度。 +如果需要哈希结果长度不属于以上任意一种,也可以采用根据Changing the FNV hash size - xor-folding上面的指导进行变换得到。 +FNV-1算法过程如下: + hash = offset_basis + for each octet_of_data to be hashed + hash = hash * FNV_prime + hash = hash xor octet_of_data + return hash + +FNV-1a算法过程如下: + hash = offset_basis + for each octet_of_data to be hashed + hash = hash xor octet_of_data + hash = hash * FNV_prime + return hash +FNV-0算法过程如下: + hash = 0 + for each octet_of_data to be hashed + hash = hash * FNV_prime + hash = hash XOR octet_of_data + return hash +*/ + #define FNV1_32A_INIT ((UINT32)0x811c9dc5) /* diff --git a/syscall/process_syscall.c b/syscall/process_syscall.c index a5df1496..380de801 100644 --- a/syscall/process_syscall.c +++ b/syscall/process_syscall.c @@ -1009,7 +1009,7 @@ void SysThreadExit(int status) * @brief SysFutex 操作用户态快速互斥锁 * 系统调用 * @param absTime 绝对时间 - * @param flags 操作标识 + * @param flags 操作标识(FUTEX_WAKE | FUTEX_WAIT) * @param newUserAddr FUTEX_REQUEUE下调整后带回新的用户空间地址 * @param uAddr 用户态下共享内存的地址,里面存放的是一个对齐的整型计数器 * @param val @@ -1020,17 +1020,18 @@ void SysThreadExit(int status) int SysFutex(const unsigned int *uAddr, unsigned int flags, int val, unsigned int absTime, const unsigned int *newUserAddr) { - if ((flags & FUTEX_MASK) == FUTEX_REQUEUE) {//调整标识 + if ((flags & FUTEX_MASK) == FUTEX_REQUEUE) {//调整队列标识 return -OsFutexRequeue(uAddr, flags, val, absTime, newUserAddr); } if ((flags & FUTEX_MASK) == FUTEX_WAKE) {//唤醒标识 - return -OsFutexWake(uAddr, flags, val); + return -OsFutexWake(uAddr, flags, val);//最多唤醒val个等待在uaddr上进程 } - - return -OsFutexWait(uAddr, flags, val, absTime);//设置线程等待 + //FUTEX_WAIT + return -OsFutexWait(uAddr, flags, val, absTime);//设置线程等待 原子性的检查uaddr中计数器的值是否为val, + //如果是则让进程休眠,直到FUTEX_WAKE或者超时(time-out)。也就是把进程挂到uaddr相对应的等待队列上去。 } - +///获取当前任务ID unsigned int SysGetTid(void) { return OsCurrTaskGet()->taskID; diff --git a/zzz/demo/futex_demo.c b/zzz/demo/futex_demo.c new file mode 100644 index 00000000..52cf0c81 --- /dev/null +++ b/zzz/demo/futex_demo.c @@ -0,0 +1,144 @@ +/* futex_demo.c + + Usage: futex_demo [nloops] + (Default: 5) + + Demonstrate the use of futexes in a program where parent and child + use a pair of futexes located inside a shared anonymous mapping to + synchronize access to a shared resource: the terminal. The two + processes each write 'num-loops' messages to the terminal and employ + a synchronization protocol that ensures that they alternate in + writing messages. + */ + #define _GNU_SOURCE + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ + } while (0) + + static uint32_t *futex1, *futex2, *iaddr; + + static int + futex(uint32_t *uaddr, int futex_op, uint32_t val, + const struct timespec *timeout, uint32_t *uaddr2, uint32_t val3) + { + return syscall(SYS_futex, uaddr, futex_op, val, + timeout, uaddr2, val3); + } + + /* Acquire the futex pointed to by 'futexp': wait for its value to + become 1, and then set the value to 0. */ + + static void + fwait(uint32_t *futexp) + { + long s; + + /* atomic_compare_exchange_strong(ptr, oldval, newval) + atomically performs the equivalent of: + + if (*ptr == *oldval) + *ptr = newval; + + It returns true if the test yielded true and *ptr was updated. */ + + while (1) { + + /* Is the futex available? */ + const uint32_t one = 1; + if (atomic_compare_exchange_strong(futexp, &one, 0)) + break; /* Yes */ + + /* Futex is not available; wait. */ + + s = futex(futexp, FUTEX_WAIT, 0, NULL, NULL, 0); + if (s == -1 && errno != EAGAIN) + errExit("futex-FUTEX_WAIT"); + } + } + + /* Release the futex pointed to by 'futexp': if the futex currently + has the value 0, set its value to 1 and the wake any futex waiters, + so that if the peer is blocked in fwait(), it can proceed. */ + + static void + fpost(uint32_t *futexp) + { + long s; + + /* atomic_compare_exchange_strong() was described + in comments above. */ + + const uint32_t zero = 0; + if (atomic_compare_exchange_strong(futexp, &zero, 1)) { + s = futex(futexp, FUTEX_WAKE, 1, NULL, NULL, 0); + if (s == -1) + errExit("futex-FUTEX_WAKE"); + } + } + + int + main(int argc, char *argv[]) + { + pid_t childPid; + int nloops; + + setbuf(stdout, NULL); + + nloops = (argc > 1) ? atoi(argv[1]) : 5; + + /* Create a shared anonymous mapping that will hold the futexes. + Since the futexes are being shared between processes, we + subsequently use the "shared" futex operations (i.e., not the + ones suffixed "_PRIVATE"). */ + + iaddr = mmap(NULL, sizeof(*iaddr) * 2, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_SHARED, -1, 0); + if (iaddr == MAP_FAILED) + errExit("mmap"); + + futex1 = &iaddr[0]; + futex2 = &iaddr[1]; + + *futex1 = 0; /* State: unavailable */ + *futex2 = 1; /* State: available */ + + /* Create a child process that inherits the shared anonymous + mapping. */ + + childPid = fork(); + if (childPid == -1) + errExit("fork"); + + if (childPid == 0) { /* Child */ + for (int j = 0; j < nloops; j++) { + fwait(futex1); + printf("Child (%jd) %d\n", (intmax_t) getpid(), j); + fpost(futex2); + } + + exit(EXIT_SUCCESS); + } + + /* Parent falls through to here. */ + + for (int j = 0; j < nloops; j++) { + fwait(futex2); + printf("Parent (%jd) %d\n", (intmax_t) getpid(), j); + fpost(futex1); + } + + wait(NULL); + + exit(EXIT_SUCCESS); + } \ No newline at end of file diff --git a/zzz/git/push.sh b/zzz/git/push.sh index 29de04b7..a6e30191 100644 --- a/zzz/git/push.sh +++ b/zzz/git/push.sh @@ -1,5 +1,5 @@ git add -A -git commit -m ' 注解Futex(Fast userspace mutex,用户态快速互斥锁)模块实现 +git commit -m ' 注解Futex内核态实现 百图画鸿蒙 + 百文说内核 + 百万注源码 => 挖透鸿蒙内核源码 鸿蒙研究站 | http://weharmonyos.com (国内) | https://weharmony.github.io (国外) -- GitLab