Linux设备分析报告_第1页
Linux设备分析报告_第2页
Linux设备分析报告_第3页
Linux设备分析报告_第4页
Linux设备分析报告_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、linux 设备分析报告杜阳 9811526 浙江大学计算机系98 研 摘要 在本文中,首先概括了linux 设备的基本概念。接着依次介绍了相关的数据结构、初始化流程和设备管理流程,接着主要介绍了如何添加一个字符设备和块设备。在附录中是一个虚拟的字符设备驱动程序,该程序是我和潘刚同学的试验结果,本来我们还打算写一个虚拟的块设备驱动程序,由于时间关系,没有能够完成, 非常遗憾, 不过主要步骤已经在本文中进行了介绍。一.linux 设备概述在概念上一般把设备分为字符设备、块设备。 字符设备是指设备发送和接收数据以字符形式的进行; 而块设备则以整个数据缓冲区的形式进行。由于网络设备等有其特殊性,实际

2、上系统对它们单独处理。系统用主设备号(major )加次设备( minor )号来唯一标识一个设备。相同主设备号表示同一类设备,例如都是硬盘;次设备号标识同类设备的个数。所有设备在适当的目录(通常在 /dev 目录下) 下必须有相应的文件,这样字符设备和块设备都可以通过文件操作的系统调用了完成。不同的是, 块设备操作经常要和缓冲区打交道,更加复杂一点。系统设备管理的总体框图如下:用户程序系统调用接口文件系统高速缓存字符设备块设备驱动程序硬件设备二.主要数据结构与设备管理有关的主要数据结构如下:1、登记设备管理系统对已登记设备的管理是由chrdevs和 blkdevs 这两张列表来完成的:/*s

3、rcfsdevices.c*/ struct device_struct const char * name; /指向设备名称struct file_operations * fops; /指向设备的访问操作函数集,file_operations 定义在include/linux/fs.h中; static struct device_struct chrdevsmax_chrdev = null, null , ; /所有系统登记的字符设备列表static struct device_struct blkdevsmax_blkdev = null, null , /所有系统登记的块设备列表实

4、际上这两张列表的结构是一样的,但在登记时每个结构元素的值会不同(见初始化部分) 。linux对设备的进行访问时,访问文件系统中相应的文件,通过文件系统和文件的属性描述块,系统可以找到该文件系统或文件对应设备的设备号。在实际访问列表时,以chrdevsmajorminor或 blkdevsmajorminor形式访问,相同主设备号(major )的元素中fops 的内容相同。文件系统中相关的的数据结构如下:struct super_block kdev_t s_dev; /该文件系统所在设备的设备标志符 /每个文件系统对应一个super_block struct inode kdev_t i_d

5、ev; /该文件所在设备的设备标志符通过它可以找到在设备列表中相应设备 /每个文件对应一个inode 2、i/o 请求管理系统会把一部分系统内存作为块设备驱动程序与文件系统接口之间的一层缓冲区,每个缓冲区与某台块设备中的特定区域相联系,文件系统首先试图存在相应的缓冲区,如未找到就向该设备发出i/o 读写请求,由设备驱动程序对这些请求进行处理。因此,需要有相应的数据结构进行管理。/*srcincludelinuxblkdev.h*/ struct blk_dev_struct void (*request_fn)(void); /指向请求处理函数的指针,请求处理函数是写设备驱动程序的重要一环,设

6、备驱动程序在此函数中通过outb 向位于 i/o 空间中的设备命令寄存器发出命令struct request * current_request; /指向当前正在处理的请求,它和plug 共同维护了该设备的请求队列struct request plug; /这是 linux2.0 版本与以前版本的一个不同之处,plug 主要被用于异步提前读写操作,在这种情况下,由于没有特别的请求,为了提高系统性能,需要等发送完所有的提前读写请求才开始进行请求处理,即unplug_device。struct tq_struct plug_tq; /设备对应的任务队列; /*srcdriversblockll_r

7、w_blk.c*/ struct blk_dev_struct blk_devmax_blkdev;其中每个请求以request的类型的结构进行传递,定义如下:/*srcincludelinuxblk_dev.h*/ struct request volatile int rq_status; /表示请求的状态kdev_t rq_dev; /是该请求对应的设备号,kdev_t 是 unsigned short类型,高8 位是主设备号,低8 位是从设备号,每一请求都针对一个设备发出的;int cmd; /表示该请求对应的命令,取read 或 write ;int errors; unsigned

