FreeBSD 系统结构手册
上一页 | 第9章 　编写 FreeBSD 设备驱动程序 | 下一页

# 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

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

补充资源

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