一个简单字符型设备驱动及其测试 | 迟思堂工作室
A-A+

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

2014-08-30 15:50 嵌入式Linux 暂无评论 阅读 720 次

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



如果本文对阁下有帮助,不妨赞助笔者以输出更多好文章,谢谢!
donate




给我留言