目的
最近几天学习了基本的字符设备驱动,今天特地写了个字符设备驱动小demo巩固一下。其中也遇到了些许问题,所以就整理了篇日志来记录自己学习的点点滴滴。最初的感悟,刚开始接触字符设备驱动的时候,感觉也好复杂。需要了解register_chrdev_region()、alloc_chrdev_region()、struct file_operations、cdev_init()、cdev_add()、struct cdev、等好多的函数、结构体。硬着头皮写了几天代码,基本的流程也就掌握了。下面贴代码。
代码
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/fs.h>
#include <linux/gfp.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#define DEVICE_NAME "cortexA9"
#define DEVICE_COUNT 1
static int dev_num;
static int major = 0;
static int minor = 0;
struct chrdev
{
struct cdev cdev;
char mem[100];
};
static struct chrdev *cortex_dev;
static struct class *cortex_class;
static ssize_t cortex_open (struct inode *inode, struct file *filp)
{
struct chrdev *dev;
dev = container_of (inode->i_cdev, struct chrdev, cdev);
filp->private_data = dev;
return 0;
}
static ssize_t cortex_release (struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t cortex_write (struct file *filp, const char __user *buf, size_t count, loff_t foppt)
{
struct chrdev *dev;
dev = filp->private_data;
if (copy_from_user (dev->mem, buf, count))
return -EFAULT;
return count;
}
static ssize_t cortex_read (struct file *filp, char __user *buf, size_t count, loff_t *foppt)
{
struct chrdev *dev;
dev = filp->private_data;
if (copy_to_user (buf, (void *)dev->mem, count))
return -EFAULT;
return count;
}
static struct file_operations f_opt =
{
.owner = THIS_MODULE, .open = cortex_open,
.release = cortex_release, .write = cortex_write,
.read = cortex_read
};
static int cortex_init (void)
{
int err,ret;
cortex_dev = kmalloc (sizeof(struct chrdev), GFP_KERNEL);
if (cortex_dev == NULL)
return -ENOMEM;
cdev_init (&cortex_dev->cdev, &f_opt);
cortex_dev->cdev.owner = THIS_MODULE;
if (major > 0)
{
dev_num = MKDEV (major, minor);
err = register_chrdev_region (dev_num, DEVICE_COUNT, DEVICE_NAME);
}
else
{
err = alloc_chrdev_region (&(cortex_dev->cdev.dev), 10, DEVICE_COUNT, DEVICE_NAME);
dev_num = cortex_dev->cdev.dev;
}
if (err < 0)
return -EFAULT;
cortex_class = class_create (THIS_MODULE, DEVICE_NAME);
ret = cdev_add (&cortex_dev->cdev, dev_num, DEVICE_COUNT);
device_create (cortex_class, NULL,dev_num, NULL,DEVICE_NAME);
return ret;
}
static void cortex_exit (void)
{
if (cortex_dev != NULL)
kfree (cortex_dev);
class_destroy (cortex_class);
unregister_chrdev_region (dev_num, DEVICE_COUNT);
}
module_init (cortex_init);
module_exit (cortex_exit);
MODULE_LICENSE ("GPL");
MODULE_AUTHOR ("Jack");
分析
代码中红色的两行是后来添上的。下面描述下我遇到的问题,没有添加这两行的时候,能够正常的生成ko文件,insmod模块也是正常的,但是在/dev/下没有生成cortexA9字符设备。去/proc/devices查看也能找到主设备号249 cortexA9。于是很好奇,以前我也没手动创建过设备节点。只好去百度,原来才弄懂原理是这样的。
在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点,当然前提条件是用户空间移植了udev。
内核中定义了struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。
注意,在2.6较早的内核版本中,device_create(…)函数名称不同,是class_device_create(…),所以在新的内核中编译以前的模块程序有时会报错,就是因为函数名称不同,而且里面的参数设置也有一些变化。
struct class和device_create(…) 以及device_create(…)都定义在/include/linux/device.h中,使用的时候一定要包含这个头文件,否则编译器会报错。
引自 点击打开链接
问题2
当添加了代码之后,在/dev/下生成了cortexA9字符设备,于是我使用下边的测试代码对设备进行读写:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void main (int argc, char **argv)
{
int fd;
char *tmp;
fd = open ("/dev/cortexA9", O_RDWR);
if (fd < 0)
{
printf ("open file error\n");
exit (1);
}
puts ("hi hello\n");
write (fd, "echo", 5);
read (fd, tmp, 5);
if (strcmp (tmp,"echo") == 0)
puts("the same string");
puts (tmp);
}
显示能够正常的打开设备,但是无法对设备进行正常的读写。能够读出几个乱码,对于验证写入读出相等时输出的字符串也迟迟部件身影。当使用cat /dev/cortexA9查看的时候终端却一直显示个不停,当然了也是显示乱码。这个问题谁遇到过呢?
结论:
原本这些文字问题想做成论坛提问的,但是考虑到篇幅过长可能导致不会有人献计献策。所以写成了博客,另一方面,这个问题我也打算持续解决,知道掌握为止,希望各位提出自己的意见,谢谢。