/* * KLD程序框架 * 受Andrew Reiter在Daemonnews上的文章所启发 */ #include sys/types.h #include sys/module.h #include sys/systm.h /* uprintf */ #include sys/errno.h #include sys/param.h /* kernel.h中用到的定义 */ #include sys/kernel.h /* 模块初始化中使用的类型 */ /* * 加载处理函数,负责处理KLD的加载和卸载。 */ static int skel_loader(struct module *m, int what, void *arg) { int err = 0; switch (what) { case MOD_LOAD: /* kldload */ uprintf("Skeleton KLD loaded.\n"); break; case MOD_UNLOAD: uprintf("Skeleton KLD unloaded.\n"); break; default: err = EOPNOTSUPP; break; } return(err); } /* 向内核其余部分声明此模块 */ static moduledata_t skel_mod = { "skel", skel_loader, NULL }; DECLARE_MODULE(skeleton, skel_mod, SI_SUB_KLD, SI_ORDER_ANY);
第 9 章 编写 FreeBSD 设备驱动程序
This translation may be out of date. To help with the translations please access the FreeBSD translations instance.
Table of Contents
9.1. 简介
本章简要介绍了如何为FreeBSD编写设备驱动程序。术语设备在 这儿的上下文中多用于指代系统中硬件相关的东西,如磁盘,打印机, 图形显式器及其键盘。设备驱动程序是操作系统中用于控制特定设备的 软件组件。也有所谓的伪设备,即设备驱动程序用软件模拟设备的行为, 而没有特定的底层硬件。设备驱动程序可以被静态地编译进系统,或者 通过动态内核链接工具'kld
'在需要时加载。
类UNIX®操作系统中的大多数设备都是通过设备节点来访问的,有时也 被称为特殊文件。这些文件在文件系统的层次结构中通常位于 /dev目录下。在FreeBSD 5.0-RELEASE以前的 发行版中, 对devfs(5)的支持还没有被集成到FreeBSD中,每个设备 节点必须要静态创建,并且独立于相关设备驱动程序的存在。系统中大 多数设备节点是通过运行MAKEDEV
创建的。
设备驱动程序可以粗略地分为两类,字符和网络设备驱动程序。
9.2. 动态内核链接工具-KLD
kld接口允许系统管理员从运行的系统中动态地添加和删除功能。 这允许设备驱动程序的编写者将他们的新改动加载到运行的内核中, 而不用为了测试新改动而频繁地重启。
kld接口通过下面的特权命令使用:
kldload
- 加载新内核模块kldunload
- 卸载内核模块kldstat
- 列举当前加载的模块
内核模块的程序框架
9.3. 访问设备驱动程序
UNIX® 提供了一套公共的系统调用供用户的应用程序使用。当用户访问 设备节点时,内核的上层将这些调用分发到相应的设备驱动程序。脚本 /dev/MAKEDEV
为你的系统生成了大多数的设备节点, 但如果你正在开发你自己的驱动程序,可能需要用 mknod
创建你自己的设备节点。
9.4. 字符设备
字符设备驱动程序直接从用户进程传输数据,或传输数据到用户进程。 这是最普通的一类设备驱动程序,源码树中有大量的简单例子。
这个简单的伪设备例子会记住你写给它的任何值,并且当你读取它的时候 会将这些值返回给你。下面显示了两个版本,一个适用于FreeBSD 4.X, 一个适用于FreeBSD 5.X。
/* * 简单‘echo’伪设备KLD * * Murray Stokely */ #define MIN(a,b) (((a) (b)) ? (a) : (b)) #include sys/types.h #include sys/module.h #include sys/systm.h /* uprintf */ #include sys/errno.h #include sys/param.h /* kernel.h中用到的定义 */ #include sys/kernel.h /* 模块初始化中使用的类型 */ #include sys/conf.h /* cdevsw结构 */ #include sys/uio.h /* uio结构 */ #include sys/malloc.h #define BUFFERSIZE 256 /* 函数原型 */ d_open_t echo_open; d_close_t echo_close; d_read_t echo_read; d_write_t echo_write; /* 字符设备入口点 */ static struct cdevsw echo_cdevsw = { echo_open, echo_close, echo_read, echo_write, noioctl, nopoll, nommap, nostrategy, "echo", 33, /* 为lkms保留 - /usr/src/sys/conf/majors */ nodump, nopsize, D_TTY, -1 }; typedef struct s_echo { char msg[BUFFERSIZE]; int len; } t_echo; /* 变量 */ static dev_t sdev; static int count; static t_echo *echomsg; MALLOC_DECLARE(M_ECHOBUF); MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module"); /* * 这个函数被kld[un]load(2)系统调用来调用, * 以决定加载和卸载模块时需要采取的动作。 */ static int echo_loader(struct module *m, int what, void *arg) { int err = 0; switch (what) { case MOD_LOAD: /* kldload */ sdev = make_dev(echo_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "echo"); /* kmalloc分配供驱动程序使用的内存 */ MALLOC(echomsg, t_echo *, sizeof(t_echo), M_ECHOBUF, M_WAITOK); printf("Echo device loaded.\n"); break; case MOD_UNLOAD: destroy_dev(sdev); FREE(echomsg,M_ECHOBUF); printf("Echo device unloaded.\n"); break; default: err = EOPNOTSUPP; break; } return(err); } int echo_open(dev_t dev, int oflags, int devtype, struct proc *p) { int err = 0; uprintf("Opened device \"echo\" successfully.\n"); return(err); } int echo_close(dev_t dev, int fflag, int devtype, struct proc *p) { uprintf("Closing device \"echo.\"\n"); return(0); } /* * read函数接受由echo_write()存储的buf,并将其返回到用户空间, * 以供其他函数访问。 * uio(9) */ int echo_read(dev_t dev, struct uio *uio, int ioflag) { int err = 0; int amt; /* * 这个读操作有多大? * 与用户请求的大小一样,或者等于剩余数据的大小。 */ amt = MIN(uio-uio_resid, (echomsg-len - uio-uio_offset 0) ? echomsg-len - uio-uio_offset : 0); if ((err = uiomove(echomsg-msg + uio-uio_offset,amt,uio)) != 0) { uprintf("uiomove failed!\n"); } return(err); } /* * echo_write接受一个字符串并将它保存到缓冲区,用于以后的访问。 */ int echo_write(dev_t dev, struct uio *uio, int ioflag) { int err = 0; /* 将字符串从用户空间的内存复制到内核空间 */ err = copyin(uio-uio_iov-iov_base, echomsg-msg, MIN(uio-uio_iov-iov_len, BUFFERSIZE - 1)); /* 现在需要以null结束字符串,并记录长度 */ *(echomsg-msg + MIN(uio-uio_iov-iov_len, BUFFERSIZE - 1)) = 0; echomsg-len = MIN(uio-uio_iov-iov_len, BUFFERSIZE); if (err != 0) { uprintf("Write failed: bad address!\n"); } count++; return(err); } DEV_MODULE(echo,echo_loader,NULL);
/* * 简单‘echo’伪设备 KLD * * Murray Stokely * * 此代码由Søren (Xride) Straarup转换到5.X */ #include sys/types.h #include sys/module.h #include sys/systm.h /* uprintf */ #include sys/errno.h #include sys/param.h /* kernel.h中用到的定义 */ #include sys/kernel.h /* 模块初始化中使用的类型 */ #include sys/conf.h /* cdevsw结构 */ #include sys/uio.h /* uio结构 */ #include sys/malloc.h #define BUFFERSIZE 256 /* 函数原型 */ static d_open_t echo_open; static d_close_t echo_close; static d_read_t echo_read; static d_write_t echo_write; /* 字符设备入口点 */ static struct cdevsw echo_cdevsw = { .d_version = D_VERSION, .d_open = echo_open, .d_close = echo_close, .d_read = echo_read, .d_write = echo_write, .d_name = "echo", }; typedef struct s_echo { char msg[BUFFERSIZE]; int len; } t_echo; /* 变量 */ static struct cdev *echo_dev; static int count; static t_echo *echomsg; MALLOC_DECLARE(M_ECHOBUF); MALLOC_DEFINE(M_ECHOBUF, "echobuffer", "buffer for echo module"); /* * 这个函数被kld[un]load(2)系统调用来调用, * 以决定加载和卸载模块时需要采取的动作. */ static int echo_loader(struct module *m, int what, void *arg) { int err = 0; switch (what) { case MOD_LOAD: /* kldload */ echo_dev = make_dev(echo_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "echo"); /* kmalloc分配供驱动程序使用的内存 */ echomsg = malloc(sizeof(t_echo), M_ECHOBUF, M_WAITOK); printf("Echo device loaded.\n"); break; case MOD_UNLOAD: destroy_dev(echo_dev); free(echomsg, M_ECHOBUF); printf("Echo device unloaded.\n"); break; default: err = EOPNOTSUPP; break; } return(err); } static int echo_open(struct cdev *dev, int oflags, int devtype, struct thread *p) { int err = 0; uprintf("Opened device \"echo\" successfully.\n"); return(err); } static int echo_close(struct cdev *dev, int fflag, int devtype, struct thread *p) { uprintf("Closing device \"echo.\"\n"); return(0); } /* * read函数接受由echo_write()存储的buf,并将其返回到用户空间, * 以供其他函数访问。 * uio(9) */ static int echo_read(struct cdev *dev, struct uio *uio, int ioflag) { int err = 0; int amt; /* * 这个读操作有多大? * 等于用户请求的大小,或者等于剩余数据的大小。 */ amt = MIN(uio-uio_resid, (echomsg-len - uio-uio_offset 0) ? echomsg-len - uio-uio_offset : 0); if ((err = uiomove(echomsg-msg + uio-uio_offset, amt, uio)) != 0) { uprintf("uiomove failed!\n"); } return(err); } /* * echo_write接受一个字符串并将它保存到缓冲区, 用于以后的访问. */ static int echo_write(struct cdev *dev, struct uio *uio, int ioflag) { int err = 0; /* 将字符串从用户空间的内存复制到内核空间 */ err = copyin(uio-uio_iov-iov_base, echomsg-msg, MIN(uio-uio_iov-iov_len, BUFFERSIZE - 1)); /* 现在需要以null结束字符串,并记录长度 */ *(echomsg-msg + MIN(uio-uio_iov-iov_len, BUFFERSIZE - 1)) = 0; echomsg-len = MIN(uio-uio_iov-iov_len, BUFFERSIZE); if (err != 0) { uprintf("Write failed: bad address!\n"); } count++; return(err); } DEV_MODULE(echo,echo_loader,NULL);
在FreeBSD 4.X上安装此驱动程序,你将首先需要用如下命令在 你的文件系统上创建一个节点:
# mknod /dev/echo c 33 0
驱动程序被加载后,你应该能够键入一些东西,如:
# echo -n "Test Data" > /dev/echo
# cat /dev/echo
Test Data
真正的硬件设备在下一章描述。
补充资源
Dynamic Kernel Linker (KLD) Facility Programming Tutorial - Daemonnews October 2000
How to Write Kernel Drivers with NEWBUS - Daemonnews July 2000
9.5. 块设备(消亡中)
其他UNIX®系统支持另一类型的磁盘设备,称为块设备。块设备是内核 为它们提供缓冲的磁盘设备。这种缓冲使得块设备几乎没有用,或者说非常 不可靠。缓冲会重新安排写操作的次序,使得应用程序丧失了在任何时刻及时 知道准确的磁盘内容的能力。这导致对磁盘数据结构(文件系统,数据库等)的 可预测的和可靠的崩溃恢复成为不可能。由于写操作被延迟,内核无法向应用 程序报告哪个特定的写操作遇到了写错误,这又进一步增加了一致性问题。 由于这个原因,真正的应用程序从不依赖于块设备,事实上,几乎所有访问 磁盘的应用程序都尽力指定总是使用字符(或"raw")设备。 由于实现将每个磁盘(分区)同具有不同语义的两个设备混为一谈,从而致使 相关内核代码极大地复杂化,作为推进磁盘I/O基础结构现代化的一部分,FreeBSD 抛弃了对带缓冲的磁盘设备的支持。
Last modified on: March 9, 2024 by Danilo G. Baio