设备文件节点的生成



--

设备文件节点的生成

Linux系统下,设备文件是种特殊的文件类型,其存在的主要意义是沟通用户空间程序和内核空间驱动程序。换句话说,用户空间的应用程序要想使用驱动程序提供的服务,需要经过设备文件来达成。当然,如果你的驱动程序只是为内核中的其他模块提供服务,则没有必要生成对应的设备文件。

按照通用的规则,Linux系统所有的设备文件都位于/dev目录下。/dev目录在Linux系统中算是一个比较特殊的目录,在Linux系统早期还不支持动态生成设备节点时,/dev目录就是挂载的根文件系统下的/dev,对这个目录下所有文件的操作使用的是根文件系统提供的接口。比如,如果Linux系统挂载的根文件系统是ext3,那么对/dev目录下所有目录/文件的操作都将使用ext3文件系统的接口。随着后来Linux内核的演进,开始支持动态设备节点的生成[1],使得系统在启动过程中会自动生成各个设备节点,这就使得/dev目录不必要作为一个非易失的文件系统的形式存在。因此,当前的Linux内核在挂载完根文件系统之后,会在这个根文件系统的/dev目录上重新挂载一个新的文件系统devtmpfs,后者是个基于系统RAM的文件系统实现。当然,对动态设备节点生成的支持并不意味着一定要将根文件系统中的/dev目录重新挂载到一个新的文件系统上,事实上动态生成设备节点技术的重点并不在文件系统上面。

动态设备节点的特性需要其他相关技术的支持,在后续的章节中会详细描述这些特性。目前先假定设备节点是通过Linux系统下的mknod命令静态创建。为方便叙述,下面用一个具体的例子来描述设备文件产生过程中的一些关键要素,这个例子的任务很简单:在一个ext3类型的根文件系统中的/dev目录下用mknod命令来创建一个新的设备文件节点demodev,对应的驱动程序使用的设备主设备号为2,次设备号是0,命令形式为:

root@LinuxDev:/home/dennis# mknod /dev/demodev c 2 0

上述命令成功执行后,将会在/dev目录下生成一个名为demodev的字符设备节点。如果用strace工具来跟踪一下上面的命令,会发现如下输出(删去了若干不相关部分):

root@LinuxDev:/home/dennis# strace mknod /dev/demodev c 2 0

execve("/bin/mknod", ["mknod", "/dev/demodev", "c", "30","0"], [/* 36 vars */]) = 0

mknod("/dev/demodev", S_IFCHR|0666, makedev(30,0)) = 0

可见Linux下的mknod命令最终是通过调用mknod函数来实现的,调用时的重要参数有两个,一是设备文件名("/dev/demodev"),二是设备号(makedev(30,0))。设备文件名主要在用户空间使用(比如用户空间程序调用open函数时),而内核空间则使用inode来表示相应的文件。本书只关注内核空间的操作,对于前面的mknod命令,它将通过系统调用sys_mknod进入内核空间,这个系统调用的原型是:

<include/linux/syscalls.h>

long sys_mknod(const char __user *filename, int mode, unsigned dev);

注意sys_mknod的最后一个参数dev,它是由用户空间的mknod命令构造出的设备号。sys_mknod系统调用将通过/dev目录上挂载的文件系统接口来为/dev/demodev生成一个新的inode[2],设备号将被记录到这个新的inode对象上。

2-7展示了通过ext3文件系统在/dev目录下生成一个新的设备节点/dev/demodev的主要流程。

2-7  ext3文件系统mknod的主要流程

完整了解设备节点产生的整个过程需要知晓VFS和特定文件系统的技术细节。然而从驱动程序员的角度来说,没有必要知道文件系统相关的所有细节,只需关注文件系统和驱动程序间是如何建立上关联的就足够了。

sys_mknod首先在根文件系统ext3的根目录“/”下寻找dev目录所对应的inode,图中对应的inode编号为168ext3文件系统的实现会通过某种映射机制,通过inode编号最终得到该inode在内存中的实际地址(图中由标号1的线段表示)。接下来会通过devinode结构中的i_op成员指针所指向的ext3_dir_inode_operations(这是个struct inode_operations类型的指针),来调用该对象中的mknod方法,这将导致ext3_mknod函数被调用。

ext3_mknod函数的主要作用是生成一个新的inode(用来在内核空间表示demodev设备文件节点,demodev设备节点文件与新生成的inode之间的关联在图2-7中由标号5的线段表示)。在ext3_mknod中会调用一个和设备驱动程序关系密切的init_special_inode函数,其定义如下:

<fs/inode.c>

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)

{

       inode->i_mode = mode;

       if (S_ISCHR(mode)) {

              inode->i_fop = &def_chr_fops;

              inode->i_rdev = rdev;

       } 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) for"

                              " inode %s:%lu\n", mode, inode->i_sb->s_id,

                              inode->i_ino);

}

这个函数最主要的功能便是为新生成的inode初始化其中的i_fopi_rdev成员。设备文件节点inode中的i_rdev成员用来表示该inode所对应设备的设备号,通过参数rdev为其赋值。设备号在由sys_mknod发起的整个内核调用链中进行传递,最早来自于用户空间的mknod命令行参数。

i_fop成员的初始化根据是字符设备还是块设备而有不同的赋值。对于字符设备,fop指向def_chr_fops,后者主要定义了一个open操作:

<fs/char_dev.c>

const struct file_operations def_chr_fops = {

       .open = chrdev_open,

      

};

相对于字符设备,块设备的def_blk_fops的定义则要有点复杂:

<fs/block_dev.c>

const struct file_operations def_blk_fops = {

       .open             = blkdev_open,

       .release    = blkdev_close,

       .llseek     = block_llseek,

       .read              = do_sync_read,

       .write             = do_sync_write,

       .aio_read = generic_file_aio_read,

       .aio_write       = blkdev_aio_write,

       .mmap           = generic_file_mmap,

       .fsync             = blkdev_fsync,

       .unlocked_ioctl      = block_ioctl,

#ifdef CONFIG_COMPAT

       .compat_ioctl  = compat_blkdev_ioctl,

#endif

       .splice_read    = generic_file_splice_read,

       .splice_write   = generic_file_splice_write,

};

关于块设备,将在本书第11块设备驱动程序中详细讨论,这里依然把考察的重点放在字符设备上。字符设备inode中的i_fop指向def_chr_fops。至此,设备节点的所有相关铺垫工作都已经结束,接下来可以看看打开一个设备文件到底意味着什么。

 

——本段文字节选自《深入Linux设备驱动程序内核机制》

图书详细信息:

http://www.cnblogs.com/broadview/archive/2012/02/21/2361867.html


[1] 这里动态生成设备节点的说法是相对于使用mknod命令生成设备节点而言的,前者直接通过文件系统接口来生成对应的设备节点。

[2] 对于实际的文件系统,比如ext3文件系统,产生一个node的过程因为同时要涉及底层存储设备的操作,因而会变得很复杂。

posted @ 2012-03-01 19:58  博文视点(北京)官方博客  阅读(590)  评论(0编辑  收藏  举报