提问人:Arvin 提问时间:11/13/2023 最后编辑:Arvin 更新时间:11/16/2023 访问量:53
Linux char 设备创建失败
linux char device create failed
问:
我编写了一个演示设备驱动程序,如下所示:
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/slab.h>
#define CDEV_NAME "mycdev"
struct my_char_dev {
unsigned char *ptr;
struct semaphore sem;
struct cdev cdev;
};
struct my_char_dev *mycdev;
int my_char_open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "%s\n", __FUNCTION__);
return 0;
}
int my_char_release(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "%s\n", __FUNCTION__);
return 0;
}
ssize_t my_char_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk(KERN_INFO "%s\n", __FUNCTION__);
return 0;
}
ssize_t my_char_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
printk(KERN_INFO "%s\n", __FUNCTION__);
return 0;
}
struct file_operations my_char_fops = {
.owner = THIS_MODULE,
.open = my_char_open,
.release = my_char_release,
.read = my_char_read,
.write = my_char_write,
};
static int __init my_char_init(void)
{
mycdev = kmalloc(sizeof(struct my_char_dev), GFP_KERNEL);
if (!mycdev) {
printk(KERN_INFO "alloc space for device failed\n");
return -ENOMEM;
}
if (!alloc_chrdev_region(&mycdev->cdev.dev, 0, 1, CDEV_NAME))
printk(KERN_INFO "%s alloc region success\n", CDEV_NAME);
else {
printk(KERN_INFO "%s alloc region failed\n", CDEV_NAME);
return -ENOMEM;
}
cdev_init(&mycdev->cdev, &my_char_fops);
if (!cdev_add(&mycdev->cdev, mycdev->cdev.dev, 1))
printk(KERN_INFO "%s add device success\n", CDEV_NAME);
else {
printk(KERN_INFO "%s add device failed\n", CDEV_NAME);
unregister_chrdev_region(mycdev->cdev.dev, 1);
return -ENOMEM;
}
return 0;
}
void __exit my_char_exit(void)
{
cdev_del(&mycdev->cdev);
unregister_chrdev_region(mycdev->cdev.dev, 1);
kfree(mycdev);
printk(KERN_INFO "%s\n", __FUNCTION__);
}
module_init(my_char_init);
module_exit(my_char_exit);
MODULE_AUTHOR("xdd");
MODULE_LICENSE("GPL");
我在内核中插入模块,当我cat时,我可以找到一个,我也成功地在下做了一个设备节点,但是当我尝试操作我创建的模块时,我得到了.mycdev
/proc/devices
/dev/mycdev
mknod
/dev/mycdev
/dev/mycdev0: No such device or address
这就是 dmesg 的样子:
$ dmesg
[ 235.845810] test: loading out-of-tree module taints kernel.
[ 235.845836] test: module verification failed: signature and/or required key missing - tainting kernel
[ 235.846238] mycdev alloc region success
[ 235.846239] mycdev add device success
这是设备:proc/devices
$ cat /proc/devices | grep myc
239 mycdev
这就是我使用 mknid 的方式:
$ sudo mknod /dev/mycdev1 c 239 1
这是我创建的设备:
$ ls -l /dev/mycdev1
crw-r--r-- 1 root root 239, 1 Nov 14 00:57 /dev/mycdev1
我尝试用于操作我创建的文件:cat
$ cat /dev/mycdev1
cat: /dev/mycdev1: No such device or address
当我删除内核模块使用时,该模块删除成功,但是当我检查时,仍然有一个活着:rmmod
/proc/devices
mycdev
$ sudo rmmod mycdev_module
$ cat /proc/devices | grep myc
239 mycdev
我不知道为什么会这样。
我想知道为什么这个过程会失败。以及我应该怎么做才能纠正我的程序。
答:
此调用存储已分配字符设备区域的基本设备编号(值):dev_t
mycdev->cdev.dev
if (!alloc_chrdev_region(&mycdev->cdev.dev, 0, 1, CDEV_NAME))
之后,将通过以下调用设置为 0:mycdev->cdev.dev
cdev_init(&mycdev->cdev, &my_char_fops);
mycdev->cdev.dev
在以下调用中为 0:cdev_add()
if (!cdev_add(&mycdev->cdev, mycdev->cdev.dev, 1))
该调用将设置为第二个参数的值,该值已经是其当前值 0。mycdev->cdev.dev
即使主要设备编号 0 是为“未命名设备”保留的,这在某种程度上还是成功的,但显然这不是预期的。
在模块退出函数中,以及在模块 int 函数中的错误清理代码中,在以下调用中仍为 0:mycdev->cdev.dev
unregister_chrdev_region()
unregister_chrdev_region(mycdev->cdev.dev, 1);
这就解释了为什么在模块退出后,注册的字符设备区域仍会注册。alloc_chrdev_region()
可以通过将调用移至调用之前来解决此问题。但是,我认为最好将值存储在单独的变量中,以将字符设备区域的注册与值的操作分离。cdev_init()
alloc_chrdev_region()
dev_t
struct cdev
例如,此文件级变量可用于存储注册字符设备编号区域的基数:
static dev_t mycdev_devt_base;
然后将呼叫更改为:alloc_chrdev_region()
if (!alloc_chrdev_region(&mycdev_base_devt, 0, 1, CDEV_NAME))
并将呼叫更改为:cdev_add()
if (!cdev_add(&mycdev->cdev, mycdev_base_devt, 1))
并将这两个调用(在模块初始化错误清理代码和模块退出函数中)更改为:unregister_chrdev()
unregister_chrdev_region(mycdev_base_devt, 1);
如果对 or 的任一调用失败,则中的错误清理代码会泄漏内存。在这些情况下,所指向的分配内存永远不会被释放。在这些情况下,该函数也会返回,但最好返回失败的函数返回的错误号。my_char_init()
alloc_chrdev_region()
cdev_add()
mycdev
-ENOMEM
通常的做法是以相反的分配顺序进行清理,如下所示:goto some_label;
static int __init my_char_init(void)
{
int rc;
mycdev = kmalloc(sizeof(struct my_char_dev), GFP_KERNEL);
if (!mycdev) {
printk(KERN_INFO "alloc space for device failed\n");
return -ENOMEM;
}
rc = alloc_chrdev_region(&mycdev_base_devt, 0, 1, CDEV_NAME);
if (rc) {
printk(KERN_INFO "%s alloc region failed - err %d\n", CDEV_NAME, rc);
goto fail_alloc_chrdev_region;
}
printk(KERN_INFO "%s alloc region success, major=%u, base minor=%u\n",
CDEV_NAME, MAJOR(mycdev_base_devt), MINOR(mycdev_base_devt));
cdev_init(&mycdev->cdev, &my_char_fops);
rc = cdev_add(&mycdev->cdev, mycdev_base_devt, 1);
if (rc) {
printk(KERN_INFO "%s add device failed - err %d\n", CDEV_NAME, rc);
goto fail_cdev_add;
return rc;
}
printk(KERN_INFO "%s add device success\n", CDEV_NAME);
return 0;
/* cdev_del(&mycdev->cdev); */
fail_cdev_add:
unregister_chrdev_region(mycdev_base_devt, 1);
fail_alloc_chrdev_region:
kfree(mycdev);
return rc;
}
上述版本的函数报告分配的字符设备编号区域的基本主要和次要编号。主要设备编号将动态分配;次要设备编号将为 0,由调用 的第二个参数设置。alloc_chrdev_region()
OP 使用该命令使用文件中报告的主要设备编号和次要设备编号 1 创建字符专用文件。即使驱动程序模块按预期工作,此文件也不会与已注册字符设备相对应,因为驱动程序模块仅保留次要设备编号 0。mknod
/proc/devices
评论
printk()
mknod
0
1
alloc_chrdev_region()
cdev_add()
-ENOMEM
mycdev
cdev_init()
将 清零,这将丢失 存储的值。您可以将呼叫移动到呼叫之前发生,以阻止这种情况发生。struct cdev
dev
alloc_chrdev_region()
cdev_init()
alloc_chrdev_region()
dev
alloc_chrdev_region()
struct cdev
struct cdev