荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: autodotcom (cpu8086), 信区: Linux
标  题: 设备分析1
发信站: 荔园晨风BBS站 (Sat Apr 27 13:11:31 2002), 转信

发信人: fist (星仔迷), 信区: SysInternals
标  题: Linux设备分析报告(1)
发信站: 武汉白云黄鹤站 (Wed Dec 13 10:19:24 2000), 站内信件

Linux设备分析报告
杜阳    9811526
浙江大学计算机系98研   duy@isee.zju.edu.cn
[摘要] 在本文中,首先概括了linux设备的基本概念。接着依次介绍了相关的数据
结构
、初始化流程和设备管理流程,接着主要介绍了如何添加一个字符设备和块设备。
在附
录中是一个虚拟的字符设备驱动程序,该程序是我和潘刚同学的试验结果,本来我
们还
打算写一个虚拟的块设备驱动程序,由于时间关系,没有能够完成,非常遗憾,不
过主
要步骤已经在本文中进行了介绍。
一. linux设备概述
在概念上一般把设备分为字符设备、块设备。字符设备是指设备发送和接收数据以
字符
形式的进行;而块设备则以整个数据缓冲区的形式进行。但是,由于网络设备等有
其特
殊性,实际上系统对它们单独处理。
系统用主设备号(MAJOR)加次设备(MINOR)号来唯一标识一个设备。相同主设备
号表
示同一类设备,例如都是硬盘;次设备号标识同类设备的个数。所有设备在适当的
目录
(通常在/dev目录下)下必须有相应的文件,这样字符设备和块设备都可以通过文
件操
作的系统调用了完成。不同的是,块设备操作经常要和缓冲区打交道,更加复杂一
点。
系统设备管理的总体框图如下:
二. 主要数据结构
与设备管理有关的主要数据结构如下:
1、登记设备管理
系统对已登记设备的管理是由chrdevs和blkdevs这两张列表来完成的:
/*src\fs\devices.c*/
struct device_struct {
const char * name;              //指向设备名称
struct file_operations * fops;      //指向设备的访问操作函数集,
file_operati
ons定义在
include/linux/fs.h中
};
static  struct device_struct  chrdevs[MAX_CHRDEV] = {
{ NULL, NULL },
};                               //所有系统登记的字符设备列表
static  struct device_struct  blkdevs[MAX_BLKDEV] = {
{ NULL, NULL },
}                               //所有系统登记的块设备列表
实际上这两张列表的结构是一样的,但在登记时每个结构元素的值会不同(见初始
化部
分)。Linux对设备的进行访问时,访问文件系统中相应的文件,通过文件系统和
文件的
属性描述块,系统可以找到该文件系统或文件对应设备的设备号。在实际访问列表
时,
以chrdevs[MAJOR][MINOR]或blkdevs[MAJOR][MINOR]形式访问,相同主设备号(
MAJOR)
的元素中fops的内容相同。文件系统中相关的的数据结构如下:
struct super_block {
kdev_t s_dev;             //该文件系统所在设备的设备标志符

}                       //每个文件系统对应一个super_block
struct inode {
kdev_t          i_dev;       //该文件所在设备的设备标志符通过它可以找到
在设备列
表中
…                  相应设备
}                      //每个文件对应一个inode
2、I/O请求管理
系统会把一部分系统内存作为块设备驱动程序与文件系统接口之间的一层缓冲区,
每个
缓冲区与某台块设备中的特定区域相联系,文件系统首先试图存在相应的缓冲区,
如未
找到就向该设备发出I/O读写请求,由设备驱动程序对这些请求进行处理。因此,
需要有
相应的数据结构进行管理。
/*src\include\linux\blkdev.h*/
struct blk_dev_struct {
void (*request_fn)(void);         //指向请求处理函数的指针,请求处理函数
是写
设备驱动
程序的重要一环,设备驱动程序在此函数中通过outb向位
于I/O空间中的设备命令寄存器发出命令
struct request * current_request;   //指向当前正在处理的请求,它和plug共
同维
护了该设备
的请求队列
struct request   plug;           //这是LINUX2.0版本与以前版本的一个不同
之处,
plug
主要被用于异步提前读写操作,在这种情况下,由于没有特别的请求,为了提高系
统性
能,需要等发送完所有的提前读写请求才开始进行请求处理,即unplug_device。

struct tq_struct plug_tq;         //设备对应的任务队列
};
/*src\drivers\block\ll_rw_blk.c*/
struct  blk_dev_struct  blk_dev[MAX_BLKDEV];
其中每个请求以request的类型的结构进行传递,定义如下:
/*src\include\linux\blk_dev.h*/
struct request {
volatile int rq_status;               //表示请求的状态
kdev_t rq_dev;                    //是该请求对应的设备号,kdev_t是
unsigned s
hort类型,高8位是主设备号,低8位是从设备号,每一请求都针对一个设备发出的

