Chinaunix首页 | 论坛 | 博客
  • 博客访问: 819855
  • 博文数量: 117
  • 博客积分: 2583
  • 博客等级: 少校
  • 技术积分: 1953
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-06 22:58
个人简介

Coder

文章分类
文章存档

2013年(1)

2012年(10)

2011年(12)

2010年(77)

2009年(13)

2008年(4)

分类: LINUX

2010-08-10 22:06:08

Linux字符设备管理

设备文件

Unix操作系统都是基于文件概念的,一切皆文件是Unix的传统啊。在系统看来,文件都是无结构的,是由字节序列而构成的信息载体。根据这一点,可以把I/O设备当做设备文件(device file)这种所谓的特殊文件来处理,与磁盘上的普通文件进行交互所用的同一系统调用可直接用于I/O设备。

根据设备驱动程序的基本特性,设备文件可以分为两种:块设备文件和字符设备文件。块设备是那些能够随机访问,并且每次都以一个比较大的单位——块来进行信息存取的,块通常是2的某次方个字节。而字符设备则通常是只能顺序的逐字符的访问的,比如说键盘,把一旦把缓冲区中的数据读走了,数据就不在了,没有办法再回到之前的状态了。而从内核实现块设备子系统和字符设备子系统的角度来看,到底是只能逐字符访问还是支持随机的,以块为单位的访问,并不是那么重要的,而只是访问设备时所经过的路径不同罢了。网络设备是第三种设备类型,网卡是不直接与设备文件相对应的硬件设备。

设备文件是存放在文件系统中的实际文件,许多具体的文件系统,比如EXT3等都会提供例程来在其下创建设备文件。然而,设备文件的索引节点并不包含指向磁盘上数据块的(文件的数据)的指针,因为它们是空的。相反,索引节点必须包含硬件设备的一个标识符,它对应字符或块设备文件。

传统上,设备标识符由设备文件的类型(字符或块)和一对参数组成。第一个参数称为主设备号(major number),它标识了设备的类型。通常具有相同主设备号和类型的所有设备共享相同的文件操作集合,因为它们是由同一个设备驱动程序处理的。第二个参数为次设备号(minor number),它标识了主设备号相同的设备组中一个特定的设备。

mknod()系统调用用来创建设备文件。

 

字符设备驱动程序

字符设备驱动程序是一种相对比较简单的驱动程序,指的是其访问方式,字符设备,通常并不需要复杂的缓冲策略,也不涉及磁盘高速缓存等复杂的东西。当然,字符设备在它们的需求方面有所不同;有些必须实现复杂的通信协议以驱动硬件设备,而有些仅仅需要从硬件设备的I/O端口读几个值。

 

字符设备驱动程序是由一个cdev结构描述的,其定义为:

---------------------------------------------------------------------

include/linux/cdev.h

12 struct cdev {

13         struct kobject kobj;

14         struct module *owner;

15         const struct file_operations *ops;

16         struct list_head list;

17         dev_t dev;

18         unsigned int count;

19 };

---------------------------------------------------------------------

各字段说明:

kobj:内嵌的kobject

owner:指向实现驱动程序的模块(如果有的话)的指针

ops:指向设备驱动程序文件操作表的指针

list:与字符设备文件对应的索引节点链表的头,该链表用于收集相同字符设备驱动程序所对应的设备文件的索引节点。可能很多设备文件具有相同的设备号,并对应于相同的字符设备。此外,一个设备驱动程序对应的设备号可以是一个范围,而不仅仅是一个号;设备号位于同一范围内的所有设备文件均由同一个字符设备驱动程序处理。

dev:给设备驱动程序所分配的初始主设备号和此设备号

count:给设备驱动程序分配的设备号的范围大小。

 

设备驱动程序模型为字符设备定义了一个kobject映射域用于管理字符设备系统,该映射域由一个kobj_map类型的描述符描述,并由全局变量cdev_map引用。来看下在系统初始化过程中调用的建立文件系统的vfs_caches_init()中调用的chrdev_init()函数:

