/* * 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 设备驱动程序
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: December 11, 2021 by Sergio Carlavilla Delgado