按需显示数据
本节中示例代码创建一个由本驱动程序控制的伪设备。本驱动程序将数据存储在该设备中,并使这些数据在用户访问该设备以读取时是可用的。
本节首先讨论了 Quote Of The Day 驱动程序这两个版本之间重要的代码区别,然后介绍了如何访问该设备以显示引用。
编写 Quote Of The Day 版本 2
控制该伪设备的驱动程序比上一节所述的驱动程序复杂。本节首先阐述了该驱动程序这一版本的某些重要特性,然后给出了该驱动程序的所有源代码。
下面的列表总结了 Quote Of The Day 驱动程序这两个版本之间的区别:
-
本驱动程序的版本 2 定义了一个状态结构来保存该设备每个实例的信息。
-
版本 2 定义了一个 cb_ops(9S) 结构和一个更完整的 complete dev_ops(9S) 结构。
-
版本 2 定义了 open(9E)、close(9E)、read(9E)、getinfo(9E)、attach(9E)
和 detach(9E) 入口点。
-
版本 1 使用 cmn_err(9F) 函数在驱动程序的 _init(9E) 入口点中将常量字符串写入系统日志。_init(9E)
入口点是在驱动程序加载时调用的。版本 2 使用 uiomove(9F) 函数从内核内存复制引用。read(9E) 入口点返回所复制的数据。read(9E)
入口点是在驱动程序接受读操作时被调用的。
-
本驱动程序的版本
2 使用 ASSERT(9F)
语句来检查数据有效性。
下一节更详细地介绍了 Quote Of The Day 驱动程序版本 2 中的新增内容和变更。
管理设备状态
_init(9E) 和 _fini(9E) 入口点以及本驱动程序中定义的
6 个新入口点都维护着该设备的软状态。大多数设备驱动程序都是通过它们所控制的设备的各个实例来维护状态信息的。实例通常是一个子设备。例如,磁盘驱动程序可以与一个已经与若干个磁盘驱动器相连的硬件控制器设备通信。请参阅
Writing Device Drivers 中的 Retrieving Driver Soft
State Information,了解软状态的更多信息。
该示例驱动程序仅支持一个实例。实例编号在配置文件中分配。请参阅例 3-4。大多数设备驱动程序都允许为设备创建任意数量的实例。系统管理着设备实例编号,DDI
软状态函数管理着实例。
-
ddi_soft_state_init(9F)
函数初始化状态指针。状态指针是一个不透明的句柄,它支持对设备每个实例的状态结构进行分配、分配解除和跟踪。状态结构是一个用户定义的类型,用于维护特定于设备该实例的数据。在本例中,状态指针和状态结构在声明了入口点之后声明。请参阅例 3-3 中的 qotd_state_head 和 qotd_state。
-
ddi_soft_state_zalloc(9F)
函数使用状态指针和设备实例来创建该实例的状态结构。
-
ddi_get_soft_state(9F)
函数使用状态指针和设备实例来检索设备该实例的状态结构。
-
ddi_soft_state_free(9F)
函数使用状态指针和设备实例来释放该实例的状态结构。
-
ddi_soft_state_fini(9F)
函数使用状态指针来删除该设备所有实例的状态指针和状态结构。
ddi_soft_state_zalloc(9F)、ddi_get_soft_state(9F) 和 ddi_soft_state_free(9F)
函数以一种安全的多线程操作方式来协调对底层数据结构的访问。无需使用附加的锁。
初始化和卸载
_init(9E)
入口点首先调用 ddi_soft_state_init(9F)
函数来初始化软状态。如果软状态初始化失败,则返回错误代码。若软状态初始化成功,则 _init(9E) 入口点则调用 mod_install(9F)
函数来加载新模块。如果模块安装失败,则 _init(9E) 入口点则调用 ddi_soft_state_fini(9F)
函数并返回来自失败的模块安装的错误代码。
您的代码必须撤消它所做的每一件事。若模块安装失败,则必须调用 ddi_soft_state_fini(9F),因为 _init(9E)
调用成功了且创建了一个状态指针。
_fini(9E)
入口点必须撤消_init(9E) 入口点所做的每一件事。_fini(9E) 入口点首先调用 mod_remove(9F)
函数来删除 that_init(9E) 入口点所安装的模块。如果模块删除失败,则返回错误代码。如果模块删除成功,则 _fini(9E)
入口点则调用 ddi_soft_state_fini(9F)
函数来删除该设备所有实例的状态指针和状态结构。
连接和分离
attach(9E)
入口点首先调用 ddi_get_instance(9F)
函数来检索设备信息节点的实例号。attach(9E) 入口点使用该实例号来调用 ddi_soft_state_zalloc(9F)、ddi_get_soft_state(9F)
和 ddi_create_minor_node(9F)
函数。
attach(9E) 入口点调用 ddi_soft_state_zalloc(9F) 函数来创建该设备实例的状态结构。如果创建软状态结构失败,则
attach(9E) 向系统日志写入一条错误消息并返回失败值。该设备实例未连接。如果创建软状态结构成功,则 则 attach(9E)
调用 ddi_get_soft_state(9F) 函数来检索该设备实例的状态结构。
如果检索状态结构失败,则
attach(9E) 向系统日志写入一条错误消息,并调用 ddi_soft_state_free(9F)
函数来删除 ddi_soft_state_zalloc(9F) 所创建的状态结构,并返回失败值。该设备实例未连接。如果检索状态结构成功,则 attach(9E)
调用 ddi_create_minor_node(9F) 函数来创建设备节点。
在本驱动程序的源文件顶部定义了一个名为
QOTD_NAME 的常量,用于保存设备的字符串名。该常量是传递给ddi_create_minor_node(9F)
的参数之一。如果创建设备节点失败,则 attach(9E) 向系统日志写入一条错误消息,并调用 ddi_soft_state_free(9F)
函数来删除 ddi_soft_state_zalloc(9F) 所创建的状态结构,调用 ddi_remove_minor_node(9F)
函数并返回失败值。该设备实例未连接。
如果创建设备节点成功,则该设备实例连接成功。attach(9E)
入口点把通过 ddi_get_instance(9F) 检索到的实例号分配给该实例状态结构的实例成员。然后 attach(9E)
将 attach(9E) 调用中传递的 dev_info 结构指针分配给该实例状态结构的 dev_info
结构指针成员。在添加设备或启动系统时 ddi_report_dev(9F)
函数向系统日志文件中写入一条消息。该消息用于公布该设备,如下例所示:
% dmesg
date time machine pseudo:[ID 129642 kern.info] pseudo-device:qotd_20
date time machine genunix:[ID 936769 kern.info] qotd_20 is /pseudo/qotd_2@0 |
detach(9E)
入口点首先调用 ddi_get_instance(9F) 函数来检索该设备信息节点的实例号。detach(9E) 入口点使用该实例号来调用
ddi_soft_state_free(9F) 函数以删除 ddi_soft_state_zalloc(9F) 在 attach(9E)
入口点中所创建的状态结构。然后 detach(9E) 入口点调用 ddi_remove_minor_node(9F) 函数来删除
ddi_create_minor_node(9F) 在 attach(9E) 入口点中所创建的设备。
打开设备、关闭设备和获取模块信息
open(9E)
和 close(9E)
入口点在本示例驱动程序中是相同的。在所有情况下,入口点都首先调用 getminor(9F)
函数来检索次要设备号。然后各入口点使用该实例号来调用 ddi_get_soft_state(9F)
函数以检索该设备实例的状态结构。如果检索到状态结构,则返回错误代码。如果检索到了状态结构,则 open(9E) 和 close(9E)
入口点都会检验该设备的类型。如果该设备不是字符设备,则返回 EINVAL(无效)错误代码。
如果用户需要该设备实例的设备信息,则 getinfo(9E)
入口点返回来自状态结构的设备信息。如果用户需要该设备实例的实例编号,则 getinfo(9E) 入口点使用 getminor(9F)
函数来返回次要设备号。
读数据
read(9E)
入口点首先调用 getminor(9F)
函数来检索次要设备号。read(9E) 入口点使用该实例号来调用 ddi_get_soft_state(9F)
函数以检索该设备实例的状态结构。如果没有检索到状态结构,read(9E) 则返回一个错误代码。如果检索到了一个状态结构,read(9E)
则调用 uiomove(9F)
函数将引用从本驱动程序复制到 uio(9S)
I/O 请求结构。
检查数据有效性
本驱动程序的版本 2 使用 ASSERT(9F)
语句来检查数据有效性。如果断言的表达式为真,则 ASSERT(9F) 语句没有反应。如果断言的表达式为假,则 ASSERT(9F)
语句将一条错误消息写入控制台,并导致系统出现忙乱。
要使用 ASSERT(9F) 语句,须在源代码中包含 sys/debug.h 头文件并定义
DEBUG 预处理程序符号。如果不定义 DEBUG 预处理程序符号,则 ASSERT(9F)
语句没有反应。只需重新编译即可激活或禁用 ASSERT(9F) 语句。
Quote Of The Day 版本 2 源代码
将下例所示的源代码输入一个名为 qotd_2.c 的文本文件。
例 3–3 Quote Of The Day 版本 2 源文件
#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#define QOTD_NAME "qotd"
#define QOTD_MAXLEN 128
static const char qotd[QOTD_MAXLEN]
= "You can't have everything. \
Where would you put it? - Steven Wright\n";
static void *qotd_state_head;
struct qotd_state {
int instance;
dev_info_t *devi;
};
static int qotd_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int qotd_attach(dev_info_t *, ddi_attach_cmd_t);
static int qotd_detach(dev_info_t *, ddi_detach_cmd_t);
static int qotd_open(dev_t *, int, int, cred_t *);
static int qotd_close(dev_t, int, int, cred_t *);
static int qotd_read(dev_t, struct uio *, cred_t *);
static struct cb_ops qotd_cb_ops = {
qotd_open, /* cb_open */
qotd_close, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
qotd_read, /* cb_read */
nodev, /* cb_write */
nodev, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
(struct streamtab *)NULL, /* cb_str */
D_MP | D_64BIT, /* cb_flag */
CB_REV, /* cb_rev */
nodev, /* cb_aread */
nodev /* cb_awrite */
};
static struct dev_ops qotd_dev_ops = {
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
qotd_getinfo, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
qotd_attach, /* devo_attach */
qotd_detach, /* devo_detach */
nodev, /* devo_reset */
&qotd_cb_ops, /* devo_cb_ops */
(struct bus_ops *)NULL, /* devo_bus_ops */
nulldev /* devo_power */
};
static struct modldrv modldrv = {
&mod_driverops;,
"Quote of the Day 2.0",
&qotd_dev_ops;};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modldrv;,
NULL
};
int
_init(void)
{
int retval;
if ((retval = ddi_soft_state_init(&qotd_state_head;,
sizeof (struct qotd_state), 1)) != 0)
return retval;
if ((retval = mod_install(&modlinkage;)) != 0) {
ddi_soft_state_fini(&qotd_state_head;);
return (retval);
}
return (retval);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage;, modinfop));
}
int
_fini(void)
{
int retval;
if ((retval = mod_remove(&modlinkage;)) != 0)
return (retval);
ddi_soft_state_fini(&qotd_state_head;);
return (retval);
}
/*ARGSUSED*/
static int
qotd_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
struct qotd_state *qsp;
int retval = DDI_FAILURE;
ASSERT(resultp != NULL);
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
if ((qsp = ddi_get_soft_state(qotd_state_head,
getminor((dev_t)arg))) != NULL) {
*resultp = qsp->devi;
retval = DDI_SUCCESS;
} else
*resultp = NULL;
break;
case DDI_INFO_DEVT2INSTANCE:
*resultp = (void *)getminor((dev_t)arg);
retval = DDI_SUCCESS;
break;
}
return (retval);
}
static int
qotd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int instance = ddi_get_instance(dip);
struct qotd_state *qsp;
switch (cmd) {
case DDI_ATTACH:
if (ddi_soft_state_zalloc(qotd_state_head, instance)
!= DDI_SUCCESS) {
cmn_err(CE_WARN, "Unable to allocate state for %d",
instance);
return (DDI_FAILURE);
}
if ((qsp = ddi_get_soft_state(qotd_state_head, instance))
== NULL) {
cmn_err(CE_WARN, "Unable to obtain state for %d",
instance);
ddi_soft_state_free(dip, instance);
return (DDI_FAILURE);
}
if (ddi_create_minor_node(dip, QOTD_NAME, S_IFCHR, instance,
DDI_PSEUDO, 0) != DDI_SUCCESS) {
cmn_err(CE_WARN, "Cannot create minor node for %d",
instance);
ddi_soft_state_free(dip, instance);
ddi_remove_minor_node(dip, NULL);
return (DDI_FAILURE);
}
qsp->instance = instance;
qsp->devi = dip;
ddi_report_dev(dip);
return (DDI_SUCCESS);
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
qotd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance = ddi_get_instance(dip);
switch (cmd) {
case DDI_DETACH:
ddi_soft_state_free(qotd_state_head, instance);
ddi_remove_minor_node(dip, NULL);
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/*ARGSUSED*/
static int
qotd_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
int instance = getminor(*devp);
struct qotd_state *qsp;
if ((qsp = ddi_get_soft_state(qotd_state_head, instance)) == NULL)
return (ENXIO);
ASSERT(qsp->instance == instance);
if (otyp != OTYP_CHR)
return (EINVAL);
return (0);
}
/*ARGSUSED*/
static int
qotd_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
struct qotd_state *qsp;
int instance = getminor(dev);
if ((qsp = ddi_get_soft_state(qotd_state_head, instance)) == NULL)
return (ENXIO);
ASSERT(qsp->instance == instance);
if (otyp != OTYP_CHR)
return (EINVAL);
return (0);
}
/*ARGSUSED*/
static int
qotd_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
struct qotd_state *qsp;
int instance = getminor(dev);
if ((qsp = ddi_get_soft_state(qotd_state_head, instance)) == NULL)
return (ENXIO);
ASSERT(qsp->instance == instance);
return (uiomove((void *)qotd, min(uiop->uio_resid, strlen(qotd)),
UIO_READ, uiop));
}
将下例所示的配置信息输入一个名为 qotd_2.conf 的文本文件。
例 3-4 Quote Of The Day 版本 2 配置文件
name="qotd_2" parent="pseudo" instance=0;
构建、安装和使用 Quote Of The Day
版本 2
本驱动程序的版本
2 使用 ASSERT(9F)
语句来检查数据有效性。要使用 ASSERT(9F) 语句,须在源代码中包含 sys/debug.h
头文件并定义 DEBUG 预处理程序符号。
编译并连接本驱动程序。若使用 ASSERT(9F)
语句来检查数据有效性,则必须定义 DEBUG 预处理程序符号:
% cc -D_KERNEL -DDEBUG -c qotd_2.c
% ld -r -o qotd_2 qotd_2.o
|
下例所示为不使用 ASSERT(9F) 语句的情况下编译和连接 32 位架构的方法:
% cc -D_KERNEL -c qotd_2.c
% ld -r -o qotd_2 qotd_2.o
|
在安装驱动程序时要确保您是用户 root 。
将驱动程序二进制文件复制到 /tmp 目录,如构建和安装驱动程序所述。
# cp qotd_2 /tmp
# ln -s /tmp/qotd_2 /usr/kernel/drv/qotd_2
|
将配置文件复制到系统的内核驱动程序区。
# cp qotd_2.conf /usr/kernel/drv
|
在一个单独的窗口中输入下列命令:
% tail -f /var/adm/messages
|
在加载驱动程序时要确保您是用户 root。使用 add_drv(1M)
命令加载驱动程序:
在查看 /var/adm/messages 的窗口中应该可以看到下列消息:
date time machine pseudo: [ID 129642 kern.info] pseudo-device: devinfo0
date time machine genunix: [ID 936769 kern.info] devinfo0 is /pseudo/devinfo@0
date time machine pseudo: [ID 129642 kern.info] pseudo-device: qotd_20
date time machine genunix: [ID 936769 kern.info] qotd_20 is /pseudo/qotd_2@0 |
在加载该版本的 Quote Of The Day 驱动程序时,它不显示其引用。qotd_1 驱动程序通过
_init(9E) 入口点将一条消息写入系统日志。该 qotd_2 驱动程序通过 read(9E) 入口点存储和提供数据。
可以使用 modinfo(1M)
命令来显示该版本 Quote Of The Day 驱动程序的模块信息。模块名是为 modldrv 结构第二个成员输入的值。96
是该模块的主编号。
% modinfo | grep qotd
182 ed115948 754 96 1 qotd_2 (Quote Of The Day 2.0)
% grep qotd /etc/name_to_major
qotd_1 94
qotd_2 96 |
本驱动程序还是
prtconf(1M) 在伪设备部分列出的最新使用的模块:
% prtconf -P | grep qotd
qotd_1, instance #0 (driver not attached)
qotd_2, instance #0 |
当对该 qotd_2 设备进行读操作时,用于访问该设备的命令会从该设备节点检索数据。该命令然后显示这些数据,其方式与显示其他任何输入一样。要获取设备专用文件名,请访问
/devices 目录:
% ls -l /devices/pseudo /qotd*
crw------- 1 root sys 96, 0 date time /devices/pseudo /qotd_2@0:qotd |
该输出表明 qotd_2@0:qotd 是一个字符设备。该清单还表明只有 root
用户对该设备具有读写权限。在测试该驱动程序时要确保您是用户 root。要测试该 qotd_2
驱动程序,可以使用 more(1)
命令对设备文件进行读操作:
# more /devices/pseudo /qotd_2@0:qotd
You can't have everything. Where would you put it? - Steven Wright
You can't have everything. Where would you put it? - Steven Wright |
|