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 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

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

  补充资源



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

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