创建设备节点问题

来源:互联网 发布:js获取自定义属性data 编辑:程序博客网 时间:2024/05/21 10:25

在原先的文章中将了怎样创建一个设备节点供读写使用,Linux用户空间与内核空间交互方法,现在回过头去看,发现当时很多代码写法都有问题,在此作为一个反面教材来讲一讲。


原先代码

static int sample_init(void)  {      /* 初始化 sample_dev 结构体 */    sample_dev = kzalloc(sizeof(struct sample), GFP_KERNEL);    if (!sample_dev)        return ERR_PTR(-ENOMEM);    /* 注册字符设备,主设备号设置为0表示由系统自动分配主设备号 */    sample_dev->sample_major = register_chrdev(0, "sample", &sample_fops);    /* 创建sample_class类 */    sample_class = class_create(THIS_MODULE, "sample_class");    /* 在sample_class类下创建sample_dev设备,这之后可以生成 /dev/sample_dev 的设备节点 */    sample_device = device_create(sample_class, NULL, MKDEV(sample_dev->sample_major, 0), NULL, "sample_dev");    init_waitqueue_head(&sample_dev->write_queue);    init_waitqueue_head(&sample_dev->read_queue);    sample_dev->completed_in_req = 1; // 一上来标记可以写    return 0;}static void sample_exit(void)  {    unregister_chrdev(sample_dev->sample_major, "sample");    device_unregister(sample_device);    class_destroy(sample_class);}

ERR_PTR用法错误

在这里如果为 sample_dev 申请内存失败,直接返回 –ENOMEM 即可,但是这里是返回 ERR_PTR(-ENOMEM)ERR_PTR 是将一个error number转换为void *的指针。因为此处是 sample_init() 返回值是int型的,所以不能用 ERR_PTR 进行转换。ERR_PTR 的定义如下:

static inline void *ERR_PTR(long error)  {    return (void *) error;  }

未对 register_chrdev() 返回值做判断

register_chrdev() 会调用 __register_chrdev() 函数,该函数原型如下:

/** * __register_chrdev() - create and register a cdev occupying a range of minors * @major: major device number or 0 for dynamic allocation * @baseminor: first of the requested range of minor numbers * @count: the number of minor numbers required * @name: name of this range of devices * @fops: file operations associated with this devices * * If @major == 0 this functions will dynamically allocate a major and return * its number. * * If @major > 0 this function will attempt to reserve a device with the given * major number and will return zero on success. * * Returns a -ve errno on failure. * * The name of this device has nothing to do with the name of the device in * /dev. It only helps to keep track of the different owners of devices. If * your module name has only one type of devices it's ok to use e.g. the name * of the module here. */int __register_chrdev(unsigned int major, unsigned int baseminor,              unsigned int count, const char *name,              const struct file_operations *fops)

之前碰到一个问题,热插拔设备每次创建节点供应用层读写使用,当拔插多次之后,调用申请字符设备的设备号总是失败,后面发现由于在拔掉的时候已申请的设备号没有unregister,导致原先申请的设备号没有释放掉,当申请的设备号到达Kernel指定的最大字符设备数后,后面要申请设备号总是会失败。
字符设备个数限制由宏 CHRDEV_MAJOR_HASH_SIZE 来决定。定义如下:

// fs/char_dev.c#define CHRDEV_MAJOR_HASH_SIZE 255static struct char_device_struct {    struct char_device_struct *next;    unsigned int major;    unsigned int baseminor;    int minorct;    char name[64];    struct cdev *cdev;      /* will die */} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

另外,如果申请设备号失败,必须释放之前申请到的资源,这里必须释放 sample_dev,否则,久而久之,有可能造成内存泄露。修改后的代码为:

/* 注册字符设备,主设备号设置为0表示由系统自动分配主设备号 */ret = register_chrdev(0, "sample", &sample_fops);if (ret < 0) {    kfree(sample_dev);    return ret;}ret = sample_dev->sample_major;

未对 device_create() 返回值做判断

如果device_create() 创建设备失败,那么必须先unregister前面申请的major,然后在kfree申请的内存。否则,如果没释放设备号,那么当达到CHRDEV_MAJOR_HASH_SIZE,其他驱动再次申请设备号肯定会失败。修改后如下:

/* 在sample_class类下创建sample_dev设备,这之后可以生成 /dev/sample_dev 的设备节点 */sample_device = device_create(sample_class, NULL, MKDEV(sample_dev->sample_major, 0), NULL, "sample_dev");if (IS_ERR(sample_device)) {ret = PTR_ERR(sample_device);class_destroy(sample_class);    unregister_chrdev(sample_dev->sample_major, "sample");    kfree(sample_dev);    return ret;}

IS_ERRERR_PTRPTR_ERR的定义在 include/linux/err.h 文件下。

#include <linux/compiler.h>  #include <asm/errno.h>  /*   * Kernel pointers have redundant information, so we can use a   * scheme where we can return either an error code or a dentry   * pointer with the same return value.   *   * This should be a per-architecture thing, to allow different   * error and pointer decisions.   */  #define IS_ERR_VALUE(x) unlikely((x) > (unsigned long)-1000L)  static inline void *ERR_PTR(long error)  {      return (void *) error;  }  static inline long PTR_ERR(const void *ptr)  {      return (long) ptr;  }  static inline long IS_ERR(const void *ptr)  {      return IS_ERR_VALUE((unsigned long)ptr);  }  #endif /* _LINUX_ERR_H */

释放资源顺序应与申请时相反

sample_init() 中,申请的资源顺序如下:
kazlloc -> register_chrdev -> class_create -> device_create

那么,在 sample_exit() 中,释放资源的顺序应该如下:
device_unregister -> class_destroy -> unregister_chrdev -> kfree

因此,最终的修改应为:

static int sample_init(void)  {      int ret;    /* 初始化 sample_dev 结构体 */    sample_dev = kzalloc(sizeof(struct sample), GFP_KERNEL);    if (!sample_dev)        return -ENOMEM;    /* 注册字符设备,主设备号设置为0表示由系统自动分配主设备号 */    ret = register_chrdev(0, "sample", &sample_fops);    if (ret < 0) {        kfree(sample_dev);        return ret;    }    ret = sample_dev->sample_major;    /* 创建sample_class类 */    sample_class = class_create(THIS_MODULE, "sample_class");    /* 在sample_class类下创建sample_dev设备,这之后可以生成 /dev/sample_dev 的设备节点 */    sample_device = device_create(sample_class, NULL, MKDEV(sample_dev->sample_major, 0), NULL, "sample_dev");    if (IS_ERR(sample_device)) {        ret = PTR_ERR(sample_device);        class_destroy(sample_class);        unregister_chrdev(sample_dev->sample_major, "sample");        kfree(sample_dev);        return ret;    }    init_waitqueue_head(&sample_dev->write_queue);    init_waitqueue_head(&sample_dev->read_queue);    sample_dev->completed_in_req = 1; // 一上来标记可以写    return 0;}static void sample_exit(void)  {    device_unregister(sample_device);    class_destroy(sample_class);    unregister_chrdev(sample_dev->sample_major, "sample");    kfree(sample_dev);}