glibc 实现代码的注释之翻译

condvar 的实现保证了对于 signal, broadcast, wait 的每次调用是以某种全序关系发生 -- 即在调用流程中保持 happens-before 关系。然而,全序关系并不一定会导致会建立附加的 happens-before 关系。对 wait 的调用实际上包括了三个独立的原子操作:(1)释放锁并进入阻塞等待; (2)等待结束; (3)加锁.


所有的 waiters 都在一个等待序列(WSEQ)中获得一个确定的位置,等待序列的长度为 64 位长的 __wseq。该序列决定哪些 waiters 可以消耗 signals。 调用一个 broadcast 等价于发送和 waiters 一
样多的 signals。当一个 signal 到达,它使用 releax-MO(松散内存顺序 releax memory order) load 的方式去读取 __wseq 值(即下一个 waiter 获得的位置)。使用 releax-MO 是足够的因为 signal 的调用已经是获得锁之后发生的。只有当 waiters 的 position 小于 signal 观察到的 __wseq 值时,waiters 才有机会消耗这个 signal [意为在 signal 到达之前已经在等待的 waiters 可以消耗这个 signal].


如果所有的 waiters 仅自旋等待那么是很容易实现的,但是我们需要用到 futex 使 waiters 进入阻塞等待。Futexes 并没有作出 FIFO 唤醒顺序的保证,所以我们仅使用 futex 是无法实现可靠唤醒的[有可能有机会消耗 signal 的 waiters 没有被唤醒]。而且,futex word 是 32 位长的,但我们需要区分多于 1<<32 个状态,因为我们需要表达出 wake-up 的顺序(即那些有机会消耗 signal 的 waiters); 一个 waiter 在 futex 上进入等待和决定它在 __wseq 上的位置,这两个操作并不是原子的,所以我们需要 futex word 可靠地通知 waiters 不应该再尝试进入等待因为它们在此时已经被唤醒了。因为 ABA 问题在 32 位长的值上是很小概率事件,当意识到它可能带来副作用时我们可以忽略它。
所以我们使用一个 64 位长的计数器去表示 __wseq(在仅支持32位长值原子操作的架构上,我们使用少于 64 位长的值). 我们使用两个组去处理在 futexes 上的等待:
1.G1 组由那些有机会消耗 signals 的 waiters 组成。新到来的 signals 将一直唤醒该组的 waiters 直到 G1 所有的 waiters 都被唤醒。
2.G2 组由后到达的 waiters 组成(此时 G1 中还存在未被唤醒的 waiters)。当 G1 中所有的 waiters 都被唤醒且有一个新的 signal 到达,则这个 signal 将 G2 转化为 G1 且“新建”
一个 G2 为将来的 waiters 使用.
由于 condvar 可能在多个进程之间共享,所以我们不能分配新的内存. 上面的“新建”其实是 G1 和 G2 之间的角色互换。它们各有独立的: futex word, 可供消耗的 signals 数量, 没有被唤醒的 waiters 数量,引用计数。


组引用计数维护了使用组的 futex 的 waiters 数量。当组角色互换之前必须的条件是没有 waiters 正使用组的 futex。这正好避免了 futex word 上的 ABA 问题。


为了表示 __wseq 中各个组覆盖的范围以及区分 G1/G2,我们使用一个 64位的计数器来指示 G1 的开始位置,以及它中间的一位来表示哪一个组是 G2。这允许了我们原子地做这两件事:
转换 G1/G2 角色,waiters 获得 WSEQ 中的位置. G1 开始的位置作用在于 waiters 可据此计算出它们是否处于一个已经被全部唤醒的组中(即,如果 G1 开始位置在当前 waiter 之后则说明它已经被唤醒了). waiters 不能实时知道它们处于 G1 或 G2 中,但它们不需如此,因为 waiters 关心的仅仅是当前有没有可消耗的 signals,并且 waiters 经常一开始位于 G2 中(waiters 知道 G2 的 slot 因为 __wseq 有一个位指示了). signals 将简单地填充那个完全唤醒且可以 close 的组(组的角色将被转换,直到组有必要去减少等待 waiters 仍持有即将关闭的 G1 的引用的可能性)。


signals 维护了 G1 初始化大小,这样它可以计算出 G2 的起始位置(G2 是右开区间的直到它变为 G1)。signals 可以跟踪任何一个 group 的剩余大小; 当 waiters 取消等待(调用 pthread 系列函数或超时),signals 将减少剩余大小.


为实现 convar 必要的销毁操作(如 ptrhead_cond_destroy 可以在所有 waiters 都被唤醒后被调用),waiters在等待之前会增加引用计数,结束等待之后且获得用户锁之前会减少引用计数。


