Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1316042
  • 博文数量: 554
  • 博客积分: 10425
  • 博客等级: 上将
  • 技术积分: 7555
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-09 09:49
文章分类

全部博文(554)

文章存档

2012年(1)

2011年(1)

2009年(8)

2008年(544)

分类:

2008-04-11 15:14:15


锁定原语
第3 章• 多线程63
线程同步
除了保护共享数据外,驱动程序通常需要在多个线程之间同步执行。
线程同步中的条件变量
条件变量是线程同步的标准形式。这些变量专门用于互斥锁。关联互斥锁可以确保条件的
检查是原子操作,并且线程可以基于关联的条件变量阻塞,同时不会忽略对条件的更改或
条件已更改的信号。
condvar(9F) 函数包括:
cv_broadcast(9F) 向基于条件变量等待的所有线程发出信号。
cv_destroy(9F) 销毁条件变量。
cv_init(9F) 初始化条件变量。
cv_signal(9F) 向基于条件变量等待的一个线程发出信号。
cv_timedwait(9F) 等待条件、超时或信号。请参见第69 页中的“线程无法接收信
号”。
cv_timedwait_sig(9F) 等待条件或超时。
cv_wait(9F) 等待条件。
cv_wait_sig(9F) 等待条件或在收到信号时返回零。请参见第69 页中的“线程无
法接收信号”。
初始化条件变量
用于针对每个条件声明kcondvar_t 类型的条件变量。通常,条件变量是驱动程序定义的数
据结构中的一个变量。使用cv_init(9F) 可初始化每个条件变量。与互斥锁类似,条件变量
通常在执行attach(9E) 时初始化。以下是一个初始化条件变量的典型示例:
cv_init(&xsp->cv, NULL, CV_DRIVER, NULL);
有关条件变量初始化的较完整示例,请参见第6 章。
等待条件
要使用条件变量,请在等待条件的代码路径中执行以下步骤:
1. 获取用于保护条件的互斥锁。
2. 测试条件。
线程同步
64 编写设备驱动程序• 2006 年11 月
3. 如果测试结果表明不允许线程继续执行,请使用cv_wait(9F) 根据条件阻塞当前线
程。cv_wait(9F) 将在阻塞线程之前释放互斥锁,并在返回之前重新获取互斥锁。从
cv_wait(9F) 返回时,重复该测试。
4. 测试表明允许线程继续执行后,请将条件设置为其新值。例如,将设备标志设置为繁
忙。
5. 释放互斥锁。
发出条件信号
请在代码路径中执行以下步骤以发出条件信号:
1. 获取用于保护条件的互斥锁。
2. 设置条件。
3. 使用cv_broadcast(9F) 向阻塞的线程发出信号。
4. 释放互斥锁。
以下示例使用繁忙标志以及互斥锁和条件变量来强制read(9E) 例程进行等待,直到设备不
再繁忙时为止,然后开始传送。
示例3–1使用互斥锁和条件变量
static int
xxread(dev_t dev, struct uio *uiop, cred_t *credp)
{
struct xxstate *xsp;
[...]
mutex_enter(&xsp->mu);
while (xsp->busy)
cv_wait(&xsp->cv, &xsp->mu);
xsp->busy = 1;
mutex_exit(&xsp->mu);
/* perform the data access */
}
线程同步
第3 章• 多线程65
示例3–1 使用互斥锁和条件变量(续)
static uint_t
xxintr(caddr_t arg)
{
struct xxstate *xsp = (struct xxstate *)arg;
mutex_enter(&xsp->mu);
xsp->busy = 0;
cv_broadcast(&xsp->cv);
mutex_exit(&xsp->mu);
}
cv_wait() 和cv_timedwait() 函数
如果使用cv_wait(9F) 根据某个条件将线程阻塞,但该条件不发生,则该线程将永远等待。
要避免此情况,请使用cv_timedwait(9F),它取决于执行唤醒的其他线程。cv_timedwait()
采取绝对等待时间作为参数。如果时间已到但未发生事件,则cv_timedwait() 将返回-1。
如果满足条件,则cv_timedwait() 将返回一个正值。
cv_timedwait(9F) 要求自上次重新引导系统以来的绝对等待时间(以时钟周期表示)。通过
使用ddi_get_lbolt(9F) 检索当前值可确定该等待时间。驱动程序通常具有的是最大等待秒
数或微秒数,因此需要使用drv_usectohz(9F) 将该值转换为时钟周期,然后与
ddi_get_lbolt(9F) 的值相加。
以下示例说明如何使用cv_timedwait(9F) 最多等待五秒钟便访问设备,然后向调用方返回
EIO。
示例3–2 使用cv_timedwait()
clock_t cur_ticks, to;
mutex_enter(&xsp->mu);
while (xsp->busy) {
cur_ticks = ddi_get_lbolt();
to = cur_ticks + drv_usectohz(5000000); /* 5 seconds from now */
线程同步
66 编写设备驱动程序• 2006 年11 月
示例3–2 使用cv_timedwait() (续)
if (cv_timedwait(&xsp->cv, &xsp->mu, to) == -1) {
/*
* The timeout time ’to’ was reached without the
* condition being signalled.
*/
/* tidy up and exit */
mutex_exit(&xsp->mu);
return (EIO);
}
}
xsp->busy = 1;
mutex_exit(&xsp->mu);
虽然设备驱动程序写入器通常首选使用cv_timedwait(9F) 而不是cv_wait(9F),但是有时选
用cv_wait(9F) 会更好。例如,如果驱动程序基于以下条件等待,则使用cv_wait(9F) 更合
适:
 内部驱动程序状态发生变化,在此情况下状态变化可能要求执行一些命令或设置要经过
