浅谈Linux的设备文件

linux下的文件分为常规文件和设备文件,常规文件一定在某一个设备上被存储,不论这个设备是真实的还是虚拟的,这里的设备是linux中vfs层中的 设备,也就是前面所说的设备文件中的设备,vfs层的设备分为字符设备和块设备,字符设备可以类比为一个fifo的队列,无论读还是写都必须顺序进行,而 块设备就可以随机进行读写,常规的文件一般都在块设备上被存储,包括设备文件本身也在一个块设备上被存储着,可以说vfs层解决了这种混乱,它提供给上面 的操作者一个十分统一的接口,实际上vfs下面十分不雅,败絮其中吗?等你看了linux源代码就不会这么认为了,linux内核是分层次的,vfs仅仅 是其中的一个罢了,即使下面很乱也不是很无序的乱,总体看来是很乱,那是因为你混合看所有设备那当然混乱,因为字符设备和块设备的管理方式就不同,如果理 一下思路就会很自然的想到在vfs接口下面有三条线,一条是常规文件,一条是字符设别文件,另一条就是块设备文件。
     linux用很好的数据结构组织了两类设备文件,对于字符设备比较简单,就是将所有的字符设备都置于一个map中,就是cdev_map,所有的字符设备在注册的时候都会加入这个map:
int register_chrdev(unsigned int major, const char *name, 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);
...       
         cdev = cdev_alloc();
         if (!cdev)
                 goto out2;
         cdev->owner = fops->owner;
         cdev->ops = fops;
         strcpy(cdev->kobj.name, name);
         for (s = strchr(cdev->kobj.name, '/'); s; s = strchr(s, '/'))
                 *s = '!';                
         err = cdev_add(cdev, MKDEV(cd->major, 0), 256);
...
         cd->cdev = cdev;
         return major ? 0 : cd->major;
...
}
注 意这个map并不是一个字符设备的链表,而是一个hash表,这个map主要作用就是和2.6内核的新的设备模型联系,也就是和kobject联 系,linux中真正将所有的字符设备连成链表的是上面函数里面的char_device_struct结构体,很早以前写过一篇《谈谈linux2.6 内核的驱动框架》中讲到驱动的两条线索,其中以kobject连接起来的第一条线索直取用户空间其实就是到了这里的这个map,在这个map将把任务交给 了vfs的接口。hash组织的kobj_map效率非常高,其实这里的hash函数很简单,就是设备号和255相除取余,将hash值相等的 kobject用next字段连接成一个链表,然后在需要查找某些值的时候通过hash找到链表然后遍历链表通过一个回调函数进行精确比对最终找到需要的 结构:
struct kobj_map {
         struct probe {
                 struct probe *next;
                 dev_t dev;
                 unsigned long range;
                 struct module *owner;
                 kobj_probe_t *get;      //这就是那个精确比对的回调函数,这个创意在于将比对策略一起放入了hash节点中,这样可以灵活实现不同的比对策略。
                 int (*lock)(dev_t, void *);
                 void *data;
         } *probes[255];      //255个hash桶
         struct rw_semaphore *sem;
};
每 当打开一个字符设备时,从这个map中得到一个cdev结构体,而cdev中有一个file_operations字段,在默认的open函数中,用这个 file_operations字段替换字符设备的默认的file_operations字段,然后从此用户就可以用这个file_operations 来操作字符设备了,这就是“狸猫换太子”了。
对于块设备远远比字符设备复杂,但是看起来要比字符设备有层次感,块设别也有一个前面的hash表,只不过它里面映射的不是简单的vfs层的块设备了,而 是更为底层通用块设备,就是gendisk,为何要这样?就是因为块设备可以利用缓存,这在linux中是很重要的,可以大大提高效率,因此必须在真正的 块设备层上面提供一个统一的缓存管理的层次,最好和常规文件的缓存管理统一用一套机制,这样的结果就是block_device层次,其实 block_device是一个vfs和gendisk之间的粘结层,可以为统一缓存管理机制提供更加统一的接口(gendisk很底层,不适合做这件 事),为了和常规文件一致的管理缓存就必须一套和常规文件一致的file_operations结构体,linux的block设备恰恰提供了这 个:def_blk_fops。另外就是以上的file_operations必须固定,不能让不同的disk任意设置,因为虽然底层设备不同,可是缓存 管理机制是统一的,缓存不属于底层,而属于vfs。gendisk就是再往下的块设备层次了,它主要管理磁盘结构信息,比如分区信息等等,再往下就是 IDE,SCSI这些特殊的层次了。
以上是字符设备和块设备文件的vfs架构,那么常规文件呢?前面说过,常规文件都在块设备中,因此常规文件的操作就成了最终的块设备的操作,最终在经过了缓存层次之后就到了块设备的架构了,最终也要经过gendisk到达底层硬件。
还有一类设备就是网络设备,比如网卡之类的,可是却没有在任何地方看到网络设备的设备文件,这到底是为什么?这就要从TCP/IP协议栈和BSD套接字说 起了,在tcp/ip之前,操作网络设备是件很平常的事,可是tcp/ip之后就没人再直接操作网络设备进行通信了,取而代之的是用协议栈进行通 信,tcp/ip规定应用通信信道是应用层的两个进程之间建立的信道,很多情况下,信道应该是独占的,而设备意味着共享,大家都可以用,因此建立设备文件 并没有问题。可是在tcp/ip之后,网络设备就被抽象成了一个通信信道,BSD套接字实现了这一点,因此通信就是两个进程独占一个信道进行通信(不考虑 广播和组播),网络设备就不能随意被共享了,更大的意义是没有必要被共享,没有人再直接操作网络设备进行通信,所有人都是通过套接字用协议栈进行通信的, 而一个套接字就是一个信道的一个端点,被一个进程独占,并且在通信开始时动态建立,通信开始时间不早于进程创建时间,因此就把网络设备的管理交给了进程, 进程可以很方便的通过协议栈管理设备和应用设备,古老的ifconfig不就是通过套接字的ioctl配置设备的吗?这并不违背unix的一切皆文件的理 念,套接字本身也是通过vfs操作的,只是有了协议栈和套接字用户接口,第一是没有必要再提供网卡设备文件了,第二就是网络通信信道的独占性和协议封装规 则已经作为协议栈的标准存在了,底层的硬件网卡也遵循这些协议栈标准(IEEE802.x),因此操作系统必须提供标准的操作方式而不是将操作自由留给用 户,用户如果不懂协议栈标准就无法操作设备并且用设备通信,而协议栈标准是统一的,因此必须由操作系统提供,然后把可以微调的配置用套接字接口的形式留给 用户操作和配置。类似于管道,很多无名管道也没有设备文件,只要在进程中以独占方式操作的文件描述符都没有必要有设别文件,一切皆文件指的是vfs这个层 次而不是必须要在文件系统有永久记录

你可能感兴趣的:(linux,struct,网络,Semaphore,File,linux内核)