linux设备模型

写了几个小驱动,感觉还是没有摸清linux驱动的脉络,因此很有必要学习一下linux的设备模型,从整体上理解设备驱动

找到了这篇文章,写的太好了,就需要这样的讲解,收藏


地址http://blog.sina.com.cn/s/blog_6774b7c10101aw04.html


一、作为开头篇,我不想写HELLLOWORLD驱动,甚至字符设备驱动的开发,这样文章充斥在各大网站上的博客上,随便搜搜,就可以找到几百篇。这是最基本的东西,通过这些内容的学习,我们要掌握LINUX驱动的基本要素,比如初始化函数,退初函数,以及去理解简单的驱动的MAKE FILE的编写,推荐去看LDD,这方面有比较详细的叙述。

但是我的理解,即使我们会写这些东西,对我们的工作也没有太大的用处,如果你深入去读LINUX的驱动代码,就会发现,你还是云里雾里。比如触摸屏是字符设备,可是怎么我看到的触摸屏驱动里根本看不到一点字符设备的影子。OH OH..., 因此我觉得学习LINUX驱动,要跳出一个圈子,不能把自己局限在某个驱动的编写上,而应该把重心放在模型或者架构的层次上去掌握它,只有这样才能更快地深入地理解LINUX驱动的精髓。

因此在看过HELLWORLD或者写过一个简单的字符设备驱动之后,应该迅速地去学习LINUX的设备模型,这是我们必须第一个要学习的模型,也是最重要一个模型。如果你不能理解它,你将掉进驱动的泥潭里。

 

学习之前,不要忘记,源码,源码!!!在看任何关于LINUX的文章的时候,都要把SOURCEINSIGHT打开,随时要做好准备去查源码,源码是理解任何LINUX驱动的捷径。

不要忘记这个目录Documentation,你会有意想不到的发现,这是LINUX开发者留给我们的精华。

 

LINUX设备模型四要素

linux设备模型的抽象是总线、设备、驱动,类。按照这个顺序来分析就可以勾勒出linux设备模型。

很多人看设备模型,会选择直接去学习Kobject、Kset 和 Subsystem(在2.6其实也是KSET),进入了个大坑让人直接对LINUX驱动模型的理解望而生畏。这么复杂的东西,实在太难以理解了。我们是不是可以试着先忽略掉它们,你们是谁呀,我不想知道你们,滚一边去。

我们先简单的讲一下总线、设备、驱动的关系(类到下一节再来说)。在LINUX驱动的世界里,所有的设备和驱动都是挂在总线上的,也就是总线来管理设备和驱动的,总线知道挂在它上边的所有驱动和设备的情况,由总线完成驱动和设备的匹配和探测。至于怎么实现,以后再讲,现在只要姐的这一点就可以了。让我们想象以前我们工作碰到的各种总线,I2C,SPI,PCI等等,看来总线也不神秘呀。如果你够聪明的话,估计你要问,有些设备不是直接连在总线上的呀,LINUX如何管理的呢?比如RTC,那我先告诉你,针对SOC(什么是SOC,自己去BAIDU去)上一些外设,系统已经虚拟了一个PLATFORM总线,更准确的说,是一套PLATFORM 总线,设备,驱动, 甚至I2C这些总线也是挂在这个PLARFORM上的。是不是迷糊了? 那就暂时忘记最后半句话。

在我们的脑海中,应该有这个概念,总线上挂着驱动和设备,一个驱动可以管理多个设备,一个设备保存一个对应驱动的信息,一般在初始化的时候,总线先初始化,然后设备先注册,最后驱动去找设备,完成他们之间的衔接。

我们要不要去写一个总线驱动呢?可以说99.999999%的人都不需要,系统已经给我们准备好了我们所学要的总线。对于我们来说,就是去学好怎么在系统中添加设备以及相关的驱动就行了。我是没写过任何总线的驱动,所以我们看看设备和驱动,回头再看总线。

 

LINUX设备

在底层,LINUX设备都可以用DEVICE结构的一个实例来表示:

struct device {
 struct device_type *type;

 struct bus_type *bus;  
 struct device_driver *driver; 
 void  *platform_data; 
 };

在这个节都中还包含着许多其他的结构成员,只是我们现在暂时不考虑,否则又要出现很多个为什么了。我们现在只关心这几个成员:

1)设备类型

2)设备所挂的总线

3)设备的驱动

4)设备的私有数据

可以看出,描述设备的结构已经把自身和总线以及设备关联起来,一般情况下,我们也不会这么定义一个设备。为了描述一个设备,常常把设备其他信息和这个结构定义在一起来描述特定的设备。以我们之前提到的虚拟的PLARFORM DEVICE为例:

struct platform_device {
 const char * name;
 int  id;
 struct device dev;
 u32  num_resources;
 struct resource * resource;

 const struct platform_device_id *id_entry;

 
 struct pdev_archdata archdata;
};

在我们写的驱动里,我们常常这么定义一个设备:platform_device  xxx_device,而不是直接device dev。

在S3C系列中,它所支持的大部分设备都是在common-sdk.c和MACH-SMDK***.C中定义好,在板级初始化的时候通过smdkXXXX_init(void)把设备添加到系统中->调用platform_add_devices。而这个函数platform_add_devices的参数smdk_devs则包含系统了S3C上支持的设备。

//共用的

static struct platform_device __initdata *smdk_devs[] = {
 &s3c_device_nand,
 &smdk_led4,
 &smdk_led5,
 &smdk_led6,
 &smdk_led7,
};

//具体芯片的

static struct platform_device *smdk2410_devices[] __initdata = {
 &s3c_device_ohci,
 &s3c_device_lcd,
 &s3c_device_wdt,
 &s3c_device_i2c0,
 &s3c_device_iis,
};

在MACH-SMDK***.C文件(比如MACH-SMDK2410.C)的最后几行看看MACHINE_START->smdk2410_init->smdk_machine_init()->platform_add_devices,就这样完成了板子上主要设备的注册,挂在PLATFORM总线上了。

关于换个PLATFORM DEVICE,还有一个很关键的地方就是该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息,定义在kernel\include\linux\ioport.h中,
struct resource {
 const char *name;
 unsigned long start, end;
 unsigned long flags;
 struct resource *parent, *sibling, *child;
};
具体可以这么定义:

static struct resource s3c_lcd_resource[] = {
 [0] = {
  .start = S3C24XX_PA_LCD,
  .end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
  .flags = IORESOURCE_MEM,
 },
 [1] = {
  .start = IRQ_LCD,
  .end   = IRQ_LCD,
  .flags = IORESOURCE_IRQ,
 }

};
这里定义了两组resource,它描述了一个LCD设备的资源,第1组描述了这个LCD设备所占用的
总线地址范围,也就是DATASHEET上LCD控制器的寄存器地址的范围IORESOURCE_MEM表示第1组描述的是内存类型的资源信息,第2组描述了这个LCD设备的中断号,IORESOURCE_IRQ表示第2组描述的是中断资源信息。设备驱动会根据flags来获取相应的资源信息。

 

设备驱动

在LINUX中,一个设备驱动是以device_driver这个结构描述的(还包含着许多其他的结构成员,只是我们现在暂时不考虑)。

struct device_driver {
 const char  *name;
 struct bus_type  *bus;

 int (*probe) (struct device *dev);
 struct driver_private *p;
};

driver_register用来把去总挂接到总线上。

和DEVICE类似,在写驱动的时候,我们也会把device_driver 包装一下,比如platform_driver。

struct platform_driver {
 int (*probe)(struct platform_device *);
 int (*remove)(struct platform_device *);
 void (*shutdown)(struct platform_device *);
 int (*suspend)(struct platform_device *, pm_message_t state);
 int (*resume)(struct platform_device *);
 struct device_driver driver;
 const struct platform_device_id *id_table;
};

我们定义一个LCD驱动:

static struct platform_driver s3c_fb_driver = {
 .probe  = s3c_fb_probe,
 .remove  = __devexit_p(s3c_fb_remove),
 .id_table = s3c_fb_driver_ids,
 .driver  = {
  .name = "s3c-fb",
  .owner = THIS_MODULE,
  .pm = &s3cfb_pm_ops,
 },
};

在模块初始化的时候,调用platform_driver_register调用driver_register把注册platform_driver到PLATFORM总线上,去看看platform_driver_register的实现就什么都清楚了。

int platform_driver_register(struct platform_driver *drv)
{
 drv->driver.bus = &platform_bus_type;
 if (drv->probe)
  drv->driver.probe = platform_drv_probe;
 if (drv->remove)
  drv->driver.remove = platform_drv_remove;
 if (drv->shutdown)
  drv->driver.shutdown = platform_drv_shutdown;

 return driver_register(&drv->driver);
}

在一个驱动中,最最重要的一个接口就是probe函数,它负责去获取对应DEVICE的数据信息,然后初始化这个设备。如果完成这个函数,我们就完成了驱动的大部分工作了。暂时先停住,在具体的驱动分析中,我们再来考虑这个问题。

到目前为止,我们还没有说清楚这个DEVICE和DRIVER是怎么联系起来的。正如你想到的,我们要去总线那里去看看,它是设备模型中的管理者嘛,因此把驱动和设备联系起来,就是它主要的工作了。

 

 

总线

我们直接看代码,看看总线在系统中是如何表述的:

struct bus_type {
 const char  *name;
 int (*match)(struct device *dev, struct device_driver *drv);
 int (*probe)(struct device *dev);
};

name 就是总线的名字,比如PLATFORM,PCI,I2C等等。

对于总线,PLATFORM总线倒是没有封装,直接采用下边方法定义:

struct bus_type platform_bus_type = {
 .name  = "platform",
 .match  = platform_match,
 };

 

这条总线在系统初始化的时候通过platform_bus_init来初始化,显然这个动作应该驱动注册之前完成。

总线是用MATCH方法来完成驱动和设备的联系的。当总线上的新设备或者新的驱动程序被添加的时候,会来调用这个函数。如果指定的驱动程序能够处理指定的设备,干函数就返回非0,去执行驱动的PROBE函数.

 

我们结合驱动的注册来看看这个过程是什么样子的?

在驱动初始化函数中调用函数platform_driver_register()注册platform_driver,需要注意的是
platform_device结构中name元素和platform_driver结构中driver.name必须是相同的,这样
在platform_driver_register()注册时会对所有已注册的所有platform_device中的name和当前注
册的platform_driver的driver.name进行比较,只有找到相同的名称的platfomr_device才能注册
成功,当注册成功时会调用platform_driver结构元素probe函数指针。

 

platform_driver_register

     driver_register

         bus_add_driver
            driver_attach

               __driver_attach

                 driver_match_device

     如果MATCH成功,测开始PROBE.

                 driver_probe_device

              really_probe

           dev->bus->probe(dev)

        dev->probe
platform_driver->probe

 

这样就直接走到我们驱动PROBE函数中,这个过程很负责,但是对于我们来说只要记住亮点:

1)MATCH的标准: NAME 要相同,或者有的驱动和设备支持ID

2)MATCH成功,我们就转向驱动的PROBE函数。

 

我们来看一个驱动的PROBE函数:

