嵌入式驱动的结构分析 点击:73 | 回复:1



武汉王工

    
  • 精华:30帖
  • 求助:95帖
  • 帖子:5269帖 | 8770回
  • 年度积分:0
  • 历史总积分:28808
  • 注册:2020年5月25日
发表于:2020-04-11 08:10:50
楼主

嵌入式驱动的结构分析

Linux系统上编写驱动程序,说简单也简单,说难也难。难在于对算法的编写和设备的控制方面,是比较让人头疼的;说它简单是因为在Linux下已经有一套驱动开发的模式,编写的时候只需要按照这个模式写就可以了,而这个模式就是它事先定义好的一些结构体,在驱动编写的时候,只要对这些结构体根据设备的需求进行适当的填充,就实现了驱动的编写。

首先在Linux下,视一切事物皆为文件,它同样把驱动设备也看成是文件,对于简单的文件操作,无非就是open/close/read/write,在Linux对于文件的操作有一个关键的数据结构:file_operation,它的定义在源码目录下的include/linux/fs.h中,内容如下:


[cpp] view plain copy1. struct file_operations {  2.     struct module *owner;  3.     loff_t (*llseek) (struct file *, loff_t, int);  4.     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  5.     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);  6.     ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);  7.     ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);  8.     int (*readdir) (struct file *, void *, filldir_t);  9.     unsigned int (*poll) (struct file *, struct poll_table_struct *);  10.     int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);  11.     long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);  12.     long (*compat_ioctl) (struct file *, unsigned int, unsigned long);  13.     int (*mmap) (struct file *, struct vm_area_struct *);  14.     int (*open) (struct inode *, struct file *);  15.     int (*flush) (struct file *, fl_owner_t id);  16.     int (*release) (struct inode *, struct file *);  17.     int (*fsync) (struct file *, int datasync);  18.     int (*aio_fsync) (struct kiocb *, int datasync);  19.     int (*fasync) (int, struct file *, int);  20.     int (*lock) (struct file *, int, struct file_lock *);  21.     ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);  22.     unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);  23.     int (*check_flags)(int);  24.     int (*flock) (struct file *, int, struct file_lock *);  25.     ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);  26.     ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);  27.     int (*setlease)(struct file *, long, struct file_lock **);  28. };   对于这个结构体中的元素来说,大家可以看到每个函数名前都有一个“*”,所以它们都是指向函数的指针。目前我们只需要关心
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);这几条,因为这篇文章就叫简单驱动。就是读(read)、写(write)、控制(ioctl)、打开(open)、卸载(release)。这个结构体在驱动中的作用就是把系统调用和驱动程序关联起来,它本身就是一系列指针的集合,每一个都对应一个系统调用。
但是毕竟file_operation是针对文件定义的一个结构体,所以在写驱动时,其中有一些元素是用不到的,所以在2.6版本引入了一个针对驱动的结构体框架:platform,它是通过结构体platform_device来描述设备,用platform_driver描述设备驱动,它们都在源代码目录下的include/linux/platform_device.h中定义,内容如下:
[cpp] view plain copy 1. struct platform_device {  2.     const char  * name;  3.     int     id;  4.     struct device   dev;  5.     u32     num_resources;  6.     struct resource * resource;  7.     const struct platform_device_id *id_entry;  8.     /* arch specific additions */  9.     struct pdev_archdata    archdata;  10. };  11. struct platform_driver {  12.     int (*probe)(struct platform_device *);  13.     int (*remove)(struct platform_device *);  14.     void (*shutdown)(struct platform_device *);  15.     int (*suspend)(struct platform_device *, pm_message_t state);  16.     int (*resume)(struct platform_device *);  17.     struct device_driver driver;  18.     const struct platform_device_id *id_table;  19. };  对于第一个结构体来说,它的作用就是给一个设备进行登记作用,相当于设备的身份证,要有姓名,身份证号,还有你的住址,当然其他一些东西就直接从旧身份证上copy过来,这就是其中的struct device dev,这是传统设备的一个封装,基本就是copy的意思了。对于第二个结构体,因为Linux源代码都是C语言编写的,对于这里它是利用结构体和函数指针,来实现了C语言中没有的“类”这一种结构,使得驱动模型成为一个面向对象的结构。对于其中的struct device_driver driver,它是描述设备驱动的基本数据结构,它是在源代码目录下的include/linux/device.h中定义的,内容如下:
[cpp] view plain copy1. struct device_driver {  2.     const char      *name;  3.     struct bus_type     *bus;  4.     struct module       *owner;  5.     const char      *mod_name;  /* used for built-in modules */  6.     bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */  7. #if defined(CONFIG_OF)  8.     const struct of_device_id   *of_match_table;  9. #endif  10.     int (*probe) (struct device *dev);  11.     int (*remove) (struct device *dev);  12.     void (*shutdown) (struct device *dev);  13.     int (*suspend) (struct device *dev, pm_message_t state);  14.     int (*resume) (struct device *dev);  15.     const struct attribute_group **groups;  16.     const struct dev_pm_ops *pm;  17.     struct driver_private *p;  18. };   依然全部都是以指针的形式定义的所有元素,对于驱动这一块来说,每一项肯定都是需要一个函数来实现的,如果不把它们集合起来,是很难管理的,而且很容易找不到,而且对于不同的驱动设备,它的每一个功能的函数名必定是不一样的,那么我们在开发的时候,需要用到这些函数的时候,就会很不方便,不可能在使用的时候去查找对应的源代码吧,所以就要进行一个封装,对于函数的封装,在C语言中一个对好的办法就是在结构体中使用指向函数的指针,这种方法其实我们在平时的程序开发中也可以使用,原则就是体现出一个“类”的感觉,就是面向对象的思想。
Linux系统中,设备可以大致分为3类:字符设备、块设备和网络设备,而每种设备中又分为不同的子系统,由于具有自身的一些特殊性质,所以有不能归到某个已经存在的子类中,所以可以说是便于管理,也可以说是为了达到同一种定义模式,所以linux系统把这些子系统归为一个新类:misc ,以结构体miscdevice描述,在源代码目录下的include/linux/miscdevice.h中定义,内容如下:
[cpp] view plain copy1. struct miscdevice  {  2.     int minor;  3.     const char *name;  4.     const struct file_operations *fops;  5.     struct list_head list;  6.     struct device *parent;  7.     struct device *this_device;  8.     const char *nodename;  9.     mode_t mode;  10. };  
对于这些设备,它们都拥有一个共同主设备号10,所以它们是以次设备号来区分的,对于它里面的元素,大应该很眼熟吧,而且还有一个我们更熟悉的list_head的元素,这里也可以应证我之前说的list_head就是一个桥梁的说法了。
其实对于上面介绍的结构体,里面的元素的作用基本可以见名思意了,所以不用赘述了。其实写一个驱动模块就是填充上述的结构体,根据设备的功能和用途写相应的函数,然后对应到结构体中的指针,然后再写一个入口一个出口(就是模块编程中的init和exit)就可以了,一般情况下入口程序就是在注册platform_device和platform_driver(当然,这样说是针对以platform模式编写驱动程序)。


1分不嫌少!


楼主最近还看过



武汉王工

  • 精华:30帖
  • 求助:95帖
  • 帖子:5269帖 | 8770回
  • 年度积分:0
  • 历史总积分:28808
  • 注册:2020年5月25日
发表于:2020-04-11 08:11:25
1楼

Linux系统上编写驱动程序,说简单也简单,说难也难。难在于对算法的编写和设备的控制方面,是比较让人头疼的;说它简单是因为在Linux下已经有一套驱动开发的模式,编写的时候只需要按照这个模式写就可以了,而这个模式就是它事先定义好的一些结构体,在驱动编写的时候,只要对这些结构体根据设备的需求进行适当的填充,就实现了驱动的编写。

首先在Linux下,视一切事物皆为文件,它同样把驱动设备也看成是文件,对于简单的文件操作,无非就是open/close/read/write,在Linux对于文件的操作有一个关键的数据结构:file_operation,它的定义在源码目录下的include/linux/fs.h中,内容如下:


[cpp] view plain copy1. struct file_operations {  2.     struct module *owner;  3.     loff_t (*llseek) (struct file *, loff_t, int);  4.     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  5.     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);  6.     ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);  7.     ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);  8.     int (*readdir) (struct file *, void *, filldir_t);  9.     unsigned int (*poll) (struct file *, struct poll_table_struct *);  10.     int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);  11.     long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);  12.     long (*compat_ioctl) (struct file *, unsigned int, unsigned long);  13.     int (*mmap) (struct file *, struct vm_area_struct *);  14.     int (*open) (struct inode *, struct file *);  15.     int (*flush) (struct file *, fl_owner_t id);  16.     int (*release) (struct inode *, struct file *);  17.     int (*fsync) (struct file *, int datasync);  18.     int (*aio_fsync) (struct kiocb *, int datasync);  19.     int (*fasync) (int, struct file *, int);  20.     int (*lock) (struct file *, int, struct file_lock *);  21.     ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);  22.     unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);  23.     int (*check_flags)(int);  24.     int (*flock) (struct file *, int, struct file_lock *);  25.     ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);  26.     ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);  27.     int (*setlease)(struct file *, long, struct file_lock **);  28. };   对于这个结构体中的元素来说,大家可以看到每个函数名前都有一个“*”,所以它们都是指向函数的指针。目前我们只需要关心
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);这几条,因为这篇文章就叫简单驱动。就是读(read)、写(write)、控制(ioctl)、打开(open)、卸载(release)。这个结构体在驱动中的作用就是把系统调用和驱动程序关联起来,它本身就是一系列指针的集合,每一个都对应一个系统调用。
但是毕竟file_operation是针对文件定义的一个结构体,所以在写驱动时,其中有一些元素是用不到的,所以在2.6版本引入了一个针对驱动的结构体框架:platform,它是通过结构体platform_device来描述设备,用platform_driver描述设备驱动,它们都在源代码目录下的include/linux/platform_device.h中定义,内容如下:
[cpp] view plain copy 1. struct platform_device {  2.     const char  * name;  3.     int     id;  4.     struct device   dev;  5.     u32     num_resources;  6.     struct resource * resource;  7.     const struct platform_device_id *id_entry;  8.     /* arch specific additions */  9.     struct pdev_archdata    archdata;  10. };  11. struct platform_driver {  12.     int (*probe)(struct platform_device *);  13.     int (*remove)(struct platform_device *);  14.     void (*shutdown)(struct platform_device *);  15.     int (*suspend)(struct platform_device *, pm_message_t state);  16.     int (*resume)(struct platform_device *);  17.     struct device_driver driver;  18.     const struct platform_device_id *id_table;  19. };  对于第一个结构体来说,它的作用就是给一个设备进行登记作用,相当于设备的身份证,要有姓名,身份证号,还有你的住址,当然其他一些东西就直接从旧身份证上copy过来,这就是其中的struct device dev,这是传统设备的一个封装,基本就是copy的意思了。对于第二个结构体,因为Linux源代码都是C语言编写的,对于这里它是利用结构体和函数指针,来实现了C语言中没有的“类”这一种结构,使得驱动模型成为一个面向对象的结构。对于其中的struct device_driver driver,它是描述设备驱动的基本数据结构,它是在源代码目录下的include/linux/device.h中定义的,内容如下:
[cpp] view plain copy1. struct device_driver {  2.     const char      *name;  3.     struct bus_type     *bus;  4.     struct module       *owner;  5.     const char      *mod_name;  /* used for built-in modules */  6.     bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */  7. #if defined(CONFIG_OF)  8.     const struct of_device_id   *of_match_table;  9. #endif  10.     int (*probe) (struct device *dev);  11.     int (*remove) (struct device *dev);  12.     void (*shutdown) (struct device *dev);  13.     int (*suspend) (struct device *dev, pm_message_t state);  14.     int (*resume) (struct device *dev);  15.     const struct attribute_group **groups;  16.     const struct dev_pm_ops *pm;  17.     struct driver_private *p;  18. };   依然全部都是以指针的形式定义的所有元素,对于驱动这一块来说,每一项肯定都是需要一个函数来实现的,如果不把它们集合起来,是很难管理的,而且很容易找不到,而且对于不同的驱动设备,它的每一个功能的函数名必定是不一样的,那么我们在开发的时候,需要用到这些函数的时候,就会很不方便,不可能在使用的时候去查找对应的源代码吧,所以就要进行一个封装,对于函数的封装,在C语言中一个对好的办法就是在结构体中使用指向函数的指针,这种方法其实我们在平时的程序开发中也可以使用,原则就是体现出一个“类”的感觉,就是面向对象的思想。
Linux系统中,设备可以大致分为3类:字符设备、块设备和网络设备,而每种设备中又分为不同的子系统,由于具有自身的一些特殊性质,所以有不能归到某个已经存在的子类中,所以可以说是便于管理,也可以说是为了达到同一种定义模式,所以linux系统把这些子系统归为一个新类:misc ,以结构体miscdevice描述,在源代码目录下的include/linux/miscdevice.h中定义,内容如下:
[cpp] view plain copy1. struct miscdevice  {  2.     int minor;  3.     const char *name;  4.     const struct file_operations *fops;  5.     struct list_head list;  6.     struct device *parent;  7.     struct device *this_device;  8.     const char *nodename;  9.     mode_t mode;  10. };  
对于这些设备,它们都拥有一个共同主设备号10,所以它们是以次设备号来区分的,对于它里面的元素,大应该很眼熟吧,而且还有一个我们更熟悉的list_head的元素,这里也可以应证我之前说的list_head就是一个桥梁的说法了。
其实对于上面介绍的结构体,里面的元素的作用基本可以见名思意了,所以不用赘述了。其实写一个驱动模块就是填充上述的结构体,根据设备的功能和用途写相应的函数,然后对应到结构体中的指针,然后再写一个入口一个出口(就是模块编程中的init和exit)就可以了,一般情况下入口程序就是在注册platform_device和platform_driver(当然,这样说是针对以platform模式编写驱动程序)。



热门招聘
相关主题

官方公众号

智造工程师