---------------------------------------------------------------------

fs/char_dev.c

26 /*

 27  * capabilities for /dev/mem, /dev/kmem and similar directly mappable

 28  * character devices

 29  * - permits shared-mmap for read, write and/or exec

 30  * - does not permit private mmap in NOMMU mode (can't do COW)

 31  * - no readahead or I/O queue unplugging required

 32  */

 33 struct backing_dev_info directly_mappable_cdev_bdi = {

 34         .name = "char",

 35         .capabilities   = (

 36 #ifdef CONFIG_MMU

 37        /* permit private copies of the data to be taken */

 38           BDI_CAP_MAP_COPY |

 39 #endif

 40                 /* permit direct mmap, for read, write or exec */

 41           BDI_CAP_MAP_DIRECT |

 42           BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP | BDI_CAP_EXEC_MAP),

 43 };

 44

 45 static struct kobj_map *cdev_map;

 

558 static struct kobject *base_probe(dev_t dev, int *part, void *data)

559 {

560         if (request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev)) > 0)

561                 /* Make old-style 2.4 aliases work */

562                 request_module("char-major-%d", MAJOR(dev));

563         return NULL;

564 }

 

566 void __init chrdev_init(void)

567 {

568         cdev_map = kobj_map_init(base_probe, &chrdevs_lock);

569         bdi_init(&directly_mappable_cdev_bdi);

570 }

---------------------------------------------------------------------

这里,先来看一下kobj_map描述符,其定义如下:

---------------------------------------------------------------------

drivers/base/map.c

struct kobj_map {

    struct probe {

       struct probe *next;

       dev_t dev;

       unsigned long range;

       struct module *owner;

       kobj_probe_t *get;

       int (*lock)(dev_t, void *);

       void *data;

    } *probes[255];

    struct mutex *lock;

};

---------------------------------------------------------------------

kobj_map描述符包括一个散列表,它有255个表项,并由0——255范围的主设备号进行索引。散列表存放probe类型的对象,每个对象都拥有一个已注册的主设备号和此设备号。

 

probe对象中的各个字段:

next:散列冲突链表中的下一个元素

dev:设备号范围的初始设备号(主、此设备号)

range:设备号范围的大小

owner:如果有的话,指向实现设备驱动程序模块的指针

get:探测谁拥有这个设备号范围

lock:增加设备号范围内拥有者的引用计数器

data:设备号范围拥有者的私有数据。

 

接着来看kobj_map_init()的定义:

---------------------------------------------------------------------

drivers/base/map.c

struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock)

{

    struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL);

    struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL);

    int i;

 

    if ((p == NULL) || (base == NULL)) {

       kfree(p);

       kfree(base);

       return NULL;

    }

 

    base->dev = 1;

    base->range = ~0;

    base->get = base_probe;

    for (i = 0; i < 255; i++)

       p->probes[i] = base;

    p->lock = lock;

    return p;

}

---------------------------------------------------------------------

看得出,申请了一个struct kobj_map对象,并由局部变量p指向它,然后最后返回的也是p,即最后把一切都献给了cdev_map。然后又为一个probe对象分配了内存,由base指向它。初始化设备号为1,范围为最大,get方法为传递进来的base_probe。而这里真正干的事情无非就是让bdev_kmap->probes[]数组全都等于base。换言之,它们的get指针全都等于了咱们这里传递进来的base_probe函数。

 

内核提供了cdev_alloc()函数,用于动态的分配cdev描述符,并初始化内嵌的kobject数据结构,因此引用计数器的值变为0时会自动释放该描述符。这个函数定义为:

---------------------------------------------------------------------

fs/char_dev.c

527 /**

528  * cdev_alloc() - allocate a cdev structure

529  *

530  * Allocates and returns a cdev structure, or NULL on failure.

531  */

532 struct cdev *cdev_alloc(void)

533 {

534         struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);

