注解Futex内核态实现

    百图画鸿蒙 + 百文说内核 + 百万注源码  => 挖透鸿蒙内核源码
    鸿蒙研究站 | http://weharmonyos.com (国内)
              | https://weharmony.github.io (国外)
    oschina | https://my.oschina.net/weharmony
    博客园 | https://www.cnblogs.com/weharmony/
    知乎 | https://www.zhihu.com/people/weharmonyos
    csdn | https://blog.csdn.net/kuangyufei
    51cto | https://harmonyos.51cto.com/column/34
    掘金 | https://juejin.cn/user/756888642000808
    公众号 | 鸿蒙研究站 (weharmonyos)
上级 3b3eb4fa
......@@ -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)
#### 三: 百万注内核 | 处处扣细节 | 细胞血管
* 百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。
......
......@@ -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;
......
......@@ -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;
......
......@@ -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)
/*
......
......@@ -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;
......
/* 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 <stdio.h>
#include <errno.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#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
git add -A
git commit -m ' 注解Futex(Fast userspace mutex,用户态快速互斥锁)模块实现
git commit -m ' 注解Futex内核态实现
百图画鸿蒙 + 百文说内核 + 百万注源码 => 挖透鸿蒙内核源码
鸿蒙研究站 | http://weharmonyos.com (国内)
| https://weharmony.github.io (国外)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册