字符设备驱动框架分析(基于linux2.6内核)(更新中2018-10-19)

字符设备驱动框架分析(基于linux2.6内核)

情景分析:

     对于驱动,我们并不陌生,在linux端,由应用软件操作一个硬件设备,一般是不是直接去操作的,并不像单片机一样,我们是通过向linux内核申请设备节点,申请成功的设备节点在/dev目录,从而操作dev目录下的设备节点,控制我们的硬件设备,其中字符设备驱动框架是常见的一种驱动类型(字符设备、块设备、网络设备驱动三大类型)

那么在dev目录下的设备节点是怎么来的呢??很简单,当然是加载我们的驱动程序生成的,linux才不会那么智能,知道我有哪些外设,就生成对应的外设的,linux只是提供一个框架给我们,需要我们自己去编写对应的驱动,这样一来的好处就是在linux上操作硬件的时候,硬件和软件就直接分离开了,方便我们软件开发人员去操作硬件设备,而不需要我们软件人员了解硬件接口,那么怎么去加载这个设备节点呢??

下面我们将从如何生成这个设备节点开始讲起:由于是在linux系统下面,要想在linux系统下面生成设备节点,肯定是需要向linux内核申请的,linux内核中申请成功,才会给你一个唯一的一个设备节点(主设备号、次设备号),在linux内核中有这样的一个结构体struct cdev;linux2.6内核中定义该结构体的文件是linux-2.6.32.1\include\linux\ cdev.h,这个结构体将是连接软件和硬件的桥梁,包括申请的设备号(主设备号、次设备号),/dev目录下生成的文件名称、支持哪些操作

struct cdev {

   struct kobject kobj; /*基于kobject*/
   struct module *owner; /*所属模块*/

              const struct file_operations *ops; /*设备文件操作函数集*/

   struct list_head list; /*挂载设备的链表头*/
   dev_t dev; /*设备号而dev_t则为U32类型的也就是32位,其中12位为主设备号,20位为次设备号*/
   unsigned int count; /*该种类型设备数目*/

};

(一)、将设备号注册到内核中

  1. 动态注册

alloc_chrdev_region();-à由内核注册,自动分配没有使用的设备号

底层还是在调用__register_chrdev_region()函数该函数有2个功能,如果第一个参数传入的是0,那么会自动扫描全局变量chrdevs,根据填充的数字,继续往下给自动分配一个主设备号

  1. 静态注册(需要申请设备号)

int register_chrdev_region(dev_t from, unsigned count, const char *name)--à由程序员自己控制,有可能会导致注册失败,一般在注册失败后在转为自动注册函数源文件的路径在:linux-2.6.32.1\fs\Char_dev.c下面我们看register_chrdev_region的参数:

dev_t from:设备号(包括主设备号和次设备号起始数),将主设备号和次设备号起始数转换成dev_t类型的一个内核函数,MKDEV原型是#define MKDEV(ma,mi) ((ma)<<20 | (mi)) 这句话的意思就是主设备号是第20位-31位表示,次设备号是0-20位表示,换句话说就是低20位表示次设备号,高12位表示主设备号

unsigned count:次设备号的个数

const char *name:名称节点

register_chrdev_region()

                             --à __register_chrdev_region()在该函数内将一个全局变量chrdevs[]填充;

自动注册也好、手动注册也罢,其实底层都是在调用__register_chrdev_region()函数。还有一个就是直接调用__register_chrdev_region()函数,通过控制第一个参数是否为0,来控制自动注册和静态注册,这样可能看起来会更清楚,以上是对跟内核相关的操作,说到底,还是没有发现是怎么操作设备的,请看下面

(二)、初始化cdev结构体cdev_init();

函数原型:

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

{

          memset(cdev, 0, sizeof *cdev);

     INIT_LIST_HEAD(&cdev->list);  //初始化链表头,指向自己

          kobject_init(&cdev->kobj, &ktype_cdev_default);//初始化kobj(有点看不懂,单独拿出来进行分析)

          cdev->ops = fops;//将驱动里面写的fops函数注册到系统中

}

struct kobject {

          const char                   *name;

          struct list_head   entry;

          struct kobject               *parent;

          struct kset                  *kset;

          struct kobj_type   *ktype;

          struct sysfs_dirent          *sd;

          struct kref                  kref;

          unsigned int state_initialized:1;

          unsigned int state_in_sysfs:1;