535         if (p) {

536                 INIT_LIST_HEAD(&p->list);

537                 kobject_init(&p->kobj, &ktype_cdev_dynamic);

538         }

539         return p;

540 }

---------------------------------------------------------------------

这里初始化cdev内嵌kobject时使用的kobj_type参数为ktype_cdev_dynamicktype_cdev_dynamic的定义为:

---------------------------------------------------------------------

fs/char_dev.c

523 static struct kobj_type ktype_cdev_dynamic = {

524         .release        = cdev_dynamic_release,

525 };

---------------------------------------------------------------------

仅仅初始化了release字段为cdev_dynamic_release,这个函数将要在cdev引用计数为0时调用cdev_dynamic_release()定义为:

---------------------------------------------------------------------

fs/char_dev.c

512 static void cdev_dynamic_release(struct kobject *kobj)

513 {

514         struct cdev *p = container_of(kobj, struct cdev, kobj);

515         cdev_purge(p);

516         kfree(p);

517 }

---------------------------------------------------------------------

这个函数调用了调用cdev_purge(p),将cdev从各个设备文件inode的设备链表中移除,然后调用kfree(p)来释放分配的内存。

 

也可以静态定义一个cdev,比如内嵌在某一个特定的设备结构体中,但在添加进系统之前则要调用cdev_init()函数来对cdev进行初始化。cdev_init()函数定义为:

---------------------------------------------------------------------

fs/char_dev.c

542 /**

543  * cdev_init() - initialize a cdev structure

544  * @cdev: the structure to initialize

545  * @fops: the file_operations for this device

546  *

547  * Initializes @cdev, remembering @fops, making it ready to add to

548  * the system with cdev_add().

549  */

550 void cdev_init(struct cdev *cdev, const struct file_operations *fops)

551 {

552         memset(cdev, 0, sizeof *cdev);

553         INIT_LIST_HEAD(&cdev->list);

554         kobject_init(&cdev->kobj, &ktype_cdev_default);

555         cdev->ops = fops;

556 }

---------------------------------------------------------------------

值得注意的就是cdev_init()在初始化cdev内嵌kobject时,其kobj_type设置的是ktype_cdev_default。其定义为:

---------------------------------------------------------------------

fs/char_dev.c

506 static void cdev_default_release(struct kobject *kobj)

507 {

508         struct cdev *p = container_of(kobj, struct cdev, kobj);

509         cdev_purge(p);

510 }

 

519 static struct kobj_type ktype_cdev_default = {

520         .release        = cdev_default_release,

521 };

---------------------------------------------------------------------

和上面的ktype_cdev_dynamic一样,也是只初始化了release字段,但不同的是release字段被初始化为了cdev_default_release()函数,而cdev_default_release()函数是不释放cdev结构体的(因为是静态定义的嘛)。

 

系统提供了cdev_add()函数向系统添加一个cdev字符设备。其定义为:

---------------------------------------------------------------------

fs/char_dev.c

480 int cdev_add(struct cdev *p, dev_t dev, unsigned count)

481 {

482      p->dev = dev;

483      p->count = count;

484   return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);

485 }

---------------------------------------------------------------------

它接收三个参数,p指向设备的cdev结构体,dev是设备管理的第一个设备号,count为对应于设备的次设备号的范围大小。

cdev_add()初始化cdev描述符的devcount字段,然后调用kobj_map()函数。kobj_map()函数定义为:

---------------------------------------------------------------------

drivers/base/map.c

int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,

         struct module *module, kobj_probe_t *probe,

         int (*lock)(dev_t, void *), void *data)

{

    unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;

    unsigned index = MAJOR(dev);

    unsigned i;

    struct probe *p;

 

    if (n > 255)

       n = 255;

 

    p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);

 

    if (p == NULL)

       return -ENOMEM;

 

    for (i = 0; i < n; i++, p++) {

       p->owner = module;

       p->get = probe;

       p->lock = lock;

       p->dev = dev;

       p->range = range;

       p->data = data;

    }

    mutex_lock(domain->lock);

    for (i = 0, p -= n; i < n; i++, p++, index++) {

       struct probe **s = &domain->probes[index % 255];

       while (*s && (*s)->range < range)

           s = &(*s)->next;

       p->next = *s;

       *s = p;

    }

    mutex_unlock(domain->lock);

    return 0;

}

