NT 同步原语驱动程序¶
此页面记录了 ntsync 驱动程序的用户空间 API。
ntsync 是一个支持驱动程序,用于用户空间 NT 模拟器模拟 NT 同步原语。它存在的原因是,使用现有工具在用户空间中实现,无法在提供准确语义的同时匹配 Windows 的性能。它完全在软件中实现,并且不驱动任何硬件设备。
此接口仅用作兼容性工具,不应用于通用同步。相反,应使用通用的、多功能的接口,例如 futex(2) 和 poll(2)。
同步原语¶
ntsync 驱动程序公开三种类型的同步原语:信号量、互斥锁和事件。
信号量保存一个 32 位易失计数器,以及一个表示最大值的静态 32 位整数。当计数器非零时,它被认为是已发信号(也就是说,可以在没有争用的情况下获取,或者会唤醒等待线程)。当满足等待时,计数器减一。初始计数和最大计数都在创建信号量时建立。
互斥锁保存一个 32 位易失递归计数,以及一个表示其所有者的 32 位易失标识符。当其所有者为零(表示它未被拥有)时,互斥锁被认为是已发信号。当满足等待时,递归计数递增,所有权设置为给定的标识符。
互斥锁还保存一个内部标志,表示其先前所有者是否已死亡;这样的互斥锁被称为已放弃。所有者死亡不会根据线程死亡自动跟踪,而是必须使用 NTSYNC_IOC_MUTEX_KILL
进行通信。放弃的互斥锁本质上被认为是未拥有的。
除了零的“未拥有”语义之外,ntsync 驱动程序根本不解释所有者标识符的实际值。其预期用途是存储线程标识符;但是,ntsync 驱动程序实际上并不验证调用线程是否提供一致或唯一的标识符。
事件类似于最大计数为 1 的信号量。它保存一个表示它是否已发信号的易失布尔状态。有两种类型的事件:自动重置和手动重置。自动重置事件在满足等待时取消发信号;手动重置事件则不然。事件类型在创建事件时指定。
除非另有说明,否则对对象的所有操作都是原子的,并且相对于同一对象的其他操作完全有序。
对象由文件表示。当关闭对象的所有文件描述符时,该对象将被删除。
字符设备¶
ntsync 驱动程序创建一个单独的字符设备 /dev/ntsync。在该设备上打开的每个文件描述都代表一个唯一实例,旨在支持单个 NT 虚拟机。由一个 ntsync 实例创建的对象只能与由同一实例创建的其他对象一起使用。
ioctl 参考¶
对设备的所有操作都通过 ioctl 完成。ioctl 调用中使用了四个结构
struct ntsync_sem_args {
__u32 count;
__u32 max;
};
struct ntsync_mutex_args {
__u32 owner;
__u32 count;
};
struct ntsync_event_args {
__u32 signaled;
__u32 manual;
};
struct ntsync_wait_args {
__u64 timeout;
__u64 objs;
__u32 count;
__u32 owner;
__u32 index;
__u32 alert;
__u32 flags;
__u32 pad;
};
根据 ioctl,结构的成员可以用作输入、输出或根本不用。
设备文件上的 ioctl 如下
-
NTSYNC_IOC_CREATE_SEM¶
创建信号量对象。接受指向 struct
ntsync_sem_args
的指针,该结构的使用方式如下count
信号量的初始计数。
max
信号量的最大计数。
如果
count
大于max
,则失败并返回EINVAL
。成功后,返回创建的信号量的文件描述符。
-
NTSYNC_IOC_CREATE_MUTEX¶
创建互斥锁对象。接受指向 struct
ntsync_mutex_args
的指针,该结构的使用方式如下count
互斥锁的初始递归计数。
owner
互斥锁的初始所有者。
如果
owner
非零且count
为零,或者如果owner
为零且count
非零,则该函数失败并返回EINVAL
。成功后,返回创建的互斥锁的文件描述符。
-
NTSYNC_IOC_CREATE_EVENT¶
创建事件对象。接受指向 struct
ntsync_event_args
的指针,该结构的使用方式如下signaled
如果非零,则事件最初已发信号,否则未发信号。
manual
如果非零,则事件是手动重置事件,否则是自动重置事件。
成功后,返回创建的事件的文件描述符。
单个对象上的 ioctl 如下
-
NTSYNC_IOC_SEM_POST¶
发布到信号量对象。接受指向一个 32 位整数的指针,该整数在输入时保存要添加到信号量的计数,在输出时包含其先前的计数。
如果添加到信号量的当前计数会导致后者超过信号量的最大计数,则 ioctl 失败并返回
EOVERFLOW
,并且信号量不受影响。如果增加信号量的计数导致它变为已发信号,则在此信号量上等待的符合条件的线程将被唤醒,并且信号量的计数将适当地减少。
-
NTSYNC_IOC_MUTEX_UNLOCK¶
释放互斥锁对象。接受指向 struct
ntsync_mutex_args
的指针,该结构的使用方式如下owner
指定尝试释放此互斥锁的所有者。
count
在输出时,包含先前的递归计数。
如果
owner
为零,则 ioctl 失败并返回EINVAL
。如果owner
不是互斥锁的当前所有者,则 ioctl 失败并返回EPERM
。互斥锁的计数将减一。如果减少互斥锁的计数导致它变为零,则互斥锁将被标记为未拥有和已发信号,并且在其上等待的符合条件的线程将适当地被唤醒。
-
NTSYNC_IOC_SET_EVENT¶
对事件对象发信号。接受指向一个 32 位整数的指针,该整数在输出时包含事件的先前状态。
符合条件的线程将被唤醒,并且自动重置事件将被适当地取消发信号。
-
NTSYNC_IOC_RESET_EVENT¶
取消对事件对象发信号。接受指向一个 32 位整数的指针,该整数在输出时包含事件的先前状态。
-
NTSYNC_IOC_PULSE_EVENT¶
唤醒等待事件对象的线程,同时将其保持在未发信号状态。接受指向一个 32 位整数的指针,该整数在输出时包含事件的先前状态。
脉冲操作可以被认为是设置,然后是重置,作为一个原子操作执行。如果两个线程正在等待被脉冲的自动重置事件,则只会唤醒一个线程。如果两个线程正在等待被脉冲的手动重置事件,则两个线程都将被唤醒。但是,在这两种情况下,事件之后都将取消发信号,并且同时进行的读取操作将始终报告事件为未发信号。
-
NTSYNC_IOC_READ_SEM¶
读取信号量对象的当前状态。接受指向 struct
ntsync_sem_args
的指针,该结构的使用方式如下count
在输出时,包含信号量的当前计数。
max
在输出时,包含信号量的最大计数。
-
NTSYNC_IOC_READ_MUTEX¶
读取互斥锁对象的当前状态。接受指向 struct
ntsync_mutex_args
的指针,该结构的使用方式如下owner
在输出时,包含互斥锁的当前所有者,如果互斥锁当前未被拥有,则为零。
count
在输出时,包含互斥锁的当前递归计数。
如果互斥锁被标记为已放弃,则该函数失败并返回
EOWNERDEAD
。在这种情况下,count
和owner
设置为零。
-
NTSYNC_IOC_READ_EVENT¶
读取事件对象的当前状态。接受指向 struct
ntsync_event_args
的指针,该结构的使用方式如下signaled
在输出时,包含事件的当前状态。
manual
在输出时,如果事件是手动重置事件,则包含 1,否则包含 0。
-
NTSYNC_IOC_KILL_OWNER¶
如果互斥锁由给定的所有者拥有,则将其标记为未拥有和已放弃。接受一个仅输入指针,指向表示所有者的 32 位整数。如果所有者为零,则 ioctl 失败并返回
EINVAL
。如果所有者不拥有互斥锁,则该函数失败并返回EPERM
。等待互斥锁的符合条件的线程将被适当地唤醒(并且此类等待将失败并返回
EOWNERDEAD
,如下所述)。
-
NTSYNC_IOC_WAIT_ANY¶
轮询对象列表中的任何一个对象,原子地最多获取一个。接受指向 struct
ntsync_wait_args
的指针,该结构的使用方式如下timeout
以纳秒为单位的绝对超时。如果设置了
NTSYNC_WAIT_REALTIME
,则超时是根据 REALTIME 时钟测量的;否则,它是根据 MONOTONIC 时钟测量的。如果超时等于或早于当前时间,则该函数会立即返回,而不休眠。如果timeout
等于 U64_MAX,则该函数将休眠,直到对象被发信号,并且不会失败并返回ETIMEDOUT
。objs
指向
count
文件描述符数组的指针(指定为整数,以便该结构具有相同的大小,而与架构无关)。如果任何对象无效,则该函数失败并返回EINVAL
。count
在
objs
数组中指定的对象数。如果大于NTSYNC_MAX_WAIT_COUNT
,则该函数失败并返回EINVAL
。owner
互斥锁所有者标识符。如果
objs
中的任何对象是互斥锁,则 ioctl 将尝试代表owner
获取该互斥锁。如果owner
为零,则 ioctl 失败并返回EINVAL
。index
成功后,包含已发信号的对象的索引(在
objs
中)。如果改为对alert
发信号,则它包含count
。alert
可选的事件对象文件描述符。如果非零,则指定一个“警报”事件对象,如果该对象被发信号,则将终止等待。如果非零,则标识符必须指向有效的事件。
flags
零个或多个标志。目前,唯一的标志是
NTSYNC_WAIT_REALTIME
,它导致超时是根据 REALTIME 时钟而不是 MONOTONIC 测量的。pad
未使用,必须设置为零。
此函数尝试获取给定的对象之一。如果无法这样做,则它会休眠,直到对象变为已发信号,随后获取它,或者超时到期。在后一种情况下,ioctl 失败并返回
ETIMEDOUT
。即使多个对象已发信号,该函数也只会获取一个对象。如果信号量的计数非零,则认为信号量已发信号,并且通过将其计数减一来获取。如果互斥锁未被拥有或者其所有者与
owner
参数匹配,则认为互斥锁已发信号,并且通过将其递归计数递增一并将所有者设置为owner
参数来获取。自动重置事件通过取消发信号来获取;手动重置事件不受获取的影响。获取是原子的,并且相对于同一对象的其他操作完全有序。如果两个等待操作(具有不同的
owner
标识符)在同一互斥锁上排队,则只会对一个进行发信号。如果两个等待操作在同一信号量上排队,并且将值 1 发布到该信号量,则只会对一个进行发信号。如果获取了放弃的互斥锁,则 ioctl 失败并返回
EOWNERDEAD
。尽管这是一个失败返回,但该函数在其他方面可以被认为是成功的。互斥锁被标记为由给定所有者拥有(递归计数为 1)并且不再被放弃,并且index
仍然设置为互斥锁的索引。alert
参数是一个“额外”事件,可以独立于所有其他对象终止等待。多次传递同一对象是有效的,包括通过在
objs
数组和alert
中传递同一事件。如果由于该对象被发信号而发生唤醒,则index
将设置为对应于该对象的最低索引。如果收到信号,则该函数可能会失败并返回
EINTR
。
-
NTSYNC_IOC_WAIT_ALL¶
轮询对象列表,原子地获取所有对象。接受指向 struct
ntsync_wait_args
的指针,该结构的使用方式与NTSYNC_IOC_WAIT_ANY
相同,只是如果不是通过警报唤醒,则index
始终填充为零。此函数尝试同时获取所有给定的对象。如果无法这样做,则它会休眠,直到所有对象同时变为已发信号,随后获取它们,或者超时到期。在后一种情况下,ioctl 失败并返回
ETIMEDOUT
,并且不修改任何对象。当此线程休眠时,对象可能会变为已发信号,随后被取消发信号(通过其他线程获取)。只有当所有对象同时被发信号时,ioctl 才会获取它们并返回。整个获取是原子的,并且相对于任何给定对象上的其他操作完全有序。
如果获取了放弃的互斥锁,则 ioctl 失败并返回
EOWNERDEAD
。与NTSYNC_IOC_WAIT_ANY
类似,所有对象仍然被标记为已获取。请注意,如果指定了多个互斥锁对象,则无法知道哪些对象被标记为已放弃。与“任何”等待一样,
alert
参数是一个“额外”事件,可以终止等待。但至关重要的是,如果objs
中的所有成员都已发信号,或者如果alert
已发信号,则“所有”等待都将成功。在后一种情况下,index
将设置为count
。与“任何”等待一样,如果同时满足这两个条件,则前者优先,并且将获取objs
中的对象。与
NTSYNC_IOC_WAIT_ANY
不同,多次传递同一对象是无效的,并且在objs
和alert
中传递同一对象也是无效的。如果尝试这样做,则该函数失败并返回EINVAL
。