Linux 字符设备驱动(二)---字符设备驱动开发流程及主要函数定义

参考资料:

      《Linux驱动开发入门与实战》,概念及源码主要参考《Linux驱动开发入门与实战》,务求准确。同时衷心感谢其他网友的分享。大部分内容都是手敲的,错漏之处望指正,谢谢!

 linux设备驱动之字符设备驱动
 https://www.linuxprobe.com/linux-device-driver.html

 Linux 字符设备驱动结构(一)—— cdev 结构体、设备号相关知识解析
 https://blog.csdn.net/zqixiao_09/article/details/50839042

前言


    不积硅步无以至千里,不积小流无以成江河。

正文

设备号:
    一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。
    linux内核中,设备号用dev_t来描述,2.6.28中定义如下:
  typedef u_long dev_t;
  在32位机中是4个字节,高12位表示主设备号,低20位表示次设备号。

    查看主设备号:  cat /proc/devices
    查看当前设备的主次设备号: ls -l /dev

字符驱动开发思维导图:

1、构建设备号dev_t

    内核为我们提供了几个方便操作的宏实现dev_t:
1.1 通过major和minor构建设备号
    MKDEV(int major,int minor);
   注:这只是构建设备号。并未注册,需要调用register_chrdev_region 静态申请;

1.2 从设备号中提取major和minor
    MAJOR(dev_t dev);                              
    MINOR(dev_t dev);

2、分配设备号
2.1 静态申分配设备号
    函数原型:int register_chrdev_region(dev_t from, unsigned count, const char *name);
    头文件:#include <linux/cdev.h>
    函数功能:申请使用从from开始的count个设备号(主设备号不变,次设备号增加);
    参数:
    from:要分配的设备号范围的起始值。一般只提供from的主设备号,from的次设备号通常被设置为0;
    count:需要申请的连续设备号的个数;
    name:和该范围编号关联的设备名称,该名称不能超过64字节;
    返回值:成功时返回0,失败时返回一个负的错误码,并且不能为字符设备分配设备号。
    源代码如下:

int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
    struct char_device_struct *cd;
    dev_t to = from + count;
    dev_t n, next;
 
    for (n = from; n < to; n = next) {
        next = MKDEV(MAJOR(n)+1, 0);
        if (next > to)
            next = to;
        cd = __register_chrdev_region(MAJOR(n), MINOR(n),
                   next - n, name);
        if (IS_ERR(cd))
            goto fail;
    }
    return 0;
fail:
    to = n;
    for (n = from; n < to; n = next) {
        next = MKDEV(MAJOR(n)+1, 0);
        kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
    }
    return PTR_ERR(cd);
}

2.2 动态分配设备号
    函数原型:int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
    头文件:#include <linux/cdev.h>
    函数功能:申请使用从from开始的count 个设备号(主设备号不变,次设备号增加);
    参数:
    dev:dev作为输出参数,在函数成功返回后将保存已经分配的设备号。函数有可能申请一段连续的设备号,这是dev返回的第  一个设备号;
    baseminor:要申请的第一个次设备号;
    count:次设备的个数;
    name:和该范围编号关联的设备名称,该名称不能超过64字节;
    返回值:成功返回0,失败返回错误码;
    源代码如下:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name)
{
    struct char_device_struct *cd;
    cd = __register_chrdev_region(0, baseminor, count, name);
    if (IS_ERR(cd))
        return PTR_ERR(cd);
    *dev = MKDEV(cd->major, cd->baseminor);
    return 0;
}

2.3 静态分配设备号与动态分配设备号的区别
    1)可以看到二者都是调用了__register_chrdev_region函数
    2)register_chrdev_region直接将Major 注册进入,而 alloc_chrdev_region从Major = 0 开始,逐个查找设备号,直到找到一个闲置的设备号,并将其注册进去;

2.4 __register_chrdev_region源码

