当前位置: 首页 > 嵌入式Linux > 正文

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

驱动对一些人来说很难,而对一些人来说很容易。窃以为,理解简单设备驱动模型不难,深入理解并与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 %dn”, buf, len);

len = read(fd, buf2, 25); // 由此指定读取数据,可大可小,但是驱动只读取这个指定的(大者读实际值),并返回

printf(“buf2: %s %dn”, 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: %sn”, __func__, tmp);

debug(“in %s() buf: %sn”, __func__, buf);

debug(“in %s() data: %sn”, __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: %sn”, __func__, buf);

debug(“in %s() data: %sn”, __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 %dn”, 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.outbuf: 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

本文固定链接: http://www.latelee.org/embedded-linux/a-simple-char-driver.html

如无特别说明,迟思堂工作室文章均为原创,转载请注明: 一个简单字符型设备驱动及其测试 | 迟思堂工作室

目前暂无评论

发表评论

*

快捷键:Ctrl+Enter