8、 long sector; /每一扇区的字节数unsigned long nr_sectors; /每一扇区的扇区数unsigned long current_nr_sectors; /当前的扇区数;char * buffer; /存放 buffer_head.b_data 值,表示发出请求的数据存取地址;struct semaphore * sem; /一个信号量,用来保证设备读写的原语操作,当sem=0 时才能处理该请求;struct buffer_head * bh; /读写缓冲区的头指针struct buffer_head * bhtail; /读写缓冲区的尾指针struct requ

9、est * next; /指向下一个请求; 对不同块设备的所有请求都放在请求数组all_requests 中,该数组实际上是一个请求缓冲池,请求的释放与获取都是针对这个缓冲池进行;同时各个设备的请求用next 指针联结起来,形成各自的请求队列。定义如下:/*srcdriversblokcll_rw_blk.c*/ static struct request all_requestsnr_request;3、中断请求设备进行实际的输入/输出操作时, 如果时间过长而始终被占用cpu,就会影响系统的效率,必须有一种机制来克服这个问题而又不引起其他问题。中断是最理想的方法。和中断有关的数据结构是;st

10、ruct irqaction void (*handler)(int, void *, struct pt_regs *); /指向设备的中断响应函数,它在系统初始化时被置入。当中断发生时,系统自动调用该函数unsigned long flags; /指示了中断类型,如正常中断、快速中断等unsigned long mask; /中断的屏蔽字const char *name; /设备名void *dev_id; /与设备相关的数据类型,中断响应函数可以根据需要将它转化所需的数据指针,从而达到访问系统数据的功能struct irqaction *next; /指向下一个irqaction ; 由

11、于中断数目有限,且很少更新,所以系统在初始化时,从系统堆中分配内存给每一个irq_action 指针,通过next 指针将它们连成一个队列。4、高速缓冲区为了加速对物理设备的访问速度,linux 将块缓冲区放在cache内, 块缓冲区是由buffer_head连成的链表结构。buffer_head 的数据结构如下:/*includelinuxfs.h*/ struct buffer_head unsigned long b_blocknr; /* block number */ kdev_t b_dev; /* device (b_free = free) */ kdev_t b_rdev;

12、/* real device */ unsigned long b_rsector; /* real buffer location on disk */ struct buffer_head * b_next; /* hash queue list */ struct buffer_head * b_this_page; /* circular list of buffers in one page */ unsigned long b_state; /* buffer state bitmap (see above) */ struct buffer_head * b_next_free;

13、 unsigned int b_count; /* users using this block */ unsigned long b_size; /* block size */ char * b_data; /* pointer to data block (1024 bytes) */ unsigned int b_list; /* list that this buffer appears */ unsigned long b_flushtime; /* time when this (dirty) buffer should be written */ unsigned long b

14、_lru_time; /* time when this buffer was last used. */ struct wait_queue * b_wait; struct buffer_head * b_prev; /* doubly linked list of hash-queue */ struct buffer_head * b_prev_free; /* doubly linked list of buffers */ struct buffer_head * b_reqnext; /* request queue */ ; 块缓冲区主要由链表组成。空闲的 buffer_hea

15、d 组成的链表是按块大小的不同分类组成,linux 目前支持块大小为512、1024、2048、4096 和 8192 字节;第二部分是正在用的块,块以 hash_table 的形式组织,具有相同hash 索引的缓冲块连在一起,hash 索引根据设备标志符和该数据块的块号得到;同时将同一状态的缓冲区块用lru 算法连在一起。对缓冲区的各个链表定义如下:/* fsbuffer.c*/ static struct buffer_head * hash_table; static struct buffer_head * lru_listnr_list = null, ; static struct

16、 buffer_head * free_listnr_sizes = null, ; static struct buffer_head * unused_list = null; static struct buffer_head * reuse_list = null; 三.设备的初始化linux启动时,完成了实模式下的系统初始化(arch/i386/boot/setup.s )与保护模式下的核心初始化包括初始化寄存器和数据区(arch/i386/boot/compressed/head.s) 、核心代码解压缩、页表初始化(arch/i386/kernel/head.s) 、初始化idt、

17、gdt 和 ldt 等工作后,系统转入了核心。调用函数start_kernel 启动核心(init/main.c )后,将继续各方面的初始化工作,其中start_kernel 最后将调用kernel_thread (init, null, 0),创建 init进程进行系统配置(其中包括所有设备的初始化工作)。static int init(void * unused) ,/* 创建后台进程bdflush ,以不断循环写出文件系统缓冲区中“脏”的内容*/ kernel_thread(bdflush, null, 0); /* 创建后台进程kswapd,专门处理页面换出工作*/ kswapd_se