static int s3c_fb_probe(struct platform_device *pdev)
{


 struct s3c_fb_driverdata *fbdrv;
 struct device *dev = &pdev->dev;
 struct s3c_fb_platdata *pd;
 struct s3c_fb *sfb;
 struct resource *res;
 int win;
 int ret = 0;

//这个数据是在设备定义的时候定义的,就是我们前面看到的内存,IRQ等资源

 fbdrv = (struct s3c_fb_driverdata *)platform_get_device_id(pdev)->driver_data;

 pd = pdev->dev.platform_data;
 sfb = kzalloc(sizeof(struct s3c_fb), GFP_KERNEL);
 dev_dbg(dev, "allocate new framebuffer %p\n", sfb);

 sfb->dev = dev;
 sfb->pdata = pd;
 sfb->variant = fbdrv->variant;

//获取时钟,并且ENABLE它

 sfb->bus_clk = clk_get(dev, "lcd");
 clk_enable(sfb->bus_clk);

//处理来自驱动的资源,记着去ioremap,为什么呢?见下边。

 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 sfb->regs_res = request_mem_region(res->start, resource_size(res),
        dev_name(dev));
 sfb->regs = ioremap(res->start, resource_size(res));
 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 sfb->irq_no = res->start;
 ret = request_irq(sfb->irq_no, s3c_fb_irq,
     0, "s3c_fb", sfb);
 platform_set_drvdata(pdev, sfb);

 

//初始化硬件
 

 pd->setup_gpio();

 writel(pd->vidcon1, sfb->regs + VIDCON1);

 

 for (win = 0; win < fbdrv->variant.nr_windows; win++)
  s3c_fb_clear_win(sfb, win);

 
 for (win = 0; win < (fbdrv->variant.nr_windows - 1); win++) {
  void __iomem *regs = sfb->regs + sfb->variant.keycon;

  regs += (win * 8);
  writel(0xffffff, regs + WKEYCON0);
  writel(0xffffff, regs + WKEYCON1);
 }

 

 for (win = 0; win < fbdrv->variant.nr_windows; win++) {
  if (!pd->win[win])
   continue;

  if (!pd->win[win]->win_mode.pixclock)
   s3c_fb_missing_pixclock(&pd->win[win]->win_mode);

  ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win],
           &sfb->windows[win]);
  if (ret < 0) {
   dev_err(dev, "failed to create window %d\n", win);
   for (; win >= 0; win--)
    s3c_fb_release_win(sfb, sfb->windows[win]);
   goto err_irq;
  }
 }

 platform_set_drvdata(pdev, sfb);
 pm_runtime_put_sync(sfb->dev);

 return 0;

//出错处理

 return ret;
}

这里说明一下如何获取资源的:

当进入probe函数后,需要获取设备的资源信息,获取资源的函数有:
struct resource * platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
根据参数type所指定类型,例如IORESOURCE_MEM,来获取指定的资源。
struct int platform_get_irq(struct platform_device *dev, unsigned int num);
获取资源中的中断号。
struct resource * platform_get_resource_byname(struct platform_device *dev, unsigned int type, char *name);
根据参数name所指定的名称,来获取指定的资源。
int platform_get_irq_byname(struct platform_device *dev, char *name);
根据参数name所指定的名称,来获取资源中的中断号。

ioremap 是用来把资源中定义的物理地址转换成内核虚拟地址的,我们的代码中用到的地址是虚拟地址,必须做这样的转换哟。还有一种静态的转换方法,我们以后再来看。

在ioremap之后,我们就可以读写外设的寄存器了,比如控制寄存器,状态寄存器,数据寄存器等等,这样就可以操作外围设备了。

 

当然,还有一些其他东西没有提,对应各种注册,添加函数,同样存在注销,移除等函数,基本就是做相反的操作,只要稍微看看就行了。

 

上边的例子是以PLATFORM 的总线,设备和驱动来讲的,其实I2C等等总线以及设备也采取的是大致的流程。


二、在上一篇中,我们大致描述了LINUX设备模型,我们先来总结一下三要素的关系。

 

Linux设备模型 - 哥哥 - 哥哥

 

从图中可以看出,Linux设备模型就是"总线、设备、驱动、类"这四个概念之前的相互关系;这也是Linux2.6内核抽象出来的用于管理系统中所有设备的模型图;
简单地描述设备模型的层次关系如下:
1、驱动核心中可以注册多种类型的总线(bus_type);
2、每一种类型的总线下面可以挂载许多设备(device);
3、每一种类型的总线可以使用很多设备驱动(device_driver);
4、每一个驱动程序可以管理一组设备;
这种基本关系的建立源于实际系统中各种总线、设备、驱动、类结构的抽象;

 

