在原先的文章中将了怎样创建一个设备节点供读写使用,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);
}
在这里如果为 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 255
static 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()
创建设备失败,那么必须先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_ERR
、ERR_PTR
、PTR_ERR
的定义在 include/linux/err.h
文件下。
#include
#include
/*
* 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;
}
sample_dev->sample_major = ret;
/* 创建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);
}