          unsigned int state_add_uevent_sent:1;

          unsigned int state_remove_uevent_sent:1;

          unsigned int uevent_suppress:1;

};

struct kobj_type {

          void (*release)(struct kobject *kobj);

          struct sysfs_ops *sysfs_ops;

          struct attribute **default_attrs;

};

 

分析函数kobject_init(struct kobject *kobj, struct kobj_type *ktype)

{

          char *err_str;

          if (!kobj) {  //输入参数1判断

                   err_str = "invalid kobject pointer!";

                   goto error;

          }

          if (!ktype) {//输入参数2判断

                   err_str = "must have a ktype to be initialized properly!\n";

                   goto error;

          }

          if (kobj->state_initialized) {      //指明kobject是否有被初始化

                   /* do not error out as sometimes we can recover */

                   printk(KERN_ERR "kobject (%p): tried to init an initialized "

                          "object, something is seriously wrong.\n", kobj);

                   dump_stack();

          }

          kobject_init_internal(kobj);

          kobj->ktype = ktype;

          return;

error:

          printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);

          dump_stack();

}

继续对上面的函数进行分析。

前面先对输入的2个参数进行参数校验,不需要管,

在判断传入的参数1是否被初始化(根据state_initialized成员变量)最重要的就是下面的2行代码:

1kobject_init_internal(kobj);

2kobj->ktype = ktype;

kobj->ktype = ktype ---》1、先分析第二行代码kobj->ktype = ktype代码很好理解就是将传入的struct kobj_type  类型的变量 ktype赋值到kobj变量中,那么问题来了?传入的参数2从哪里来的?

参数2 :

ktype_cdev_default:是一个全局变量,结构体是

struct kobj_type {

          void (*release)(struct kobject *kobj);

          struct sysfs_ops *sysfs_ops;

          struct attribute **default_attrs;

};

其中一个是void (*release)(struct kobject *kobj)是不是跟第一个参数的类型一样的呢????

是一个static的变量,继续填充该结构体

static  struct kobj_type ktype_cdev_dynamic = {

          .release  = cdev_dynamic_release,

};

可以理解的就是cdev_dynamic_release 类型是struct kobject,我们会想,会不会是用这个结构体去初始化我们自己定义的变量struct cdev cdev 变量kobj的呢??

我们继续追踪cdev_dynamic_release

 

static void cdev_dynamic_release(struct kobject *kobj)

{

          struct cdev *p = container_of(kobj, struct cdev, kobj);//看不懂。。。。不做分析,差不多就是对kobj做处理,内核处理

          cdev_purge(p);//看不太懂,大致上就是初始化一个链表,用来存储节点信息的

          kfree(p);

}

 

在分析第一行代码kobject_init_internal(kobj);

{

          if (!kobj)

                   return;

          kref_init(&kobj->kref);

          INIT_LIST_HEAD(&kobj->entry);

          kobj->state_in_sysfs = 0;

          kobj->state_add_uevent_sent = 0;

          kobj->state_remove_uevent_sent = 0;

          kobj->state_initialized = 1; //表示被初始化过

}

总结:cdev_init()函数主要是对我们自己定义的变量struct cdev _cdev进行一系列的初始化,包括建立链表,初始化表头,更包括初始化的标志位置1,好比是申请了一个容器,用来装我们的设备信息的。。。。

 

 

(三)、有的人就想问了,我们一开始我们在第一步申请了设备号,到底有什么用??不要急,慢慢来,既然我们有了第一步设备号信息,第二步还初始化了容器,建立了一些链表,第三步是不是需要将这第一步申请的设备号信息添加到容器中呢???果然,内核果然给我们提供了这个接口,int cdev_add(struct cdev *p, dev_t dev, unsigned count)连传入的参数类型都貌似一样的哦??

第一个参数是我们初始化的容器,链表,struct cdev类型的,

第二个参数是设备号dev_t类型的,

第三个参数是不是就是注册的次设备号的最大个数呢??

请看源代码

{

          p->dev = dev;

          p->count = count;

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

}

果然,跟我们猜想的一模一样,将p->dev填充devp->count填充count到容器链表中,

下面分析:kobj_map

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;

}

--------------分析不下去了,凉凉。。。可以得出结论就是将设备节点等加到系统中。

 

(四)、创建类------待更新中 10-19

猜你喜欢

转载自blog.csdn.net/weixin_38638777/article/details/83185145