static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
               int minorct, const char *name)
{
    struct char_device_struct *cd, **cp;
    int ret = 0;
    int i;
 
    cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
    if (cd == NULL)
        return ERR_PTR(-ENOMEM);
 
    mutex_lock(&chrdevs_lock);
 
    /* temporary */
    if (major == 0) {
        for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
            if (chrdevs[i] == NULL)
                break;
        }
 
        if (i == 0) {
            ret = -EBUSY;
            goto out;
        }
        major = i;
        ret = major;
    }
 
    cd->major = major;
    cd->baseminor = baseminor;
    cd->minorct = minorct;
    strlcpy(cd->name, name, sizeof(cd->name));
 
    i = major_to_index(major);
 
    for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
        if ((*cp)->major > major ||
            ((*cp)->major == major &&
             (((*cp)->baseminor >= baseminor) ||
              ((*cp)->baseminor + (*cp)->minorct > baseminor))))
            break;
 
    /* Check for overlapping minor ranges.  */
    if (*cp && (*cp)->major == major) {
        int old_min = (*cp)->baseminor;
        int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
        int new_min = baseminor;
        int new_max = baseminor + minorct - 1;
 
        /* New driver overlaps from the left.  */
        if (new_max >= old_min && new_max <= old_max) {
            ret = -EBUSY;
            goto out;
        }
 
        /* New driver overlaps from the right.  */
        if (new_min <= old_max && new_min >= old_min) {
            ret = -EBUSY;
            goto out;
        }
    }
 
    cd->next = *cp;
    *cp = cd;
    mutex_unlock(&chrdevs_lock);
    return cd;
out:
    mutex_unlock(&chrdevs_lock);
    kfree(cd);
    return ERR_PTR(ret);
}

2.5 register_chrdev_region应用:                                             
    devno = MKDEV(major,minor);
    ret = register_chrdev_region(devno, 1, "xxx_dev"); 
    cdev_init(&xxx_cdev, &xxx_fops);
    ret = cdev_add(&xxx_cdev, devno, 1);

2.6 alloc_chrdev_region应用:      
    alloc_chrdev_region(&devno, minor, 1, "xxx_dev");
    major = MAJOR(devno);
    cdev_init(&xxx_cdev,&xxx_fops);
    ret = cdev_add(&xxx_cdev, devno, 1);


2.5 申请设备号举例

    if(major)
    {
        ret = register_chrdev_region(devno, 1, "xxx_dev");
        if(ret < 0)
        {
            printk("[%s] %s %d : register_chrdev_region failed!\n", __FILE__, __func__, __LINE__);
            goto err_register_chrdev_region;
        }
    }
    else
    {
        ret = alloc_chrdev_region(devno, 0, 1, "xxx_dev");
        if(ret < 0)
        {
            printk("[%s] %s %d : alloc_chrdev_region failed!\n", __FILE__, __func__, __LINE__);
            goto err_alloc_chrdev_region;
        }
    }

2.6 register_chrdev函数
    除了前面两个函数,还有一个register_chrdev函数;这个函数的应用非常简单,只要一句就可以搞定前面函数所做之事;在linux内核2.6版本以前,注册一个字符设备的的经典方法是使用register_chrdev,相应的从设备中注销字符设备的方法是unregister_chrdev,而在2.6版本以后,字符设备函数的注册与注销使用的是如下一套方法:
#include<linux/cdev.h> 
struct cdev *cdev_alloc(void);//分配一个独立的cdev结构 
void cdev_init(struct cdev *dev,struct file_operations *fops);//初始化cdev结构 
int cdev_add(struct cdev *dev,dev_t num,unsigned int count); 
void cdev_del(struct cdev *dev);//移除一个字符设备

    函数原型:int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
    头文件:#include <linux/cdev.h>
    函数功能:为字符设备注册一个主号码;
    参数:
    major:用于动态分配的主要设备号或0;如果major == 0,这个函数将动态分配一个主设备号并返回它的号码,即第一个参数0则为系统自动分配设备号,并返回;
    name:这一系列设备的名称;
    fops:与此设备相关联的文件操作;
    返回值:成功返回0,失败返回错误码;
    应用:
    1)注册:
    第一个参数为0时;
    int major = 250;
    devno = MKDEV(major,minor);
    major = register_chrdev(0, "xxx_dev", &xxx_fops);
    此时相当于静态注册了设备号;
    第一个参数为major时;
    register_chrdev(major, "xxx_dev", &xxx_fops);
    此时相当于动态注册了设备号;
    2)注销:unregister_chrdev(major, "xxx_dev");
    3)使用方法参考这段代码:

    static int major = 250;
    static int minor=0;
    devno = MKDEV(major,minor);
    ret = register_chrdev(major,"hello",&hello_ops);
 
    cls = class_create(THIS_MODULE, "myclass");
    if(IS_ERR(cls))
    {
        unregister_chrdev(major,"hello");
        return -EBUSY;
    }
    test_device = device_create(cls,NULL,devno,NULL,"hello");//mknod /dev/hello
    if(IS_ERR(test_device))
    {
        class_destroy(cls);
        unregister_chrdev(major,"hello");
        return -EBUSY;
    }   

     代码来源:https://blog.csdn.net/zqixiao_09/article/details/50849735

    register_chrdev源代码如下:

