程序员人生 网站导航

[置顶] 图解linux char驱动

栏目:综合技术时间:2015-01-06 09:04:05

                                                                             图解Linux char 驱动

 主题:                                                                                                                                                                               
   1.字符驱动模型。
   2.字符装备的装备号。
   3.文件系统中对字符装备的访问。

   1:字符装备框架
 
        写的驱动程序只是1个程序,
        然后将底层硬件驱动抽象出来用1个结构体来表示cdev。
        那末我们需要做的就是从用户空间写的open、write、read等系统调用来操作硬件,
        通过系统调用进入内核,而内核的实现就是利用了1切皆文件的思想,
       就是上层直接操作文件但实质底层就是在操作硬件,
     这个转换就是VFS。
   
  2:大概的进程:
    大概进程就是说open(“/dev/first_drv”,..)
    会进入内核->创建1个file结构体->该结构体指向->inode
    ->通过主装备号查找到cdev结构(即是驱动)->找到cdev指向的操作函数指针->找到.open->自己的open函数。
   
  3:下面是相干的详细说明底层如何实现:
  相干数据结构:

25 struct cdev { 26 struct kobject kobj; 27 struct module *owner; 28 const struct file_operations *ops; 29 struct list_head list; 30 dev_t dev; 31 unsigned int count; 32 }; 33 34 struct kobj_map { 35 36 struct probe { 37 38 struct probe *next; 39 dev_t dev; 40 unsigned long range; 41 struct module *owner; 42 kobj_probe_t *get; 43 int (*lock)(dev_t, void *); 44 void *data; 45 } *probes[255]; 46 struct mutex *lock; 47 }; 48 49 static struct char_device_struct { 50 51 struct char_device_struct *next; 52 unsigned int major; 53 unsigned int baseminor; 54 int minorct; 55 char name[64]; 56 struct file_operations *fops; 57 struct cdev *cdev; /* will die */ 58 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; 59 60 #define CHRDEV_MAJOR_HASH_SIZE 255


 1.字符装备驱动模型。
    每个字符设驱动有1个cdev结构提表示。
    再装备驱动模型那个(device driver model)中。使用(kobject mapping domain)
    来记录字符装备文件驱动。这是由struct kobj_map结构体来表示。
    它内嵌了255个struct probe指针数组。kobj_map由全局变量cdev_map援用。
    static struct kobj_map *cdev_map; 这个全局变量的定义在char_dev.c中。

70 /------------- 71 | cdev_map | 72 -------------/ 73 | 74 | /------> probe /------> probe 75 | | +---------+ | +---------+ /------- 76 | | | *next |----/ | *next |----->| NULL | 77 --->kobj_map | +---------+ +---------+ -------/ 78 +-------------------+ | | dev | | dev | 79 | *probes[255] | | +---------+ +---------+ 80 | +-----------+ | | | range | | range | 81 | | *probe | | | +---------+ +---------+ 82 | +-----------+ | | | *owner | | *owner | 83 | | *probe |---------/ +---------+ +---------+ 84 | +-----------+ | | *get | | *get | 85 | | .... | | +---------+ +---------+ 86 | +-----------+ | | *lock | | *lock | 87 | | *probe | | +---------+ +---------+ 88 | +-----------+ | | *data |------ | *data |------ 89 | | *probe |--------- +---------+ | +---------+ | 90 | +-----------+ | | | | 91 +------------------- | | | 92 | | | 93 | | | 94 | /------- | | 95 ----->| NULL | | | 96 -------/ | | 97 | | 98 cdev<-----------/ cdev<----/ 99 +---------+ +---------+ 100 | *kobj | | *kobj | 101 +---------+ +---------+ 102 | *owner | | *owner | 103 +---------+ +---------+ 104 | *ops | | *ops | 105 +---------+ +---------+ 106 | *list | | *list | 107 +---------+ +---------+ 108 | dev | | dev | 109 +---------+ +---------+ 110 | *count | | *count | 111 +---------+ +---------+


1:cdev_add()函数详解。

1.

  1般会使用kzalloc(size,GFP_KERNEL)给cdev分配1块空间,然后初始化好,ops对应的结构体。  

2.
  再使用cdev_init把ops函数操作集和cdev绑定起来。
 
3.
   使用cdev_add() 用来将cdev对象添加到驱动模型中,其主要是通过kobj_map()来实现的.
kobj_map() 会创建1个probe对象,然后将其插入cdev_map中的某1项中,并关联probe->data 指向 cdev

 4.

   struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
根据装备号,在cdev_map中查找其cdev对象内嵌的kobject. (probe->data->kobj),返回的是cdev的kobject

int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range, 133 struct module *module, kobj_probe_t *probe, 134 int (*lock)(dev_t, void *), void *data) 135 { 136 137 unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; 138 unsigned index = MAJOR(dev); 139 unsigned i; 140 struct probe *p; 141 142 if (n > 255) 143 n = 255; 144 //为prob结构体分配空间。 145 p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL); 146 if (p == NULL) 147 return -ENOMEM; 148 149 //给结构体赋值。 150 for (i = 0; i < n; i++, p++) { 151 p->owner = module; 152 p->get = probe; 153 p->lock = lock; 154 p->dev = dev; 155 p->range = range; 156 p->data = data; 157 } 158 mutex_lock(domain->lock); 159 160 //把分配的结构体probe寄存到该hash表中。结构如上。 161 for (i = 0, p -= n; i < n; i++, p++, index++) { 162 struct probe **s = &domain->probes[index % 255]; 163 while (*s && (*s)->range < range) 164 s = &(*s)->next; 165 p->next = *s; 166 *s = p; 167 } 168 mutex_unlock(domain->lock); 169 return 0; 170 }



 2:字符装备的装备号。


174 0 1 2 253 254 175 +--------+--------+--------+--------+--------+-------- 176 cdevs | | | | .... | | | 177 +--------+--------+--------+--------+--------+-------- 178 | | | | 179 | | | | 180 | | | | 181 /------- | | | | 182 | NULL |<--/ | | | 183 -------/ | | | /------- 184 | | ------>| NULL | 185 | | -------/ 186 /-----------------/ /------- 187 | | NULL | 188 | -------/ 189 | 190 ------>char_devices_struct /----> char_devices_struct 191 +----------------+ | +----------------+ /------- 192 | *next |--------/ | *next |------>| NULL | 193 +----------------+ +----------------+ -------/ 194 | major | | major | 195 +----------------+ +----------------+ 196 | vaseminor | | vaseminor | 197 +----------------+ +----------------+ 198 | *name[64] | | *name[64] | 199 +----------------+ +----------------+ 200 | *fops | | *fops | 201 +----------------+ +----------------+ 202 | *cdev | | *cdev | 203 +----------------+ +----------------+

 字符装备的主,次装备号的分配:

 1.
    全局数组 chrdevs 包括了255(CHRDEV_MAJOR_HASH_SIZE 的值)个struct char_device_struct的元素.
 每个对应1个相应的主装备号.

2.
  分配了1个装备号,就会创建1个 struct char_device_struct 的对象,
并将其添加到chrdevs中.这样,通过chrdevs数组,我们就能够知道分配了哪些装备号.

 相干函数:
 1.  register_chrdev_region( ) 分配指定的装备号范围
  2.alloc_chrdev_region( ) 动态分配装备范围他们都主要是通过调用函数__register_chrdev_region() 来实现的
      要注意,这两个函数仅仅是注册装备号! 如果要和cdev关联起来,还要调用cdev_add()

   register_chrdev( ) 申请指定的装备号,并且将其注册到字符装备驱动模型中.
  它所做的事情为:
   1. 注册装备号, 通过调用 __register_chrdev_region() 来实现
   2. 分配1个cdev, 通过调用 cdev_init()来实现
   3. 将cdev添加到驱动模型中,这1步将装备号和驱动关联了起来.通过调用 cdev_add() 来实现
   4. 将第1步中创建的 struct char_device_struct 对象的 cdev 指向第2步中分配的cdev. 由于register_chrdev()是老的接口,
       这1步在新的接口中其实不需要.

236 /-------------------------------------->cdev<------------------------------------ 237 | +---------+ | 238 | | *kobj | | 239 | +---------+ | 240 | | *owner | | 241 | +---------+ | 242 | | *ops | | 243 | +---------+ | 244 | /----------------------------->| *list |<--------------------------- | 245 | | +---------+ | | 246 | | | dev | | | 247 | | +---------+ | | 248 | | | *count | | | 249 | | +---------+ | | 250 | | | | 251 | | inode inode | | 252 | | +---------+ +---------+ | | 253 | | | i_rdev | | i_rdev | | | 254 | | +---------+ +---------+ | | 255 | ----------|i_devices|<----------.........--------->|i_devices|<------/ | 256 | +---------+ +---------+ | 257 --------------- |*i_cdev | |*i_cdev |------------/ 258 +---------+ +---------+ 259 |i_cindex | |i_cindex | 260 +---------+ +---------+ 261 | ..... | | ..... | 262 +---------+ +---------+

 

系统调用open打开1个字符装备的时候, 通过1系列调用,终究会履行到 chrdev_open.
    (终究是通过调用到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open. 这1系列的调用进程,本文暂不讨论)

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

     chrdev_open()所做的事情可以概括以下:
   1. 根据装备号(inode->i_rdev), 在字符装备驱动模型中查找对应的驱动程序,
        这通过kobj_lookup() 来实现, kobj_lookup()会返回对应驱动程序cdev的kobject.   
    2. 设置inode->i_cdev , 指向找到的cdev  
    3. 将inode添加到cdev->list的链表中.
    4. 使用cdev的ops 设置file对象的f_op
    5. 如果ops中定义了open方法,则调用该open方法
    6. 返回.
  履行完chrdev_open()以后,file对象的f_op指向cdev的ops,因此以后对装备进行的read, write等操作,就会履行cdev的相应操作.




285 static int chrdev_open(struct inode *inode, struct file *filp) 286 { 287 288 struct cdev *p; 289 struct cdev *new = NULL; 290 int ret = 0; 291 292 spin_lock(&cdev_lock); 293 p = inode->i_cdev; 294 if (!p) { 295 struct kobject *kobj; 296 int idx; 297 spin_unlock(&cdev_lock); 298 299 // 1. 根据装备号(inode->i_rdev), 在字符装备驱动模型中查找对应的驱动程序, 300 //这通过kobj_lookup() 来实现, kobj_lookup()会返回对应驱动程序cdev的kobject. 301 302 kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); 303 if (!kobj) 304 return -ENXIO; 305 new = container_of(kobj, struct cdev, kobj); 306 spin_lock(&cdev_lock); 307 /* Check i_cdev again in case somebody beat us to it while 308 we dropped the lock. */ 309 310 //2. 设置inode->i_cdev , 指向找到的cdev. 311 p = inode->i_cdev; 312 if (!p) { 313 inode->i_cdev = p = new; 314 315 //3. 将inode添加到cdev->list的链表中. 316 list_add(&inode->i_devices, &p->list); 317 318 new = NULL; 319 } else if (!cdev_get(p)) 320 ret = -ENXIO; 321 } else if (!cdev_get(p)) 322 ret = -ENXIO; 323 spin_unlock(&cdev_lock); 324 cdev_put(new); 325 if (ret) 326 return ret; 327 328 ret = -ENXIO; 329 330 //4. 使用cdev的ops 设置file对象的f_op 331 filp->f_op = fops_get(p->ops); 332 if (!filp->f_op) 333 goto out_cdev_put; 334 335 if (filp->f_op->open) { 336 5. 如果ops中定义了open方法,则调用该open方法 337 ret = filp->f_op->open(inode, filp); 338 if (ret) 339 goto out_cdev_put; 340 } 341 342 return 0; }


 
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