的时间
 驱动程序的某些部分必须单线程执行
 已在管理可能超时的情况,如"A" 取决于"B",同时"B" 使用cv_timedwait(9F)
cv_wait_sig() 函数
驱动程序可能正在等待不会产生或长时间不会发生的条件。在此类情况下,用户可发送信
号中止该线程。根据驱动程序设计,信号可能无法将驱动程序唤醒。
cv_wait_sig(9F) 允许使用信号解除阻塞线程。借助此功能,用户可以通过使用kill(1) 向线
程发送信号或键入中断字符,从而免于可能的长时间等待。如果cv_wait_sig(9F) 由于收到
线程同步
第3 章• 多线程67
信号而返回,则会返回零;如果条件发生,则返回非零值。但是,对于可能未收到信号的
情况,请参见第69 页中的“线程无法接收信号”。
以下示例说明如何使用cv_wait_sig(9F) 以允许使用信号解除阻塞线程。
示例3–3 使用cv_wait_sig()
mutex_enter(&xsp->mu);
while (xsp->busy) {
if (cv_wait_sig(&xsp->cv, &xsp->mu) == 0) {
/* Signalled while waiting for the condition */
/* tidy up and exit */
mutex_exit(&xsp->mu);
return (EINTR);
}
}
xsp->busy = 1;
mutex_exit(&xsp->mu);
cv_timedwait_sig() 函数
cv_timedwait_sig(9F) 与cv_timedwait(9F) 和cv_wait_sig(9F) 相似,不同之处在于,达到超
时后,如果没有发出条件信号,则cv_timedwait_sig() 将返回-1,如果向线程发送了信号
(例如kill(2)),则将返回0。
对于cv_timedwait(9F) 和cv_timedwait_sig(9F),系统将使用上次重新启动系统以来的绝对
时钟周期度量时间。
选择锁定方案
在设计大多数设备驱动程序时,都应该确保锁定方案简单易懂。使用额外的锁允许更多并
发,但会增加开销。使用的锁越少,占用的时间越短,但允许的并发会更少。通常,对每
选择锁定方案
68 编写设备驱动程序• 2006 年11 月
个数据结构使用一个互斥锁,对驱动程序必须等待的每个事件或条件使用一个条件变量,
对驱动程序的每个主要全局数据集使用一个互斥锁。请避免长时间持有互斥锁。选择锁定
方案时,请遵循以下指导原则:
 优先使用程序入口点的多线程语义。
 确保所有程序入口点可重入。可以通过将静态变量更改为自动变量来减少共享数据量。
 如果驱动程序获取了多个互斥锁,请在所有代码路径中按相同顺序获取和释放这些互斥
锁。
 在相同的功能空间中持有锁和释放锁。
 调用可阻塞的DDI 接口时请避免持有驱动程序互斥锁,例如使用KM_SLEEP 调用