---------------------------------------------------------------------

这个函数完成如下操作:

1、获得设备号所跨越的主设备号的范围大小,保存在局部变量n中。

2、获得第一个主设备号,保存在index中。

3、分配元素个数为nprobe结构体数组。如果分配失败,则返回-ENOMEM

4、初始化分配的probe结构体数组,每个元素都被初始化为相同的值,owner 字段为传递进来的module,对于cdev_add()来说为NULLget字段为传递进来的probe,对于cdev_add()来说为exact_match函数,lock字段为传递进来的lock参数,cdev_add()来说为exact_lock函数,dev字段为传递进来的dev参数,即初始设备号, range字段为传递进来的range参数,都为总的设备号范围大小,而data字段为设备号范围拥有者的私有数据,对于cdev_add()来说则为对应的cdev描述符地址。

probeget方法用于探测谁拥有这个设备号范围,lock方法用于增加设备号范围内拥有者的引用计数器,对于cdev_add(),这两个方法分别为:

---------------------------------------------------------------------

fs/char_dev.c

458 static struct kobject *exact_match(dev_t dev, int *part, void *data)

459 {

460         struct cdev *p = data;

461         return &p->kobj;

462 }

463

464 static int exact_lock(dev_t dev, void *data)

465 {

466         struct cdev *p = data;

467         return cdev_get(p) ? 0 : -1;

468 }

---------------------------------------------------------------------

5、将创建的nprobe对象添加进kobject映射域的散列表中,在散列冲突链表中,各个probe对象是按照设备号范围大小从小到大排列的。在分配设备号的时候,保证分配的设备号不会有重叠。

 

设备号管理

内核使用了散列表chrdevs来记录字符设备设备号的分配,表的大小不超过设备号范围。两个不同的设备号范围可能共享同一个主设备号,但是范围不能重叠,因此它们的次设备号应该完全不同。Chrdevs包含255个表项,但由于散列函数屏蔽了设备号的高四位,因此主设备号的个数少于255个,它们被散列到不透光的表项中。每个表项指向冲突链表的第一个元素,而该链表是按主次设备号的递增顺序进行排序的。冲突链表中的每一个元素是一个char_device_struct 结构。chrdevs定义为:

---------------------------------------------------------------------

fs/char_dev.c

49 static struct char_device_struct {

 50         struct char_device_struct *next;

 51         unsigned int major;

 52         unsigned int baseminor;

 53         int minorct;

 54         char name[64];

 55         struct cdev *cdev;              /* will die */

 56 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

---------------------------------------------------------------------

上面的个字段的意义基本上都比较清楚。minorct指的是次设备号范围的大小。

 

内核正是利用chrdevs散列表来管理字符设备设备号的管理的。内核提供了几个函数用以分配一个范围内的设备号。使用alloc_chrdev_region()register_chrdev_region()函数为驱动分配任意范围内的设备号。例如,为了获得从dev(类型为dev_t)开始的大小为size的一个设备号范围:

  register_chrdev_region(dev, size, “foo”);

使用register_chrdev()函数分配一个固定的设备号范围,该范围包含唯一一个主设备号以及0——255的次设备号。在这种情形下,设备驱动程序不必调用cdev_add()函数。

 

alloc_chrdev_region()函数和register_chrdev_region()函数

register_chrdev_region()函数注册一个设备号范围,它接收三个参数:需要的设备号范围的第一个设备号,必须包含主设备号,请求的连续设备号的数量,设备或驱动的名字。分配成功的时候,返回0,否则返回一个负的错误码。register_chrdev_region()函数定义为:

---------------------------------------------------------------------

fs/char_dev.c

193 int register_chrdev_region(dev_t from, unsigned count, const char *name)

194 {

195         struct char_device_struct *cd;

196         dev_t to = from + count;

197         dev_t n, next;

198

199         for (n = from; n < to; n = next) {

200                 next = MKDEV(MAJOR(n)+1, 0);

201                 if (next > to)

202                         next = to;

203                 cd = __register_chrdev_region(MAJOR(n), MINOR(n),

204                                next - n, name);

205                 if (IS_ERR(cd))

206                         goto fail;

207         }

208         return 0;

209 fail:

210         to = n;

211         for (n = from; n < to; n = next) {

212                 next = MKDEV(MAJOR(n)+1, 0);

213                 kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));

214         }