        int cmd;                         //表示该请求对应的命令,取READ
或WRITE;
        int errors;
        unsigned 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 request * next;              //指向下一个请求
};
对不同块设备的所有请求都放在请求数组all_requests中,该数组实际上是一个请
求缓
冲池,请求的释放与获取都是针对这个缓冲池进行;同时各个设备的请求用next指
针联
结起来,形成各自的请求队列。定义如下:
/*src\drivers\blokc\ll_rw_blk.c*/
static struct request all_requests[NR_REQUEST];
3、中断请求
设备进行实际的输入/输出操作时,如果时间过长而始终被占用CPU,就会影响系统
的效
率,必须有一种机制来克服这个问题而又不引起其他问题。中断是最理想的方法。
和中
断有关的数据结构是;
struct irqaction {
        void (*handler)(int, void *, struct pt_regs *);   //指向设备的中
断响应函
数,
它在系统初
始化时被置入。当中断发生时,系统自动调用该函数
        unsigned long flags;                 //指示了中断类型,如正常中
断、快速
中断

        unsigned long mask;                //中断的屏蔽字
        const char *name;                  //设备名
        void *dev_id;                     //与设备相关的数据类型,中断响
应函数可
以根

据需要将它转化所需的数据指针,从而达到访问系统数据的功能
        struct irqaction *next;               //指向下一个irqaction
};
由于中断数目有限,且很少更新,所以系统在初始化时,从系统堆中分配内存给每
一个
irq_action指针,通过next指针将它们连成一个队列。
4、高速缓冲区
为了加速对物理设备的访问速度,Linux将块缓冲区放在Cache内,块缓冲区是由
buffer
_head连成的链表结构。buffer_head的数据结构如下:
/*include\linux\fs.h*/
struct buffer_head {
        unsigned long b_blocknr;                /* block number */
        kdev_t b_dev;                                   /* device
(B_FREE = free
) */
        kdev_t b_rdev;                                  /* 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 o
ne page *
/
        unsigned long b_state;                  /* buffer state bitmap
(see abov
e) */
        struct buffer_head * b_next_free;
        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_lru_time;       /* Time when this buffer was
last used.
*/
        struct wait_queue * b_wait;
        struct buffer_head * b_prev;            /* doubly linked list of
 hash-qu
eue */
        struct buffer_head * b_prev_free;       /* doubly linked list of
 buffers
 */
        struct buffer_head * b_reqnext; /* request queue */
};
块缓冲区主要由链表组成。空闲的buffer_head组成的链表是按块大小的不同分类
组成,
Linux目前支持块大小为512、1024、2048、4096和8192字节;第二部分是正在用的
块,
块以Hash_table的形式组织,具有相同hash索引的缓冲块连在一起,hash索引根据
设备
标志符和该数据块的块号得到;同时将同一状态的缓冲区块用LRU算法连在一起。
对缓冲
区的各个链表定义如下:
/* fs\buffer.c*/
static struct buffer_head ** hash_table;
static struct buffer_head * lru_list[NR_LIST] = {NULL, };
static struct buffer_head * free_list[NR_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、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_setup();
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保证只被调用实际只一次,再次调用时后面的
初始
化程序不执行。在该调用开始就先进行设备的初始化:device_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的初始化。
2、 对块设备,需要将输入/输出处理程序的入口地址告诉内核。
3、 对块设备,需要告诉缓冲区设备存取的数据块的大小。
四. 设备管理的流程
下面我们介绍一下整个设备管理的流程。我们以块设备为例,字符设备的流程也和
块设
备类似,只是没有请求队列管理。
首先,文件系统通过调用ll_rw_block发出块读写命令,读写请求管理层接到命令
后,向
系统申请一块读写请求缓冲区,在填写完请求信息后,请求进入设备的读写请求队
列等
候处理。如果队列是空的,则请求立即得到处理,否则由系统负责任务调度,唤醒
请求
处理。在请求处理过程中,系统向I/O空间发出读写指令返回。当读写完毕后,通
过中断
通知系统,同时调用与设备相应的读写中断响应函数。对设备的读写过程的流程可
以用
下图表示。

--
※ 来源:.武汉白云黄鹤站 bbs.whnet.edu.cn.[FROM: 202.114.1.60]



--
-再见了
-我爱的那个人
-从此,我将与DDK独行

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.55.48]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店