第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
通过某些 LDI 内核接口,LDI 可以跟踪和报告内核设备使用信息。请参见分层标识符-内核设备使用方。
通过其他 LDI 内核接口,内核模块可以对目标设备执行 open、read 和 write 之类的访问操作。 另外,通过这些 LDI 内核接口,内核设备使用方可以查询有关目标设备的属性和事件信息。请参见分层驱动程序句柄-目标设备。
LDI 内核接口示例介绍了使用其中多个 LDI 接口的驱动程序示例。
通过分层标识符,LDI 可以跟踪和报告内核设备使用信息。分层标识符 (ldi_ident_t) 用于标识内核设备使用方。内核设备使用方必须先获取分层标识符,然后才能使用 LDI 打开目标设备。
分层驱动程序是唯一受支持的内核设备使用方类型。因此,分层驱动程序必须获取与设备编号、设备信息节点或分层驱动程序流关联的分层标识符。分层标识符与分层驱动程序关联。分层标识符与目标设备没有关联。
可以通过 libdevinfo(3LIB) 接口、fuser(1M) 命令或 prtconf(1M) 命令,检索通过 LDI 收集的内核设备使用信息。例如,使用 prtconf(1M) 命令可以显示分层驱动程序正在访问哪些目标设备,或者哪些分层驱动程序正在访问特定目标设备。要了解有关如何检索设备使用情况的更多信息,请参见用户接口。
下面介绍了 LDI 分层标识符接口:
分层标识符。属于不透明类型。
分配和检索与 dev_t 设备编号关联的分层标识符。
分配和检索与 dev_info_t 设备信息节点关联的分层标识符。
分配和检索与流关联的分层标识符。
释放使用 ldi_ident_from_dev(9F)、ldi_ident_from_dip(9F) 或 ldi_ident_from_stream(9F) 分配的分层标识符。
内核设备使用方必须使用分层驱动程序句柄 (ldi_handle_t) 来通过 LDI 接口访问目标设备。ldi_handle_t 类型仅对 LDI 接口有效。当 LDI 成功打开某个设备时,将分配并返回此句柄。然后,内核设备使用方可使用此句柄通过 LDI 接口访问目标设备。LDI 在关闭设备时会取消分配该句柄。有关示例,请参见LDI 内核接口示例。
本节讨论内核设备使用方如何访问目标设备并检索不同类型的信息。要了解内核设备使用者如何打开和关闭目标设备,请参见打开和关闭目标设备。要了解内核设备使用方如何对目标设备执行 read、write、strategy 和 ioctl 之类的操作,请参见访问目标设备。检索目标设备信息介绍了用于检索目标设备信息(如设备打开类型和设备次要名称)的接口。检索目标设备属性值介绍了用于检索目标设备属性的值和地址的接口。要了解内核设备使用方如何接收来自目标设备的事件通知,请参见接收异步设备事件通知。
本节介绍用于打开和关闭目标设备的 LDI 内核接口。打开接口采用指向分层驱动程序句柄的指针。打开接口会尝试打开由设备编号、设备 ID 或路径名指定的目标设备。如果打开操作成功,则打开接口将分配并返回可用于访问目标设备的分层驱动程序句柄。关闭接口用于关闭与指定分层驱动程序句柄关联的目标设备,然后释放该分层驱动程序句柄。
用于访问目标设备的分层驱动程序句柄。一种成功打开设备时返回的不透明数据结构。
打开由 dev_t 设备编号参数指定的设备。
打开由 ddi_devid_t 设备 ID 参数指定的设备。另外,还必须指定要打开的次要节点名称。
根据路径名打开设备。路径名是内核地址空间中以 NULL 结尾的字符串。路径名必须是以正斜杠字符 (/) 开头的绝对路径。
关闭使用 ldi_open_by_dev(9F)、ldi_open_by_devid(9F) 或 ldi_open_by_name(9F) 打开的设备。在 ldi_close(9F) 返回之后,已关闭设备的分层驱动程序句柄不再有效。
本节介绍用于访问目标设备的 LDI 内核接口。通过这些接口,内核设备使用方可以对由分层驱动程序句柄指定的目标设备执行操作。内核设备使用方可以对目标设备执行 read、write、strategy 和 ioctl 之类的操作。
用于访问目标设备的分层驱动程序句柄。属于不透明数据结构。
将读取请求传递到目标设备的设备入口点。块设备、字符设备和 STREAMS 设备支持此操作。
将异步读取请求传递到目标设备的设备入口点。块设备和字符设备支持此操作。
将写入请求传递到目标设备的设备入口点。块设备、字符设备和 STREAMS 设备支持此操作。
将异步写入请求传递到目标设备的设备入口点。块设备和字符设备支持此操作。
将策略请求传递到目标设备的设备入口点。块设备和字符设备支持此操作。
将转储请求传递到目标设备的设备入口点。块设备和字符设备支持此操作。
将轮询请求传递到目标设备的设备入口点。块设备、字符设备和 STREAMS 设备支持此操作。
将 ioctl 请求传递到目标设备的设备入口点。块设备、字符设备和 STREAMS 设备支持此操作。LDI 支持 STREAMS 链接和 STREAMS ioctl 命令。请参见 ldi_ioctl(9F) 手册页的 "STREAM IOCTLS" 一节。另请参见 streamio(7I) 手册页中的 ioctl 命令。
将 devmap 请求传递到目标设备的设备入口点。块设备和字符设备支持此操作。
从流中获取消息块。
将消息块放在流中。
本节介绍内核设备使用方可用于检索有关指定目标设备的设备信息的 LDI 接口。目标设备由分层驱动程序句柄指定。内核设备使用方可以接收设备编号、设备打开类型、设备 ID、设备次要名称和设备大小之类的信息。
获取由分层驱动程序句柄指定的目标设备的 dev_t 设备编号。
获取用于打开由分层驱动程序句柄指定的目标设备的打开标志。此标志指示目标设备是字符设备还是块设备。
获取由分层驱动程序句柄指定的目标设备的 ddi_devid_t 设备 ID。使用完设备 ID 后,应使用 ddi_devid_free(9F) 释放 ddi_devid_t。
检索包含为目标设备打开的次要节点的名称的缓冲区。使用完次要节点名称后,应使用 kmem_free(9F) 释放该缓冲区。
检索由分层驱动程序句柄指定的目标设备的分区大小。
本节介绍内核设备使用方可用于检索有关指定目标设备的属性信息的 LDI 接口。目标设备由分层驱动程序句柄指定。内核设备使用方可以接收属性的值和地址,以及确定某属性是否存在。
如果由分层驱动程序句柄指定的目标设备的属性存在,则返回 1。如果指定目标设备的属性不存在,则返回 0。
搜索与由分层驱动程序句柄指定的目标设备关联的 int 整数属性。如果找到整数属性,则返回属性值。
搜索与由分层驱动程序句柄指定的目标设备关联的 int64_t 整数属性。如果找到整数属性,则返回属性值。
检索由分层驱动程序句柄指定的目标设备的 int 整数数组属性值的地址。
检索由分层驱动程序句柄指定的目标设备的 int64_t 整数数组属性值的地址。
检索由分层驱动程序句柄指定的目标设备的以 null 结尾的字符串属性值的地址。
检索字符串数组的地址。字符串数组是一个指针数组,指向由分层驱动程序句柄指定的目标设备的以 null 结尾的字符串属性值。
检索字节数组的地址。字节数组是由分层驱动程序句柄指定的目标设备的属性值。
通过 LDI,内核设备使用方可以注册事件通知以及接收来自目标设备的事件通知。内核设备使用方可以注册发生事件时将会调用的事件处理程序。内核设备使用方必须先打开设备并接收分层驱动程序句柄,然后才能通过 LDI 事件通知接口注册事件通知。
通过 LDI 事件通知接口,内核设备使用方可以指定事件名称以及检索关联的内核事件 cookie。然后,内核设备使用方可以将分层驱动程序句柄 (ldi_handle_t)、cookie (ddi_eventcookie_t) 及事件处理程序传递到 ldi_add_event_handler(9F) 以注册事件通知。成功完成注册后,内核设备使用方会收到一个唯一的 LDI 事件处理程序标识符 (ldi_callback_id_t)。LDI 事件处理程序标识符属于不透明类型,只能用于 LDI 事件通知接口。
LDI 提供了一个框架,以用于注册其他设备生成的事件。LDI 本身并不定义任何事件类型,也不提供用于生成事件的接口。
下面介绍了 LDI 异步事件通知接口:
事件处理程序标识符。属于不透明类型。
检索由分层驱动程序句柄指定的目标设备的事件服务 cookie。
添加由 ldi_callback_id_t 注册标识符指定的回调处理程序。发生由 ddi_eventcookie_t cookie 指定的事件时,将会调用该回调处理程序。
删除由 ldi_callback_id_t 注册标识符指定的回调处理程序。
本节介绍了一个使用本章前面几节中讨论的一些 LDI 调用的内核设备使用方示例。本节讨论此示例模块的下列几个方面:
此内核设备使用方示例名为 lyr。lyr 模块是一个分层驱动程序,它使用 LDI 调用向目标设备发送数据。在其 open(9E) 入口点中,lyr 驱动程序将打开由 lyr.conf 配置文件中的 lyr_targ 属性指定的设备。在其 write(9E) 入口点中,lyr 驱动程序将其所有传入数据写入由 lyr_targ 属性指定的设备。
在下面所示的配置文件中,lyr 驱动程序向其中写入数据的目标设备为控制台。
示例 14-1 配置文件
# # Use is subject to license terms. # #pragma ident "%Z%%M% %I% %E% SMI" name="lyr" parent="pseudo" instance=1; lyr_targ="/dev/console";
在下面所示的驱动程序源文件中,lyr_state_t 结构保存 lyr 驱动程序的软状态。该软状态包括 lyr_targ 设备的分层驱动程序句柄 (lh) 和 lyr 设备的分层标识符 (li)。有关软状态的更多信息,请参见检索驱动程序软状态信息。
在 lyr_open()入口点中,ddi_prop_lookup_string(9F) 将从 lyr_targ 属性中检索要打开的 lyr 设备的目标设备的名称。ldi_ident_from_dev(9F) 函数用于获取 lyr 设备的 LDI 分层标识符。ldi_open_by_name(9F) 函数用于打开 lyr_targ 设备并获取 lyr_targ 设备的分层驱动程序句柄。
请注意,如果 lyr_open() 中发生任何故障,ldi_close(9F)、ldi_ident_release(9F) 和 ddi_prop_free(9F) 调用将会撤消所执行的所有操作。ldi_close(9F) 函数用于关闭 lyr_targ 设备。ldi_ident_release(9F) 函数用于释放 lyr 分层标识符。ddi_prop_free(9F) 函数用于释放检索 lyr_targ 设备名称时分配的资源。如果未发生故障,则会在 lyr_close() 入口点中调用 ldi_close (9F) 和 ldi_ident_release(9F) 函数。
在驱动程序模块的最后一行中,调用了 ldi_write(9F) 函数。ldi_write(9F) 函数先获取在 lyr_write() 入口点中写入 lyr 设备的数据,然后将该数据写入 lyr_targ 设备。ldi_write(9F) 函数使用 lyr_targ 设备的分层驱动程序句柄将数据写入 lyr_targ 设备。
示例 14-2 驱动程序源文件
#include <sys/types.h> #include <sys/file.h> #include <sys/errno.h> #include <sys/open.h> #include <sys/cred.h> #include <sys/cmn_err.h> #include <sys/modctl.h> #include <sys/conf.h> #include <sys/stat.h> #include <sys/ddi.h> #include <sys/sunddi.h> #include <sys/sunldi.h> typedef struct lyr_state { ldi_handle_t lh; ldi_ident_t li; dev_info_t *dip; minor_t minor; int flags; kmutex_t lock; } lyr_state_t; #define LYR_OPENED 0x1 /* lh is valid */ #define LYR_IDENTED 0x2 /* li is valid */ static int lyr_info(dev_info_t *, ddi_info_cmd_t, void *, void **); static int lyr_attach(dev_info_t *, ddi_attach_cmd_t); static int lyr_detach(dev_info_t *, ddi_detach_cmd_t); static int lyr_open(dev_t *, int, int, cred_t *); static int lyr_close(dev_t, int, int, cred_t *); static int lyr_write(dev_t, struct uio *, cred_t *); static void *lyr_statep; static struct cb_ops lyr_cb_ops = { lyr_open, /* open */ lyr_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ lyr_write, /* write */ nodev, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* poll */ ddi_prop_op, /* prop_op */ NULL, /* streamtab */ D_NEW | D_MP, /* cb_flag */ CB_REV, /* cb_rev */ nodev, /* aread */ nodev /* awrite */ }; static struct dev_ops lyr_dev_ops = { DEVO_REV, /* devo_rev, */ 0, /* refcnt */ lyr_info, /* getinfo */ nulldev, /* identify */ nulldev, /* probe */ lyr_attach, /* attach */ lyr_detach, /* detach */ nodev, /* reset */ &lyr_cb_ops, /* cb_ops */ NULL, /* bus_ops */ NULL, /* power */ ddi_quiesce_not_needed, /* quiesce */ }; static struct modldrv modldrv = { &mod_driverops, "LDI example driver", &lyr_dev_ops }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; int _init(void) { int rv; if ((rv = ddi_soft_state_init(&lyr_statep, sizeof (lyr_state_t), 0)) != 0) { cmn_err(CE_WARN, "lyr _init: soft state init failed\n"); return (rv); } if ((rv = mod_install(&modlinkage)) != 0) { cmn_err(CE_WARN, "lyr _init: mod_install failed\n"); goto FAIL; } return (rv); /*NOTEREACHED*/ FAIL: ddi_soft_state_fini(&lyr_statep); return (rv); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int rv; if ((rv = mod_remove(&modlinkage)) != 0) { return(rv); } ddi_soft_state_fini(&lyr_statep); return (rv); } /* * 1:1 mapping between minor number and instance */ static int lyr_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { int inst; minor_t minor; lyr_state_t *statep; char *myname = "lyr_info"; minor = getminor((dev_t)arg); inst = minor; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: statep = ddi_get_soft_state(lyr_statep, inst); if (statep == NULL) { cmn_err(CE_WARN, "%s: get soft state " "failed on inst %d\n", myname, inst); return (DDI_FAILURE); } *result = (void *)statep->dip; break; case DDI_INFO_DEVT2INSTANCE: *result = (void *)inst; break; default: break; } return (DDI_SUCCESS); } static int lyr_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int inst; lyr_state_t *statep; char *myname = "lyr_attach"; switch (cmd) { case DDI_ATTACH: inst = ddi_get_instance(dip); if (ddi_soft_state_zalloc(lyr_statep, inst) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: ddi_soft_state_zallac failed " "on inst %d\n", myname, inst); goto FAIL; } statep = (lyr_state_t *)ddi_get_soft_state(lyr_statep, inst); if (statep == NULL) { cmn_err(CE_WARN, "%s: ddi_get_soft_state failed on " "inst %d\n", myname, inst); goto FAIL; } statep->dip = dip; statep->minor = inst; if (ddi_create_minor_node(dip, "node", S_IFCHR, statep->minor, DDI_PSEUDO, 0) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: ddi_create_minor_node failed on " "inst %d\n", myname, inst); goto FAIL; } mutex_init(&statep->lock, NULL, MUTEX_DRIVER, NULL); return (DDI_SUCCESS); case DDI_RESUME: case DDI_PM_RESUME: default: break; } return (DDI_FAILURE); /*NOTREACHED*/ FAIL: ddi_soft_state_free(lyr_statep, inst); ddi_remove_minor_node(dip, NULL); return (DDI_FAILURE); } static int lyr_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int inst; lyr_state_t *statep; char *myname = "lyr_detach"; inst = ddi_get_instance(dip); statep = ddi_get_soft_state(lyr_statep, inst); if (statep == NULL) { cmn_err(CE_WARN, "%s: get soft state failed on " "inst %d\n", myname, inst); return (DDI_FAILURE); } if (statep->dip != dip) { cmn_err(CE_WARN, "%s: soft state does not match devinfo " "on inst %d\n", myname, inst); return (DDI_FAILURE); } switch (cmd) { case DDI_DETACH: mutex_destroy(&statep->lock); ddi_soft_state_free(lyr_statep, inst); ddi_remove_minor_node(dip, NULL); return (DDI_SUCCESS); case DDI_SUSPEND: case DDI_PM_SUSPEND: default: break; } return (DDI_FAILURE); } /* * on this driver's open, we open the target specified by a property and store * the layered handle and ident in our soft state. a good target would be * "/dev/console" or more interestingly, a pseudo terminal as specified by the * tty command */ /*ARGSUSED*/ static int lyr_open(dev_t *devtp, int oflag, int otyp, cred_t *credp) { int rv, inst = getminor(*devtp); lyr_state_t *statep; char *myname = "lyr_open"; dev_info_t *dip; char *lyr_targ = NULL; statep = (lyr_state_t *)ddi_get_soft_state(lyr_statep, inst); if (statep == NULL) { cmn_err(CE_WARN, "%s: ddi_get_soft_state failed on " "inst %d\n", myname, inst); return (EIO); } dip = statep->dip; /* * our target device to open should be specified by the "lyr_targ" * string property, which should be set in this driver's .conf file */ if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_NOTPROM, "lyr_targ", &lyr_targ) != DDI_PROP_SUCCESS) { cmn_err(CE_WARN, "%s: ddi_prop_lookup_string failed on " "inst %d\n", myname, inst); return (EIO); } /* * since we only have one pair of lh's and li's available, we don't * allow multiple on the same instance */ mutex_enter(&statep->lock); if (statep->flags & (LYR_OPENED | LYR_IDENTED)) { cmn_err(CE_WARN, "%s: multiple layered opens or idents " "from inst %d not allowed\n", myname, inst); mutex_exit(&statep->lock); ddi_prop_free(lyr_targ); return (EIO); } rv = ldi_ident_from_dev(*devtp, &statep->li); if (rv != 0) { cmn_err(CE_WARN, "%s: ldi_ident_from_dev failed on inst %d\n", myname, inst); goto FAIL; } statep->flags |= LYR_IDENTED; rv = ldi_open_by_name(lyr_targ, FREAD | FWRITE, credp, &statep->lh, statep->li); if (rv != 0) { cmn_err(CE_WARN, "%s: ldi_open_by_name failed on inst %d\n", myname, inst); goto FAIL; } statep->flags |= LYR_OPENED; cmn_err(CE_CONT, "\n%s: opened target '%s' successfully on inst %d\n", myname, lyr_targ, inst); rv = 0; FAIL: /* cleanup on error */ if (rv != 0) { if (statep->flags & LYR_OPENED) (void)ldi_close(statep->lh, FREAD | FWRITE, credp); if (statep->flags & LYR_IDENTED) ldi_ident_release(statep->li); statep->flags &= ~(LYR_OPENED | LYR_IDENTED); } mutex_exit(&statep->lock); if (lyr_targ != NULL) ddi_prop_free(lyr_targ); return (rv); } /* * on this driver's close, we close the target indicated by the lh member * in our soft state and release the ident, li as well. in fact, we MUST do * both of these at all times even if close yields an error because the * device framework effectively closes the device, releasing all data * associated with it and simply returning whatever value the target's * close(9E) returned. therefore, we must as well. */ /*ARGSUSED*/ static int lyr_close(dev_t devt, int oflag, int otyp, cred_t *credp) { int rv, inst = getminor(devt); lyr_state_t *statep; char *myname = "lyr_close"; statep = (lyr_state_t *)ddi_get_soft_state(lyr_statep, inst); if (statep == NULL) { cmn_err(CE_WARN, "%s: ddi_get_soft_state failed on " "inst %d\n", myname, inst); return (EIO); } mutex_enter(&statep->lock); rv = ldi_close(statep->lh, FREAD | FWRITE, credp); if (rv != 0) { cmn_err(CE_WARN, "%s: ldi_close failed on inst %d, but will ", "continue to release ident\n", myname, inst); } ldi_ident_release(statep->li); if (rv == 0) { cmn_err(CE_CONT, "\n%s: closed target successfully on " "inst %d\n", myname, inst); } statep->flags &= ~(LYR_OPENED | LYR_IDENTED); mutex_exit(&statep->lock); return (rv); } /* * echo the data we receive to the target */ /*ARGSUSED*/ static int lyr_write(dev_t devt, struct uio *uiop, cred_t *credp) { int rv, inst = getminor(devt); lyr_state_t *statep; char *myname = "lyr_write"; statep = (lyr_state_t *)ddi_get_soft_state(lyr_statep, inst); if (statep == NULL) { cmn_err(CE_WARN, "%s: ddi_get_soft_state failed on " "inst %d\n", myname, inst); return (EIO); } return (ldi_write(statep->lh, uiop, credp)); }
使用 -D_KERNEL 选项指示这是一个内核模块。
如果要针对 SPARC 体系结构进行编译,请使用 -xarch=v9 选项:
% cc -c -D_KERNEL -xarch=v9 lyr.c
如果要针对 32 位 x86 体系结构进行编译,请使用以下命令:
% cc -c -D_KERNEL lyr.c
% ld -r -o lyr lyr.o
以 root 用户身份,将配置文件复制到计算机的内核驱动程序区域:
# cp lyr.conf /usr/kernel/drv
以 root 用户身份,将驱动程序二进制文件复制到 SPARC 体系结构的 sparcv9 驱动程序区域:
# cp lyr /usr/kernel/drv/sparcv9
以 root用户身份,将驱动程序二进制文件复制到 32 位 x86 体系结构的 drv 驱动程序区域:
# cp lyr /usr/kernel/drv
以 root 用户身份,使用 add_drv(1M) 命令装入驱动程序。
# add_drv lyr
列出伪设备,确认目前是否存在 lyr 设备:
# ls /devices/pseudo | grep lyr lyr@1 lyr@1:node
要测试 lyr 驱动程序,请向 lyr 设备写入一条消息,并验证该消息是否显示在 lyr_targ 设备上。
示例 14-3 向分层设备写入一条短消息
在本示例中,lyr_targ 设备是安装了 lyr 设备的系统的控制台。
如果要查看的显示屏幕也是安装了 lyr 设备的系统的控制台设备的显示屏幕,请注意,向控制台写入将会破坏显示屏幕上的信息。控制台消息将显示在窗口系统范围以外。测试 lyr 驱动程序之后,需要重画或刷新显示器。
如果要查看的显示屏幕不是安装了 lyr 设备的系统的控制台设备的显示屏幕,请登录或以其他方式查看目标控制台设备的显示屏幕上的信息。
以下命令将一条很短的消息写入 lyr 设备:
# echo "\n\n\t===> Hello World!! <===\n" > /devices/pseudo/lyr@1:node
目标控制台上将会显示以下消息:
console login: ===> Hello World!! <=== lyr: lyr_open: opened target '/dev/console' successfully on inst 1 lyr: lyr_close: closed target successfully on inst 1
执行 lyr_open() 和 lyr_close() 时所显示的消息来自在 lyr_open() 和 lyr_close() 入口点中执行的 cmn_err(9F) 调用。
示例 14-4 向分层设备写入一条较长的消息
以下命令将一条较长的消息写入 lyr 设备:
# cat lyr.conf > /devices/pseudo/lyr@1:node
目标控制台上将会显示以下消息:
lyr: lyr_open: opened target '/dev/console' successfully on inst 1 # # Use is subject to license terms. # #pragma ident "%Z%%M% %I% %E% SMI" name="lyr" parent="pseudo" instance=1; lyr_targ="/dev/console"; lyr: lyr_close: closed target successfully on inst 1
示例 14-5 更改目标设备
要更改目标设备,请编辑 /usr/kernel/drv/lyr.conf,并将 lyr_targ 属性的值更改为指向其他目标设备的路径。例如,该目标设备可以是在本地终端执行 tty 命令后的输出结果。例如,此类设备路径可以是 /dev/pts/4。
在将驱动程序更新为使用新目标设备之前,应确保 lyr 设备未被使用。
# modinfo -c | grep lyr 174 3 lyr UNLOADED/UNINSTALLED
使用 update_drv(1M) 命令重新装入 lyr.conf 配置文件:
# update_drv lyr
再次向 lyr 设备写入一条消息,并验证该消息是否显示在新的 lyr_targ 设备上。