static inline int register_chrdev(unsigned int major, const char *name,
                  const struct file_operations *fops)
{
    return __register_chrdev(major, 0, 256, name, fops);
}

调用了 __register_chrdev(major, 0, 256, name, fops) 函数,

int __register_chrdev(unsigned int major, unsigned int baseminor,
              unsigned int count, const char *name,
              const struct file_operations *fops)
{
    struct char_device_struct *cd;
    struct cdev *cdev;
    int err = -ENOMEM;
 
    cd = __register_chrdev_region(major, baseminor, count, name);
    if (IS_ERR(cd))
        return PTR_ERR(cd);
 
    cdev = cdev_alloc();
    if (!cdev)
        goto out2;
 
    cdev->owner = fops->owner;
    cdev->ops = fops;
    kobject_set_name(&cdev->kobj, "%s", name);
 
    err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
    if (err)
        goto out;
 
    cd->cdev = cdev;
 
    return major ? 0 : cd->major;
out:
    kobject_put(&cdev->kobj);
out2:
    kfree(__unregister_chrdev_region(cd->major, baseminor, count));
    return err;
}

     可以看到这个函数不只帮我们注册了设备号,还帮我们做了cdev的初始化(cdev_init)以及cdev的注册(cdev_add);

2.7 注销设备号
    void unregister_chrdev_region(dev_t from, unsigned count);
    int unregister_chrdev(unsigned int major, const char *name);

3、分配cdev
3.1 静态分配cdev

    struct cdev xxx_cdev;
    cdev_init(&xxx_cdev, &xxx_fops);
    xxx_cdev.owner = THIS_MODULE;

3.2 动态分配cdev
    法一:
    struct cdev *xxx_pcdev = cdev_alloc();
    xxx_pcdev->owner = THIS_MODULE;
    xxx_pcdev->ops = &xxx_fops;
    法二:
    struct cdev *xxx_pcdev = kmalloc(sizeof(struct cdev), GFP_KERNEL);
    cdev_init(&xxx_cdev, &xxx_fops);
    xxx_cdev.owner = THIS_MODULE;
    补充:
    kmalloc函数原型和头文件:
    #include <linux/slab.h> 
    void *kmalloc(size_t size, int flags);


3.3 静态分配cdev与动态分配cdev的区别
     1)struct cdev xxx_cdev; xxx_cdev一般是定义为全局变量,在数据段中分配内存;cdev_alloc和kmalloc分配的内存处于堆中;如果对struct cdev做了封装,且封装后的结构体较大,那么会在数据段中占很大内存;
    2)cdev_alloc函数更简洁,但不适合struct cdev xxx_cdev封装后的自定义cdev结构体。见cdev_alloc函数定义。

4、初始化cdev
    在1.1和1.2分配cdev内存中已经用到了cdev_init()函数。此处是cdev_init()函数的定义;
    函数原型:void cdev_init(struct cdev *, const struct file_operations *);
    头文件:#include <linux/cdev.h>
   函数功能:对struct cdev结构体做初始化,最重要的就是建立cdev和file_operations之间的连接;
   参数:
   struct cdev *:已定义的cdev结构体地址;
   const struct file_operations *:已定义的file_operations结构体地址;;
   返回值:
   源代码:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
    memset(cdev, 0, sizeof *cdev);
    INIT_LIST_HEAD(&cdev->list);
    kobject_init(&cdev->kobj, &ktype_cdev_default);
    cdev->ops = fops;
}

    函数解析:
    (1) 将整个结构体清零;
    (2) 初始化list成员使其指向自身;
    (3) 初始化kobj成员;
    (4) 初始化ops成员;

5、注册cdev
    函数原型:int cdev_add(struct cdev *p, dev_t dev, unsigned count);
    头文件:#include <linux/cdev.h>
    函数功能:该函数向内核注册一个struct cdev结构,即正式通知内核由struct cdev *p代表的字符设备已经可以使用了;
