1. 程式人生 > >linux裝置驅動中重要的3個數據結構 &&Linux裝置驅動模型幾個基本資料結構模型:kobject,kset,subsystem

linux裝置驅動中重要的3個數據結構 &&Linux裝置驅動模型幾個基本資料結構模型:kobject,kset,subsystem


大多數基本的驅動操作涉及到核心的3個重要資料結構:file_operations,file 和inode。

我們已經擁有一些裝置號,但是如何將其與驅動操作連在一起呢?file_operations結構就是這個橋樑,這個結構體定義在<Linux/fs.h>中,它是一群函式的指標集合,每個所開啟的檔案都存在一個f_op指標指向file_operations結構體,裡面的操作大部分主要完成系統呼叫,如open,read等。我們可以將file看成物件,對它操作的操作看成是方法,使用面向物件程式設計(object-orientedprogramming)這個術語表徵某一物件的行為宣告會作用於它自身。後面將會看到更多這種情況。

一般來說,一個指向file_operations結構的指標稱為fops。這個結構體裡面的每個域必須指向驅動中的某些函式以完成一些特定的操作,或者賦予NULL值表示沒有支援的操作。當被賦予NULL時,核心的具體行為對每個函式來說都不盡相同。

1 首先我們來看看file_operations結構吧

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);


    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, int datasync);

    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
};

看到上面一大坨是不是有點暈呢?其實我們只關注一些在寫驅動程式時需要用到的相關項就行了。

當你瀏覽file_operations結構中的各種操作方法時,我們可以發現有許多的引數含有”__user”,可以說這是一種規範吧,用以表明這是使用者層空間的指標,在核心中我們不能直接對它進行引用。

先來看第一個域吧:struct module *owner;

這個域並非某種操作方式,它只是一個指向擁有此結構體的模組的指標,它主要是用來防止模組在使用的過程中被解除安裝。在大多數情況下,這個域一般都被初始化成THIS_MODULE,即 .owner = THIS_MODULE;

loff_t (*llseek)(struct file *, loff_t, int);

此方法是用來改變檔案中的讀寫位置,函式正確返回的是一個新的位置。第二個引數:loff_t是一個“long”型的偏移量,一般是64位。函數出錯時返回的是負值。

ssize_t (*read)(struct file *, char _ _user *, size_t, loff_t *);

此方法用於儲存來自裝置的資料,當返回值為非負值時表示成功讀取的位元組數,返回值一般是有符號整型的資料型別。

ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);

預設情況下為非同步讀取– 在函式返回之前,可能讀操作並未全部完成。如果此方法為NULL型,所有的操作方式都由read(同步)代替。

ssize_t (*write)(struct file *, const char _ _user *, size_t, loff_t *);

主要是給裝置發資料,如果函式返回值為非負整數,表示成功寫入的位元組數。

ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t *);

對裝置進行異常的寫操作

int (*readdir)(struct file *, void *, filldir_t);

對裝置檔案而言,此域應為NULL,因為它是用於讀目錄,在檔案系統中使用。

unsigned int(*poll) (struct file *, struct poll_table_struct *);

這三個系統呼叫(poll, epoll,select)最終都用呼叫底層的poll方法 ,換句話說,這三個系統呼叫最終在驅動裡都是執行poll方法。他們是用於查詢是否對阻塞的檔案描述符進行讀寫操作。Poll方法將返回一位隱碼標誌位(mask)來指明是否有可用的非阻塞讀或寫操作,如果有,則給核心提供一些資訊,這些資訊用於使相應的程序進入休眠直到IO可用。如果驅動中給此域賦NULL,此時裝置無阻塞發生時依舊都是可讀、可寫的。

int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);

ioctl系統呼叫提供了一種解決裝置特殊命令的方法,如格式化磁碟操作,這些既不是讀也不是寫操作。此外,許多ioctl命令是由核心組織的而非從ftops表中引用,如果裝置沒有提供ioctl方法,此係統呼叫將返回錯誤。

int (*mmap) (structfile *, struct vm_area_struct *);