设备模型之kobject、kset

 

kobject是设备模型中一个很基本的概念,最初支持为了支持引用计数,但是随着时间的转移,它承担了越来越多的任务:

1)对象的引用计数

2)SYS表述

3)数据结构关联

4)热插拔处理

 

 

它的定义如下:

struct kobject {

//名称
 const char  *name;


 struct list_head entry;

//指向kobject的父对象,以此来在内核中构造一个对象层次结构,并且可以将多个对象之间的关系表现初来,这就是sysfs的真相:一个用户空间的文件系统,用来表示内核中kobject对象的层次结构。
 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;
};

一个kobject存在的意义在于把高级对象连接到设备模型上。kobject正如最顶层的基类,而其他类则是派生物,它实现了一系列方法,对自身并没有额数的作用,但是对其他对象却非常有效。

这句话可以这么理解,在LINUX中,是用C语言实现的,没有对象的概念,但是kobject 被包含在各个结构中,就如同面向对象的的基类,根据它描述的父子兄弟关系,把各个结构实例联系起来,想成目录结构。现在回过头去看看总线,设备,驱动的结构定义中其实都有这个KOBJECT的影子,只是在前面我们分析的时候没有把列出来。

kobject的初始化:
1首先使用memest函数将整个kobject清零。
2调用kobject_init()函数。设置结构内部的一些成员。void kobject_init(struct kobject *kobj);kobject_init设置kobject的引用计数为 1。
3设置kobject的名字,这是sysfs入口中使用的名字。int kobject_set_name(struct kobject *kobj, const char *format, ...)

kobject的引用计数:
kobject 的其中一个关键函数是作为一个引用计数器, 给一个它被嵌入的对象. 只要对这个对象的引用存在, 这个对象( 和支持它的代码)必须继续存在.
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);

释放函数和 kobject 类型:
通知由 kobject 的一个释放函数来完成. 常常地, 这个方法有一个形式如下:
void my_object_release(struct kobject *kobj)
{
    struct my_object *mine = container_of(kobj, struct my_object, kobj);
    kfree(mine);
}
有一点需要注意:每一个kobject都必须有一个release方法,并且kobject在该方法被调用前必须保持不变。
而需要在意一点, release函数并没有包含在kobject自身内,而是与包含kobject的结构类型相关联的kobj_type数据结构负责对该类型进行跟踪。

注意结构中的ktype,以及SYSFS_OPS,见下边SYS相关的分析。
struct kobj_type
{
    void (*release)(struct kobject *);//保存kobject类型的release函数
    struct sysfs_ops *sysfs_ops;
    struct attribute ** default_attrs;
}


kobject层次结构、kset

内核用kobject结构将各个对象连接起来组成一个分层的结构体系,从而与模型化的子系统相匹配。有两种独立的机制用于连接:parent指针和kset
在kobject结构的parent成员中,保存了另外一个kobject结构的指针,这个结构表示了分层结构中上一层的节点,而parent最重要的用途是在sysfs分层结构中定位对象。

kset是嵌入相同类型结构的kobject集合,但是不同之处在于,kobject在乎的是对象的类型,而kset关心的是对象的集合与聚合。需要注意的是,kset总是在sysfs中出现,一旦设置了kset并把它添加到系统中,将在sysfs中创建一个目录。kobject不必在sysfs中表示,但是kset中的每一个 kobject成员都将在sysfs中得到表述。

创建一个对象时,要把一个kobject添加到kset中,要先将kobject的kset成员指向目的的kset,调用extern int kobject_register(struct kobject *kobj);
这个函数仅仅是一个 kobject_init 和 kobject_add 的结合.

struct kset {
 struct list_head list;
 spinlock_t list_lock;
 struct kobject kobj;
 const struct kset_uevent_ops *uevent_ops;
};
对于初始化和设置, kset有一个接口非常类似于 kobjects.
void kset_init(struct kset *kset);
int kset_add(struct kset *kset);
int kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);

