Linux内核API cdev_init

函数cdev_init()用于初始化一个静态分配的cdev结构体变量,函数cdev_init会自动初始化cdev->ops对象,将函数的第二个输入参数赋值给cdev->ops对象,不会初始化cdev->owner对象,因此在经过函数cdev_alloc()和函数cdev_init()处理之后的cdev结构体变量,在应用程序中只需要给cdev->owner对象赋值,此结构变量就可以被插入Linux内核系统了,作为一个可用的字符设备使用。

cdev_init文件包含

#include <linux/cdev.h>

cdev_init函数定义

在内核源码中的位置:linux-3.19.3/fs/char_dev.c

函数定义格式:

void cdev_init(struct cdev *, const struct file_operations *)

cdev_init输入参数说明

函数cdev_init()输入两个参数,第一个参数表示一个字符设备,在函数中即将被初始化,此结构体在函数cdev_alloc()分析文档中已说明,详细信息请读者参考极客笔记函数cdev_alloc()分析文档的返回参数说明部分。

第二个输入参数是struct file_operations结构体类型的指针,通过这个结构体中提供的函数完成对设备的操作,其定义见文件linux-3.19.3/include/linux/fs.h,如下所示:

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);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    void (*mremap)(struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, loff_t, loff_t, 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 (*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);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t,unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t,unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **, void **);
    long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
    void (*show_fdinfo)(struct seq_file *m, struct file *f);
};

对于一个设备驱动并不需要完成所有函数的映射,可以有选择地进行使用,下面对最常用的几项进行说明:

struct module *owner;

这个域是用来设置指向“拥有”该结构的模块指针,内核使用该指针维护模块的使用计数。

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

read域用来从设备中读数据,需要提供字符串指针。从设备中读取数据时,成功返回所读取的字节数,read等于NULL时,将导致调用失败,并返回-EINVAL。

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

write域用来向字符设备写数据,需要提供所写指针内容。当向设备写入数据时,成功返回实际写入的字节数,write等于NULL时,将导致调用失败,并返回-EINVAL。

int (*open) (struct inode *, struct file *);

open域用来打开设备,并初始化设备,准备进行操作。如果该方法没有实现,系统调用open也会是成功的,但驱动程序得不到任何打开设备的通知。在实现中,可以给出一定的提示信息。

int (*release) (struct inode *, struct file *);

release域用来关闭设备,释放设备资源。当且仅当结构struct file释放时被调用,用来关闭一个文件或设备。

cdev_init返回参数说明

  • 函数cdev_init()的返回值是void类型的变量,即不返回任何值。

cdev_init实例解析

编写测试文件:cdev_init.c

头文件引用、全局变量定义及相关函数声明:

/*头文件引用*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>

MODULE_LICENSE("GPL");
struct cdev *mem_cdev;            //字符设备对象指针
static int mem_open(struct inode *ind, struct file *filp);     //设备打开函数声明
static int mem_release(struct inode *ind, struct file *filp); //设备关闭函数声明

/*设备读函数声明*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *fpos);

/*设备写函数声明*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size,loff_t *fpos);
/*定义设备驱动文件结构体*/
struct file_operations mem_fops =
{
    .open = mem_open,              //设备打开函数
    .release = mem_release,        //设备打开函数
    .read = mem_read,              //设备读函数
    .write = mem_write,            //设备写函数
};

模块加载函数定义:

static int __init cdev_init_init (void)
{
    printk("into the cdev_init_init\n");
    mem_cdev = cdev_alloc();    //动态分配一个新的字符设备对象
    if (mem_cdev == NULL)      //检查分配结果
    {
        printk("cdev_alloc failed! \n");
        return -1;
    }

    /*显示字符设备内存空间地址*/
    printk("cdev_alloc success! addr = 0x%x\n", (unsigned int)mem_cdev); if(mem_cdev->ops==NULL)                                 //函数调用之前判断字段ops是否被初始化
    {
        printk("the ops the mem_cdev has not been initialized\n");
    }
    else
    {
        printk("the ops the mem_cdev has been initialized\n");
    }
    cdev_init(mem_cdev, &mem_fops);       //初始化字符设备对象
    if(mem_cdev->ops==NULL)               //函数调用之后判断字段ops是否被初始化
    {
        printk("the ops the mem_cdev has not been initialized\n");
    }
    else
    {
        printk("the ops the mem_cdev has been initialized\n");
    }
    printk("out the cdev_init_init\n");
    return 0;
}

模块退出函数定义:

static void __exit cdev_init_exit (void)
{
    printk("into cdev_init_exit\n");
    if (mem_cdev ! = NULL)
        kfree(mem_cdev);                   //释放字符设备内存空间
    printk("kfree mem_cdev OK! \n");
    printk("out cdev_init_exit\n");
}

相关函数定义:

/*设备打开函数定义*/
int mem_open(struct inode *ind, struct file *filp)
{
    printk("open vmalloc space\n");
    try_module_get(THIS_MODULE);         //模块引用自加
    printk("open vmalloc space success\n");
    return 0;
}

/*设备读函数定义,在此没有实际意义,只是为了初始化mem_fops变量*/
ssize_t mem_read(struct file *filp, char *buf, size_t size, loff_t *lofp)
{
    printk("copy data to the user space\n");
    return 0;
}

/*设备写函数定义,在此没有实际意义,只是为了初始化mem_fops变量*/
ssize_t mem_write(struct file *filp, const char *buf, size_t size, loff_t *lofp)
{
    printk("read data from the user space\n");
    return 0;
}
/*设备关闭函数定义*/
int mem_release(struct inode *ind, struct file *filp)
{
    printk("close vmalloc space\n");
    module_put(THIS_MODULE);              //模块引用自减
    printk("close vmalloc space success\n");
    return 0;
}

模块加载、退出函数调用:

module_init(cdev_init_init);              //模块加载函数调用
module_exit(cdev_init_exit);              //模块卸载函数调用

实例运行结果及分析

首先编译模块,执行命令insmod cdev_init.ko插入内核模块,然后输入命令dmesg -c查看模块插入结果,会出现如图所示的结果。

Linux内核API cdev_init

结果分析:

由图中可以看出函数cdev_init()调用之前字符设备的字段ops没有被初始化,函数调用之后字段ops被初始化了。

赞(0)
未经允许不得转载:极客笔记 » Linux内核API cdev_init
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址