215         return PTR_ERR(cd);

216 }

---------------------------------------------------------------------

这个函数将设备号范围划分为属于一个个不同主设备号的小的范围,然后在每个相应的小的范围上调用__register_chrdev_region()

 

 

alloc_chrdev_region()函数动态的为驱动程序分配设备号,它接收四个参数:dev用于返回分配的第一个设备号,baseminor为请求范围的第一个次设备号,count为请求的次设备号的数量,name为相关的设备或驱动的名称。分配成功返回0,否则返回负的错误码。alloc_chrdev_region()函数的定义为:

---------------------------------------------------------------------

fs/char_dev.c

229 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,

230                         const char *name)

231 {

232         struct char_device_struct *cd;

233         cd = __register_chrdev_region(0, baseminor, count, name);

234         if (IS_ERR(cd))

235                 return PTR_ERR(cd);

236         *dev = MKDEV(cd->major, cd->baseminor);

237         return 0;

238 }

---------------------------------------------------------------------

结束时,它也调用__register_chrdev_region()函数。

 

__register_chrdev_region()函数针对一个主设备号来注册一个给定的次设备号范围,如果给定的主设备号为0,则将自动分配。如果主设备号大于0,则将分配给定的次设备号范围的设备号,成功时返回char_device_struct描述符地址,出错时返回错误码。其定义如下:

---------------------------------------------------------------------

fs/char_dev.c

58 /* index in the above */

59 static inline int major_to_index(int major)

60 {

61         return major % CHRDEV_MAJOR_HASH_SIZE;

62 }

 

91 static struct char_device_struct *

 92 __register_chrdev_region(unsigned int major, unsigned int baseminor,

 93                            int minorct, const char *name)

 94 {

 95         struct char_device_struct *cd, **cp;

 96         int ret = 0;

 97         int i;

 98

 99         cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);

100         if (cd == NULL)

101                 return ERR_PTR(-ENOMEM);

102

103         mutex_lock(&chrdevs_lock);

104

105         /* temporary */

106         if (major == 0) {

107                 for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {

108                         if (chrdevs[i] == NULL)

109                                 break;

110                 }

111

112                 if (i == 0) {

113                         ret = -EBUSY;

114                         goto out;

115                 }

116                 major = i;

117                 ret = major;

118         }

119

120         cd->major = major;

121         cd->baseminor = baseminor;

122         cd->minorct = minorct;

123         strlcpy(cd->name, name, sizeof(cd->name));

124

125         i = major_to_index(major);

126

127         for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)

128                 if ((*cp)->major > major ||

129                     ((*cp)->major == major &&

130                      (((*cp)->baseminor >= baseminor) ||

131                       ((*cp)->baseminor + (*cp)->minorct > baseminor))))

132                         break;

133

134         /* Check for overlapping minor ranges.  */

135         if (*cp && (*cp)->major == major) {

136                 int old_min = (*cp)->baseminor;

137                 int old_max = (*cp)->baseminor + (*cp)->minorct - 1;

138                 int new_min = baseminor;

139                 int new_max = baseminor + minorct - 1;

140

141                 /* New driver overlaps from the left.  */

142                 if (new_max >= old_min && new_max <= old_max) {

143                         ret = -EBUSY;

144                         goto out;

145                 }

146

147                 /* New driver overlaps from the right.  */

148                 if (new_min <= old_max && new_min >= old_min) {

149                         ret = -EBUSY;

150                         goto out;

151                 }

152         }

