Linux内核驱动学习(三)字符型设备驱动之初体验

网友投稿 705 2022-05-29

linux字符型设备驱动之初体验

文章目录

Linux字符型设备驱动之初体验

前言

框架

字符型设备

程序实现

cdev

kobj

owner

file_operations

dev_t

设备注册过程

申请设备号

注册设备

register_device

如何构建

模块编译

内核编译

Makefile

Kconfig

总结

参考

前言

驱动总共分为字符型设备驱动,块设备驱动,网络设备驱动。对于字符型设备驱动的资料,网上比较多,《Linux Kernel Driver》这本书可以了解一下,对于学习Linux驱动有很大的帮助,当然还有很多优秀的书籍,暂不一一列举,本文简单总结了在学习字符型设备驱动的过程中遇到的问题,以及对该类驱动的理解。

框架

字符型设备

什么是字符型设备?字符型以字符(Byte/Char)为单位进行数据传输的设备,如键盘,串口等等设备,所以Linux环境编程中文件I/O进行操作的系统接口如open,read,write,close等等,在字符型设备驱动中同样需要支持这些接口。这里会用到file_operations结构体,在后面会讲到。

程序实现

下面是一个简单字符型设备驱动程序,可以在系统注册一个字符型设备驱动,目前未实现open,read,write,close等接口。

#include #include #include #include #include #define DRIVER_DATA_SIZE 4096 static int major_dev_index = 0; struct cnc_character_st{ struct cdev device; u8 data[DRIVER_DATA_SIZE]; }; static struct cnc_character_st *character_dev; //TODO static ssize_t cnc_character_read (struct file * fd, char __user * data, size_t len, loff_t * offset){ ssize_t ret = 0; printk("%s call\n",__func__); return ret; } //TODO static ssize_t cnc_character_write (struct file * fd, const char __user * data, size_t len, loff_t * offset){ ssize_t ret = 0; return ret; } //TODO static long cnc_character_unlocked_ioctl (struct file * fd, unsigned int data, unsigned long cmd){ long ret = 0; return ret; } //TODO static int cnc_character_open (struct inode * node, struct file * fd){ int ret = 0; return ret; } //TODO static int cnc_character_release (struct inode * node, struct file * fd){ int ret = 0; return ret; } static const struct file_operations cnc_character_ops = { .owner = THIS_MODULE, .read = cnc_character_read, .write = cnc_character_write, .open = cnc_character_open, .unlocked_ioctl = cnc_character_unlocked_ioctl, .release = cnc_character_release, }; static int register_device(struct cnc_character_st *mdev,int major_dev_index,int minor_dev_index){ int ret = 0; int dev_no = MKDEV(major_dev_index, minor_dev_index); // 初始化dev cdev_init(&mdev->device, &cnc_character_ops); mdev->device.owner = THIS_MODULE; ret = cdev_add(&mdev->device,dev_no,1); if(ret){ printk(KERN_ERR "cdev add device failed\n"); } return ret; } static int unregister_device(struct cnc_character_st *mdev){ int ret= 0; kfree(character_dev); return ret; } static int __init cnc_character_init(void){ int ret = 0; dev_t devno = MKDEV(major_dev_index, 0); if(major_dev_index){ ret = register_chrdev_region(devno, 1, "cnc_character"); }else{ ret = alloc_chrdev_region(&devno, 0, 1, "cnc_character"); major_dev_index = MAJOR(devno); } if(ret < 0){ return ret; } character_dev = kmalloc(sizeof(struct cnc_character_st),GFP_KERNEL); if(!character_dev){ printk("%s failed malloc character_dev call\n",__func__); ret = -ENOMEM; goto failed; }else{ printk("%s success malloc character_dev call\n",__func__); } register_device(character_dev,major_dev_index,0); return 0; failed: unregister_chrdev_region(devno, 1); return ret; } module_init(cnc_character_init); static void __exit cnc_character_exit(void){ printk("%s call\n",__func__); unregister_device(character_dev); } module_exit(cnc_character_exit); MODULE_AUTHOR("zhaojunhui@cncgroup.top"); MODULE_VERSION("1.0"); MODULE_LICENSE("GPL");

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

Linux内核驱动学习(三)字符型设备驱动之初体验

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

cdev

在linux/cdev.h中可以阅读相关字符型设备驱动的信息,其中包括cdev结构体可以做一下分析,先定位到源码做一下分析

