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

举报
小麦大叔 发表于 2021/12/29 23:58:04 2021/12/29
【摘要】 Linux字符型设备驱动之初体验 文章目录 Linux字符型设备驱动之初体验前言框架字符型设备程序实现cdevkobjownerfile_operationsdev_t 设备注册过程申请...

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

前言

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

框架

在这里插入图片描述

字符型设备

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

程序实现

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

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>

#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
  • 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 <linux/kobject.h>
#include <linux/kdev_t.h>
#include <linux/list.h>
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_initcdev_alloccdev_putcdev_addcdev_delcd_forget

kobj

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

owner

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

file_operations

这个结构体位于/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结构体变量的时候,重新赋于自定义功能的函数,如下,可以理解readwriteopenunlocked_ioctlrelease是对抽象函数的实现。

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

dev_t

设备注册过程

设备的初始化在函数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

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

内核编译

Makefile

obj-$(CONFIG_DEMO_CHARACTER_DRIVER) +=demo_character.o

  
 
  • 1

Kconfig

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内核源码中面向对象的设计思想,但是这里还没有对devfssysfs做相应的介绍,后面继续学习这两者的区别以及总线驱动模型,总之,加油吧。

参考

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

文章来源: great.blog.csdn.net,作者:小麦大叔,版权归原作者所有,如需转载,请联系作者。

原文链接:great.blog.csdn.net/article/details/86764967

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。