一个简单字符型设备驱动及其测试

来源:互联网 发布:手机更换软件图标 编辑:程序博客网 时间:2024/05/16 11:41

驱动对一些人来说很难,而对一些人来说很容易。窃以为,理解简单设备驱动模型不难,深入理解并与Linux内核设计联系到一起需要花费时间。对于移植者来说,如何将自己自定义的模块天衣无缝放到内核中,是比较重要的——不过,有许多现在的“模板”可供参考,总算不用白手起家。

 

本文所述的仅是一个独立的、简单的字符型设备驱动。最简单的字符设备当属经典的“Hello World”。而这个比它复杂一点:在用户空间写一字符串到内核空间,再将其写到用户空间。从应用层角度来看,先写一字符串到内核,再读这个字符串。

 

先说测试程序,代码如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#define device "/dev/bar"

int main(void)
{
        int fd;
        char buf[] = "Late Lee";
        char buf2[30]={0};
        int len;
        fd = open(device, O_RDWR);
        if (fd < 0)
        {
                perror("Open device faile!");
                return -1;
        }
        len = write(fd, buf, sizeof(buf));
        printf("buf: %s %d/n", buf, len);
        len = read(fd, buf2, 25); // 由此指定读取数据,可大可小,但是驱动只读取这个指定的(大者读实际值),并返回
        printf("buf2: %s %d/n", buf2, len);
        close(fd);
        return 0;
}

 

代码很简单,使用open打开设备,调用write将buf的东西写到由fd指定的设备(文件)中。之后再使用read读到buf2中,打印字符串,最后关闭设备。就这么简单。

 

实际运行结果如下:

# ./a.out
buf: Late Lee 9
buf2: The voice from hell: Late 25

 其中“The voice from hell:”仅仅表明这个字符来自驱动,无实际意义。

 

驱动程序也很简单,代码如下:

/*************************************************************************
        Late Lee from http://www.latelee.org
简单的字符型设备驱动
从应用层获取一数据,再复制到应用层(在前面添加字符串)。
注册设备号及设备号的几个宏,均系ldd3例子scull。

何处释放data更好?看ldd3,似乎exit中更好。
2011-04-29 & 2011-05-06
*************************************************************************/

#include <linux/module.h>
#include <linux/kernel.h>       /**< printk() */
#include <linux/init.h>

#include <linux/cdev.h>                /**< cdev_* */
#include <linux/fs.h>
#include <asm/uaccess.h>        /**< copy_*_user */

#include <linux/types.h>        /**< size_t */
#include <linux/errno.h>        /**< error codes */
#include <linux/string.h>

#ifdef DEBUG /* define it in Makefile or somewhere */
/* KERN_INFO */
#define debug(fmt, ...) printk(KERN_DEBUG fmt, ##__VA_ARGS__)
#else
#define debug(fmt, ...)
#endif

#define DEV_NAME "foo"

//#define HAVE_MAJOR
#ifdef HAVE_MAJOR
#define DEVICE_MAJOR 248
#else        /* auto alloc */
#define DEVICE_MAJOR 0
#endif /* HAVE_MAJOR */

#define FOO_NR_DEVS 1

struct cdev foo_cdev;
int foo_major = DEVICE_MAJOR;
int foo_minor = 0;
int foo_nr_devs = FOO_NR_DEVS;
dev_t devno;
char *data;

static int foo_open(struct inode *inode, struct file *filp)
{
        debug("in %s()/n", __func__);
        return 0;
}

static int foo_release(struct inode *inode, struct file *filp)
{
        debug("in %s()/n", __func__);
        //kfree(data);
        //data = NULL;
        return 0;
}

static ssize_t foo_read(struct file *filp, char *buf, size_t count, loff_t *f_ops)
{
        int len;
        char *tmp;
       
        if (count < 0)
                return -EINVAL;
        tmp = (char *)kmalloc(sizeof(char) * (count+1), GFP_KERNEL); // ?? here
        sprintf(tmp, "The voice from hell: %s", data);
        len = strlen(tmp);
        if (len < count)
                count = len;
        if ( copy_to_user(buf, tmp, count) )
                return -EFAULT;
        debug("in %s() tmp: %s/n", __func__, tmp);
        debug("in %s() buf: %s/n", __func__, buf);
        debug("in %s() data: %s/n", __func__, data);
        kfree(tmp);
        return count;
}