18、tup(); kernel_thread(kswapd, null, 0); ,setup(); ,在 setup 函数中,调用系统调用sys_setup() 。sys_setup()的定义如下:/fs/filesystems.c asmlinkage int sys_setup(void) static int callable = 1; if (!callable) return -1; callable = 0; device_setup(); 在该系统调用中,静态变量callable 保证只被调用实际只一次,再次调用时后面的初始化程序不执行。在该调用开始就先进行设备的初始化:devic

19、e_setup()。/dirvers/block/genhd.c void device_setup(void) extern void console_map_init(void); chr_dev_init(); blk_dev_init(); 可以看到device_setup()将依次执行chr_dev_init() 、 blk_dev_init() 等各类设备的初始化程序。每个具体的init 函数的内容和具体设备就有关了,但是它们都有一些必须完成的任务:1、告诉内核这一驱动程序使用的主设备号,同时提供指向file_operation 的指针,以完成对chrdevs 和 blkdevs

20、的初始化。2、对块设备,需要将输入/输出处理程序的入口地址告诉内核。3、对块设备,需要告诉缓冲区设备存取的数据块的大小。四.设备管理的流程下面我们介绍一下整个设备管理的流程。我们以块设备为例,字符设备的流程也和块设备类似,只是没有请求队列管理。首先,文件系统通过调用ll_rw_block发出块读写命令,读写请求管理层接到命令后,向系统申请一块读写请求缓冲区,在填写完请求信息后,请求进入设备的读写请求队列等候处理。如果队列是空的,则请求立即得到处理,否则由系统负责任务调度,唤醒请求处理。在请求处理过程中,系统向 i/o 空间发出读写指令返回。当读写完毕后, 通过中断通知系统,同时调用与设备相应的

21、读写中断响应函数。对设备的读写过程的流程可以用下图表示。五.添加一个字符设备作为对 linux 设备管理的分析的总结,我们介绍一下如何添加一个设备,首先介绍如何添加一个字符设备。 在后面的文章中,我们将新添加的设备称为新设备,说明以我们实现的虚拟的字符设备为例,步骤基本如下:1.确定设备的设备名称和主设备号:我们必须找一个还没有被使用的主设备号,分配给自己的字符设备。假设主设备号为30 (在2.0.34 的内核中还没有以30 作为主设备号的字符设备)。2.确定编写需要的file_operations 中的操作函数,包括:static int my_open(struct inode * ino

22、de,struct file * file) 进程向文件系统发出读命令文件系统根据文件i 节点找出对应的设备的读操作读操作通过ll_rw_disk发出读请求读请求被处理,调用读请求处理函数读请求 向设备 发出读命令,等待操作结果中断处理程序返回读操作结果/通过宏指令minor ()提取inode 参数的 i_rdev 字段,确定辅助设备号,然后检查相应的读写忙标志,看新设备是否已经打开。如果是,返回错误信息;否则置读写忙标志为true,阻止再次打开新设备。static void my_release(struct inode * inode,struct file * file)/同 my_o

23、pen 类似,只是置读写忙标志为false,允许再次打开新设备。static int my _write(struct inode * inode,struct file * file,const char * buffer,int count) /用于对该设备的写static int my _read(struct inode * inode , struct file * file,char * buffer, int count) /用于对该设备的读static int my_ioctl(struct inode * inode, struct file * file, unsigned

24、 int cmd, unsigned long arg) /用于传送特殊的控制信息给设备驱动程序,或者长设备驱动程序取得状态信息,在我们实现的虚拟字符设备中,这个函数的功能是用来打开和关闭跟踪功能。3.确定编写需要的初始化函数:void my_init(void) /首先需要将上述的file_operations 中的操作函数的地址赋给某个file_operations 的结构变量my_fops 中的相应域;然后调用标准内核函数登记该设备:register_chrdev(30,mychd,&my_fops);最后对必要的变量(例如读写忙标志、跟踪标志等)赋初值。4.在 drivers/

25、char/mem.c 中添加相应语句;在 chr_dev_init 函数之前添加drgn_init 的原型说明:void my_init (void) ;在 chr_dev_init 函数的 return 语句之前添加以下语句:my_init () ;/用于在字符设备初始化时初始化新设备5.修改 drivers/char/makefile ;假设我们把所以必要的函数写mychd.c 中,则找到“l_objs ”行,将“ mychd.o ”加到其中。6.将该设备私有的*.c, *.h 复制到目录drivers/char 下。7.用命令: make clean;make dep;make zima

