字符设备驱动程序直接从用户进程传输数据,或传输数据到用户进程。 这是最普通的一类设备驱动程序,源码树中有大量的简单例子。
这个简单的伪设备例子会记住你写给它的任何值,并且当你读取它的时候 会将这些值返回给你。下面显示了两个版本,一个适用于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 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);
例 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 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
本文档和其它文档可从这里下载:ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.
如果对于FreeBSD有问题,请先阅读文档,如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.