struct ucred
是内核内部的凭据结构体,
它通常作为内核中以进程为导向的访问控制的依据。 BSD-派生的系统采用一种 “写时复制”
的模型来处理凭据数据: 同一凭据结构体可能存在多个引用, 如果需要对其进行修改,
则这个结构体将被复制、 修改, 然后替换该引用。
由于在打开时用于实现访问控制的凭据快取缓存广泛存在, 这种做法会极大地节省内存。
在迁移到细粒度的 SMP 时, 这一模型也省去了大量的锁操作,
因为只有未共享的凭据才能实施修改, 因而避免了在使用共享凭据时额外的同步操作。
凭据结构体只有一个引用时, 被认为是可变的; 不允许改变共享的凭据结构体,
否则将可能导致发生竞态条件。 cr_mtxp
mutex 用于保护
struct ucred
的引用计数, 以维护其一致性。
使用凭据结构体时, 必须在使用过程中保持有效的引用,
否则它就可能在这个不合理的消费者使用过程中被释放。
struct ucred
mutex 是一种叶 mutex, 出于性能考虑,
它通过 mutex 池实现。
由于多用于访问控制决策, 凭据通常情况下是以只读方式访问的, 此时一般应使用 td_ucred
, 因为它不需要上锁。 当更新进程凭据时,
检查和更新过程中必须持有 proc 锁。 检查和更新操作必须使用 p_ucred
, 以避免检查时和使用时的竞态条件。
如果所调系统调用将在更新进程凭据之后进行访问控制检查, 则 td_ucred
也必须刷新为当前进程的值。
这样做能够避免修改后使用过时的凭据。 内核会自动在进程进入内核时, 将线程结构体的 td_ucred
指针刷新为进程的 p_ucred
, 以保证内核访问控制能用到新的凭据。
详细内容将在稍后增加。
struct prison
保存了用于维护那些通过 jail(2) API 创建的
jail 所用到的管理信息。 这包括 jail 的主机名、 IP 地址, 以及一些相关的设置。
这个结构体包含引用计数, 因为指向这一结构体实例的指针会在多种凭据结构之间共享。 用了一个
mutex, pr_mtx
来保护对引用计数以及所有 jail
结构体中可变变量的读写访问。 有一些变量只会在创建 jail 的时刻发生变化, 只需持有有效的
struct prison
就可以开始读这些值了。
关于每个项目具体的上锁操作的文档, 可以在 sys/jail.h
的注释中找到。
TrustedBSD MAC 框架会以 struct label
的形式维护一系列内核对象的数据。 一般来说, 内核中的 label (标签)
是由与其对应的内核对象同样的锁保护的。 例如, struct
vnode
上的 v_label
标签是由其所在 vnode 上的
vnode 锁保护的。
除了嵌入到标准内核对象中的标签之外, MAC
框架也需要维护一组包含已注册的和激活策略的列表。 策略表和忙计数由一个全局 mutex (mac_policy_list_lock
) 保护。
由于能够同时并行地进行许多访问控制检查, 对策略表的只读访问, 在增减忙计数时,
框架的入口处需要首先持有这个 mutex。 MAC 入口操作的过程中并不需要长时间持有此 mutex --
有些操作, 例如文件系统对象上的标签操作 -- 是持久的。 要修改策略表,
例如在注册和解除注册策略时, 需要持有此 mutex, 而且要求引用计数为零,
以避免在用表时对其进行修改。
对于需要等待表进入闲置状态的线程, 提供了一个条件变量 mac_policy_list_not_busy
,
但这一条件变量只能在调用者没有持有其它锁时才能使用, 否则可能会引发锁逆序问题。
忙计数在整个框架中事实上还扮演了某种形式的 共享/排他 锁的作用: 与 sx 锁不同的地方在于,
等待列表进入闲置状态的线程可以饿死, 而不是允许忙计数和其它在 MAC 框架入口 (或内部)
的锁之间的逆序情况。
对于模块子系统, 用于保护共享数据使用了一个单独的锁, 它是一个 共享/排他 (SX) 锁,
许多情况需要获得它 (以共享或排他的方式),
因此我们提供了几个方便使用的宏来简化对这个锁的访问, 这些宏可以在 sys/module.h 中找到, 其用法都非常简单明了。 这个锁保护的主要是
module_t
(当以共享方式上锁) 和全局的 modulelist_t
这两个结构体, 以及模块。
要更进一步理解这些锁策略, 需要仔细阅读 kern/kern_module.c
的源代码。
newbus 系统使用了一个 sx 锁。 读的一方应持有共享 (读) 锁 (sx_slock(9)) 而写的一方则应持有排他 (写) 锁 (sx_xlock(9))。 内部函数一般不需要进行上锁, 而外部可见的则应根据需要上锁。 有些项目不需上锁, 因为这些项目在全程是只读的, (例如 device_get_softc(9)), 因而并不会产生竞态条件。 针对 newbus 数据结构的修改相对而言非常少, 因此单个的锁已经足够使用, 而不致造成性能折损。
...
本文在其它地方已经提供了很多关于 sched_lock
的参考和注释。
SIGIO 服务允许进程请求在特定文件描述符的读/写状态发生变化时, 将 SIGIO
信号群发给其进程组。 任意给定内核对象上, 只允许一进程或进程组注册 SIGIO,
这个进程或进程组称为属主 (owner)。 每一支持 SIGIO 注册的对象, 都包含一指针字段,
如果对象未注册则为 NULL
, 否则是一指向描述这一注册的 struct sigio
的指针。 这一字段由一全局 mutex, sigio_lock
保护。 调用 SIGIO 维护函数时, 必须以 “传引用”
方式传递这一字段, 以确保本地注册副本的中这个字段不脱离锁的保护。
每个关联到进程或进程组的注册对象, 都会分配一 struct
sigio
结构, 并包括指回该对象的指针、 属主、 信号信息、 凭据,
以及关于这一注册的一般信息。 每个进程或进程组都包含一个已注册 struct sigio
结构体的列表, 对进程来说是 p_sigiolst
, 而对进程组则是 pg_sigiolst
。 这些表由相应的进程或进程组锁保护。 除了用以将
struct sigio
连接到进程组上的 sio_pgsigio
字段之外, 在 struct
sigio
中的多数字段在注册过程中都是不变量。 一般而言, 开发人员在实现新的支持 SIGIO
的内核对象时, 会希望避免在调用 SIGIO 支持函数, 例如 fsetown
或 funsetown
持有结构体锁,
以免去需要在结构体锁和全局 SIGIO 锁之间定义锁序。
通常可以通过提高结构体上的引用计数来达到这样的目的, 例如, 在进行管道操作时,
使用引用某个管道的文件描述符这样的操作, 就可以照此办理。
sysctl
MIB 服务会从内核内部,
以及用户态的应用程序以系统调用的方式触发。 这会引发至少两个和锁有关的问题:
其一是对维持命名空间的数据结构的保护, 其二是与那些通过 sysctl
接口访问的内核变量和函数之间的交互。 由于 sysctl 允许直接导出 (甚至修改)
内核统计数据以及配置参数, sysctl 机制必须知道这些变量相应的上锁语义。 目前, sysctl
使用一个全局 sx 锁来实现对 sysctl
操作的串行化; 然而,
这些是假定用全局锁保护的, 并且没有提供其它保护机制。 这一节的其余部分将详细介绍上锁和
sysctl 相关变动的语义。
- 需要将 sysctl 更新值所进行的操作的顺序, 从原先的读旧值、 copyin 和 copyout、 写新值, 改为 copyin、 上锁、 读旧值、 写新值、 解锁、 copyout。 一般的 sysctl 只是 copyout 旧值并设置它们 copyin 所得到的新值, 仍然可以采用旧式的模型。 然而, 对所有 sysctl 处理程序采用第二种模型并避免锁操作方面, 第二种方式可能更规矩一些。
- 对于通常的情况, sysctl 可以内嵌一个 mutex 指针到 SYSCTL_FOO 宏和结构体中。 这对多数 sysctl 都是有效的。 对于使用 sx 锁、 自旋 mutex, 或其它除单一休眠 mutex 之外的锁策略, 可以用 SYSCTL_PROC 节点来完成正确的上锁。
任务队列 (taskqueue) 的接口包括两个与之关联的用于保护相关数据的锁。 taskqueue_queues_mutex
是用于保护 taskqueue_queues
TAILQ 的锁。 与这个系统关联的另一个 mutex
锁是位于 struct taskqueue
结构体上。
在此处使用同步原语的目的在于保护 struct taskqueue
中数据的完整性。 应注意的是, 并没有单独的、 帮助用户对其自身的工作进行锁的细化用的宏,
因为这些锁基本上不会在 kern/subr_taskqueue.c
以外的地方用到。
本文档和其它文档可从这里下载:ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.
如果对于FreeBSD有问题,请先阅读文档,如不能解决再联系<[email protected]>.
关于本文档的问题请发信联系 <[email protected]>.