26、ge 重新编译内核。8.用 mknod 命令在目录 /dev 下建立相应主设备号的用于读写的特殊文件。完成了上述步骤,你在linux 环境下编程时就可以使用新设备了。六.添加一个块设备接下来我们将介绍如何添加一个块设备。在后面的文章中,我们将新添加的块设备称为新设备, 块设备的添加过程和字符设备有相似之处,我们将主要介绍其不同点,步骤基本如下:1.确定设备的设备名称和主设备号我们必须找一个还没有被使用的主设备号,分配给自己的新设备。假设主设备号为30(在2.0.34 的内核中还没有以30 作为主设备号的块设备), 则需要在 include/linux/major.h 中加入如下句:#defin

27、e my_major 30 这样我们可以通过my_major来确定设备为新设备,保证通用性。2.确定编写需要的file_operations 中的操作函数:static int my_open(struct inode * inode,struct file * file) static void my_release(struct inode * inode,struct file * file)static int my_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg)

28、由于使用了高速缓存,块设备驱动程序就不需要包含自己的read()、write() 和 fsync() 函数,但必须使用自己的open()、 release()和ioctl() 函数,这些函数的作用和字符设备的相应函数类似。3.确定编写需要的输入/输出函数:static int my _read(void) /正确处理时返回值为1,错误时返回值为0 static int my _write(void) /正确处理时返回值为1,错误时返回值为0 值得注意的是这两个函数和字符设备中的my read()、mywrite() 函数不同:参数不同: 字符设备中的函数是带参数的,用于对其工作空间(也可以看成

29、是简单的缓冲区) 中的一定长度 (长度也是参数传递的)的字符进行读写;而块设备的函数是没有参数的,它通过当前请求中的信息访问高速缓存中的相应的块,因此不需要参数输入。调用形式不同:字符设备中的函数地址是存放在file_operations 中的,在对字符设备进行读写时就调用了;而块设备的读写函数是在需要进行实际i/o 时在 request 中调用。这在后面可以看到。4.确定编写需要的请求处理函数:static void my_request(void) 在块设备驱动程序中,不带中断服务子程序的请求处理函数是简单的,典型的格式如下:static void my_request(void) loo

