字符设备驱动的学习总结
2010-10-03 15:07
246 查看
这个周报告本来应该是上一周交的,但之前对于这一章理解的有限,加上事情比较多也没来得及写,现在把它补上,总算把所有的报告都交了。今天再简单把第六章的内容看了一遍,也算是复习了。第六章主要学习了字符设备驱动,第一节讲了字符设备驱动的结构,最初讲了一个很重要的结构体-
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
模块的引用计数。如果驱动中包含了两个设备的话,我们可以为其分别创建设备节点,来验证是否能正确读写到对应的设备中。
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
模块的引用计数。如果驱动中包含了两个设备的话,我们可以为其分别创建设备节点,来验证是否能正确读写到对应的设备中。
相关文章推荐
- 关于字符设备驱动学习的总结
- 嵌入式Linux驱动学习之路(二十一)字符设备驱动程序总结和块设备驱动程序的引入
- 基于mini6410的linux驱动学习总结(二 字符设备与块设备的区别)
- 基于mini6410的linux驱动学习总结(五 字符设备驱动程序实例分析(虚拟设备驱动))
- 字符设备驱动框架学习总结
- 字符设备驱动函数解析(学习总结)
- 基于mini6410的linux驱动学习总结(四 设计字符设备驱动程序)
- linux 学习笔记--字符设备驱动相关数据结构
- 学习Ldd3--字符设备驱动(第三章)
- linux字符设备驱动总结之:全自动创建设备及节点 .
- linux驱动学习--第二十一天:第十二章:Linux 字符设备驱动综合实例(一) 键盘驱动
- linux驱动学习--第二十二天:第十二章:Linux 字符设备驱动综合实例(三)DSP HPI 的设备驱动
- Linux字符设备驱动学习1
- linux字符设备驱动总结之:全自动创建设备及节点
- 嵌入式Linux驱动学习之路(二十七)字符设备驱动的另一种写法
- [Linux驱动]字符设备驱动学习笔记(一)
- 设备驱动学习之字符设备驱动内核代码分析(二)——字符设备结构体cdev
- 嵌入式linux学习笔记4之字符设备驱动
- 国嵌--linux字符设备驱动学习之memdev设备
- 《深入Linux设备驱动程序机制》学习心得---字符设备驱动原理图解