1. 设备驱动

   1.1 设备号在系统中用dev_t来表示

      typedef __u32 __kernel_dev_t;

      typedef __kernel_dev_t      dev_t;

      由以上两个宏可知,dev_t就是一个无符号32位的整型

      其中高12位作为主设备号,一般用major表示,主设备号表示哪类设备

      其中低20位作为次设备号,一般用minor表示,次设备号表示哪个设备

  

   1.2 设备号的操作宏

      /*从设备号中分离出主设备号*/

      #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))

 

      /*从设备号中分离出次设备号*/

      #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

 

      /*由主设备号和次设备号组成一个设备号*/

      #define MKDEV(ma,mi)  (((ma) << 20) | (mi))

 

1.3使用cdev结构体描述一个字符设备结构体,cdev结构体的定义如代码:

Linux/include/cdev.h

 

 

  struct cdev {

 

  structkobject kobj; /* 内嵌的kobject对象 */

 

  structmodule *owner;    /*所属模块*/

 

  structfile_operations *ops;  /*文件操作结构体*/

 

  structlist_head list;

 

dev_tdev;           /*设备号*/

 

  unsignedint count;

 

  };

 

1.4 cdev结构体的另一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数。

- - -Linux 2.6内核提供了一组函数用于操作cdev结构体:

》》cdev_init()函数用于初始化cdev的成员,并建立cdev和file_operations之间的连接,其源代码如下:

1  void cdev_init(struct cdev *cdev, structfile_operations *fops)

 

2  {

 

3              memset(cdev, 0, sizeof *cdev);

 

4              INIT_LIST_HEAD(&cdev->list);

 

5              kobject_init(&cdev->kobj,&ktype_cdev_default);

 

6              cdev->ops = fops; /*将传入的文件操作结构体指针赋值给cdev的ops*/

 

7        }

》》cdev_alloc()函数用于动态申请一个cdev内存,其源代码如下:

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

1  struct cdev *cdev_alloc(void)

 

2  {

 

3           struct cdev *p =kzalloc(sizeof(struct cdev), GFP_KERNEL);

4              if (p) {

5                           INIT_LIST_HEAD(&p->list);

6                        kobject_init(&p->kobj,&ktype_cdev_dynamic);

7              }

8              returnp;

}

cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。对cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。

int cdev_add(struct cdev *, dev_t, unsigned);

void cdev_del(struct cdev *)

 

1.5 申请主设备号

内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。这三个函数都会调用一个共用的__register_chrdev_region() 函数来注册一组设备编号范围(即一个 char_device_struct 结构)。

register_chrdev_region( ) //分配指定的设备号范围

alloc_chrdev_region( ) //动态分配设备范围

register_chrdev( )//申请指定的设备号,并且将其注册到字符设备驱动模型中.是一个老式分配设备编号范围的函数

 

 

     

- - - - - - - - - 老方法2.4内核/*1. 静态申请/注册主设备号*/ - - -

      ret = register_chrdev(dev_major,"dev_module",&dev_fops);

      /*2. 动态申请/注册主设备号*/

      dev_major = register_chrdev(0,"dev_module",&dev_fops);

                __register_chrdev(major, 0, 256, name, fops);

                  /*表示一个字符设备*/

                   struct cdev *cdev;

                  

                   /*申请主设备号*/

                   cd = __register_chrdev_region(major, baseminor, count, name);

                  

                   /*分配cdev*/

                   cdev = cdev_alloc();

                  

                   /*设备操作方法*/

                   cdev->owner = fops->owner;

                   cdev->ops = fops;

 

                   /*注册字符设备*/

                   err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);

 

- - - - - - - -使用静态新方法2.6内核注册/申请主设备号- - - - - - - -

         1) 静态新方法 注册/申请主设备号

            /*

            **param1: 设备号

            **param2: 个数

            **param3: 设备名称

            **返回值: 失败返回非0值,成功返回0

            */

            ret = register_chrdev_region(devno,1,"dev_module");

 

         2) 涉及重要结构体

            struct cdev {         //表示一个字符设备

                struct kobject kobj;

                struct module *owner;

                const struct file_operations *ops; //设备操作方法

                struct list_head list;

                dev_t dev;                     //设备号

                unsigned int count;               //个数

            };

 

         3) struct cdev结构体操作

            /*构建struct cdev结构体*/

            struct cdev *cdev;

            /*为cdev分配空间*/

            struct cdev *cdev_alloc(void);

            /*初始化cdev,构建设备操作方法*/

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

            /*注册字符设备*/

            int cdev_add(struct cdev *, dev_t, unsigned);

            /*注销字符设备*/

            void cdev_del(struct cdev *);

 

         4) 动态新方法 注册/申请主设备号

                /*

                **param1: 设备指针

                **param2: 起始个数

                **param3: 个数

                **param4: 名称

                **返回值: 失败返回非0值,成功返回0

                */

                ret = alloc_chrdev_region(&devno,0,1,"dev_module");

 

                /*从设备号中分离主设备号*/

                dev_major = MAJOR(devno);

1.6创建设备文件

创建设备文件的步骤为,获取设备编号à创建内核类à创建设备文件。利用cat /proc/devices查看申请到的设备名,设备号。

   >> 设备文件也称为设备节点

   >> 如何创建设备文件

      >>方法一: 手动创建

              mknod /dev/xxx c major minor

 

>>方法二: 自动创建(由系统动态自动创建设备文件),需要同时满足两个条件

         a. 要求根文件系统中,实现mdev或者udev机制

          b. 在驱动中必须调用两个函数

class_create(………..);

                   device_create(………..);

 

