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 数据结构的修改相对而言非常少, 因此单个的锁已经足够使用, 而不致造成性能折损。
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 节点来完成正确的上锁。
本文档和其它文档可从这里下载: ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.
如果对于FreeBSD有问题,请先阅读
文档,如不能解决再联系
<questions@FreeBSD.org>.
关于本文档的问题请发信联系
<doc@FreeBSD.org>.