9.4. 字符设备

字符设备驱动程序直接从用户进程传输数据,或传输数据到用户进程。 这是最普通的一类设备驱动程序,源码树中有大量的简单例子。

这个简单的伪设备例子会记住你写给它的任何值,并且当你读取它的时候 会将这些值返回给你。下面显示了两个版本,一个适用于FreeBSD 4.X, 一个适用于FreeBSD 5.X。

例 9.1. 适用于FreeBSD 4.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 intecho_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);}intecho_open(dev_t dev, int oflags, int devtype, struct proc *p){ int err = 0; uprintf("Opened device \"echo\" successfully.\n"); return(err);}intecho_close(dev_t dev, int fflag, int devtype, struct proc *p){ uprintf("Closing device \"echo.\"\n"); return(0);}/* * read函数接受由echo_write()存储的buf,并将其返回到用户空间, * 以供其他函数访问。 * uio(9) */intecho_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接受一个字符串并将它保存到缓冲区,用于以后的访问。 */intecho_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);

例 9.2. 适用于FreeBSD 5.X回显伪设备驱动程序实例
/* * 简单‘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 intecho_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 intecho_open(struct cdev *dev, int oflags, int devtype, struct thread *p){ int err = 0; uprintf("Opened device \"echo\" successfully.\n"); return(err);}static intecho_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 intecho_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 intecho_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/echoTest Data

真正的硬件设备在下一章描述。

补充资源

本文档和其它文档可从这里下载: ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题,请先阅读 文档,如不能解决再联系 <questions@FreeBSD.org>.

关于本文档的问题请发信联系 <doc@FreeBSD.org>.