参数:
     p:已定义的cdev结构体地址;
    dev:第一个设备号;
    返回值:成功时返回0,失败时返回一个负的错误码。
    源代码:

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);
}

    函数解析:
    1)关于 kobj_map():内核中所有都字符设备都会记录在一个 kobj_map结构的cdev_map变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map()函数就是用来把字符设备编号和 cdev结构变量一起保存到 cdev_map这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup()函数,根据设备编号就可以找到cdev结构变量,从而取出其中的ops字段。
    2)cdev_map封装了系统中的所有的cdev结构和对应的设备号;cdev_map是一个kobj_map类型的结构体。这个结构体中包含了probe指针类型、大小为255的数组;因此最多只能注册255个字符设备。
备注:查询kobj_map结构体。


6、向内核注销一个struct cdev结构
    函数原型:void cdev_del(struct cdev *p);
    头文件:#include <linux/char_dev.h>
    函数功能:该函数向内核注销一个struct cdev结构,即正式通知内核由struct cdev *p代表的字符设备已经不可以使用了,同时释放cdev 占用的内存;
    参数:已定义的cdev结构体地址;
    返回值:无

void cdev_del(struct cdev *p)
{
    cdev_unmap(p->dev, p->count);
    kobject_put(&p->kobj);
}

    其中 cdev_unmap()调用kobj_unmap()来释放cdev_map散列表中的对象。kobject_put()释放cdev结构本身。


7、动态分配cdev函数cdev_alloc()
    函数原型:struct cdev *cdev_alloc(void);
    头文件:#include <linux/char_dev.h>
    函数功能:该函数主要分配一个struct cdev结构,动态申请一个cdev内存,并做了cdev_init中所做的前面3步初始化工作(第四步初始化工作需要在调用cdev_alloc后,显式的做初始化即: .ops=xxx_ops);
    参数:
    返回值:成功返回struct cdev类型变量的首地址,失败返回NULL。
    源代码如下:

struct cdev *cdev_alloc(void)
{
    struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
    if (p) {
        INIT_LIST_HEAD(&p->list);
        kobject_init(&p->kobj, &ktype_cdev_dynamic);
    }
    return p;
}

   函数解析:
    在上面的两个初始化的函数中,我们没有看到关于owner成员、dev成员、count成员的初始化;其实,owner成员的存在体现了驱动程序与内核模块间的亲密关系,struct module是内核对于一个模块的抽象,该成员在字符设备中可以体现该设备隶属于哪个模块,在驱动程序的编写中一般由用户显式的初始化 .owner = THIS_MODULE, 该成员可以防止设备的方法正在被使用时,设备所在模块被卸载。而dev成员和count成员则在cdev_add中才会赋上有效的值。

8、创建设备文件

8.1.1 使用mknod手工创建
    利用cat /proc/devices查看申请到的设备名,设备号。
    mknod filename type major minor

8.1.2 自动创建设备节点
    利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。在驱动初始化代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。
    在驱动用加入对udev 的支持主要做的就是:在驱动初始化的代码里调用class_create(...)为该设备创建一个class,再为每个设备调用device_create(...)创建对应的设备。
    内核中定义的struct class结构体,一个struct class结构体类型变量对应一个类,内核同时提供了class_create()函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用 device_create()函数来在/dev目录下创建相应的设备节点。
    这样,加载模块的时候,用户空间中的udev会自动响应 device_create()函数,去/sys下寻找对应的类从而创建设备节点。8.1.3 自动创建设备节点的步骤
    1)busybox配置,使应用层支持udev/mdev;
    2)调用class_create()函数创建设备类;可在/sys/class/目录下查看创建的设备类;
    3)调用device_create()函数创建设备节点;这一步的作用是导出设备信息到用户空间;可在/sys/class/类/设备名/ 目录下看到设备号等信息。
    4)在模块卸载时,先调用device_destroy()移除设备,再调用class_destroy()移除设备类;

    //加载:
    xxx_cls = class_create(THIS_MODULE, "xxx_class");
    if(IS_ERR(xxx_cls))
    {
        ret = PTR_ERR(xxx_cls);
        goto err_class_create;
    }
    
    xxx_device = device_create(xxx_cls, NULL, devno, "xxx_dev", 0);
    if(IS_ERR(xxx_device))
    {
        ret = PTR_ERR(xxx_device);
        goto err_device_create;
    }
    
err_device_create:
    device_destroy(xxx_device, devno);

err_class_create:
    class_destroy(xxx_cls);
    
   // 卸载:
    device_destroy(xxx_device, devno);
    class_destroy(xxx_cls);

    