mmap用於將裝置儲存空間與程序的地址空間進行對映,如果此域為NULL,mmap系統呼叫將返回-ENODEV.

int (*open) (structinode *, struct file *);

open總是在裝置檔案上執行的第一步操作。這個比較簡單,不多說啦。。

int (*flush)(struct file *);

當程序關閉裝置的檔案描述時,flush操作就被觸發,它將對裝置執行一些非常重要的操作。不能將其與應用層上的fsync操作混淆。在目前,flush用在非常少的驅動上,如SCSI磁軌驅動就使用它來確保在裝置關閉之前所有的資料都被寫入硬碟,如果flush為NULL,那麼核心會忽略使用者應用層的請求。

int (*release)(struct inode *, struct file *);

當file結構體被釋放時,此操作被觸發。與open一樣,它也可以為NULL.

int (*fsync)(struct file *, struct dentry *, int);

此操作用於通知裝置其FASYNC標誌位發生變化了,此方法在非同步通知得到了應用,如果此域為NULL,驅動就不支援非同步通知操作了。

int (*lock) (structfile *, int, struct file_lock *);

用於完成檔案的加鎖操作。鎖對一般檔案而言是必不可少的特性,但是在裝置驅動中不會實現它。

ssize_t (*readv)(struct file *, const struct iovec *, unsigned long, loff_t *);

ssize_t (*writev)(struct file *, const struct iovec *, unsigned long, loff_t *);

此方法主要完成分散或聚集式的read/write操作,應用程式偶爾需要在多個記憶體區域中做一些簡單的讀或寫操作,這些系統呼叫可以使應用程式不需要在資料上做一些額外的工作就可以實現在多個記憶體中做簡單的讀寫。如果為NULL,那麼呼叫的就是read/write了。

ssize_t (*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int);

ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);

裝置驅動一般不涉及此項

int(*check_flags)(int)

此方法允許模組檢查傳遞給fcntl(F_SETFL…)的標誌量。

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

2file 檔案結構

在裝置驅動中,這也是個非常重要的資料結構,必須要注意一點,這裡的file與使用者空間程式中的FILE指標是不同的,使用者空間FILE是定義在C庫中,從來不會出現在核心中。而struct file,卻是核心當中的資料結構,因此,它也不會出現在使用者層程式中。

file結構體指示一個已經開啟的檔案,其實系統中的每個開啟的檔案在核心中都有一個相應的structfile結構體,直至檔案被關閉。如果檔案被關係,核心就會釋放相應的資料結構。

在核心原始碼中, struct file 要麼表示為file,或者為filp(意指“file pointer”), 注意區分一點,file指的是struct file本身,而filp是指向這個結構體的指標。

fmode_t f_mode;

此檔案模式通過FMODE_READ, FMODE_WRITE識別了檔案為可讀的,可寫的,或者是二者。在open或ioctl函式中可能需要檢查此域以確認檔案的讀/寫許可權,你不必直接去檢測讀或寫許可權,因為在進行open/ioctl等操作時核心本身就需要對其許可權進行檢測。

loff_t f_pos;

當前讀寫檔案的位置。為64位。如果想知道當前檔案當前位置在哪,驅動可以讀取這個值而不會改變其位置。對read,write來說,當其接收到一個loff_t型指標作為其最後一個引數時,他們的讀寫操作便作更新檔案的位置,而不需要直接執行filp ->f_pos操作。而llseek方法的目的就是用於改變檔案的位置。

unsigned intf_flags;

檔案標誌,如O_RDONLY, O_NONBLOCK以及O_SYNC。在驅動中還可以檢查O_NONBLOCK標誌檢視是否有非阻塞請求。其它的標誌較少使用。特別地注意的是,讀寫許可權的檢查是使用f_mode而不是f_flog。所有的標量定義在標頭檔案<linux/fcntl.h>中

structfile_operations *f_op;

與檔案相關的各種操作。當檔案需要迅速進行各種操作時,核心分配這個指標作為它實現檔案開啟,讀,寫等功能的一部分。filp->f_op 其值從未被核心儲存作為下次的引用,即你可以改變與檔案相關的各種操作,這種方式效率非常高。