30、p: init_request; if (minor(current-dev)my_minor_max) end_request(0); goto loop; if (current -cmd=read) /cureent是指向请求队列头的request 结构指针 end_request(my_read(); /my_read() 在前面已经定义goto loop; if (current -cmd=write) end_request(my_write(); /my_write() 在前面已经定义goto loop; end_request(0); goto loop; 实际上, 一个真正的

31、块设备一般不可能没有中断服务子程序,另外设备驱动程序是在系统调用中被调用的,这时由内核程序控制cpu,因此不能抢占,只能自愿放弃;因此驱动程序必须调用sleep_on()函数, 释放对 cpu 的占用; 在中断服务子程序将所需的数据复制到内核内存后,再由它来发出wake_up()调用,5.如果需要,编写中断服务子程序实际上, 一个真正的块设备一般不可能没有中断服务子程序,另外设备驱动程序是在系统调用中被调用的,这时由内核程序控制cpu,因此不能抢占,只能自愿放弃;因此驱动程序必须调用sleep_on()函数, 释放对 cpu 的占用; 在中断服务子程序将所需的数据复制到内核内存后,再由它来发出

32、wake_up()调用。另外两段中断服务子程序都要访问和修改特定的内核数据结构时,必须要仔细协调,以防止出现灾难性的后果。首先,在必要时可以禁止中断,这可以通过sti() 和 cli() 来允许和禁止中断请求。其次,修改特定的内核数据结构的程序段要尽可能的短,使中断不至于延时过长。含有中断服务子程序的块设备驱动程序的编写相对比较复杂,我们还没有完全实现,主要问题是在中断处理之间的协调。因为这些程序是要加入内核的,系统默认为你是完全正确的,如果引起循环或者中断长时间不响应,结果非常严重。我们正在努力实现这一程序。6.确定编写需要的初始化函数:void my_init(void) 需要将的file

33、_operations 中的操作函数的地址赋给某个file_operations 的结构变量my_fops中的相应域;一个典型的形式是:struct file_operations my_fops= 0, block_read, block_write, 0, 0, my_ioctl, 0, my_open, my_release, block_fsync, 0, 0, 0, my_init 中需要作的工作有:首先调用标准内核函数登记该设备:register_chrdev(my_mojor,my bdev,&my_fops) ;将 request()函数的地址告诉内核:blk_devm

34、y_major.request_fn=device_request;device_request是请求处理函数的地址,它的定义将在稍后可以看到。告诉新设备的高速缓存的数据块的块大小:my_block_size=512; /也可以是1024 等等blksize_sizemy_major=& my_block_size; 为了系统在初始化时能够对新设备进行初始化,需要在 blk_dev_init() 中添加一行代码,可以插在 blk_dev_init() 中 return 0 的前面,格式为:my_init(); 7.在 include/linux/blk.h 中添加相应语句;到目前为止,

35、除了device_request符合外,还没有告诉内核到那里去找你的request()函数,为此需要将一些宏定义加到blk.h 中。在 blk.h 中找到类似的一行:#endif /*major_nr=whatever */ 在这行前面加入如下宏定义:#elif (major_nr=whatever) static void my_request(void); #define device_name “ my_blk_dev ”/驱动程序名称#define device_request my_request /request() 函数指针#define deviec_nr(device) (m

36、inor(device) /计算实际设备号#define deviec_on(device) /用于需要打开的设备#define deviec_off(device) /用于需要关闭的设备8修改 drivers/block/makefile ;假设我们把所以必要的函数写mybd.c 中,则找到“ l_objs ”行,将“ mybd.o”加到其中。8.将该设备私有的*.c, *.h 复制到目录drivers/block 下。9.用命令: make clean;make dep;make zimage 重新编译内核。10.用 mknod 命令在目录 /dev 下建立相应主设备号的用于读写的特殊文件

37、。完成了上述步骤,你在linux 环境下编程时就可以使用新设备了。七.一个虚拟的字符设备驱动程序以下是一个虚拟的字符设备驱动程序,该程序是我和潘刚同学的试验结果,本来我们还打算写一个虚拟的块设备驱动程序,由于时间关系, 没有能够完全明白中断中断服务子程序的编写方法, 因此没有没有能够实现一个可以所有的虚拟的块设备,非常遗憾。 不过主要步骤已经在上文中进行了介绍,我想再有一段时间应该能够完成,到时候一定交给李老师看一下。虚拟的字符设备驱动程序如下,在潘刚同学的试验报告中也有介绍:/* drgn.h */ #ifdef kernel #define trace_txt(text) if(drgn_

38、trace) console_print(text);console_print(n); #define trace_chr(chr) if(drgn_trace) console_print(chr); #define drgn_read 1 #define drgn_write 0 #endif #define false 0 #define true 1 #define max_buf 120 #define drgn_tron (m 8)|0 x01) #define drgn_troff (m 8)|0 x02) struct drgn_buf int buf_size; char

39、buffermax_buf; struct drgn_buf *link; ; /* drgn.c */ #define kernel #include #include #include #include #include #include #include #include #include #include #include #include drgn.h static int drgn_trace; static int write_busy; static int read_busy; static struct drgn_buf * qhead; static struct drg

40、n_buf * qtail; static int drgn_read(struct inode * , struct file * , char * , int ); static int drgn_write(struct inode * , struct file * , const char *, int ); static int drgn_ioctl(struct inode * , struct file * , unsigned int , unsigned long ); static int drgn_open(struct inode *,struct file *);

41、static void drgn_release(struct inode *,struct file *); /* extern void console_print(char *);*/ struct file_operations drgn_fops= null, drgn_read, drgn_write, null, null, drgn_ioctl, null, drgn_open, drgn_release, null, null, null, null ; void drgn_init(void) drgn_trace=true; if(register_chrdev(30,d

42、rgn,&drgn_fops) trace_txt(cannot register drgn driver as major device 30.) else trace_txt(tiny devie driver registered successfully.)qhead=0; write_busy=false; read_busy=false; /* drgn_trace=false;*/ return; static int drgn_open(struct inode * inode,struct file * file) trace_txt(drgn_open) switc

43、h (minor(inode-i_rdev) case drgn_write: if(write_busy) return -ebusy; else write_busy=true; return 0; case drgn_read: if(read_busy) return -ebusy; else read_busy=true; return 0; default: return -enxio; static void drgn_release(struct inode * inode,struct file * file) trace_txt(drgn_release) switch (minor(inode-i_rdev) case drgn_write: write_busy=false; return; case drgn_read: read_busy=fals

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论