#ifndef _LINUX_CDEV_H #define _LINUX_CDEV_H #include #include #include struct file_operations; struct inode; struct module; struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; }; void cdev_init(struct cdev *, const struct file_operations *); struct cdev *cdev_alloc(void); void cdev_put(struct cdev *p); int cdev_add(struct cdev *, dev_t, unsigned); void cdev_del(struct cdev *); void cd_forget(struct inode *); #endif

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

其中包括结构体cdev和cdev的一系列函数接口cdev_init,cdev_alloc,cdev_put,cdev_add,cdev_del,cd_forget。

kobject是所有设备驱动模型的基类,而cdev可以理解为是它的派生类,这里使用了面向对象的思想,通过访问cdev中的kobj成员,就能使用kobject中所有功能。关于kobject的详细内容可以参考内核文档Documentation/kobject.txt。

首先明确一点的是owner是struct module的指针变量,owner=THIS_MODULE;,这里将指针指向当前的模块,关于THIS_MODULE以及struct module的知识可以参考这篇博客。

这个结构体位于/linux/include/fs.h,代码如下。

struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif };

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

在file_operations定义了很多I/O操作接口,这里同样使用了面向对象编程的思想,每个接口可以在重新定义file_operations结构体变量的时候,重新赋于自定义功能的函数,如下,可以理解read,write,open,unlocked_ioctl,release是对抽象函数的实现。

static const struct file_operations cnc_character_ops = { .owner = THIS_MODULE, .read = cnc_character_read, .write = cnc_character_write, .open = cnc_character_open, .unlocked_ioctl = cnc_character_unlocked_ioctl, .release = cnc_character_release, };

1

2

3

4

5

6

7

8

设备注册过程

设备的初始化在函数cnc_character_init中完成具体的功能实现,主要分为两个部分,设备号的申请和设备的注册。其中设备注册单独封装到register_device函数中。

dev_t devno = MKDEV(major_dev_index, 0); if(major_dev_index){ ret = register_chrdev_region(devno, 1, "cnc_character"); }else{ ret = alloc_chrdev_region(&devno, 0, 1, "cnc_character"); major_dev_index = MAJOR(devno); }

1

2

3

4

5

6

7

8

character_dev = kmalloc(sizeof(struct cnc_character_st),GFP_KERNEL); if(!character_dev){ printk("%s failed malloc character_dev call\n",__func__); ret = -ENOMEM; goto failed; }else{ printk("%s success malloc character_dev call\n",__func__); } register_device(character_dev,major_dev_index,0);

1

2

3

4

5

6

7

8

9

10

11

在register_device中,主要用到了cdev提供的函数接口。

cdev_init初始化一个字符型设备并传入自定义的file_operations类型变量cnc_character_ops。

cdev_add将初始化的字符型设备添加到内核,并分配已经申请好的设备号。

static int register_device(struct cnc_character_st *mdev,int major_dev_index,int minor_dev_index){ int ret = 0; int dev_no = MKDEV(major_dev_index, minor_dev_index); // 初始化dev cdev_init(&mdev->device, &cnc_character_ops); mdev->device.owner = THIS_MODULE; ret = cdev_add(&mdev->device,dev_no,1); if(ret){ printk(KERN_ERR "cdev add device failed\n"); } return ret; }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

如何构建

模块编译

使用这个Makefile

KVERS = $(shell uname -r) # Kernel modules obj-m += demo_character.o # Specify flags for the module compilation. EXTRA_CFLAGS=-g -O0 build: kernel_modules kernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules clean: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

1

2

3

4

5

6

7

8

9

10

内核编译

obj-$(CONFIG_DEMO_CHARACTER_DRIVER) +=demo_character.o

1

menuconfig DEMO_DRIVERS tristate "demo drivers" config DEMO_CHARACTER_DRIVER tristate "the most simplest character driver" help character driver endif

1

2

3

4

5

6

7

总结

总体上来说,字符型设备驱动框架还是相对简单的,通过这次学习加深了对cdev的认识和linux内核源码中面向对象的设计思想,但是这里还没有对devfs和sysfs做相应的介绍,后面继续学习这两者的区别以及总线驱动模型,总之,加油吧。

参考

https://blog.csdn.net/lucky_liuxiang/article/details/83413946

https://www.cnblogs.com/helloahui/p/3677192.html

https://blog.csdn.net/jk110333/article/details/8563647

Linux

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:旋转的Apriltag码
下一篇:物联网设备天线设计与选型指南
相关文章