Linux字符设备驱动总结

Linux2.6内核中使用cdev结构体描述字符设备:

struct cdev
{
    struct kobject kobj;          // 内嵌的kobject对象,描述设备引用计数
    struct module *owner;         // 所属模块,一般赋值为THIS_MODULE
    struct file_operations *ops;  // 文件操作结构体
    struct list_head list;
    dev_t dev;                    // 设备号
    unsigned int count;
};

cdev结构体的dev_t定义了设备号,32位。高12位为主设备号,低20位为次设备号。

下列宏可从dev_t获得主、次设备号:

MAJOR(dev_t dev)
MINOR(dev_t dev)

通过主、次设备号生成dev_t:

MKDEV(int major, int minor)

 

一组函数操作cdev结构体:

void cdev_init(struct cdev *, struct file_operations *); // 初始化cdev成员,建立cdev和file_operations之间的连接
struct cdev *cdev_alloc(void);  // 动态申请一个cdev内存
void cdev_put(struct cdev *p);  // 减少模块的引用计数,释放结构体空间
int cdev_add(struct cdev *, dev_t, unsigned);  // 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中
void cdev_del(struct cdev *);  // 向系统删除一个dev,完成字符设备的注销,常用于模块卸载函数中

调用cdev_add()前,先调用register_chrdev_region()或alloc_chrdev_region():

int register_chrdev_region(dev_t from, unsigned count, const char *name);  // 用于已知起始设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name); // 设备号未知,向系统动态申请未占用设备号放入dev

cdev_del()后,unregister_chrdev_region()应被调用以释放设备号:

void unregister_chrdev_region(dev_t from, unsigned count);

file_operations结构:

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 (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
10      int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
11      long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
12      long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
13      int (*mmap) (struct file *, struct vm_area_struct *);
14      int (*open) (struct inode *, struct file *);
15      int (*flush) (struct file *, fl_owner_t id);
16      int (*release) (struct inode *, struct file *);
17      int (*fsync) (struct file *, int datasync);
18      int (*aio_fsync) (struct kiocb *, int datasync);
19      int (*fasync) (int, struct file *, int);
20      int (*lock) (struct file *, int, struct file_lock *);
21      ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
22      unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
23      int (*check_flags)(int);
24      int (*flock) (struct file *, int, struct file_lock *);
25      ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
26      ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
27      int (*setlease)(struct file *, long, struct file_lock **);
28  };

Linux字符设备驱动函数模板:

// 设备结构体
struct xxx_dev_t
{
    struct cdev cdev;
    ...
}
 
// 设备驱动模块加载函数
static int __init xxx_init(void)
{
    ...
    cdev_init(&xxx_cdev.cdev, &xxx_fops);  // 初始化dev
    xxx_dev.cdev.owner = THIS_MODULE;
    //
    if(xxx_major)
    {
        register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
    }
    else
    {
        alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
    }
   
    ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1);  // 注册设备
    ...
}
 
// 设备驱动模块卸载函数
static void __exit xxx_exit(void)
{
    unregister_chrdev_region(xxx_dev_no, 1);  // 释放占用的设备号
    cdev_del(&xxx_dev.cdev);  // 注销设备
}
 
// 读设备
// filp是文件结构指针,buf是用户空间的内存地址,该地址空间不能直接读写,count是要读的字节数,f_pos是读的位置相对于文件开头的偏移
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
    ...
    copy_to_user(buf, ..., ...);
    ...
}
 
// 写设备
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
    ...
    copy_from_user(..., buf, ...);
    ...
}
 
// ioctl函数
int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
    ...
    switch(cmd)
    {
        case xxx_CMD1:
            ...
            break;
        case xxx_CMD2:
            ...
            break;
        default:
            // 不支持的命令
            return - ENOTTY;
    }
    return 0;
}
 
// 字符设备文件操作结构体,在模块加载函数的cdev_init(&xxx_dev.cdev, &xxx_fops)的语句中被建立与cdev的连接
struct file_operations xxx_fops =
{
    .owner = THIS_MODULE,
    .read = xxx_read,
    .write = xxx_write,
    .ioctl = xxx_ioctl,
    ...
};

其他说明:

    内核空间和用户空间的内存不能直接访问,需借助copy_from_user()和copy_to_user():

unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);
unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);

    上述函数返回不能被复制的字节数。若完全复制成功,返回0.

    若要复制的内存是简单类型,如char、int、long等,则可以使用简单的put_user()和get_user():

int val;  // 内核空间整形变量
...
get_user(val, (int *)arg);  // 用户空间到内核空间,arg是用户空间的地址
...
put_user(val, (int *)arg);  // 内核空间到用户空间,arg是用户空间的地址

读和写函数中的__user是一个宏,表明其后的指针指向用户空间:

#ifdef __CHECKER__
#define __user    __attribute__((noderef, address_space(1)))
#else
#define __user
#endif
posted @ 2010-10-14 21:56  yangzd  阅读(1357)  评论(0编辑  收藏  举报