linux 设备驱动总结,linux设备驱动归纳总结(三):3面向对象思想和lseek

linux设备驱动归纳总结(三):3.设备驱动面向对象思想和lseek的实现

一、结构体struct

file和struct inode

在之前写的函数,全部是定义了一些零散的全局变量。有没有办法整合成到一个结构体当中?这样的话,看起来和用起来都比较方便。接下来就要说这方面的问题。

不过先要介绍一下除了fops以外的两个比较重要的结构体:

1)struct

file

在内核中,file结构体是用来维护打开的文件的。每打开一次文件,内核空间里就

会多增加一个file来维护,当文件关闭是释放。

所以,在内核中可以存在同一个文件的多个file,因为该文件被应用程序打开被打

开。

在struct

file中有几个重要的成员:

1)loff_tf_pos;

这是用来记录文件的偏移量。在应用程序中,打开文件时偏移量为0,每次的读写操作都会使偏移量增加。

从这个原因可以看出为什么每打开一次文件就新建一个file结构体了。不然的话,每个打开文件的读写操作都修改同一个偏移量,那读写岂不是乱套了吗?

2)void*private_data;

这是空类型的指针可以用于存放任何数据,我会用这个指针来存放待会要定义的结构体指针。

回想一下,文件操作结构体fops中所有的函数成员里面都有一个参数是file结构体,所以每个函数都可以在file->private_data中拿到我自己定义的结构体了。

3)struct file_operations *fops;

打开文件后,内核会把fops存放在这里,以后的操作就在这里在这里找函数了。

2)struct

inode

这个结构体是用来保存一个文件的基本信息的结构体,即使打开多个相同的文件,也只会有一个对应的inode。

它也有两个常用的成员:

1)dev_t i_rdev;

这里存放着这个文件的设备号。

2)struct cdev *i_cdev;

这个结构体很熟悉吧,这就是注册设备时用的cdev就存在这。这个结构体的用处现在我还不好说,待会看程序就知道了。

二、面向对象的思想

接下来就封装一下之前程序的数据类型吧:

18 struct _test_t{

19 char

kbuf[DEV_SIZE];//这里存放数据

20 unsigned int

major;//这里存放主设备号

21 unsigned int

minor;//这里存放次设备号

22 unsigned int

cur_size;//这里存放当前的kbuf的大小

23 dev_t devno;//这里存放设备号

24 struct cdev

test_cdev;//这里存放cdev结构体

25 };

定义了这样的一个结构体后,在操作函数中怎么拿到这个结构体的指针呢?

先来个函数:

#define container_of(ptr, type,

member) ({\

const typeof( ((type *)0)->member

) *__mptr = (ptr);\

(type *)( (char *)__mptr -

offsetof(type,member) );})

使用:

已知一个结构体里面一个成员的指针ptr,同时,这个成员也是另外一个结构体类型中的一个成员,这个结构体的类型是type,而这个成员以member这个名字命名。就可以通过这个函数找到指向类型是type的结构体的指针。

返回值:

返回值就是指向type结构体类型的数据的指针。

如:现在定义这样的两个结构体:

struct A {

int *xiaobai_a;

};

struct B {

int xiaobai_b;

};

struct A a;

在遥远的另一处有这样的定义:struct

B b;

并且,a.xiaobai_a = &b.xiaobai_b;

这样,在不知道b只知道a的情况下也可以找到b的位置:

struct B *bb =

container_of(a.xiaobai_a, struct B, xiaobai_b);

估计被上面的解释说晕了吧。我还是举个例比较方便:

虽然一个函数不值得说这么久,但是我觉得这种思想很不错,内核中很多时候都用到这个函数,如在内核链表中。

来个邪恶的例子名字——老板与小秘:

8e6192b06d9d19fad801dd80a799c780.png

老板他请了个年轻的小秘,他就跟客户说:“我电话号码经常换,你记着我小秘的电话,想找我嘛,找我小秘就可以了!”

于是,客户想找老板了,就打通小秘的电话,说:“我知道你是秘书小红,我想找你老板小黑,麻烦给他的电话号码我。”

这样,客户就拿到了老板最新的电话号码了。

