Linux将所有的设备当作一个文件,为了达到这个目的,内核提供了一个结构体
struct file_operations {
struct module *owner;
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long,
unsigned long);
};
这个结构体定义了设备作为一个文件可以被操作方法。这么多方法,有些比较简单,有些比较复杂。作为最简单的字符设备,我们只需要实现一些常用的接口,就能实现一般用用的设备操作
驱动的注册和销毁接口
设备的打开和关闭接口
设备的读和写接口
设备的命令控制接口
下面是一个hello world模板
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <asm/jzsoc.h>
#include <linux/device.h>
#define TEST1_DEBUG
//#undef TEST_DEBUG
#if defined(TEST1_DEBUG)
#define TEST_DEBUG(fmt, ...) \
printk(KERN_ALERT fmt, ##__VA_ARGS__)
#else
#define TEST_DEBUG(fmt, ...)
#endif
static struct cdev *freg_cdev; //An instance of a character device
static dev_t ndev; //The node of the device
static char freg_var[256] = "hello jin youzhi";
//static int freg_major = 250;
static int freg_major = 0;
static int freg_minor = 0;
static int howmany=1;
static char* whoem="hello";
module_param(howmany,int,S_IRUGO);
module_param(whoem,charp,S_IRUGO);
static int freg_open(struct inode *inode, struct file *filp)
{
TEST_DEBUG("device opened\n");
return 0;
}
static int freg_close(struct inode *inode, struct file *filp)
{
TEST_DEBUG("device closed\n");
return 0;
}
static ssize_t freg_read(struct file *filp, char __user *buf, size_t sz, loff_t *off)
{
printk("read :%d,pos:%d\n",sz,*off);
//Copy from kernel space to user space
if(sz>sizeof(freg_var)-1-*off)
sz=sizeof(freg_var)-1-*off;
if (copy_to_user(buf, &freg_var, sz)) {
return - EFAULT;
}
*off+=sz;
return sz;
}
static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
{
printk("write:%d,pos:%d!\n",count,*f_pos);
//Copy from user space to kernel space
if(count>sizeof(freg_var)-1-*f_pos)
count=sizeof(freg_var)-1-*f_pos;
if (copy_from_user(&freg_var, buf, count)) {
return -EFAULT;
}
return count;
}
static int freg_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case 0:
TEST_DEBUG("device cmd 0\n");
return 0;
default:
TEST_DEBUG("device invalid command\n");
return -1;
}
}
// file_operations is a very important data struct in character type device
struct file_operations freg_ops = {
.owner = THIS_MODULE,
.open= freg_open,
.release = freg_close,
.read = freg_read,
.write = freg_write,
.ioctl=freg_ioctl
};
// initialization function of the module
struct class *freg_class;
static int __init freg_init(void)
{
int err;
printk(KERN_ALERT"Initializing freg device.\n");
// -----------------------register device node number--------------------------
if(freg_major)
{
ndev=MKDEV(freg_major,0);
err=register_chrdev_region(ndev,1,"testc");
}
else
err = alloc_chrdev_region(&ndev, 0, 1, "testc"); //申请字符设备编号
if(err < 0)
return err;
freg_major = MAJOR(ndev);
freg_minor = MINOR(ndev);
printk("freg_init():major=%d, minor=%d\n", MAJOR(ndev), MINOR(ndev));
// ------------------------------------------------------
// ----------------------register cdev structure--------------------------
freg_cdev = cdev_alloc(); //分配字符设备实例
//初始化设备实例
//主要设置owner和ops字段
cdev_init(freg_cdev, &freg_ops);
freg_cdev->owner = THIS_MODULE; //设置所有者字段
//freg_cdev->ops = &freg_ops; //
err = cdev_add(freg_cdev, ndev, 1);//将设备添加到字符设备列表中,能在/proc/devices中查到
if(err < 0)
return err;
// -------------------------------------------------------
//==================register device node==================================
freg_class = class_create(THIS_MODULE, "testc_class"); //建立设备类,在/sys/class能找到freg_class目录
if(IS_ERR(freg_class))
{
return -1;
}
device_create(freg_class, NULL, ndev, "testc", "testc"); //建立设备节点,在/dev/目录下能找到设备
TEST_DEBUG("test: all done\n");
//===================================================================
printk(KERN_ALERT"Succedded to initialize freg device.\n");
return 0;
}
static void __exit freg_exit(void)
{
ndev = MKDEV(freg_major, freg_minor);
printk(KERN_ALERT"Desctroy freg device class\n");
device_destroy(freg_class, ndev);
class_destroy(freg_class);
printk(KERN_ALERT"Destroy freg device.\n");
cdev_del(freg_cdev); //unregister the char_dev from the system
unregister_chrdev_region(ndev, 1);//free the device node number
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Fake Register Driver");
module_init(freg_init);
module_exit(freg_exit);
其他一些接口例如poll也很有用,可以作为读写的补充。
分析:
1、初始化设备号
初始化设备号一般使用 alloc_chrdev_region 、 register_chrdev_region 或者 register_chrdev
参考http://blog.csdn.net/tommy_wxie/article/details/7195471
内核中所有已分配的字符设备编号都记录在一个名为 chrdevs 散列表里。该散列表中的每一个元素是一个 char_device_struct 结构
register_chrdev成功后,设备名称会出现在/proc/devices文件中,此后可以使用mknod将设备映射成文件节点
2、初始化cdev结构体
静态初始化
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
或者动态初始化
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops=&fops;
my_cdev->owner=THIS_MODULE
3、注册字符设备
cdev_add(&cdev, devno, 1)定义如下:返回负值表示错误
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
内核中所有的字符设备都会记录在一个 kobj_map结构的 cdev_map变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map()函数就是用来把字符设备编号和 cdev结构变量一起保存到 cdev_map这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup()函数,根据设备编号就可以找到cdev结构变量,从而取出其中的ops字段。
4、生成设备节点
struct class *myclass ;class_create(THIS_MODULE, “my_device_driver”);device_create(myclass, NULL, MKDEV(major_num, minor_num), NULL, “my_device”);
class_create在/sys/class中创建一个类目录,device_create在/dev目录下建立一个设备节点。不过设备节点的建立实际上是udev在应用层实现的。
参考:http://www.embedu.org/Column/Column476.htm