kmem_alloc(9F)。
要查看锁的用法,请使用lockstat(1M)。lockstat(1M) 可监视所有内核锁定事件,收集有
关事件的频率和计时数据,并显示这些数据。
有关多线程操作的更多详细信息,请参见《Multithreaded Programming Guide》。
潜在的锁定缺点
同一线程不可重复获取互斥锁。如果已持有互斥锁,则再次尝试声明此互斥锁会导致产生
以下系统崩溃信息:
panic: recursive mutex_enter. mutex %x caller %x
释放当前线程未持有的互斥锁会产生以下系统崩溃信息:
panic: mutex_adaptive_exit: mutex not held by thread
以下系统崩溃信息仅在单处理器上出现:
panic: lock_set: lock held and only one CPU
lock_set 系统崩溃信息指明线程持有自旋互斥锁(spin mutex),并且该锁将会永久旋转,因
为没有其他CPU 可以释放此互斥锁。如果驱动程序忘记释放某个代码路径上的互斥锁,或
在持有互斥锁时阻塞,则会发生此情况。
具有高级中断的设备调用的例程阻塞(如cv_wait(9F))时通常会导致出现lock_set 系统崩
溃信息。另一个常见原因是高级处理程序通过调用mutex_enter(9F) 获取自适应互斥锁。
线程无法接收信号
线程收到信号时,可以唤醒sema_p_sig()、cv_wait_sig() 和cv_timedwait_sig() 函数。由
于某些线程无法接收信号,因此可能会出现问题。例如,如果由于应用程序调用close(2)
而导致调用close(9E),则可以收到信号。但是,如果是从exit(2) 处理(关闭所有打开的文
选择锁定方案
第3 章• 多线程69
件描述符)中调用close(9E),则线程无法收到信号。如果线程无法收到信号,则
sema_p_sig() 的行为与sema_p() 相同,cv_wait_sig() 的行为与cv_wait() 相同,
cv_timedwait_sig() 的行为与cv_timedwait() 相同。
对于可能永远不会发生的事件,请注意避免永久休眠。永远不会发生的事件会创建不可中
止(defunct) 的线程并使设备不可用,除非重新引导系统。失效进程无法接收信号。
要检测当前线程是否可接收信号,请使用ddi_can_receive_sig(9F) 函数。如果
ddi_can_receive_sig() 函数返回B_TRUE,则以上函数可在收到信号时唤醒。如果
ddi_can_receive_sig() 函数返回B_FALSE,则以上函数无法在收到信号时唤醒。如果
ddi_can_receive_sig() 函数返回B_FALSE,则驱动程序会使用替代方法(如timeout(9F) 函
数)重新唤醒。
出现此问题的一个重要情况是使用串行端口。如果远程系统声明了流量控制,并且
close(9E) 函数在尝试清空输出数据时阻塞,则端口会堵塞,直到解决流量控制情况或重新
引导系统为止。此类驱动程序应检测到此情况并设置计时器,以便在流量控制情况持续过
长时间时中止清空操作。
此问题还会影响qwait_sig(9F) 函数。此函数将在《STREAMS Programming Guide》中的第
7 章“STREAMS Framework – Kernel Level”中介绍。
选择锁定方案
70 编写设备驱动程序• 2006 年11 月
属性
属性是用户定义的名称-值对结构,该结构使用DDI/DKI 接口进行管理。本章介绍有关以下
主题的信息:
 第72 页中的“设备属性名称”
 第72 页中的“创建和更新属性”
 第72 页中的“查找属性”
 第74 页中的“prop_op() 入口点”
设备属性
设备特性(attribute) 信息可由称为属性(proerty) 的名称-值对表示法表示。
例如,设备寄存器和主板内存可由reg 属性表示。reg 属性是描述设备硬件寄存器的软件抽
象术语。reg 属性的值对设备寄存器地址位置和大小进行编码。驱动程序使用reg 属性访问
设备寄存器。
另外一个示例是interrupt 属性。interrupt 属性表示设备中断。interrupt 属性的值对设备中断
PIN 进行编码。
可以为属性指定五种类型的值:
 字节数组-任意长度的一系列字节
 整数属性-整数值
 整数数组属性-整数数组
 字符串属性-以NULL结尾的字符串
 字符串数组属性-以NULL结尾的字符串列表