想象老板和客户是个结构体,秘书和他的电话号码是个各自成员,电话号码想象成指针:

老板的电话 =

container_of(秘书的电话, 老板,小秘)

说了半天还没进入正题,这个函数用在哪里呢?谁当小秘呢?

就是那个说了半天都不知道能做什么还经常出现的struct

cdev!

而我把cdev添加到了我自己建的结构体struct

_test_t中,所哟struct

_test_t就是老板!

而struct

inode就是客户了,因为它的成员里面有小秘的电话号码:struct

cdev *i_cdev;

所以,如果想得到_test_t,只要调用这个函数就行了。

下面看一下改良后的open函数

27 int test_open(struct inode *node,

struct file *filp)

28 {

29 struct _test_t *dev;

30 dev =

container_of(node->i_cdev, struct _test_t, test_cdev);

31 filp->private_data

= dev;

32 return 0;

33 }

上面还有一句,将获得的结构体指针存放到filp的private_data中。

这是因为,struct

file_operations中的每个函数的第一个参数就是struct

file,只要有file,每个函数都可以从private_data中得到数据了。相反,struct

inode这个参数并不是file_operations中所有的函数都有。

下面贴上部分代码:1st/test.c

18 struct _test_t{

19 char kbuf[DEV_SIZE];

20 unsigned int major;

21 unsigned int minor;

22 unsigned int cur_size;

23 dev_t devno;

24 struct cdev test_cdev;

25 };

26

27 int test_open(struct inode *node,

struct file *filp)

28

{/*open操作需要给把拿到的结构体指针赋值给private_data*/

29 struct _test_t *dev;

30 dev =

container_of(node->i_cdev, struct _test_t, test_cdev);

31 filp->private_data = dev;

32 return 0;

33 }

34

35 int test_close(struct inode

*node, struct file *filp)

36 {

37 return 0;

38 }

39

40 ssize_t test_read(struct file

*filp, char __user *buf, size_t count, loff_t *offset)

41 {

42 int ret;

43 struct _test_t *dev =

filp->private_data;

44

45 if(!dev->cur_size){

46 return 0;

47 }

48

49 if (copy_to_user(buf,

dev->kbuf, count)){

50 ret = - EFAULT;

51

}else{/*read函数成功读取后要修改cur_size*/

52 ret = count;

53 dev->cur_size -=

count;

54 }

55 P_DEBUG("cur_size:[%d]\n",

dev->cur_size);

56

57 return ret;

58 }

59

60 ssize_t test_write(struct file

*filp, const char __user *buf, size_t count, loff_t *offset)

61 {

62 int ret;

63 struct _test_t *dev =

filp->private_data;

64

65 if(copy_from_user(dev->kbuf,

buf, count)){

66 ret = - EFAULT;

67

}else{/*write函数成功写入后也要修改cur_size*/

68 ret = count;

69 dev->cur_size +=

count;

70 P_DEBUG("kbuf is

[%s]\n", dev->kbuf);

71

P_DEBUG("cur_size:[%d]\n", dev->cur_size);

72 }

73

74 return ret;

//返回实际写入的字节数或错误号

75 }

上面的程序其实就多了比上一个程序多了三步:

1)封装了一个结构体。

2)open函数要获得结构体并存放到private_data中。

3)read和write函数成功后要更新cur_size这个值。

这样,一个像样点的程序出来了,写个应用程序验证一下:

1 #include

2 #include

3 #include

4 #include

5

6 int main(void)