为管理 ksets 的引用计数, 情况大概相同:
struct kset *kset_get(struct kset *kset);
void kset_put(struct kset *kset);

一个 kset 还有一个名子, 存储于嵌入的 kobject. 因此, 如果你有一个 kset 称为 my_set, 你将设置它的名子用:
kobject_set_name(&my_set->kobj, "The name");

ksets 还有一个指针( 在 ktye 成员 )指向 kobject_type 结构来描述它包含的 kobject. 这个类型优先于在 kobject 自身中的 ktype 成员. 结果, 在典型的应用中, 在 struct kobject 中的 ktype 成员被留为 NULL, 因为 kset 中的相同成员是实际使用的那个.

低层sysfs操作
(什么是SYSFS?? 我们就把它先看做设备文件系统,用来管理系统的各种设备,驱动,总线,类的文件系统)。
kobject隐藏在sysfs文件系统之后的机制,对于sysfs每个目录,内核中都会存在一个对应的kobject。每一个kobject都输出一个或者多个属性。在sysfs目录中表现为文件,其中的内容由内核生成。在<linux/sysfs.h>中包含了sysfs工作代码。

使kobject在sysfs出现仅仅是调用kobject_add的事情.kobjects的sysfs入口一直为目录,因此一个对kobject_add的调用导致在sysfs中创建一个目录. 常常地, 这个目录包含一个或多个属性;
分配给 kobject 的名子( 用 kobject_set_name ) 是给 sysfs 目录使用的名子. 因此, 出现在 sysfs 层次的相同部分的 kobjects 必须有独特的名子. 分配给 kobjects 的名子也应当是合理的文件名子: 它们不能包含斜线字符, 并且空白的使用强烈不推荐.
sysfs 入口位于对应 kobject 的 parent 指针的目录中. 如果 parent 是 NULL 当 kobject_add 被调用时, 它被设置为嵌在新 kobject 的 kset 中的 kobject;

当被创建时, 每个kobject被给定一套缺省属性. 这些属性通过kobj_type结构来指定.

default_attr 成员列举了对每个这样类型的 kobject 被创建的属性, 并且 sysfs_ops 提供方法来实现这些属性.

struct attribute {
 char *name;
 struct module *owner;
 mode_t mode;
};

在这个结构中, name 是属性的名子( 如同它出现在 kobject 的 sysfs 目录中), owner 是一个指向模块的指针(如果有一个), 模块负责这个属性的实现, 并且 mode 是应用到这个属性的保护位. mode 常常是 S_IRUGO 对于只读属性; 如果这个属性是可写的, 你可以扔出 S_IWUSR 来只给 root 写权限( modes 的宏定义在 <linux/stat.h> 中). default_attrs 列表中的最后一个入口必须用 0 填充.

实现这些属性则需要kobj_type->sysfs_ops成员, 它指向一个结构, 定义为:

struct sysfs_ops {
 ssize_t (*show)(struct kobject *kobj, struct attribute *attr, char *buffer);
 ssize_t (*store)(struct kobject *kobj, struct attribute *attr, const char *buffer, size_t size);
};

无论何时一个属性从用户空间读取, show 方法被用一个指向 kobject 的指针和适当的属性结构来调用. 这个方法应当将给定属性值编码进缓冲, 要确定没有覆盖它( 它是 PAGE_SIZE 字节), 并且返回实际的被返回数据的长度. sysfs 的惯例表明每个属性应当包含一个单个的, 人可读的值; 如果你有许多消息返回, 你可要考虑将它分为多个属性.

同样的 show 方法用在所有的和给定 kobject 关联的属性. 传递到函数的 attr 指针可用来决定需要哪个属性. 一些 show 方法包含对属性名子的一系列测试. 其他的实现将属性结构嵌入另一个结构, 来包含需要返回属性值的信息; 在这种情况下, container_of 可能用在 show 方法中来获得一个指向嵌入结构的指针.