8.2 struct class 结构体
    struct class定义在头文件include/linux/device.h中,结构体定义如下:

    struct class{
        const char        *name;
        struct module     *owner;

        nbsp;struct kset         subsys;
        struct list_head         devices;
        struct list_head         interfaces;
        struct kset              class_dirs;
        struct semaphore sem;    /* locks children, devices, interfaces */
        struct class_attribute   *class_attrs;
        struct device_attribute      *dev_attrs;

        int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);

        void (*class_release)(struct class *class);
        void (*dev_release)(struct device *dev);

        int (*suspend)(struct device *dev, pm_message_t state);
        int (*resume)(struct device *dev);

    };

8.3 创建设备类class_create()
    函数原型:struct class *class_create(struct module *owner, const char *name);
    头文件:#include <linux/device.h>
    函数功能:创建一个设备类;
    参数:
    owner:通常为THIS_MODULE;
    name:要创建的设备类的名称;
    返回值:可以定义一个struct class的指针变量cls接受返回值,然后通过IS_ERR(cls)判断是否失败,如果成功,这个宏会返回0,失败返回非0值。可以使用PTR_ERR(cls)来获得失败返回的错误码。
源代码如下:
    class_create()在/drivers/base/class.c中实现: 

     /**
    * class_create - create a struct class structure
    * @owner: pointer to the module that is to "own" this struct class
    * @name: pointer to a string for the name of this class.
    *
    * This is used to create a struct class pointer that can then be used
    * in calls to device_create().
    *
    * Note, the pointer created here is to be destroyed when finished by
    * making a call to class_destroy().
    */
   struct class *class_create(struct module *owner, const char *name)
   {
        struct class *cls;
        int retval;
        cls = kzalloc(sizeof(*cls), GFP_KERNEL);
        if (!cls) {
           retval = -ENOMEM;
           goto error;
        }

        cls->name = name;
        cls->owner = owner;
        cls->class_release = class_create_release;

        retval = class_register(cls);
        if (retval)
           goto error;

        return cls;

        error:
            kfree(cls);
            return ERR_PTR(retval);
    }

8.4 创建设备节点device_create()
    函数原型:struct device *device_create(struct class *class, struct device *parent,
                        dev_t devt, const char *fmt, ...);
    头文件:#include <linux/device.h>
    函数功能:用于动态的建立逻辑设备,并对新的逻辑设备类进行相应初始化,将其与函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到linux内核系统的设备驱动程序模型中。函数能够自动在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建于逻辑类对应的设备文件
    参数:
    第一个参数指定所要创建的设备所从属的类;
    第二个参数是这个设备的父设备,如果没有就指定为NULL;
   第三个参数是设备号;
   第四个参数是逻辑设备的设备名,即在目录 /sys/devices/virtual创建的逻辑设备目录的目录名。
   第五个参数是从设备号;
   返回值:可以定义一个struct class的指针变量cls接受返回值,然后通过IS_ERR(cls)判断是否失败,如果成功,这个宏会返回0,失败返回非0值。可以使用PTR_ERR(cls)来获得失败返回的错误码。
    源代码如下:

    device_create(…)函数在/drivers/base/core.c中实现: 
    /**
     * device_create - creates a device and registers it with sysfs
     * @class: pointer to the struct class that this device should be registered to
     * @parent: pointer to the parent struct device of this new device, if any
     * @devt: the dev_t for the char device to be added
     * @fmt: string for the device's name
     *
     * This function can be used by char device classes. A struct device
     * will be created in sysfs, registered to the specified class.
     *
     * A "dev" file will be created, showing the dev_t for the device, if
     * the dev_t is not 0,0.
     * If a pointer to a parent struct device is passed in, the newly created
     * struct device will be a child of that device in sysfs.
     * The pointer to the struct device will be returned from the call.
     * Any further sysfs files that might be required can be created using this
     * pointer.
     *
     * Note: the struct class passed to this function must have previously
     * been created with a call to class_create().
     */
    struct device *device_create(struct class *class, struct device *parent,
                        dev_t devt, const char *fmt, ...)
    {
         va_list vargs;
         struct device *dev;

     va_start(vargs, fmt);
         dev = device_create_vargs(class, parent, devt, NULL, fmt, vargs);
         va_end(vargs);
         return dev;
    }


8.5 删除设备类class_destroy()
    函数原型:void class_destroy(struct class *cls);
    头文件:#include <linux/device.h>
    函数功能:用于在模块卸载时删除类;
    参数:
    第一个参数指定所要删除的设备类;
    返回值:无。
    源代码如下:

void class_destroy(struct class *cls)
{
    if ((cls == NULL) || (IS_ERR(cls)))
        return;
 
    class_unregister(cls);
}

8.6 移除设备device_destroy()
    函数原型:void device_destroy(struct class *dev, dev_t devt);
    头文件:#include <linux/device.h>
    函数功能:用于从linux内核系统设备驱动程序模型中移除一个设备,并删除/sys/devices/virtual目录下对应的设备目录及/dev/目录下对应的设备文件;
   参数:
   第一个参数指定所要创建的设备所从属的类;
   第二个参数是设备号;
   返回值:无。

9、file_operations操作函数集合中的函数实现
    struct file_operations在linux/fs.h这个文件里面被定义,以下操作函数在struct _file_operations结构体中定义。

9.1
/* 打开 */
int (*open) (struct inode * inode , struct file * filp );
    (inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构;但是filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息)
    尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.与open()函数对应的是release()函数。
    
9.2
/* 关闭 */
int (*release) (struct inode *, struct file *);
    release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数:
    void release(struct inode inode,struct file *file),release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
       
9.3
/* 从设备中同步读取数据  */
函数原型:ssize_t (*read) (struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
函数功能:从设备中同步读取数据;
参数:
filp:待读取信息的目标文件file结构体指针;
buf:对应放置信息的缓冲区(即用户空间内存地址);
count:要读取的信息长度;
f_pos:为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值;
返回值:成功实际读取的字节数,失败返回负值(-EINVAL);
    
9.4
/* 向设备发送数据 */
函数原型:ssize_t (*write)(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
函数功能:向设备发送数据;
参数:
filp:待操作的设备文件file结构体指针;
buf:要写入文件的信息缓冲区;
count:要写入信息的长度;
f_pos:为当前的偏移位置,这个值通常是用来判断写文件是否越界;
返回值:成功实际写入的字节数,失败返回负值(-EINVAL);
备注:对文件进行读的操作read和对文件进行写的操作write均为阻塞操作;
     
9.5
/* 执行设备I/O控制命令 */
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    (inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值, 和传递给 open 方法的相同参数.cmd 参数从用户那里不改变地传下来, 并且可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针.如果调用程序不传递第 3 个参数, 被驱动操作收到的 arg 值是无定义的.因为类型检查在这个额外参数上被关闭, 编译器不能警告你如果一个无效的参数被传递给 ioctl, 并且任何关联的错误将难以查找.)
    ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表.如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.
    (指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量;参数orig为对文件定位的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2))

    llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
    loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示;如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).


9.6 用户空间和内核空间数据相互拷贝
9.6.1 说明:

    在Linux操作系统中,用户空间和内核空间使相互独立的。也就是说内核空间式不能直接访问用户空间内存地址,同理用户空间也不能直接访问内核空间的地址。
    如果要实现将用户空间的数据拷贝到内核空间或将内核空间的数据拷贝到用户空间,就必须借助内核给我们提供的接口来完成。

9.6.2 从用户空间拷贝数据到内核空间
    头文件:#include <asm/uaccess.h>
    函数原型:unsigned long copy_from_user(void *to, const void *from, unsigned long n);
    函数功能:从用户空间拷贝数据到内核空间;
    参数:
    to:内核空间地址;
    from:用户空间地址;
    n:数据大小;
    返回值:成功返回0,失败返回未拷贝的字节数;
    源代码:

copy_from_user(void *to, const void __user *from, unsigned long n)
{
    might_sleep();
    if (access_ok(VERIFY_READ, from, n))
        n = __copy_from_user(to, from, n);
    else
        memset(to, 0, n);
    return n;
}

9.6.3 从内核空间拷贝数据到用户空间
    头文件:#include <asm/uaccess.h>
    函数原型:unsigned long copy_to_user(void *to, const void *from, unsigned long n)
    函数功能:从内核空间拷贝数据到用户空间;
    参数:
    to:用户空间地址;
    from:内核空间地址;
    n:数据大小;
    返回值:成功返回0,失败返回未拷贝的字节数;
    备注:__user是一个空的宏,主要用来显示的告诉程序员它修饰的指针变量存放的是用户空间的地址;
    源代码:

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
{
    if (access_ok(VERIFY_WRITE, to, n))
        n = __copy_to_user(to, from, n);
    return n;
}

猜你喜欢

转载自blog.csdn.net/the_wan/article/details/108434863