相对于块设备来说,字符设备的使用要简单很多。但是简单的东西,也有很多值得一看的东西。比方说,字符设备,与inode如何关联;在打开字符设备的时候,又是如何层层递进,最终执行相应的从设备的实际例程呢?下面拿Mem.c这个文件下面的例子来分析,该字符设备的主设备号为1,文件为/dev/mem,含义是物理内存。
1、chr_dev_init:
/* 内存字符设备初始化*/
static int __init chr_dev_init(void)
{
int i;
int err;
err = bdi_init(&zero_bdi);
if (err)
return err;
/* key:负责内存操作的字符设备的初始化,将分配器memory_fops赋值给cdev->ops*/
if (register_chrdev(MEM_MAJOR,"mem",&memory_fops))
printk("unable to get major %d for memory devs\n", MEM_MAJOR);
/* 创建class对象 ,为sysfs系统使用 */
mem_class = class_create(THIS_MODULE, "mem");
for (i = 0; i < ARRAY_SIZE(devlist); i++)
device_create(mem_class, NULL,
MKDEV(MEM_MAJOR, devlist[i].minor),
devlist[i].name);
return 0;
}
2、register_chrdev:
/** * register_chrdev() - Register a major number for character devices. * register_chrdev() - 为字符设备注册一个主设备号 * @major: major device number or 0 for dynamic allocation * @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. * * This function registers a range of 256 minor numbers. The first minor number * is 0. * 该函数注册一个从设备范围,有256个从设备号。第一个 * 从设备号是0. */ int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { struct char_device_struct *cd; struct cdev *cdev; char *s; int err = -ENOMEM; cd = __register_chrdev_region(major, 0, 256, name); if (IS_ERR(cd)) return PTR_ERR(cd); cdev = cdev_alloc(); /* 分配内存:一个cdev结构实例*/ if (!cdev) goto out2; cdev->owner = fops->owner; cdev->ops = fops; /* 这里的实际效果是,刚刚申请的cdev实例代表mem设备,其中的cdev->ops指针指向相应的memory_fops,在实际操作时,先根据主设备号选择cdev,在根据从设备号从memory_fops里选择相应的实际例程*/ kobject_set_name(&cdev->kobj, "%s", name); for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/')) *s = '!'; err = cdev_add(cdev, MKDEV(cd->major, 0), 256); /* 将初始化完毕的cdev内存设备添加到字符设备数据库,即散列表中*/ if (err) goto out; cd->cdev = cdev; return major ? 0 : cd->major; out: kobject_put(&cdev->kobj); out2: kfree(__unregister_chrdev_region(cd->major, 0, 256)); return err; }
1、在linux下,一切皆为文件,所以,我们这里讨论的mem字符设备也要与系统的文件系统结合。关键的概念在于inode:在打开一个设备文件时,各种文件系统的实现会调用init_special_inode函数,为mem设备创建它在文件系统中的表示inode:
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) { /* 在这里显然是进入这个分支,根据mode选择文件类型。*/
inode->i_fop = &def_chr_fops;/* 给定inode相关的文件操作指针,至此,inode->i_fop->open = chrdev_open */
inode->i_rdev = rdev; /* 给定inode设备的主设备号*/
} else if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n",
mode);
}
2、chrdev_open:
/* * Called every time a character special file is opened * 每次当一个字符设备文件被打开时,会调用该函数 */ int chrdev_open(struct inode * inode, struct file * filp) { struct cdev *p; struct cdev *new = NULL; int ret = 0; spin_lock(&cdev_lock); p = inode->i_cdev; /* 获取与mem相关的cdev,即在注册的时候分配的那个结构体实例 */ if (!p) { /* 如果设备文件的inode此前没有被打开过*/ struct kobject *kobj; int idx; spin_unlock(&cdev_lock); /* 根据主设备号查询字符设备数据库,即从散列表中查*/ kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); if (!kobj) return -ENXIO; new = container_of(kobj, struct cdev, kobj); /* 获取struct cdev 实例*/ spin_lock(&cdev_lock); p = inode->i_cdev; if (!p) { inode->i_cdev = p = new; /* 初始化inode实例*/ inode->i_cindex = idx; list_add(&inode->i_devices, &p->list);/* 将该inode添加到cdev->list中,即inode中的i_devices用作链表元素*/ new = NULL; } else if (!cdev_get(p)) ret = -ENXIO; } else if (!cdev_get(p)) ret = -ENXIO; spin_unlock(&cdev_lock); cdev_put(new); if (ret) return ret; /* 找到指定于设备的file_operations ,这里就通过inode找到相应的cdev->ops,即memory_fops*/ filp->f_op = fops_get(p->ops); if (!filp->f_op) { cdev_put(p); return -ENXIO; } /* 执行打开操作*/ if (filp->f_op->open) { lock_kernel(); ret = filp->f_op->open(inode,filp);/* 执行打开操作,这里filp->f_op->open执行的其实是memory_open函数*/ unlock_kernel(); } if (ret) cdev_put(p); return ret; }3、memory_open函数:作为一个分配器,根据从设备号,执行更加具体的不同的字符操作
static int memory_open(struct inode * inode, struct file * filp) { switch (iminor(inode)) { /*根据从设备号区分各个设备,并且选择适当的文件操作:mem_fops、kmem_fops等等,这里选择mem_fops*/ case 1: filp->f_op = &mem_fops; filp->f_mapping->backing_dev_info = &directly_mappable_cdev_bdi; break; case 2: filp->f_op = &kmem_fops; filp->f_mapping->backing_dev_info = &directly_mappable_cdev_bdi; break; case 3: filp->f_op = &null_fops; break; #ifdef CONFIG_DEVPORT case 4: filp->f_op = &port_fops; break; #endif case 5: filp->f_mapping->backing_dev_info = &zero_bdi; filp->f_op = &zero_fops; break; case 7: filp->f_op = &full_fops; break; case 8: filp->f_op = &random_fops; break; case 9: filp->f_op = &urandom_fops; break; case 11: filp->f_op = &kmsg_fops; break; #ifdef CONFIG_CRASH_DUMP case 12: filp->f_op = &oldmem_fops; break; #endif default: return -ENXIO; } if (filp->f_op && filp->f_op->open) return filp->f_op->open(inode,filp); return 0; }
static const struct file_operations mem_fops = { .llseek = memory_lseek, .read = read_mem, .write = write_mem, .mmap = mmap_mem, .open = open_mem, .get_unmapped_area = get_unmapped_area_mem, };
总结:最初只知道打开字符设备的一般函数,然后由打开与内存相关的设备文件的具体函数所替代。接下来根据选择的从设备号,进一步细化函数指针。