7 {

8 char buf[20];

9 int fd;

10 fd = open("/dev/test",

O_RDWR);

11 if(fd < 0)

12 {

13 perror("open");

14 return -1;

15 }

16

17 read(fd, buf, 10);

18 printf("buf

is [%s]\n", buf);

19

20 write(fd, "xiao bai",

10);

21

22 read(fd, buf, 10);

23 printf("buf

is [%s]\n", buf);

24

25 close(fd);

26 return 0;

27 }

运行一下:

[root: 1st]# insmod test.ko

major[253] minor[0]

hello kernel

[root: 1st]# mknod /dev/test c 253 0

[root: 1st]# ./app

buf is

[]//第一次读取时cur_size==0,没数据就会返回

[test_write]kbuf is

[xiao bai]//成功写入

[test_write]cur_size:[10]//更新cur_size

[test_read]cur_size:[0]//read读取成功,跟新cur_size

buf is [xiao

bai]//应用程序返回读到的内容

[root: 1st]#

三、read、write的改进

上面的函数还是不完善的,想象一下,平时的read、write函数会增加偏移量,但上面的函数是不会的。这是因为还有一个参数我没用上,就是"loff_t

offset"。

"loff_t

offset"这个参数是内核在调用函数时,从"struct

file"的成员"f_ops"拿到指针并当作参数传入。这样的做法让用户不用再从"struct

file"提取成员,直接拿参数用就行了!

通过这个参数,我们就可以改进并且实现三个函数:

1test_read:当应用程序调用read时内核会调用test_read。读取数据的同时,偏移量会增加。

2test_write:当应用程序调用write时内核会调用test_write。写入数据的同时,偏移量也会增加。

3test_llseek:这是跟应用程序的lseek对应的,用来修改偏移量的位置。

有了上面的三个函数的功能,这样才算是个像样的函数!

先改进一下read、write函数

40 ssize_t test_read(struct file

*filp, char __user *buf, size_t count, loff_t *offset)

41 {

42 int ret;

43 struct _test_t *dev =

filp->private_data;

44

45 if(*offset >=

DEV_SIZE){//如果偏移量已经超过了数组的容量

46 return count ? - ENXIO :

0; //count为0则返回0,表示读取0个数据成功

47 }

//count不为0则分会错误号,地址越界

48 if(*offset + count >

DEV_SIZE){ //如果读取字节数超过了最大偏移量

49 count = DEV_SIZE -

*offset; //则减少读取字节数。

50 }

51 /*copy_to_user的参数也要改一下*/

52 if (copy_to_user(buf,

dev->kbuf + *offset, count)){

53 ret = - EFAULT;

54 }else{

55 ret = count;

56 dev->cur_size -=

count; //读取后数组的字节数减少

57 *offset

+= count; //偏移量增加

58 P_DEBUG("read %d

bytes, cur_size:[%d]\n", count,dev->cur_size);

59 }

60

61 return ret;

//返回实际写入的字节数或错误号

62 }

63

64 ssize_t test_write(struct file

*filp, const char __user *buf, size_t count, loff_t *offset)

65 {

66 int ret;

67 struct _test_t *dev =

filp->private_data;

68 /*copy_from_user的参数也要改一下*/

69 if(*offset >=

DEV_SIZE){//如果偏移量已经超过了数组的容量

70 return count ? - ENXIO :

0; //count为0则返回0,表示读取0个数据成功

71 }

//count不为0则分会错误号,地址越界

72 if(*offset + count >

DEV_SIZE){ //如果读取字节数超过了最大偏移量

73 count = DEV_SIZE -

*offset; //则减少读取字节数。

74 }

75

76 if(copy_from_user(dev->kbuf,

buf, count)){

77 ret = - EFAULT;

78 }else{

79 ret = count;

80 dev->cur_size +=

count; //写入后数组的字节数增加

81 *offset

+= count; //偏移量增加

82 P_DEBUG("write %d

bytes, cur_size:[%d]\n", count, dev->cur_size);

83 P_DEBUG("kbuf is

[%s]\n", dev->kbuf);

84 }

85

86 return ret;

//返回实际写入的字节数或错误号

87 }

话说得好,越是需要检测出错,代码就会几何级增加,如果不想看这么多代码,把这两个函数前面的两个if(45-50、69-74)都删掉!反正写应用程序的时候小心翼翼一点就好了。这个程序只是为了验证"offset"的作用。

再来个小心翼翼的应用程序:

1 #include

2 #include

3 #include

4 #include

5

6 int main(void)

7 {

8 char buf[20];

9 int fd;

10 fd = open("/dev/test",

O_RDWR);

11 if(fd < 0)

12 {

13 perror("open");

14 return -1;

15 }

16

17 write(fd, "xiao bai",

10);

18

19 read(fd, buf, 10);

20 printf("buf

is [%s]\n", buf);

21

22 close(fd);

23 return 0;

24 }

验证一下:

[root: 2nd]# insmod test.ko

major[253] minor[0]

hello kernel

[root: 2nd]# mknod /dev/test c 253 0

[root: 2nd]# ./app

[test_write]write 10

bytes, cur_size:[10]//写入

[test_write]kbuf is

[xiao bai]

[test_read]read 10

bytes, cur_size:[0]//但读不出,因为偏移量增加

buf is []

上面的read函数根本读不出数据,这是因为偏移量增加了。这个时候需要一个函数来把偏移量移到开头,lseek函数就用上场了。下面就讲一下。

四、lseek函数的实现

应用层的函数lseek函数对应驱动的函数是llseek(为什么多了一个l我也想不懂)。

内核驱动:loff_t (*llseek)

(struct file * filp, loff_t offset, int whence);

对应应用层:off_t lseek(int

fd, off_t offset, int whence);

使用:

一看参数就知道,这两个函数的第二和第三个参数就是对应的,当应用层调用函数时,对应的参数就会让内核传给驱动的函数llseek。

参数:

offset:一看这个参数不是指针,就知道和read、write的参数不一样。这是应用层传来的参数,并不是"struct

file"的偏移量"f_ops"。

whence:这个也跟应用层的参数一样,指定从哪个位置开始偏移。

从开头位置:#define

SEEK_SET0

从当前位置:#define

SEEK_CUR1

从文件末端:#define

SEEK_END2

返回值:成功返回当前的更新的偏移量,失败返回错误号,而应用层会返回-1。

下面来个程序:/3rd_char/3rd_char_3/3rd/test.c

/*test_llseek*/

89 loff_t test_llseek (struct file

*filp, loff_t offset, int whence)

90 {

91 loff_t new_pos;

//新偏移量

92 loff_t old_pos = filp->f_pos;

//旧偏移量

93

94 switch(whence){

95 case SEEK_SET:

96 new_pos = offset;

97 break;

98 case SEEK_CUR:

99 new_pos = old_pos +

offset;

100 break;

101 case SEEK_END:

102 new_pos = DEV_SIZE +

offset;

103 break;

104 default:

105 P_DEBUG("unknow

whence\n");

106 return - EINVAL;

107 }

108

109 if(new_pos < 0 || new_pos

> DEV_SIZE){ //如果偏移量越界,返回错误号

110 P_DEBUG("f_pos

failed\n");

111 return - EINVAL;

112 }

113

114 filp->f_pos = new_pos;

115 return

new_pos;//正确返回新的偏移量

116 }

再来个应用程序:/3rd_char/3rd_char_3/3rd/app.c

1 #include

2 #include

3 #include

4 #include

5

6 int main(void)

7 {

8 char buf[20];

9 int fd;

10 int ret;

11

12 fd = open("/dev/test",

O_RDWR);

13 if(fd < 0)

14 {

15 perror("open");

16 return -1;

17 }

18

19 write(fd, "xiao bai",

10);

20 /*让偏移量移至开头,这样才能读取数据*/

21 ret = lseek(fd, 0, SEEK_SET);

22

23 read(fd, buf, 10);

24 printf("buf

is [%s]\n", buf);

25

26 close(fd);

27 return 0;

28 }

验证一下:

[root: 2nd]# ./app

[test_write]write 10

bytes, cur_size:[10]

[test_write]kbuf is

[xiao bai]

[test_read]read 10

bytes, cur_size:[0]//读到数据了!

buf is [xiao

bai]//读到数据了!

五、总结

拉风的时序图我就不画了。

上面讲的东西不多:

1)container_of的使用

2)怎么使用偏移量"filp->f_ops"。

3)llseek的编写。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/566886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

idea junit 测试看不到控制台报错信息_高手都这么给 Spring MVC 做单元测试!

本章节主要讲解以下两部分内容&#xff1a;1、Mock 测试简介2、测试用例演示一、Mock 测试简介1、什么是 mock 测试在测试过程中&#xff0c;对于某些不容易构造或者不容易获取的对象&#xff0c;用一个「虚拟的对象」来创建以便测试的测试方法&#xff0c;就是 「mock 测试」在…

windows查询每个线程占用的内存_C#多线程

一、基本概念1、进程首先打开任务管理器&#xff0c;查看当前运行的进程&#xff1a;从任务管理器里面可以看到当前所有正在运行的进程。那么究竟什么是进程呢&#xff1f;进程&#xff08;Process&#xff09;是Windows系统中的一个基本概念&#xff0c;它包含着一个运行程序所…

linux 录屏软件 按键,linux下常用的截图、录屏工具

linux下常用的截图、录屏工具(2010-01-05 10:47:21)由于和老公一起做一个百度俱乐部的小项目&#xff0c;在编写测试文档时要使用截图、录屏的小工具&#xff0c;于是展开搜索什么工具比较好使。录屏&#xff1a;在linux下常用的录屏工具有5种&#xff0c;可以baidu或者google下…

实体类blob类型_Mysql的数据类型和JPA的实体类

​MySQL中定义数据字段的类型对你数据库的优化是非常重要的。MySQL支持多种类型&#xff0c;大致可以分为三类&#xff1a;数值、日期/时间和字符串(字符)类型。数值类型Int,4字节,(-2147483648&#xff0c;2147483647), (0,4294967295)BIGINT,8 字节,(-9223372036854775808,92…

python升级版本命令_如何在python中安装和配置kivy库

kivy是python的UI开发工具包&#xff0c;主要关注用户界面显示效果&#xff0c;可以在Android、IOS、Linux、OS X和Windows上运行。如果python开发中使用kivy&#xff0c;需要安装和配置相关文件和依赖模块。1、在电脑左下角打开开始菜单&#xff0c;输入cmd打开终端窗口在电脑…

linux系统如何安装bt5,BT5硬盘安装(多系统linux + win + BT5)

BT5硬盘安装(多系统linux win BT5)实际上&#xff0c;Win BT5的安装不是很困难&#xff0c;但据我所知我也有两种: 1如官方说明中所述&#xff0c;工具unetbootin&#xff0c;运行unetbootin&#xff0c;在“ CD映像”中&#xff0c;选择BT5的ISO&#xff0c;在“类型”中选…

android主板读取vga线数据_智锐通掘金新基建上新系列之3.5quot; 与ATX工业主板图鉴...

4月份&#xff0c;工信部正式公布了《工业互联网体系架构(版本2.0)》&#xff01;5G、大数据、人工智能、区块链、边缘计算等新技术加速融入并不断拓宽工业互联网的内涵与赋能潜力。新一代信息技术为驱动的数字浪潮正在深刻重塑经济社会各个领域的当前&#xff0c;智锐通科技聚…

Python 爬虫之下载视频(五)

爬取第三方网站视频 文章目录 爬取第三方网站视频前言一、基本情况二、基本思路三、代码编写四、注意事项&#xff08;ffmpeg&#xff09;总结 前言 国内主流的视频平台有点难。。。就暂且记录一些三方视频平台的爬取吧。比如下面这个&#xff1a; 一、基本情况 这次爬取的方…

c语言json映射,GitHub - xujun621/cson: 基于C语言的json数据映射解析库

CSON基于cJSON,运行于C语言平台的json-struct模型解析工具简介CSON是一个简单的cJSON的二次封装&#xff0c;相比于使用原生cJSON一层一层解析的方式&#xff0c;CSON采用模型映射的方式&#xff0c;使用模型将结构体的特征进行描述&#xff0c;然后根据模型&#xff0c;将json…

cad渐变线怎么画_怎么画压力线和支撑线

支撑线与压力线的基本画法画法&#xff1a;将两个或两个以上的相对低点连成一条直线即得到支撑线将两个或两个以上的相对高点连成一条直线即得到压力线用法&#xff1a;1.支撑线和压力线的作用支撑线又称为抵抗线。当股价跌到某个价位附近时&#xff0c;股价停止下跌&#xff0…

c语言为什么要建项目,一个C语言小项目为什么都说牛逼

原标题&#xff1a;一个C语言小项目为什么都说牛逼意在鼓励C语言学者。更有兴趣&#xff0c;学习更富有创业和乐趣&#xff01;推荐加学习交流群&#xff1a;658807522 可以在一起学习交流&#xff0c;既是参赛选手&#xff0c;又是学者&#xff0c;也可以先学习再参赛&#xf…

单片机c语言 oxfe,AVR单片机入门及C语言高效设计实践(五)

ATMEAGl6L的中断系统什么是“中断”?顾名思义中断就是中断某一工作过程去处理一些与本工作过程无关或间接相关或临时发生的事件&#xff0c;处理完后&#xff0c;则继续原工作过程。比如&#xff1a;你在看书&#xff0c;电话响了&#xff0c;你在书上做个记号后去接电话&…

android 生成 资源文件,SVG-Android开源库——SVG生成Vector资源文件的编辑预览工具...

Vector矢量图在Android项目中的利用愈来愈广泛&#xff0c;但是如果你想用Android Studio自带的工具将SVG图片转化成Vector资源文件却是相当麻烦&#xff0c;首先能支持的SVG规范较少&#xff0c;其次操作流程麻烦。而另外一种方式就是通过http://inloop.github.io/svg2android…

android微信朋友圈相册背景,Android 仿微信朋友圈图片拖拽返回

目前的app的动画效果是越来越炫了&#xff0c;很多主流app的图片预览返回都有类似功能&#xff0c;比较常见的是ios自带相册&#xff0c;微信朋友圈等等。自己项目中也有类似功能&#xff0c;最近整理了一下这个功能的代码&#xff0c;做个笔记记录&#xff0c;有兴趣的朋友可以…

erp采购总监个人总结_《用友 ERP 培训教程:财务核算/供应链管理/物料需求计划》ERP概述 : ERP基础知识...

第1章 ERP概述本章重点_- _ERP概要_- _ERP实施成功的必要条件_- _ERP几个重要的名词解释用友ERP-U8&#xff08;V8 .72&#xff09;简介及功能按钮说明1.1 ERP基础知识1.1.1 ERP基本概念ERP&#xff08;Enterprise Resources Planning&#xff09;中文叫做企业资源规划&…

soc 设计soc设计 uml实务手册_企业内训“软件需求设计建模方法学全程实例剖析”训练方案(2020年)...

※训练介绍※利润需求-设计。软件开发中&#xff0c;需求是解决“系统怎样好卖”的问题&#xff0c;设计是解决“降低开发成本”的问题。要迈向“低成本制造好卖的产品”的境界&#xff0c;并非喊喊口号就能达到。口号&#xff1a;我们只做最重要的需求&#xff0c;尽快把系统推…

鸿蒙分布式通讯子系统,【鸿蒙】分布式通信子系统--让华为手机发现Hi3861开发板...

目录&#xff1a;工具步骤运行结果工具&#xff1a;1. 华为手机&#xff0c;需要有多设备协同功能。在设置->更多连接->多设备协同&#xff0c;查看是否有多设备协同功能&#xff0c;此功能使用的就是coap协议。2. Hi3861开发板步骤&#xff1a;1. 修改源码&#xff0c;…

winform 统计大量数据重复的元素个数_DAY10——推断统计之概率与概率分布:常见的离散型概率分布...

「数学期望——某件事情大量发生之后的平均结果」----------------分割又分割----------------------昨天文章排版出了问题&#xff0c;今天重新排版。数据的三个统计维度&#xff1a;集中程度、离散程度、分布情况。集中程度&#xff1a;期望离散程度&#xff1a;方差、标准差…

xss跨站脚本攻击_常见攻击之xss跨站脚本攻击

前言随着互联网的不断发展&#xff0c;web应用的互动性也越来越强。相应的&#xff0c;在用户体验提升的同时安全风险也会跟着有所增加。今天&#xff0c;我们就来讲一讲web渗透中常见的攻击方式之一&#xff0c;XSS攻击。首先需要了解他是如何工作的&#xff0c;以及我们如何利…

缺陷调研报告_质量零缺陷 | 打造极致产品的质量管理之道

质量是政治质量是生命质量是效益为强化全员“零缺陷”质量意识&#xff0c;坚决打赢质量提升攻坚战&#xff0c;现开设“质量零缺陷”专栏&#xff0c;着力宣传全院各单位在加强质量管理&#xff0c;落实零缺陷理念等方面的典型做法和质量故事。今天为大家带来的是曾获得“全国…