void *private_data;

    在驅動呼叫open方法之前,open系統呼叫設定此指標為NULL值。你可以很自由的將其做為你自己需要的一些資料域或者不管它,如,你可以將其指向一個分配好的資料,但是你必須記得在file struct被核心銷燬之前在release方法中釋放這些資料的記憶體空間。private_data用於在系統呼叫期間儲存各種狀態資訊是非常有用的。

3 inode結構

    核心使用inode結構體在核心內部表示一個檔案。因此,它與表示一個已經開啟的檔案描述符的結構體(即file 檔案結構)是不同的,我們可以使用多個file 檔案結構表示同一個檔案的多個檔案描述符,但此時,所有的這些file檔案結構全部都必須只能指向一個inode結構體。

   inode結構體包含了一大堆檔案相關的資訊,但是就針對驅動程式碼來說,我們只要關心其中的兩個域即可:

(1) dev_t i_rdev;

表示裝置檔案的結點,這個域實際上包含了裝置號。

(2)struct cdev *i_cdev;

struct cdev是核心的一個內部結構,它是用來表示字元裝置的,當inode結點指向一個字元裝置檔案時,此域為一個指向inode結構的指標。

    此外,核心也提供了兩個巨集可以從inode結點中獲取主次裝置號,巨集的原型如下:

unsigned int iminor(struct inode *inode);

unsigned int imajor(struct inode *inode);

Linux裝置驅動模型有幾個基本資料結構模型:kobject,kset,subsystem

kobject:這是裝置驅動模型的基礎,就想是一座樓的地板磚和磚頭。sysfs是它的子子孫孫,父父爺爺撐起來的

struct kobject

{

    const char *name;     //顯示在sysfs中的名稱

    struct list_head entry;   //下一個kobject結構

    struct kobject *parent;   //指向父kobject結構體,如果存在

    struct kset   *kset;    //指向kset集合

    struct kobj_type  *ktype;  //指向kobject型別描述符

    struct sysfs_dirent *sd;        //對應sysfs的檔案目錄

    struct kref kref;        //kobject引用計數

    unsigned int state_initialized:1;  //是否初始化

    unsigned int state_in_sysfs:1;   //是否加入sysfs

    unsigned int state_add_uevent_sent:1;  //是否支援熱插

    unsigned int state_remove_uevent_sent:1; //是否支援熱拔

}

void  kobject_init(struct kobject *kobj,struct kobj_type *ktype)

{

  char * err_str;

  if(!kobj)

  {

    err_str = "invalid kobject pointer!"

    goto error;

  }

  if(!ktype)

  {

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

    goto error;

  }

  if(kobj->state_initialized)

  {

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

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

    dump_stack();

  }

  kobject_init_internal(kobj);         //初始化kobject的內部成員

  kobj->ktype = ktype ;    //為kobject繫結一個ktype屬性   

  return ;

error:

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

   dump_stack();

}

static void kobject_init_internal(struct koject *kobj)

{

  if(!kobj)

    return ;

  kref_init(&kobj->kerf);

  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;

}

核心介面:

    kobject_init();  始化kobject

    kobject_get();     增加kobject引用計數

    kobject_put();  減少kobject引用計數,計數為零時,呼叫kobject_release()釋放,它在kobj_type裡面

    kobject_set_name();   設定名字

    kobject_rename();    重新命名

    kobject_add()      新增

每個kobject都會有一個屬性kobj_type

struct kobj_type

{

  void (*release)(struct kobject *kobj);    //釋放kobject和其他佔用資源的函式

  struct sysfs_ops *sysfs_ops;      //操作屬性的方法

  struct attribute **default_attrs;      //屬性陣列

};

struct attribute

{

  const char *name;       //屬性的名稱

  struct module *owner;    //只用擁有該屬性的模組,已經不常使用

  mode_t mode;        //屬性讀寫許可權

};

struct sysfs_ops

{

  ssize_t (*show)(struct kobject *,struct attribute *,char *);  //讀屬性操作函式