store 方法类似; 它应当将存在缓冲的数据编码( size 包含数据的长度, 这不能超过 PAGE_SIZE ), 存储和以任何有意义的的方式响应新数据, 并且返回实际编码的字节数. store 方法只在属性的许可允许写才被调用. 当编写一个 store 方法时, 不要忘记你在接收来自用户空间的任意信息; 你应当在采取对应动作之前非常小心地验证它. 如果到数据不匹配期望, 返回一个负的错误值, 而不是可能地做一些不想要的和无法恢复的事情. 如果你的设备输出一个自销毁的属性, 你应当要求一个特定的字符串写到那里来引发这个功能; 一个偶然的, 随机写应当只产生一个错误.

非默认属性:

多数情况下,kobject类型的default_attrs成员描述了kobject拥有的所有属性。但是我们还可以根据需要对kobject捏的树型进行添加和删除,希望在kobject的sysfs目录中添加新的属性,只需要填写一个个attribute结构,并调用下面的函数:
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
将用attribute中的名字创建文件,并返回0,否则返回一个错误编码。而下面的函数则是删除属性:
int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);

符号链接:

sysfs 文件系统有通常的树结构, 反映它代表的 kobjects 的层次组织.sysfs 子树 (/sys/devices )代表所有的系统已知的设备, 而其他的子树( 在 /sys/bus 之下 )表示设备驱动.这些树,不代表驱动和它们所管理的设备间的关系.展示这些附加关系需要额外的指针,指针在sysfs中通过符号连接实现.

创建符号连接:
int sysfs_create_link(struct kobject *kobj, struct kobject *target, char *name);
这个函数创建一个连接(称为name)指向目标的sysfs入口作为一个kobj的属性.它是一个相对连接,因此它不管sysfs在任何特殊的系统中安装在哪里都可用.
去除符号连接可使用:
void sysfs_remove_link(struct kobject *kobj, char *name);

 

二进制属性:

struct bin_attribute {
 struct attribute attr;
 size_t   size;
 void   *private;
 ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *,
   char *, loff_t, size_t);
 ssize_t (*write)(struct file *,struct kobject *, struct bin_attribute *,
    char *, loff_t, size_t);
 int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
      struct vm_area_struct *vma);
};

 

int sysfs_create_bin_file(struct kobject *kobj, struct bin_attribute *attr);
我们可以显示的定义一个KOBJ的二进制属性,这三个函数指针对应月SYS_***,记住这点就够了。用的地方不是特别多,在我们PROBE一个设备的时候,可以针对该驱动的KOBJ设置二进制属性,然后就可以在用户空间通过VFS来访问这个设备了。以后降到具体的驱动的例子再详细探讨。


三、

在上一篇分析中,多次提到了SYSFS,这是个什么东西?这可是个很大的TOPIC,关于它的讲述可以写本书,但是我们暂时的目标不是要完全啃投它所有的东西,没时间,没精力,我们只要掌握我们需要的进行了。以下都是从网络上找的,我们读一遍就行了。

 

sysfs 是 Linux 内核中设计较新的一种虚拟的基于内存的文件系统,它的作用与 proc 有些类似,但除了与 proc 相同的具有查看和设定内核参数功能之外,还有为 Linux 统一设备模型作为管理之用。相比于 proc 文件系统,使用 sysfs 导出内核数据的方式更为统一,并且组织的方式更好,它的设计从 proc 中吸取了很多教训。

 sysfs 的挂载点 /sys 目录结构,现在你可以进入这个目录看看再结合前面我们谈到的总线,设备,驱动,类,感受一下各个目录。如果你已经开始写一些驱动了,可以注册,或者卸载这个驱动看看这些目录下的文件有什么变化。我就不详细说了-> 先记住这句话,我们通过KOBJ关联起来的各个设备模型要素就是在SYS目录下体现的。我们添加总线,添加驱动,添加设备,创建属性文件等等都会在SYS下生成目录或者文件。

 

如果需要连接更多SYSFS的东西,可以参考http://www.docin.com/p-290065463.html,说实话,我并没有深入理解其中的机制,虽然它是必须的,等以后看完这部分,再来补充。




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值