没有值的属性将被视为布尔属性。对于布尔属性,如果存在,则值为True;如果不存在,
则值为False。
4第4 章
71
设备属性名称
严格地说,DDI/DKI 软件属性名称没有限制。但建议使用某些限制。IEEE 1275-1994
Standard for Boot Firmware 引导固件标准按如下方法定义属性:
属性是由1 到31 个可列显字符组成的人工可读文本字符串。属性名称不应包含大写字符
或字符“/”、“\”、“:”、“[”、“]” 和“@”。以字符“+” 开头的属性名称保留供IEEE 1275-1994
的将来修订使用。
根据约定,属性名称中不能使用下划线。可使用连字符(-)。根据约定,以问号字符(?) 结尾
的属性名称包含字符串值(通常为TRUE 或FALSE),例如auto-boot?。
IEEE 1275 工作组的出版物中列出了预定义的属性名称。有关如何获取这些出版物的信息,
请访问。有关在驱动程序配置文件中添加属性的讨论,请
参见driver.conf(4) 手册页。pm(9P) 和pm-components(9P) 手册页说明了如何在电源管理中
使用属性。有关应该如何在设备驱动程序中记录属性的信息,请阅读sd(7D) 手册页。
创建和更新属性
要为驱动程序创建属性,或者更新现有属性,请将DDI 驱动程序更新接口(如
ddi_prop_update_int(9F) 或ddi_prop_update_string(9F))中的一个接口与相应的属性类型
一起使用。有关可用属性接口的列表,请参见表4–1。这些接口通常从驱动程序的
attach(9E) 入口点调用。在以下示例中,ddi_prop_update_string() 创建一个名为
pm-hardware-state 且值为needs-suspend-resume 的字符串属性。
/* The following code is to tell cpr that this device
* needs to be suspended and resumed.
*/
(void) ddi_prop_update_string(device, dip,
"pm-hardware-state", "needs-suspend-resume");
在大多数情况下,使用ddi_prop_update() 例程即可满足更新属性的要求。但是,有时更新
经常更改的属性值的系统开销可能会导致性能问题。有关使用属性值的本地实例以避免使
用ddi_prop_update() 的说明,请参见第74 页中的“prop_op() 入口点”。
查找属性
驱动程序可以请求其父级的属性,而后者又可以请求其父级。驱动程序可以控制是否将请
求传递到其父级以上。
例如,以下示例中的esp 驱动程序为每个目标维护一个名为targetx-sync-speed 的整数属
性。targetx-sync-speed 中的x 表示目标编号。prtconf(1M) 命令以详细模式显示驱动程序
属性。以下示例列出了esp 驱动程序的部分内容。
设备属性
72 编写设备驱动程序• 2006 年11 月
% prtconf -v
[...]
esp, instance #0
Driver software properties:
name length <4>
value <0x00000fa0>.
[...]
下表汇总了属性接口。
表4–1属性接口用法
系列属性接口说明
ddi_prop_lookup ddi_prop_exists(9F) 查找属性,如果该属性存在,则成功返
回。如果该属性不存在,则失败。
ddi_prop_get_int(9F) 查找并返回整数属性
ddi_prop_get_int64(9F) 查找并返回64 位整数属性
ddi_prop_lookup_int_array(9F) 查找并返回整数数组属性
ddi_prop_lookup_int64_array(9F) 查找并返回64 位整数数组属性
ddi_prop_lookup_string(9F) 查找并返回字符串属性
ddi_prop_lookup_string_array(9F) 查找并返回字符串数组属性
ddi_prop_lookup_byte_array(9F) 查找并返回字节数组属性
ddi_prop_update ddi_prop_update_int(9F) 更新或创建整数属性
ddi_prop_update_int64(9F) 更新或创建单个64 位整数属性
ddi_prop_update_int_array(9F) 更新或创建整数数组属性
ddi_prop_update_string(9F) 更新或创建字符串属性
ddi_prop_update_string_array(9F) 更新或创建字符串数组属性
ddi_prop_update_int64_array(9F) 更新或创建64 位整数数组属性
ddi_prop_update_byte_array(9F) 更新或创建字节数组属性
ddi_prop_remove ddi_prop_remove(9F) 删除单个属性
ddi_prop_remove_all(9F) 删除与设备关联的所有属性
 
 

以上文章转自于 : http://developers.sun.com.cn/
阅读(593) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~