  ssize_t (*store)(struct kobject *,struct attribute *,const char *,size_t);  //寫屬性操作函式

};

struct kobject *kobject_get(struct kobject *kobj)

{

  if(kobj)

    kref_get(&kobj->kerf);

  return kobj;

}

void kobject_put(struct kobject *kobj)

{

  if(kobj)

  {

     if(!kobj->state_initialized)

      WARN(1,KERN_WARNING"kobject: ‘%s' (%p):is not initialized,yet kobject_put() is being called.\n",kobject_name(kobj),kobj);

      kref_put(&kobj->kref,kobject_release);

  }

}

通常kobject型別的default_attr成員定義了kobjet擁有的所有預設屬性。但是特殊情況下,可以新增一些預設的屬性:

新增屬性檔案:

int sysfs_create_file(struct kobject *kobj,const struct attribute  *attr);

刪除屬性檔案:

void sysfs_remove_file(struct kobject  *kobj , const   struct attribute  *attr);

struct kset

{

  struct list_head list;   //連線所包含的kobject物件的連結串列首地址

  spinlock_t  list_lock;   //維護list連結串列的自旋鎖

  struct kobject kobj;  //內嵌kobject,說明kset本身也是一個目錄

  struct kset_uevent_ops *uevent_ops;     //熱插拔事件

}; 

struct kset_uevent_ops

{

  int (*filter)(struct kset *kset,struct kobject *kobj);

  const char *(*name)(struct kset *kset,struct kobject *kobj);

  int (*uevent)(struct kset *kset,struct kobject *kobj,struct kobj_uevent_ent *env);

};

kset和kobject關係:

1,kset集合包含了屬於其的kobject結構體,kset.list連結串列用來 連線第一個和最後一個kobject物件。第一個kobject使用entry連線kset集合和第二個kobject物件。第二個kobject物件使用entry連線第一個kobject物件和第三個kobject物件,依次類推,最終形成了一個kobject物件的連結串列

2,所有的kobject結構的parent指標指向kset包含的kobject物件,構成一個父子層次關係

3,kobject的所有kset指標指向包含它的kset集合,所以通過kobject物件很容易就能找到kset集合

4,kobject的kobj_type指標指向自身的kobj_type,每一個kobject都有一個單獨的kobj_type結構。另外在kset集合中也有一個kobject結構體,該結構體的XXX也指向一個kobj_type結構體。可知,kobj_type中定義了一組屬性和操作屬性的方法。這裡注意:kset中kobj_type的優先順序要高於kobject物件中的kobj_type的優先順序。如果兩個kobj_type都存在,那麼優先呼叫kset中的函式。如果kset中的kobj_type為空,才呼叫各個kobject結構體本身對應的kobj_type中的函式

5,kset中的kobj也負責對kset的引用計數

kset操作

void kset_init(struct kset *k)  //初始化

{

  kobject_init_internal(&k->kobj);

  INIT_LIST_HEAD(&k->list);

  spin_lock_init(&k->list_lock);

}

int kset_register(struct kset *k); //註冊函式

void kset_unregister(struct kset *k);  //登出函式

static inline struct kset *kset_get(struct kset *k);

static inline void kset_put(struct kset *k);

裝置驅動模型的三大元件

匯流排:

 struct bus_type

{

  const char *name;

  struct bus_attribute *bus_attrs;

  struct device_attribute *dev_attrs;

  struct driver_attribute *drv_attrs;

  int (*match)(struct device *dev,struct device_driver *drv);

  int (*uevent)(struct device *dev,struct kobj_uevent_env *env);

  int (*probe)(struct  device *dev);

  int (*remove)(struct device *dev);

  void (*shutdown)(struct device *dev);

  int (*suspend)(struct device *dev,pm_message_t state);

  int (*suspend_late)(struct device *dev,pm_message_t state);

  int (*resume_early)(struct device *dev);

  struct dev_pm_ops *pm;

  struct bus_type_private *p;

};

struct bus_type_private

{

  struct kset subsys;  //代表該bus子系統,裡面的kobj是該bus的主kobj,也就是最頂層