153

154         cd->next = *cp;

155         *cp = cd;

156         mutex_unlock(&chrdevs_lock);

157         return cd;

158 out:

159         mutex_unlock(&chrdevs_lock);

160         kfree(cd);

161         return ERR_PTR(ret);

162 }

---------------------------------------------------------------------

这个函数执行以下步骤:

1、分配一个新的char_device_struct结构,并用0填充。

 

2、如果设备号范围内的主设备号为0,那么设备驱动程序请求动态分配一个主设备号。函数从散列表的末尾表项开始继续向前寻找一个尚未使用的主设备号对应的空冲突链表(NULL指针)。若没有找到,则返回-EBUSY。若找到,则设置major为找到的主设备号。在这里,我们可以看到,动态分配设备号的一些局限性,那就是其主设备好不能够大于255,而实际上主设备号通常为12位的。同时也没有办法多个设备共用主设备号,这对于设备号资源来说岂不是一大浪费。

 

3、初始化char_device_struct结构中的主设备号、初始次设备号、范围大小以及设备或驱动程序名称。

 

4、执行散列函数major_to_index(major),计算与主设备号对应的散列表索引。

 

5、遍历冲突链表,为新的char_device_struct结构寻找正确的位置。在冲突链表中,按主设备号由小到大排列,若主设备号相同,则按次设备号由小到大排列。如果找到与请求的设备号范围重叠的一个范围,则释放先前分配的char_device_struct结构并返回-EBUSY

 

6、将新的char_device_struct结构描述符插入冲突链表中。

 

7、返回新的char_device_struct描述符的地址。

 

register_chrdev()函数

驱动程序使用register_chrdev()函数时需要一个老式的设备号范围:一个单独的0——255的此设备号范围。干函数接收的参数为:请求的主设备号major(如果是0,则动态分配)、设备驱动程序的名称name和一个指针fops(它指向设备号范围内的特定字符设备文件的文件操作表)。该函数定义如下:

---------------------------------------------------------------------

include/linux/fs.h

2005 static inline int register_chrdev(unsigned int major,

2006            const char *name, const struct file_operations *fops)

2007 {

2008         return __register_chrdev(major, 0, 256, name, fops);

2009 }

---------------------------------------------------------------------

它只是对__register_chrdev()函数的一个封装而已,而__register_chrdev()函数定义为:

---------------------------------------------------------------------

fs/char_dev.c

261 int __register_chrdev(unsigned int major, unsigned int baseminor,

262                       unsigned int count, const char *name,

263                       const struct file_operations *fops)

264 {

265         struct char_device_struct *cd;

266         struct cdev *cdev;

267         int err = -ENOMEM;

268

269         cd = __register_chrdev_region(major, baseminor, count, name);

270         if (IS_ERR(cd))

271                 return PTR_ERR(cd);

272        

273         cdev = cdev_alloc();

274         if (!cdev)

275                 goto out2;

276

277         cdev->owner = fops->owner;

278         cdev->ops = fops;

279         kobject_set_name(&cdev->kobj, "%s", name);

280                

281         err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);

282         if (err)

283                 goto out;

284

285         cd->cdev = cdev;

286

287         return major ? 0 : cd->major;

288 out:

289         kobject_put(&cdev->kobj);

290 out2:

291         kfree(__unregister_chrdev_region(cd->major, baseminor, count));

292         return err;

293 }

---------------------------------------------------------------------

__register_chrdev()函数执行下列操作:

1、调用__register_chrdev_region(major, baseminor, count, name)函数分配请求的设备号范围。如果返回一个错误码(不能分配该范围),函数将终止运行。

 

2、为设备分配一个cdev结构。

 

