Linux内核源代码情景分析-设备文件系统devfs
我们以前多次讲过到,以主设备号/次设备号为基础的设备文件管理方式是有根本性的缺点的。这种从Unix早期一直沿用下来的方案一方面给设备号的管理带来了麻烦,一方面也破坏了/dev目录结构。Unix/Linux系统中的所有目录的结构都是层次的,惟独/dev目录是"平面"的。这不光是风格的问题,也直接影响着访问的效率和管理的方便与否。
那么理想中的/dev目录应该是什么样的呢?首先,它应该是层次的、树状的。其次,它的规模应该是可伸缩的,而且不受数量的限制(例如256个主设备号)。还有,/dev目录中的内容应该反映系统当前在设备驱动方面的实际情形。例如,这样一套方案就是比较理想的:
1、系统加电之初/dev目录为空。
2、系统在初始化阶段扫描并枚举所有连接着的设备,就像对PCI总线的扫描枚举一样。每找到一项设备就分门别类地在/dev目录下创建起子目录,然后以设备的序号作为最底层的节点名,例如"/dev/ide/hd/1",”/dev/ide/floppy/1“等等。
3、以后,每插入一个设备,或安装一个可安装模块,就由内核在/dev子树中增加一个或几个节点。
4、反之,如果关闭或拆除一个设备,或拆除一个可安装模块,就由内核在/dev子树中删去相应的节点。
5、还得与原来的方案兼容。
其实设备文件系统devfs,和特殊文件系统/proc思路基本一样。
一、文件系统类型devfs_fs_type的定义如下:
static DECLARE_FSTYPE (devfs_fs_type, DEVFS_NAME, devfs_read_super, FS_SINGLE);经过gcc的编译预处理以后,就会成为如下的定义:
struct file_system_type devfs_fs_type = { name: "devfs", read_super: devfs_read_super, fs_flags: FS_SINGLE, owner: THIS_MODULE, }系统在初始化时会调用init_devfs_fs进行对devfs特殊文件系统的初始化,代码如下:
int __init init_devfs_fs (void) { int err; printk ("%s: v%s Richard Gooch ([email protected])\n", DEVFS_NAME, DEVFS_VERSION); #ifdef CONFIG_DEVFS_DEBUG devfs_debug = devfs_debug_init; printk ("%s: devfs_debug: 0x%0x\n", DEVFS_NAME, devfs_debug); #endif printk ("%s: boot_options: 0x%0x\n", DEVFS_NAME, boot_options); err = register_filesystem (&devfs_fs_type);//向系统登记文件系统类型devfs_fs_type if (!err) { struct vfsmount *devfs_mnt = kern_mount (&devfs_fs_type);//初始安装特殊文件系统devfs err = PTR_ERR (devfs_mnt); if ( !IS_ERR (devfs_mnt) ) err = 0; } return err; }
struct vfsmount *kern_mount(struct file_system_type *type) { kdev_t dev = get_unnamed_dev(); struct super_block *sb; struct vfsmount *mnt; if (!dev) return ERR_PTR(-EMFILE); sb = read_super(dev, NULL, type, 0, NULL, 0); if (!sb) { put_unnamed_dev(dev); return ERR_PTR(-EINVAL); } mnt = add_vfsmnt(NULL, sb->s_root, NULL); if (!mnt) { kill_super(sb, 0); return ERR_PTR(-ENOMEM); } type->kern_mnt = mnt;//重点 return mnt; }
static struct super_block * read_super(kdev_t dev, struct block_device *bdev, struct file_system_type *type, int flags, void *data, int silent) { struct super_block * s; s = get_empty_super(); if (!s) goto out; s->s_dev = dev; s->s_bdev = bdev; s->s_flags = flags; s->s_dirt = 0; sema_init(&s->s_vfs_rename_sem,1); sema_init(&s->s_nfsd_free_path_sem,1); s->s_type = type; sema_init(&s->s_dquot.dqio_sem, 1); sema_init(&s->s_dquot.dqoff_sem, 1); s->s_dquot.flags = 0; lock_super(s); if (!type->read_super(s, data, silent))//devfs_read_super goto out_fail; unlock_super(s); /* tell bdcache that we are going to keep this one */ if (bdev) atomic_inc(&bdev->bd_count); out: return s; out_fail: s->s_dev = 0; s->s_bdev = 0; s->s_type = NULL; unlock_super(s); return NULL; }
static struct super_block *devfs_read_super (struct super_block *sb, void *data, int silent) { struct inode *root_inode = NULL; if (get_root_entry () == NULL) goto out_no_root; atomic_set (&fs_info.devfsd_overrun_count, 0); init_waitqueue_head (&fs_info.devfsd_wait_queue); init_waitqueue_head (&fs_info.revalidate_wait_queue); fs_info.sb = sb; sb->u.generic_sbp = &fs_info; sb->s_blocksize = 1024; sb->s_blocksize_bits = 10; sb->s_magic = DEVFS_SUPER_MAGIC; sb->s_op = &devfs_sops; if ( ( root_inode = get_vfs_inode (sb, root_entry, NULL) ) == NULL )//为devfs的根节点创建一个inode数据结构 goto out_no_root; sb->s_root = d_alloc_root (root_inode);//为devfs的根节点创建一个dentry数据结构,这个节点也叫"/",并保存在sb->s_root中 if (!sb->s_root) goto out_no_root; #ifdef CONFIG_DEVFS_DEBUG if (devfs_debug & DEBUG_DISABLED) printk ("%s: read super, made devfs ptr: %p\n", DEVFS_NAME, sb->u.generic_sbp); #endif return sb; out_no_root: printk ("devfs_read_super: get root inode failed\n"); if (root_inode) iput (root_inode); return NULL; }像/proc一样,devfs在磁盘上也没有对应物,不像常规的文件系统那样在磁盘上有连接成树状的目录节点和索引节点,所以在内存中要为之建立起一套连接成树状的数据结构。对于devfs文件系统,这种数据结构是devfs_entry,代码如下:
struct devfs_entry { void *info; union { struct directory_type dir; struct fcb_type fcb; struct symlink_type symlink; struct fifo_type fifo; } u; struct devfs_entry *prev; /* Previous entry in the parent directory */ struct devfs_entry *next; /* Next entry in the parent directory */ struct devfs_entry *parent; /* The parent directory */ struct devfs_entry *slave; /* Another entry to unregister */ struct devfs_inode inode; umode_t mode; unsigned short namelen; /* I think 64k+ filenames are a way off... */ unsigned char registered:1; unsigned char show_unreg:1; unsigned char hide:1; unsigned char no_persistence:1; char name[1]; /* This is just a dummy: the allocated array is bigger. This is NULL-terminated */ }结构中的字符数组name[]就是节点名,其大小在为具体的数据结构分配空间时根据具体的字符串长度确定。每个devfs_entry结构通过指针prev、next、parent、slave连接成树状,以此来实现一个文件系统子树,从数据结构的定义可以看出,devfs子树中的节点有四种不同的类型。第一种是dir,即目录,这是不言自明的。第二种是fcb,即"文件控制块",这是devfs子树中的叶节点。第三种是symlink这也是不言自明的。最后一种是fifo,专门用于管道文件。因节点类型的不同,devfs_entry结构中union_u也相应地解释成不同的数据结构。
struct file_type { unsigned long size; }; struct device_type { unsigned short major; unsigned short minor; }; struct fcb_type /* File, char, block type */ { uid_t default_uid; gid_t default_gid; void *ops; union { struct file_type file; struct device_type device; } u; unsigned char auto_owner:1; unsigned char aopen_notify:1; unsigned char removable:1; /* Belongs in device_type, but save space */ unsigned char open:1; /* Not entirely correct */ };可见,devfs中的叶节点有两种类型,一种是文件,另一种是设备。对于设备,fcb_type结构提供了16位的主/次设备号,不过在devfs中主设备号与具体的驱动程序没有固定的对应关系,而是动态分配的。
内核中还有个数据结构fs_info,代码如下:
struct fs_info /* This structure is for each mounted devfs */ { unsigned int num_inodes; /* Number of inodes created */ unsigned int table_size; //数组当前的大小 struct devfs_entry **table; //指向了一个devfs_entry指针数组 struct super_block *sb; volatile struct devfsd_buf_entry *devfsd_buffer; volatile unsigned int devfsd_buf_in; volatile unsigned int devfsd_buf_out; volatile int devfsd_sleeping; volatile int devfsd_buffer_in_use; volatile struct task_struct *devfsd_task; volatile struct file *devfsd_file; volatile unsigned long devfsd_event_mask; atomic_t devfsd_overrun_count; wait_queue_head_t devfsd_wait_queue; wait_queue_head_t revalidate_wait_queue; };
介绍完这些数据结构后,我们回到devfs_read_super,继续看get_root_entry,代码如下:
static struct devfs_entry *get_root_entry (void) { struct devfs_entry *new; /* Always ensure the root is created */ if (root_entry != NULL) return root_entry; if ( ( root_entry = create_entry (NULL, NULL, 0) ) == NULL ) return NULL;//先创建devfs的根节点root_entry root_entry->registered = TRUE; root_entry->mode = S_IFDIR; /* Force an inode update, because lookup() is never done for the root */ update_devfs_inode_from_entry (root_entry); /* And create the entry for ".devfsd" */ if ( ( new = create_entry (root_entry, ".devfsd", 0) ) == NULL )//在devfs的根节点下创建一个节点".devfsd"。 return NULL; new->registered = TRUE; new->u.fcb.u.device.major = next_devnum_char >> 8;////.devfsd是个fcb节点。在devfs中,设备号是由系统自动分配的,设备号在devfs中并不起着原来那么重要的作用 new->u.fcb.u.device.minor = next_devnum_char & 0xff; ++next_devnum_char; new->mode = S_IFCHR | S_IRUSR | S_IWUSR; new->u.fcb.default_uid = 0; new->u.fcb.default_gid = 0; new->u.fcb.ops = &devfsd_fops;//ops指向devfsd_fops,代码在下面 return root_entry; }
static struct devfs_entry *create_entry (struct devfs_entry *parent, const char *name,unsigned int namelen) { struct devfs_entry *new, **table; /* First ensure table size is enough */ if (fs_info.num_inodes >= fs_info.table_size)//目前num_inodes为0,table_size为0 { if ( ( table = kmalloc (sizeof *table * (fs_info.table_size + INODE_TABLE_INC), GFP_KERNEL) ) == NULL ) return NULL;//分配最初的250个指针分配空间 fs_info.table_size += INODE_TABLE_INC;//INODE_TABLE_INC为250 #ifdef CONFIG_DEVFS_DEBUG if (devfs_debug & DEBUG_I_CREATE) printk ("%s: create_entry(): grew inode table to: %u entries\n", DEVFS_NAME, fs_info.table_size); #endif if (fs_info.table)//如果不是第一次分配空间,则还要把已经存在的数组复制到新的空间,并释放原有的空间 { memcpy (table, fs_info.table, sizeof *table *fs_info.num_inodes); kfree (fs_info.table); } fs_info.table = table;//指向了一个devfs_entry指针数组 } if ( name && (namelen < 1) ) namelen = strlen (name); if ( ( new = kmalloc (sizeof *new + namelen, GFP_KERNEL) ) == NULL ) return NULL; /* Magic: this will set the ctime to zero, thus subsequent lookups will trigger the call to <update_devfs_inode_from_entry> */ memset (new, 0, sizeof *new + namelen); new->parent = parent; if (name) memcpy (new->name, name, namelen); new->namelen = namelen; new->inode.ino = fs_info.num_inodes + FIRST_INODE;//FIRST_INODE为1,以后则逐次递增,设置了节点号 new->inode.nlink = 1; fs_info.table[fs_info.num_inodes] = new;//更新devfs_entry指针数组 ++fs_info.num_inodes;//递增 if (parent == NULL) return new; new->prev = parent->u.dir.last;//将新的节点与父节点以及同一目录中的其它节点链接在一起 /* Insert into the parent directory‘s list of children */ if (parent->u.dir.first == NULL) parent->u.dir.first = new; else parent->u.dir.last->next = new; parent->u.dir.last = new; return new; }
static struct file_operations devfsd_fops = { read: devfsd_read, ioctl: devfsd_ioctl, release: devfsd_close, };
static struct inode *get_vfs_inode (struct super_block *sb, struct devfs_entry *de, struct dentry *dentry) { struct inode *inode; if (de->inode.dentry != NULL) { printk ("%s: get_vfs_inode(%u): old de->inode.dentry: %p \"%s\" new dentry: %p \"%s\"\n", DEVFS_NAME, de->inode.ino, de->inode.dentry, de->inode.dentry->d_name.name, dentry, dentry->d_name.name); printk (" old inode: %p\n", de->inode.dentry->d_inode); return NULL; } if ( ( inode = iget (sb, de->inode.ino) ) == NULL ) return NULL;//首先在inode结构的杂凑队列中查找,如果找不到就新创建一个 de->inode.dentry = dentry; return inode; }d_alloc_root,为devfs的根节点创建一个dentry数据结构,这个节点也叫"/",代码如下:
struct dentry * d_alloc_root(struct inode * root_inode) { struct dentry *res = NULL; if (root_inode) { res = d_alloc(NULL, &(const struct qstr) { "/", 1, 0 }); if (res) { res->d_sb = root_inode->i_sb; res->d_parent = res; d_instantiate(res, root_inode); } } return res; }最后返回到kern_mount,继续执行add_vfsmnt,代码如下:
static struct vfsmount *add_vfsmnt(struct nameidata *nd, struct dentry *root, const char *dev_name) { struct vfsmount *mnt; struct super_block *sb = root->d_inode->i_sb; char *name; mnt = kmalloc(sizeof(struct vfsmount), GFP_KERNEL); if (!mnt) goto out; memset(mnt, 0, sizeof(struct vfsmount)); if (nd || dev_name) mnt->mnt_flags = MNT_VISIBLE; /* It may be NULL, but who cares? */ if (dev_name) { name = kmalloc(strlen(dev_name)+1, GFP_KERNEL); if (name) { strcpy(name, dev_name); mnt->mnt_devname = name; } } mnt->mnt_owner = current->uid; atomic_set(&mnt->mnt_count,1); mnt->mnt_sb = sb; spin_lock(&dcache_lock); if (nd && !IS_ROOT(nd->dentry) && d_unhashed(nd->dentry)) goto fail; mnt->mnt_root = dget(root); mnt->mnt_mountpoint = nd ? dget(nd->dentry) : dget(root); mnt->mnt_parent = nd ? mntget(nd->mnt) : mnt; if (nd) { list_add(&mnt->mnt_child, &nd->mnt->mnt_mounts); list_add(&mnt->mnt_clash, &nd->dentry->d_vfsmnt); } else { INIT_LIST_HEAD(&mnt->mnt_child); INIT_LIST_HEAD(&mnt->mnt_clash); } INIT_LIST_HEAD(&mnt->mnt_mounts); list_add(&mnt->mnt_instances, &sb->s_mounts); list_add(&mnt->mnt_list, vfsmntlist.prev); spin_unlock(&dcache_lock); out: return mnt; fail: spin_unlock(&dcache_lock); if (mnt->mnt_devname) kfree(mnt->mnt_devname); kfree(mnt); return NULL; }
void __init mount_devfs_fs (void) { int err; if ( (boot_options & OPTION_NOMOUNT) ) return; err = do_mount ("none", "/dev", "devfs", 0, ""); if (err == 0) printk ("Mounted devfs on /dev\n"); else printk ("Warning: unable to mount devfs, err: %d\n", err); }
可见,设备文件系统devfs的安装点是"/dev"。由于devfs_fs_type中的FS_SINGLE标志位为1,安装时会把保存在devfs_fs_type中的vfsmount结构指针安装到”/dev“节点上。
do_mount请参考Linux内核源代码情景分析-文件系统的安装和Linux内核源代码情景分析-特殊文件系统/proc。
三、完成了devfs的安装以后,各种设备的驱动程序就可以通过devfs_register_chrdev或devfs_register_blkdev向devfs登记,从而在/dev目录下创建起相应的节点。
1、通过devfs_register_chrdev向devfs登记一类设备的主设备号、设备号以及file_operations数据结构,建立起三者间的联系。主设备号可以是静态指定的,也可以要求devfs动态地分配。
2、通过devfs_mk_dir建立目录节点
3、通过devfs_register登记具体的设备,并在指定目录下建立叶节点
上面三个过程类似于/proc特殊文件系统,创建proc节点以下的子节点的proc_dir_entry结构,代码如下:
void __init proc_root_init(void) { proc_misc_init(); proc_net = proc_mkdir("net", 0); #ifdef CONFIG_SYSVIPC proc_mkdir("sysvipc", 0); #endif #ifdef CONFIG_SYSCTL proc_sys_root = proc_mkdir("sys", 0); #endif proc_root_fs = proc_mkdir("fs", 0); proc_root_driver = proc_mkdir("driver", 0); #if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE) /* just give it a mountpoint */ proc_mkdir("openprom", 0); #endif proc_tty_init(); #ifdef CONFIG_PROC_DEVICETREE proc_device_tree_init(); #endif proc_bus = proc_mkdir("bus", 0); }
四、完成了以上三步后,我们就可以通过"/dev/ide/hd/1",”/dev/ide/floppy/1“来访问具体的设备了(shell下会看到相应的文件和目录)。和原来对”/dev/hd1“,”/dev/floppy1“的操作没有什么不同的。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。