Linux设备分析报告杜阳9811526浙江大学计算机系98研duy@isee.zju.edu.cn[摘要]在本文中,首先概括了linux设备的基本概念。接着依次介绍了相关的数据结构、初始化流程和设备管理流程,接着主要介绍了如何添加一个字符设备和块设备。在附录中是一个虚拟的字符设备驱动程序,该程序是我和潘刚同学的试验结果,本来我们还打算写一个虚拟的块设备驱动程序,由于时间关系,没有能够完成,非常遗憾,不过主要步骤已经在本文中进行了介绍。一.linux设备概述在概念上一般把设备分为字符设备、块设备。字符设备是指设备发送和接收数据以字符形式的进行;而块设备则以整个数据缓冲区的形式进行。由于网络设备等有其特殊性,实际上系统对它们单独处理。系统用主设备号(MAJOR)加次设备(MINOR)号来唯一标识一个设备。相同主设备号表示同一类设备,例如都是硬盘;次设备号标识同类设备的个数。所有设备在适当的目录(通常在/dev目录下)下必须有相应的文件,这样字符设备和块设备都可以通过文件操作的系统调用了完成。不同的是,块设备操作经常要和缓冲区打交道,更加复杂一点。系统设备管理的总体框图如下:用户程序系统调用接口文件系统高速缓存字符设备块设备驱动程序硬件设备二.主要数据结构与设备管理有关的主要数据结构如下:1、登记设备管理系统对已登记设备的管理是由chrdevs和blkdevs这两张列表来完成的:/*src\fs\devices.c*/structdevice_struct{constchar*name;//指向设备名称structfile_operations*fops;//指向设备的访问操作函数集,file_operations定义在include/linux/fs.h中};staticstructdevice_structchrdevs[MAX_CHRDEV]={{NULL,NULL},};//所有系统登记的字符设备列表staticstructdevice_structblkdevs[MAX_BLKDEV]={{NULL,NULL},}//所有系统登记的块设备列表实际上这两张列表的结构是一样的,但在登记时每个结构元素的值会不同(见初始化部分)。Linux对设备的进行访问时,访问文件系统中相应的文件,通过文件系统和文件的属性描述块,系统可以找到该文件系统或文件对应设备的设备号。在实际访问列表时,以chrdevs[MAJOR][MINOR]或blkdevs[MAJOR][MINOR]形式访问,相同主设备号(MAJOR)的元素中fops的内容相同。文件系统中相关的的数据结构如下:structsuper_block{kdev_ts_dev;//该文件系统所在设备的设备标志符…}//每个文件系统对应一个super_blockstructinode{kdev_ti_dev;//该文件所在设备的设备标志符通过它可以找到在设备列表中…相应设备}//每个文件对应一个inode2、I/O请求管理系统会把一部分系统内存作为块设备驱动程序与文件系统接口之间的一层缓冲区,每个缓冲区与某台块设备中的特定区域相联系,文件系统首先试图存在相应的缓冲区,如未找到就向该设备发出I/O读写请求,由设备驱动程序对这些请求进行处理。因此,需要有相应的数据结构进行管理。/*src\include\linux\blkdev.h*/structblk_dev_struct{void(*request_fn)(void);//指向请求处理函数的指针,请求处理函数是写设备驱动程序的重要一环,设备驱动程序在此函数中通过outb向位于I/O空间中的设备命令寄存器发出命令structrequest*current_request;//指向当前正在处理的请求,它和plug共同维护了该设备的请求队列structrequestplug;//这是LINUX2.0版本与以前版本的一个不同之处,plug主要被用于异步提前读写操作,在这种情况下,由于没有特别的请求,为了提高系统性能,需要等发送完所有的提前读写请求才开始进行请求处理,即unplug_device。structtq_structplug_tq;//设备对应的任务队列};/*src\drivers\block\ll_rw_blk.c*/structblk_dev_structblk_dev[MAX_BLKDEV];其中每个请求以request的类型的结构进行传递,定义如下:/*src\include\linux\blk_dev.h*/structrequest{volatileintrq_status;//表示请求的状态kdev_trq_dev;//是该请求对应的设备号,kdev_t是unsignedshort类型,高8位是主设备号,低8位是从设备号,每一请求都针对一个设备发出的;intcmd;//表示该请求对应的命令,取READ或WRITE;interrors;unsignedlongsector;//每一扇区的字节数unsignedlongnr_sectors;//每一扇区的扇区数unsignedlongcurrent_nr_sectors;//当前的扇区数;char*buffer;//存放buffer_head.b_data值,表示发出请求的数据存取地址;structsemaphore*sem;//一个信号量,用来保证设备读写的原语操作,当sem=0时才能处理该请求;structbuffer_head*bh;//读写缓冲区的头指针structbuffer_head*bhtail;//读写缓冲区的尾指针structrequest*next;//指向下一个请求};对不同块设备的所有请求都放在请求数组all_requests中,该数组实际上是一个请求缓冲池,请求的释放与获取都是针对这个缓冲池进行;同时各个设备的请求用next指针联结起来,形成各自的请求队列。定义如下:/*src\drivers\blokc\ll_rw_blk.c*/staticstructrequestall_requests[NR_REQUEST];3、中断请求设备进行实际的输入/输出操作时,如果时间过长而始终被占用CPU,就会影响系统的效率,必须有一种机制来克服这个问题而又不引起其他问题。中断是最理想的方法。和中断有关的数据结构是;structirqaction{void(*handler)(int,void*,structpt_regs*);//指向设备的中断响应函数,它在系统初始化时被置入。当中断发生时,系统自动调用该函数unsignedlongflags;//指示了中断类型,如正常中断、快速中断等unsignedlongmask;//中断的屏蔽字constchar*name;//设备名void*dev_id;//与设备相关的数据类型,中断响应函数可以根据需要将它转化所需的数据指针,从而达到访问系统数据的功能structirqaction*next;//指向下一个irqaction};由于中断数目有限,且很少更新,所以系统在初始化时,从系统堆中分配内存给每一个irq_action指针,通过next指针将它们连成一个队列。4、高速缓冲区为了加速对物理设备的访问速度,Linux将块缓冲区放在Cache内,块缓冲区是由buffer_head连成的链表结构。buffer_head的数据结构如下:/*include\linux\fs.h*/structbuffer_head{unsignedlongb_blocknr;/*blocknumber*/kdev_tb_dev;/*device(B_FREE=free)*/kdev_tb_rdev;/*Realdevice*/unsignedlongb_rsector;/*Realbufferlocationondisk*/structbuffer_head*b_next;/*Hashqueuelist*/structbuffer_head*b_this_page;/*circularlistofbuffersinonepage*/unsignedlongb_state;/*bufferstatebitmap(seeabove)*/structbuffer_head*b_next_free;unsignedintb_count;/*usersusingthisblock*/unsignedlongb_size;/*blocksize*/char*b_data;/*pointertodatablock(1024bytes)*/unsignedintb_list;/*Listthatthisbufferappears*/unsignedlongb_flushtime;/*Timewhenthis(dirty)buffershouldbewritten*/unsignedlongb_lru_time;/*Timewhenthisbufferwaslastused.*/structwait_queue*b_wait;structbuffer_head*b_prev;/*doublylinkedlistofhash-queue*/structbuffer_head*b_prev_free;/*doublylinkedlistofbuffers*/structbuffer_head*b_reqnext;/*requestqueue*/};块缓冲区主要由链表组成。空闲的buffer_head组成的链表是按块大小的不同分类组成,Linux目前支持块大小为512、1024、2048、4096和8192字节;第二部分是正在用的块,块以Hash_table的形式组织,具有相同hash索引的缓冲块连在一起,hash索引根据设备标志符和该数据块的块号得到;同时将同一状态的缓冲区块用LRU算法连在一起。对缓冲区的各个链表定义如下:/*fs\buffer.c*/staticstructbuffer_head**hash_table;staticstructbuffer_head*lru_list[NR_LIST]={NULL,};staticstructbuffer_head*free_list[NR_SIZES]={NULL,};staticstructbuffer_head*unused_list=NULL;staticstructbuffer_head*reuse_list=NULL;三.设备的初始化LINUX启动时,完成了实模式下的系统初始化(arch/i386/boot/setup.S)与保护模式下的核心初始化包括初始化寄存器和数据区(arch/i386/boot/compressed/head.S)、核心代码解压缩、页表初始化(arch/i386/kernel/head.S)、初始化idt、gdt和ldt等工作后,系统转入了核心。调用函数start_kernel启动核心(init/main.c)后,将继续各方面的初始化工作,其中start_kernel最后将调用kernel_thread(init,NULL,0),创建init进程进行系统配置(其中包括所有设备的初始化工作)。staticintinit(void*unused){…………/*创建后台进程bdflush,以不断循环写出文件系统缓冲区中“脏”的内容*/kernel_thread(bdflush,NULL,0);/*创建后台进程kswapd,专门处理页面换出工作*/kswapd_setup();kernel_thread(kswapd,NULL,0);…………setup();…………在setup函数中,调用系统调用sys_setup()。sys_setup()的定义如下://fs/filesystems.casmlinkageintsys_setup(void){staticintcallable=1;……if(!callable)return-1;callable=0;……devic