3、初始化cdev->ownerfops->owner,初始化cdev->opsfops,将设备或驱动的名称拷贝到cdev内嵌的kobject结构的那么字段中。

 

4、调用cdev_add()函数(前面解释过)。

 

5、将__register_chrdev_region()函数在第1步中返回的char_device_struct描述符的cdev字段值为设备驱动程序的cdev描述符的地址。

 

6、返回分配的设备号范围的主设备号。

 

字符设备文件的VFS处理

当进程访问普通文件时,它会通过文件系统访问磁盘分区中的一些数据;而在进程访问设备文件时,它只要驱动硬件设备就可以了。为应用程序隐藏设备文件与普通文件之间的差异正是VFS的责任。为了做到这点,VFS在设备文件打开时改变其缺省文件操作;因此,可以把设备文件的每个系统调用都转换成与设备相关的函数的调用,而不是对主文件系统相应函数的调用。与设备相关的函数对设备进行操作以完成进程所请求的操作。

 

在设备文件上执行open()系统调用,从本质上说,相应的服务例程解析到设备文件的路径名,并建立相应的索引节点对象、目录项对象和文件对象。

 

通过适当的文件系统函数读取磁盘上相应索引节点来对索引节点对象进行初始化(通常为文件系统目录inode节点表的lookup方法)。当这个函数确定磁盘索引节点与设备文件对应时,则调用init_special_inode(),该函数把索引节点对象的i_rdev字段初始化为设备文件的主设备号和次设备号,而把索引节点对象的i_fop字段设为def_chr_fopsdef_blk_fops文件操作表的地址(根据设备文件的类型)。

---------------------------------------------------------------------

fs/inode.c

 

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

1595 {

1596         inode->i_mode = mode;

1597         if (S_ISCHR(mode)) {

1598                 inode->i_fop = &def_chr_fops;

1599                 inode->i_rdev = rdev;

1600         } else if (S_ISBLK(mode)) {

1601                 inode->i_fop = &def_blk_fops;

1602                 inode->i_rdev = rdev;

1603         } else if (S_ISFIFO(mode))

1604                 inode->i_fop = &def_fifo_fops;

1605         else if (S_ISSOCK(mode))

1606                 inode->i_fop = &bad_sock_fops;

1607         else

1608                 printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"

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

1610                                   inode->i_ino);

1611 }

1612 EXPORT_SYMBOL(init_special_inode);

---------------------------------------------------------------------

open()系统调用的服务例程也调用__dentry_open()函数(sys_open()-> do_sys_open()->do_filp_open()->do_last()->finish_open()->nameidata_to_filp()-> __dentry_open()),后者把一个新文件对象的f_op字段设置为i_fop中存放的地址,即再一次指向def_chr_fopsdef_blk_fops文件操作表的地址。正是这两个表的引入,才使得在设备文件上所发出的任何系统调用都将激活设备驱动程序的函数而不是基本文件系统的函数。

 

def_chr_fops文件操作表几乎为空,它仅仅只定义了open()方法。这个方法有__dentry_open()函数直接调用。chrdev_open()接收的参数为设备文件索引节点的地址inode,指向所打开文件对象的指针filp。这个函数定义如下:

---------------------------------------------------------------------

fs/char_dev.c

366 static int chrdev_open(struct inode *inode, struct file *filp)

367 {

368         struct cdev *p;

369         struct cdev *new = NULL;

370         int ret = 0;

371

372         spin_lock(&cdev_lock);

373         p = inode->i_cdev;

374         if (!p) {

375                 struct kobject *kobj;

376                 int idx;

377                 spin_unlock(&cdev_lock);

378                 kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);

379                 if (!kobj)

380                         return -ENXIO;

381                 new = container_of(kobj, struct cdev, kobj);

382                 spin_lock(&cdev_lock);

383                 /* Check i_cdev again in case somebody beat us to it while

384                    we dropped the lock. */

385                 p = inode->i_cdev;

386                 if (!p) {

387                         inode->i_cdev = p = new;

388                         list_add(&inode->i_devices, &p->list);

389                         new = NULL;

390                 } else if (!cdev_get(p))

391                         ret = -ENXIO;

392         } else if (!cdev_get(p))