所以 pthread_cond_t 数据结构包含以下成员(用于标志的一些位变量虽不是流程需要的一部分,但为了实现原子化,抑或是为了没有别的空间可以放置,所以某些成员的位就单独拿出来作为此用):
__wseq(waiter 序列计数)
1.LSB(least significant bit 最低有效位) 表示 G2 的索引
2.waiters 在获得用户锁之后对此变量执行 fetch-add(增加并返回旧值) 操作,signalers 加载并对它进行 fetch-xor. (以上两个操作者是并行的)


__g1_start(G1 开始位置, inclusive)
1.LSB 表示 G2 的索引
2.signalers 获得 condvar-internal 锁之后会修改此值,且该值会被并行地被 waiters 观察。

__g1_orig_size(G1 的初始化大小)
1.最低两位表示 condvar-internal 锁(00 表示没有获得锁;01 表示获得锁; 02 表示使用 futex_wake 的方式获得了锁)
2.仅获得了 condvar-internal 锁才能访问此值

__wrefs(waiter reference count)
1.第 2 位为 true 表示 waiters 应该执行 futex_wake 当最后一个引用被移除。pthread_cond_destory 使用该位作为 futex word
2.第 1 位表示 clock ID(0 表示实时时间,1 表示系统运行的流逝时间)
3.第 0 位为 true 表示当前的 condvar 是进程间共享的
4.如果 _wrefs 的格式变化了,应该....


对于每个 group 有下面这些成员:
__g_refs(futex 的 waiter 的引用计数)
1.LSB 为 true 表示最后一个引用被移除后应该运行 futex_wake 
2.该引用计数同时被 waiters 和 signalers 访问,前提是获得 condvar-internal 锁


__g_signals(可被消耗的 signals 的个数)
1.waiters 用此变量作为 futex word. 且它是同时被 signalers 和 waiters 访问的
2.LSB 为 true 表示此 group 已经被完全唤醒了(即此组被关闭了)


__g_size(当前组中还没有被唤醒的 waiters 的个数)
1.被 signalers 和取消等待的 waiters 访问,前提是获得 condvar-internal 锁
2.G2 的此值一直为 0 ,直到该组转变为 G1 才去计算
3.虽然此变量是 unsigned 的,但是我们可以依赖于 unsigned 的溢出规则来保存一个负数(特别地,G2 中的 waiters 取消等待)。


PTHREAD_COND_INITIALIZER 会将 condvar 的各个成员置为 0,且 G2 开始位置是 0 且 G1 是关闭的。


因为 waiters 正当从 __wseq 中获得一个位置时,并没有要求明确归属的 group,而只是在使用 futex 进入等待前明确了 group 的引用计数,于是便可能发生
一个问题:waiter 增加一个组的引用计数前组已经关闭了。所以 waiters 需要使用 __g1_start 检查组是否已经关闭(从 __wseq 中获得一个位置时),且在为了
从 __g_signals 中获得一个可用的 signal 进行的自旋等待时也需要作此检查. 需要注意的是, 检查中使用 releax-MO 加载 __g1_start 便已足够,  因为如果一个 waiter 可以看到一个足够大的值则它也可以在 waiter 组中消耗一个signal。


waiters 尝试从 __g_signals 中获得一个 signal 时,没有使用引用计数,有可能导致:它自从更近一个组中取得 signal,因为自己的组已被关闭。waiters 并不能时时监测到
这一行为,因为他们不知道什么时候会有这种行为,但是它们可以保守地往那个组加回一个 signal,如果这样-导致的结果将是虚假唤醒。为了尽可能避免这发生, __g1_start 
也包含了当前 G2 的索引,它将允许 waiters 进行是否存在组混淆的检查,如果没有,则它们并没有从 G1 错误地取得 signal,这意味着,G1 已经关闭了所以不需要任何修复。


pthread_cond_t 最后一个成员是 __g_signals[1],这是必要要。在老的版本的实现中,它指针大小的,所以使用 PTHREAD_COND_INITIALIZER 初始化后将得到一个0填充的四字节
空间,然而我们需要的是8字节的,__g_signals[1] 不会被访问直到第一次的组转换发生(G2 的 index 是0):经过一次无害的 fetch-or 操作(返回值被忽略)后设置 __g_signals[1]
为 0。








We need 3 least-significant bits on __wrefs for something else


以上翻译基于:

https://code.woboq.org/userspace/glibc/nptl/pthread_cond_wait.c.html#__condvar_cancel_waiting

