字符设备驱动的学习总结

      这个周报告本来应该是上一周交的,但之前对于这一章理解的有限,加上事情比较多也没来得及写,现在把它补上,总算把所有的报告都交了。今天再简单把第六章的内容看了一遍,也算是复习了。第六章主要学习了字符设备驱动,第一节讲了字符设备驱动的结构,最初讲了一个很重要的结构体- cdev, 其中包括了 kobject 结构体变量(第五章有定义,但没细看), module owner 结构的指针变量(可以用作为 try_module_get() module_put() 两个模块计数管理接口的变量,还可用作文件操作等),文件操作结构体的指针变量(基本定义了文件的所有相关操作), list_head 结构变量(无数据域的双链表,来完成对 cdev 结构变量的遍历,相关知识可参照头文件的几篇博文), dev_t 结构变量(定义了 32 位的设备号,高 12 位主,低 20 位次),以及 unsigned count 变量。

      2.6 内核中提供了相关函数来操作 cdev 结构体,主要函数如下: cdev_init() struct cdev *cdev_alloc() cdev_put() cdev_add() cdev_del() cdev_init(cdev *cdev,file_operations *fops) 函数用于初始化 cdev 的成员,并建立 cdev file_operations 之间的连接,其中的 memset(cdev,0,sizeof *cdev) 函数是获取 cdev 的字节数,然后把 cdev 结构体中这个字节数大小的空间置为 0 ,完成结构体初始化,用 cdev->ops = fops 来完成结构体与文件操作之间的连接。 cdev_alloc() 用于动态申请一个 cdev 内存 ,cdev_add() cdev_del() 用于添加和删除一个 cdev, 完成字符设备的注册和注销。

      6.1.3 节定义了文件操作结构体的成员,其中的函数全是采用函数指针来定义,这对于函数的通用性有极大的好处,我们只要完成对函数指针的赋值便可以实现对一个函数的相关操作,其中的成员也没有指定它的返回值类型, () 内的参数也是可有可无的,有的话只是完成对函数原型相关参数的类型说明,而无实际的意义。其中我们以前可能接触到的函数有 llseek() read() write() 等,而其他像 poll() release() flush() 等则是我们不太熟悉的,其用法我们现在也没法去深究。

        字符设备驱动组成大概有:

      (1). 字符驱动模块加载与卸载函数,在加载函数中应包括设备号的申请和 cdev 的注册,卸载函数则完成其相反的功能,其中设备号的申请则涉及到设备号的注册函数和动态分配函数, cdev 的注册则要用到内核提供的 cdev_add() 函数, cdev 的注销则用到内核提供的 cdev_del() 函数。

      (2). 字符设备驱动的 file_operations 结构体成员函数的实现,我们应该清楚用户调用到驱动操作的大致过程,先由用户空间到内核空间,最终落实到驱动中的文件操作结构体中的成员函数,成员函数是字符设备驱动与内核的接口,由于内核空间与用户空间的内存不能直接互访,我们需借助函数 copy_from/to_user() 来完成用户到内核 / 内核到用户的复制操作,也就是写 / 读设备。函数原型如下:

        unsigned long copy_from_user(void *to, const void __user *from,unsigned long count);

        unsigned long copy_to_user(void __user *to, const void *from,unsigned long count);

       函数返回值是不能被复制的字节数,由于 *from 指向的数据暂时是不变的,需定义为静态类型。在初始化一个 file_operations 变量之前,我们需要用 cdev_init() 函数来初始化一个 cdev 结构体 , 并从中获取一个 file_operations 变量并通过对其成员赋值来完成准备工作。

下面来回顾一下模块加载和卸载函数的结构:

      globalmem_init()( 模块加载函数 )

       …...

       申请设备号

       …...

      globalmem_setup_cdev();/ 初始化并添加 cdev 结构体

       …... (1). 调用 cdev_init() 展开- > 初始化 cdev 结构体

       …... (2).cdev.ops= globalmem_fops< -变量定义及成员的赋值

       …...      ( 来完成文件操作函数与 cdev 的关连 )

       …...

       globalmem_exit()( 模块卸载函数 )

      后面的几节通过实例来讲解驱动的具体结构和原理,我先把其结构回顾一下:

首先是包含所用的内核 内的头文件,如 fs.h 头文件中包含了对文件的相关操作的函数指针, module.h 则包含了模块所需的注册和卸载的函数,然后是相关宏的定义,包括全局内存的大小,主设备号等,接着定义了一个很重要的类型为 globalmem_dev 的全局指针变量,接着定义了文件打开函数,释放函数、读、写、定位函数等,再下来就是模块的加载和卸载函数,最后是模块的注册和注销函数,以及一些模块附加信息。

      接下来我对上一段中的全局变量,以及 file 结构的私用数据进行说明其用途和含义,如果我们只包含一个设备的话,则可以直接在文件操作的相关函数中通过访问全局变量来获得设备结构体的指针,但如果驱动中包含两个同样的设备,则这个全局变量则可以用来指向两个设备 ( 用数组来实现 ) 。我简单说明一下实现的过程,在 globalmem_open() 函数中重新定义一个 globalmem_dev 局部指针变量 ( 因为在文件打开时会在内核空间中自动生成一个 file inode 结构体,并可以传递给进行操作的任何函数,当一切相关操作结束后时 file inode 结构体则自动消亡 ), 因为在 inode 结构体中包含 cdev 结构体 , 由生成的 inode 结构体中的 cdev 结构体 , 再调用 container_of(inode->i_cdev,struct globalmem_dev,cdev) 来获得 dev 结构体 ( 最外层的 ), 并赋给局部指针变量 , 这时这个局部变量便可以赋值给私用数据了 , 在我看来私用数据只是起来一个传递的作用 , 类型于中间变量 , 只是为了避免直接访问原变量而导致出错。上面有部分内容是我自己的推测,如果理解有错误还请指正 , 谢谢 !

      在整个设备驱动中涉及到的结构体较多,如果我们能够较清晰的把握各结构体的内在联系的话对我们了解整个结构的帮助会非常的大,结构体之间的关系大致如下:

struct globalmem_dev

{

     sruct cdev cdev

      {

         module *owner;

         file_operations *ops;

         list_head list;

         dev_t dev;

         unsigned int count;

       }

   mem[GLOBALMEM_SIZE]

}

      这就是设备结构体之间的关系, dev 是最外层的结构体,文件操作便是对其进行操作, 而内部的 cdev 结构体则负责和文件操作的联系。

      inode 结构体中包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。

struct inode

{

    struct cdev *i_cdev;

}

      file 结构体如下:

struct file

{

    file_operations *f_ops;

    void *private_data;

}

      对于文件的相关操作都至少需要这两个结构体, file 结构体中包含了对文件操作的引用,对操作的函数来说只要对最外层的结构体进行操作就可以了,这也相当于在两个大的结构体中,是由内部的小的结构体建立联系,而对外部则屏蔽掉其具体的细节,但相关操作却是由这些小的结构体来完成。这些结构体的设计都涉及到了面向对象的封装思想,这对我来说比较难理解,可能其中有很多错误,希望大家如果谁懂的话给我提个醒,不要让我一直将迷糊进行到底。谢谢!

     下面是文件操作结构体的赋值,也就是完成函数指针的指引,对于其中的相关操作函数我就不细说了。

static const struct file_operations globalmem_fops =

 

{

.owner = THIS_MODULE,

.llseek = globalmem_llseek,

.read = globalmem_read,

.write = globalmem_write,

.ioctl = globalmem_ioctl,

.open = globalmem_open,

.release = globalmem_release,

};

      这一章最后一节讲了模块在用户空间的验证,首先编译,并加载模块,用 cat /proc/devices 命令查看,会发现多出一个 globalmem 设备,假设其主设备号是 254 ,使用 mknod /dev/globalmem c 254 0 命令创建设备节点,然后通过 echo 'hello world' >/dev/globalmem 命令来将信息写入到 globalmem 设备,其中 > 是重定向符,表示重定向输出到另一个文件中,同理, < 也是重定向符,表示从哪里获取信息到 ... ,接着使用 cat /dev/globalmem 命令验证结果,看信息是否正确写入到了设备中了。我们可以采用 tree /sys/module/globalmem 命令来查看其目录的树型结构,其中的 refcnt 记录了 globalmem 模块的引用计数。如果驱动中包含了两个设备的话,我们可以为其分别创建设备节点,来验证是否能正确读写到对应的设备中。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值