1.7  file_operations结构体

 

file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被调用。file_operations结构体目前已经比较庞大,它的定义如代码清单6.4。

代码清单6.4 file_operations结构体

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

1  struct file_operations {  

2    struct module *owner;  

3      /* 拥有该结构的模块的指针,一般为THIS_MODULES */  

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

5      /* 用来修改文件当前的读写位置         */  

6    ssize_t(*read)(struct file *, char __user*, size_t, loff_t*);  

7      /* 从设备中同步读取数据 */  

8    ssize_t(*write)(struct file *, const char__user *, size_t, loff_t*);  

9      /* 向设备发送数据*/  

10   ssize_t(*aio_read)(struct kiocb *, char__user *, size_t, loff_t);  

11     /* 初始化一个异步的读取操作*/  

12   ssize_t(*aio_write)(struct kiocb *, constchar __user *, size_t, loff_t);  

13     /* 初始化一个异步的写入操作*/  

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

15     /* 仅用于读取目录,对于设备文件,该字段为 NULL */  

16   unsigned int(*poll)(struct file *, structpoll_table_struct*);  

17     /* 轮询函数,判断目前是否可以进行非阻塞的读取或写入*/  

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

19     /* 执行设备IO控制命令*/  

20   long(*unlocked_ioctl)(struct file *,unsigned int, unsigned long);  

21     /* 不使用BLK的文件系统,将使用此种函数指针代替ioctl */  

22   long(*compat_ioctl)(struct file *, unsignedint, unsigned long);  

23     /* 在64位系统上,32位的ioctl调用,将使用此函数指针代替*/  

24   int(*mmap)(struct file *, structvm_area_struct*);  

25     /* 用于请求将设备内存映射到进程地址空间*/  

26   int(*open)(struct inode *, struct file*);  

27     /* 打开 */  

28   int(*flush)(struct file*);  

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

30     /* 关闭*/  

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

32     /* 刷新待处理的数据*/  

33   int(*aio_fsync)(struct kiocb *, intdatasync);  

34     /* 异步fsync */  

35   int(*fasync)(int, struct file *, int);  

36     /* 通知设备FASYNC标志发生变化*/  

37   int(*lock)(struct file *, int, structfile_lock*);  

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

39     /* 通常为NULL */  

40   unsigned long(*get_unmapped_area)(structfile *,unsigned long, unsigned long,  

41     unsigned long, unsigned long);  

42     /* 在当前进程地址空间找到一个未映射的内存段 */  

43   int(*check_flags)(int);  

44     /* 允许模块检查传递给fcntl(F_SETEL...)调用的标志 */  

45   int(*dir_notify)(struct file *filp, unsignedlong arg); 

46     /* 对文件系统有效,驱动程序不必实现*/  

47   int(*flock)(struct file *, int, structfile_lock*);  

48   ssize_t (*splice_write)(struct pipe_inode_info*, struct file *, loff_t *, size_t,  

49    unsigned int); /* 由VFS调用,将管道数据粘接到文件 */  

50   ssize_t (*splice_read)(struct file *, loff_t*, struct pipe_inode_info *, size_t,  

51   unsigned int); /* 由VFS调用,将文件数据粘接到管道 */  

52   int (*setlease)(struct file *, long, structfile_lock **);  

53};

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

下面对file_operations结构体中的主要成员进行分析:

>>llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。

read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。

>>write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行write()系统调用时,将得到-EINVAL返回值。

>>readdir()函数仅用于目录,设备节点不需要实现它。

>>ioctl()提供设备相关控制命令的实现(既不是读操作也不是写操作),当调用成功时,返回给调用程序一个非负值。

>>mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进行mmap()系统调用时将获得-ENODEV返回值。这个函数对于帧缓冲等设备特别有意义。

当用户空间调用LinuxAPI函数open()打开设备文件时,设备驱动的open()函数最终被调用。驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。与open()函数对应的是release()函数。

>>poll()函数一般用于询问设备是否可被非阻塞的立即读写。当询问的条件未触发时,用户空间进行select()和poll()系统调用将引起进程的阻塞。

>>aio_read()和aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。设备实现这两个函数后,用户空间可以对该设备文件描述符调用aio_read()、aio_write()等系统调用进行读写。

1.8  linux字符设备驱动框架总结

================================================

1. 包含模块头文件

   #include<linux/init.h>

   #include<linux/module.h>

2. 模块加载函数

   a. 注册/申请主设备号

      静态

      动态

   b. 创建设备文件

      手动

      自动

c. 初始化

3. 模块卸载函数

   做加载函数反操作

4. 模块许可声明

5. 构建struct file_operations结构体

6. 实现xxx_open, xxx_read...函数

1.9 用户空间与内核空间数据进行交互

=======================================

1. 在系统中为指针分配空间

   /*

   ** param1: 大小

   ** param2: 标号GFP_KERNEL:  表示分配不成功,则休眠   

   **               GFP_ATOMIC:表示分配不成功,则不休眠

   */

   s5pc100_dev = kmalloc(sizeof(struct dev_device),GFP_KERNEL);

 

2. 数据交互函数

   /*获取用户空间数据*/

   /*

   **param1: 目标

   **param2:

   **返回值: 失败返回剩余没有拷贝成功的字节数,成功返回0

   */

   ret=copy_from_user(&s5pc100_dev->val,buf,count);

 

   /*上报数据给用户空间*/

   ret=copy_to_user(buf,&s5pc100_dev->val,count);

posted on 2020-08-26 10:50  六翅天蚕  阅读(72)  评论(0)    收藏  举报