https://code.woboq.org/userspace/glibc/nptl/pthread_cond_signal.c.html#__pthread_cond_signal




































































  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: glibc是GNU计划的一部分,是一套C语言标准库。内存管理是其中的一个重要组件。而ptmalloc2是glibc内存管理的一种算法,用于分配和释放内存块。 要下载glibc内存管理ptmalloc2源代码,可以通过以下几个步骤进行: 1. 打开GNU官方网站,找到glibc的相关页面,通常在https://www.gnu.org/software/libc/ 。 2. 在该页面上,找到下载链接或源代码仓库地址,这个地址通常会提供给用户下载最新版本的glibc。 3. 点击下载链接或者复制源代码仓库地址,将其粘贴到浏览器地址栏中。 4. 打开该链接后,您将能够下载一个压缩文件(通常是tar.gz或tar.bz2格式),包含了glibc的全部源代码。 5. 下载完毕后,解压压缩文件。您可以使用解压软件,如WinRAR或7-Zip。解压缩后,您将获得一个包含许多目录和文件的文件夹。 6. 在解压后的文件夹中,找到与ptmalloc2相关的源代码文件。通常这些文件会位于glibc代码的malloc目录下。 7. 在malloc目录中,您将能够找到ptmalloc2源代码文件,这些文件名通常以"ptmalloc"或"ptmalloc2"开头。 以上是下载glibc内存管理ptmalloc2源代码的一个大致过程。通过该源代码,您可以深入了解ptmalloc2算法是如何在glibc实现内存分配和释放的。但是请注意,阅读和理解源代码需要一定的计算机编程经验和相关背景知识。 ### 回答2: glibc是Linux操作系统中非常重要的一个C标准库,ptmalloc2是glibc中负责内存管理的模块之一。该模块负责动态分配和释放内存,并提供了多种内存分配器算法。 ptmalloc2源代码分析是深入研究该模块源代码的过程。通过分析ptmalloc2源代码,可以了解到它的实现原理、内存分配算法以及性能优化等方面的细节。 在下载ptmalloc2源代码之后,我们可以通过阅读和分析源代码来了解其内部结构和工作原理。在源代码中,我们可以找到一些关键的数据结构和函数,如malloc、free、realloc等。这些函数实现了动态内存分配和释放的基本功能。 通过阅读源代码,我们可以学习到ptmalloc2内存管理器的特点和优势。例如,ptmalloc2采用了分离的空闲链表来管理不同大小的内存块,利用了空闲块合并和分割等技术来提高内存的利用率和性能。此外,源代码还可能包含一些与内存操作相关的底层函数和宏定义。 分析ptmalloc2源代码不仅可以帮助我们理解其内部实现,还可以为我们定位和解决内存管理相关的问题提供指导。如果遇到性能问题或者内存泄漏等现象,我们可以通过分析源代码来找到问题的根源,并提出相应的优化措施。 总之,通过对glibc内存管理模块ptmalloc2的源代码进行分析,我们可以深入了解其实现原理和内部机制,为我们在实际项目中正确、高效地使用内存管理功能提供帮助。 ### 回答3: glibc是Linux系统上使用最广泛的C语言函数库,而ptmalloc2则是glibc中负责内存分配和管理的部分源代码。 首先,需要明确的是,glibc的ptmalloc2源代码并不是一个独立的项目,而是glibc库中的一部分。如果需要下载该源代码,可以通过访问glibc的官方网站或者使用git等工具来获取。 分析glibc内存管理ptmalloc2源代码可以帮助开发者更好地理解和使用glibc的内存分配功能。ptmalloc2实现了一种基于堆的内存分配算法,它采用了多种策略来管理内存,如bin和fastbin等。源代码的分析可以帮助我们了解这些策略的具体实现细节,以及它们在不同场景下的行为。 要对ptmalloc2源代码进行分析,可以首先阅读相关文档,如glibc的官方文档或论文。 掌握ptmalloc2的整体架构、数据结构和算法等基本知识后,可以通过逐行或逐函数地阅读源代码来深入理解其内部工作机制。可以关注一些关键函数的实现,如malloc、free、realloc等,以及相关的数据结构和算法。 此外,还可以参考开源社区中对ptmalloc2源代码的分析和解读,如一些博客文章、论文或代码注释等。这些资源通常提供了对源代码更深入的解释和讨论,对于理解ptmalloc2的实现细节会有所帮助。 总之,通过下载并分析glibc内存管理ptmalloc2源代码,可以帮助我们更好地理解和使用glibc库中的内存分配功能。同时,也可以通过分析源代码来提高我们的代码调试和性能优化能力,并为开发更高效的内存管理算法提供参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值