static ssize_t foo_write(struct file *filp, const char *buf, size_t count, loff_t *f_ops)
{
        if (count < 0)
                return -EINVAL;
        data = (char *)kmalloc(sizeof(char) * (count+1), GFP_KERNEL);
        if (data == NULL)
                return -ENOMEM;
        if (copy_from_user(data, buf, count+1))
                return -EFAULT;
        debug("in %s() buff: %s/n", __func__, buf);
        debug("in %s() data: %s/n", __func__, data);
        return count;
}

static int foo_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
        switch (cmd)
        {
       
        default:
                return -EINVAL;
        }
        return 0;
}

static struct file_operations foo_fops = {
        .owner   = THIS_MODULE,
        .open    = foo_open,
        .release = foo_release,
        .read    = foo_read,
        .write   = foo_write,
        .ioctl   = foo_ioctl,
};

static int __init foo_init(void)
{
        int ret = -1;

        cdev_init(&foo_cdev, &foo_fops);
        foo_cdev.owner = THIS_MODULE;
        /* register to who? */
        if (foo_major)
        {
                devno = MKDEV(foo_major, foo_minor);
                ret = register_chrdev_region(devno, foo_nr_devs, DEV_NAME);
        }
        else
        {
                ret = alloc_chrdev_region(&devno, foo_minor, foo_nr_devs, DEV_NAME); /* get devno */
                foo_major = MAJOR(devno); /* get major */
               
        }
        if (ret < 0)
        {
                debug(" %s can't get major %d/n", DEV_NAME, foo_major);
                return -EINVAL;
        }
       
        ret = cdev_add(&foo_cdev, devno, 1);
        if (ret < 0)
        {
                debug(" %s cdev_add failure!/n", DEV_NAME);
                return -EINVAL;
        }
       
        debug("%s init ok!/n", DEV_NAME);
        return 0;
}

static void __exit foo_exit(void)
{
        if (data != NULL)
                kfree(data);
        unregister_chrdev_region(devno, 1);
        cdev_del(&foo_cdev);
        debug("%s exit ok!/n", DEV_NAME);
}

module_init(foo_init);
module_exit(foo_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Chiangchin Li");

 

其中最后两个函数foo_init和foo_exit分别在加载驱动和卸载驱动时执行。如果驱动程序直接编入内核,可以在启动信息中看到,如果是单独加载,在执行insmod时执行,当然,在何处显示与printk打印级别有关系。

我们的驱动中使用KERN_DEBUG,因此可以使用dmesg查看——注意,由于insmod、rmmod等等需要使用root权限,文中统一使用root,shell提示符为“#”。

# insmod GotoHell.ko
# dmesg | tail
……
foo init ok!

 

当执行应用层的测试程序时,dmesg的显示如下:

# ./a.out
buf: Late Lee 9
buf2: The voice from hell: Late 25
# dmesg | tail
……
foo init ok!
in foo_open()
in foo_write() buff: Late Lee
in foo_write() data: Late Lee
in foo_read() tmp: The voice from hell: Late Lee
in foo_read() buf: The voice from hell: Late
in foo_read() data: Late Lee
in foo_release()

可以看到,当执行close时,驱动中执行foo_release。当卸载模块时,dmesg显示如下:

# rmmod GotoHell.ko
# dmesg | tail     
……
foo init ok!
in foo_open()
in foo_write() buff: Late Lee
in foo_write() data: Late Lee
in foo_read() tmp: The voice from hell: Late Lee
in foo_read() buf: The voice from hell: Late
in foo_read() data: Late Lee
in foo_release()
foo exit ok!

对照测试程序及驱动显示的调试信息,可以看到应用层调用的如open、write、read、close等等系统调用,在驱动中均在对应的函数,它正是通过如下结构体来实现的:

static struct file_operations foo_fops = {
 .owner   = THIS_MODULE,
 .open    = foo_open,
 .release = foo_release,
 .read    = foo_read,
 .write   = foo_write,
 .ioctl   = foo_ioctl,
};

由于程序比较简单,不作注释和解释,一切尽在代码中。本文中的“模块”、“驱动”在某种意义上是同义词。
本文完整的“工程”代码可以在下面地址下载,为确保下载的东西的确是本文所提及的代码,最好确认一下它的md5和:deb16fcfbdc5e7f136ae988a0a26aad3

工程:简单字符型设备驱动例子-来自latelee.org

原创粉丝点击