393                 ret = -ENXIO;

394         spin_unlock(&cdev_lock);

395         cdev_put(new);

396         if (ret)

397                 return ret;

398

399         ret = -ENXIO;

400         filp->f_op = fops_get(p->ops);

401         if (!filp->f_op)

402                 goto out_cdev_put;

403

404         if (filp->f_op->open) {

405                 ret = filp->f_op->open(inode,filp);

406                 if (ret)

407                         goto out_cdev_put;

408         }

409

410         return 0;

411

412  out_cdev_put:

413         cdev_put(p);

414         return ret;

415 }

---------------------------------------------------------------------

这个函数完成以下操作:

1、设置cdev指针类型局部变量pinode->i_cdev。检查p指向的设备驱动程序的cdev描述符。如果不为空,则inode结构已经被访问,跳转到第6步。

 

2、调用kobj_lookup(cdev_map, inode->i_rdev, &idx)函数搜索包括该设备号在内的范围。kobj_lookup()函数定义为:

---------------------------------------------------------------------

drivers/base/map.c

int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,

         struct module *module, kobj_probe_t *probe,

         int (*lock)(dev_t, void *), void *data)

{

    unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;

    unsigned index = MAJOR(dev);

    unsigned i;

    struct probe *p;

 

    if (n > 255)

       n = 255;

 

    p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);

 

    if (p == NULL)

       return -ENOMEM;

 

    for (i = 0; i < n; i++, p++) {

       p->owner = module;

       p->get = probe;

       p->lock = lock;

       p->dev = dev;

       p->range = range;

       p->data = data;

    }

    mutex_lock(domain->lock);

    for (i = 0, p -= n; i < n; i++, p++, index++) {

       struct probe **s = &domain->probes[index % 255];

       while (*s && (*s)->range < range)

           s = &(*s)->next;

       p->next = *s;

       *s = p;

    }

    mutex_unlock(domain->lock);

    return 0;

}

---------------------------------------------------------------------

kobj_lookup()函数使用一个循环来查找设备号包含于其范围内的probe结构体。如果范围不存在,则返回NULL,否则,返回设备cdev结构内嵌的kobject对象的地址。

 

3、调用container_of(kobj, struct cdev, kobj)获得cdev结构体的地址,将这个地址赋给局部cdev指针变量new

 

4、获得cdev_lock锁,将inode->i_cdev赋值给局部变量p,再一次检查指向设备驱动程序的cdev描述符的指针inode->i_cdev,若为NULL,则使inode->i_cdev为新查找到的cdev对象的地址。并将inode添加进cdev的索引节点链表list中。设置局部变量newNULL。跳到第7步。

 

5、否则,再一次检查的时候inode->i_cdev已为有效值,则增加cdev的引用计数,则增加p指向的对象的引用计数。,若失败,则设置ret值为-ENXIO,并跳转到第7步。

 

6inode->i_cdev一开始即是有效的,则增加其引用计数,若失败,则设置ret值为-ENXIO

 

7、解锁cdev_lock,释放new指向的cdev对象。检查ret值,若为非零值,则返回ret值。

 

8、将filp->f_op文件操作指针赋值为cdev描述符的ops字段的值。

 

9、如果filp->f_opNULL,则释放p指向的cdev,并返回-ENXIO

 

10、如果定义了filp->f_op->open方法,chrdev_open()函数就会执行该方法。若该方法返回错误码,则释放cdev,并返回-ENXIO。若设备驱动程序处理一个以上的设备号,则chrdev_open()一般会再次设置file对象的文件操作,这样可以为所访问的设备文件安装合适的文件操作方法。

 

11、成功时返回0

阅读(2427) | 评论(0) | 转发(0) |
0

上一篇:sysfs 后记

下一篇:硬盘数据部分结构

给主人留下些什么吧!~~