  struct kset *drivers_kset;  //掛載到該總線上的所有驅動集合

  struct kset * devices_kset;  //掛載到該總線上的所有裝置集合

  struct klist klist_devices;  //所有的裝置列表

  struct klist klist_drivers;  //所有的驅動程式列表

  struct block_notifier_head bus_notifier;

  unsigned int drivers_autoprobe:1; //設定是否在驅動註冊是,自動彈出裝置

  struct bus_type *bus;  //回指向包含自己的匯流排

};

int bus_register(struct bus_type *bus);

void bus_unregister(struct bus_type *bus);

struct bus_attribute

{

  struct attribute attr;

  ssize_t (*show)(struct bus_type *bus,char *buf);

  ssize_t (*store)(struct bus_type *bus,const char *buf,size_t count);

};

int bus_create_file(struct bus_type *bus,struct bus_attribute *attr);

void bus_remove_file(struct bus_type *bus,struct bus_attribute *attr);

裝置:

 struct device

{

  struct klist klist_children;   //連線子裝置的連結串列

  struct device *parent;     //指向父裝置的指標

  struct kobject kobj;      //內嵌的kobject

  char bus_id[BUS_ID_SIZE];   //連線到總線上的位置

  unsigned uevent_supress:1;  //是否支援熱插拔事件

  const char *init_name;       //裝置的初始化名字

  struct device_type *type;   //裝置相關的特殊處理函式

  struct bus_type *bus;    //指向連線的匯流排指標

  struct device_driver *driver;  //指向該裝置的驅動程式

  void *driver_data;   //指向驅動程式私有資料的指標

  struct dev_pm_info power;  //電源管理資訊

  dev_t devt;    //裝置號

  struct class *class; //指向裝置所屬類

  struct attribute_group **groups; //裝置的組屬性

  void (*release)(struct device *dev);  //釋放裝置描述符的回撥函式

  ...

};

int device_register(struct device *dev);

void device_unregister(struct device *dev);

struct device_attribute

{

  struct attribute attr;

  ssize_t  (*show)(struct device *dev,struct device_attribute *attr,char *buf);

  ssize_t (*store)(struct device *dev,struct device_attribute *attr,const char *buf,size_t count);

};

int device_create_file(struct device *device,struct device_attribute);

void device_remove_file(struct device *dev,struct device_attribute *attr);

驅動:

struct device_driver

{

  const char *name;  //裝置驅動名字

  struct bus_type *bus;  //指向驅動屬於的匯流排,總線上有很多裝置

  struct module *owner;   //裝置驅動自身模組

  const char *mod_name;  //裝置驅動名字

  int (*probe)(struct device *dev);  /探測函式

  int (*remove)(struct device *dev);

  void (*shutdown)(struct device *dev);

  int (*suspend)(struct device *dev,pm_message_t state);

  int (*resume)(struct device *dev);

  struct attribute_group **group;

  struct dev_pm_ops *pm;

  struct driver_private *p;

};

struct driver_private

{

  struct kobject kobj;   //內嵌kobject結構,用來構建裝置驅動程式模型

  struct klist klist_devices;  //該驅動支援的所有裝置連結串列

  struct klist_node knode_bus;  //該驅動所屬匯流排

  struct module_kobject *mkobj;  //驅動的模組

  struct device_driver *driver;  //指向驅動本身

};

int driver_register(struct device_driver *drv);

void driver_unregister(struct device_driver *drv);

struct driver_attribute

{

  struct attribute attr;

  ssize_t (*show)(struct device_driver *driver ,char *buf);

  ssize_t (*store)(struct device_driver*driver,const char *buf,size_t count);

};

int driver_create_file(struct device_driver *drv,struct driver_attribute *attr);

void driver_remove_file(struct device_driver *drv,struct driver_attribute *attr);


下面是好的一些說明文件:

http://blog.csdn.net/new_abc/article/details/7559732 http://blog.csdn.net/new_abc/article/details/7558845 http://blog.csdn.net/xiahouzuoxin/article/details/8943863

相關推薦

no