Linux设备驱动开发---字符设备驱动程序

字符设备驱动程序

  • 1 主设备和次设备的概念
    • 设备号的注册和释放
      • 静态方法
      • 动态方法
      • 区别
  • 2 设备文件操作
    • struct file_operations与struct file、struct inode关系
  • 3 分配和注册字符设备
    • class_create
    • cdev_add
    • device_create
  • 4 字符设备驱动程序

字符设备通过字符(一个接一个的字符)以流方式向用户程序传递数据,就像串行端口那样。字符设备驱动通过/dev目录下的特殊文件公开设备的属性和功能,通过这个文件可以在设备和用户应用程序之间交换数据,也可以通过它来控制实际的物理设备。这也是Linux的基本概念,一切皆文件。字符设备驱动程序是内核源码中最基本的设备驱动程序。字符设备在内核中表示为struct cdev的实例,struct cdev定义在include/linux/cdev.h中:

struct cdev {struct kobject kobj; struct module *owner;	/* 指向提供驱动程序的模块 */const struct file_operations *ops; /* 是一组文件操作,实现了与设备通信的具体操作 */struct list_head list; /* 用来将已经向内核注册的所有字符设备形成链表.*/dev_t dev; /* 设备号 */unsigned int count; /* 隶属于同一主设备号的次设备号的个数.*/
} __randomize_layout;

1 主设备和次设备的概念

字符设备在/dev目录下,使用 ls -l命令查看
在这里插入图片描述

开头为c的代表字符设备文件,开头为b的代表块设备文件,日期左边的第五列、第六列用<X,Y>格式表示,X代表的是主设备号,Y代表的次设备号,这是典型的从用户空间标识字符设备,及其主次设备号的方法。

内核用dev_t类型变量维持设备号,该变量是u32。主设备号仅占12位,次设备号占20位。

typedef __kernel_dev_t		dev_t;typedef __u32 __kernel_dev_t;typedef unsigned int __u32;

dev_t类型定义在include/linux/kdev_t.h中,可以通过如下两个宏定义来获取主、次设备号:

MAJOR(dev_t dev);
MINOR(dev_t dev);

如果有主设备和次设备号,也可以通过宏MKDEV(int major,int minor)来构建dev_t。

设备注册时,必须使用主设备号和次设备号,前者标识一个特定的驱动程序,后者用作标识使用该驱动程序的各设备(设备列表中的数组索引),因为同一个驱动可处理多个设备,而不同的驱动程序可以处理相同类型的不同设备。

设备号的注册和释放

设备号在系统范围内标识设备文件,有两种不同的方法分配设备号。
下面两个函数都在fs/char_dev.c实现

静态方法

静态方法是调用register_chardev_region()函数,该方法必须事先知道所需的设备号

int register_chrdev_region(dev_t from, unsigned count, const char *name)

这个函数成功返回0,失败返回错误码。from是由我们所需的主设备号和合理范围内的次设备号组成,可由MKDEV构建。count是所需的连续设备号数目,name是相关设备或者驱动程序的名字。

动态方法

使用alloc_chardev_region()函数,使内核自动分配设备号,建议采用这种方法获得有效的设备号。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

这个函数成功返回0,失败返回错误码。dev获取分配的设备号,baseminor代表申请的次设备号范围内的第一个数字,count代表次设备的数目,name代表相关设备或者驱动程序的名字。

区别

这两种分配方法的区别在于,第一种方法必须事先知道所需的设备号,这就是注册制:把所需的设备号告诉内核。这可能在教学中使用,只有自己使用该驱动程序时,才会这样选择,如果在其他机器上加载该驱动程序,就无法保证所选择的设备号在这台机器未被占用,这会引起设备号的冲突和麻烦。第二种更安全,因为内核帮助获取一个合适的设备号,所以我们甚至不需要关心在其他机器上加载该模块所出现的问题,内核将根据具体情况来自动分配。

2 设备文件操作

可以在文件上执行的操作取决于管理文件的设备驱动程序。这样的操作在内核中定义为struct file_operations的实例。struct file_operations定义了一组回调函数,用于处理文件上的所有用户空间的系统调用。举个例子,如果想让用户在设备文件上执行write操作,必须在驱动中实现write函数对于的回调函数,并把它添加到绑定在设备上的struct file_operations中,struct file_operations定义在include/linux/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 *);int (*iterate_shared) (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 (*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_MMUunsigned (*mmap_capabilities)(struct file *);
#endifssize_t (*copy_file_range)(struct file *, loff_t, struct file *,loff_t, size_t, unsigned int);int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,u64);ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,u64);
};

其中的每一个函数都和系统调用链接在一起,它们都不是必需的。当用户代码在指定文件上调用与文件相关的系统调用时,内核会查找负责这个文件的驱动程序,定位它的struct file_operations结构,并检查和该系统调用匹配的方法是否已经定义。如果已经定义了,就运行它。如果未定义,则根据系统调用不同返回不同的错误码。

struct file_operations与struct file、struct inode关系

struct inode表示一个具体文件。一个设备或者驱动会由struct inode的实例表示。在该结构体中,我们需要注意以下几个域。

struct inode {...const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */union {struct pipe_inode_info	*i_pipe;	/* 如果是Linux管道,则设置并使用 */struct block_device	*i_bdev;		/* 如果是块设备,则设置并使用 */struct cdev		*i_cdev;			/* 如果是字符设备,则设置并使用 */char			*i_link;unsigned		i_dir_seq;};
....
};

struct inode里面也有struct file_operations,但是i_fop指向的是默认的索引节点操作,如果struct inode代表的是字符设备,则i_cdev会指向一个struct cdev结构,对文件进行操作时,使用的是cdev中file_operations中定义的文件操作方法。

struct file代表的是一个进程打开的文件,其里面也有struct file_operations

struct file {...const struct file_operations	*f_op;...
};

当我们在应用层使用open函数打开一个文件时,会创建struct file对象,初始化struct file对象时,struct file对象中的file_operations将指向struct inode的file_operations(准备的来说,struct inode如果没有定义文件的具体操作,将指向默认的file_operations,如果定义了,比如字符设备,将指向字符设备的file_operations)

比如我们使用open打开两个字符设备

fd0 = open("/dev/com0",O_RDWR);fd1 = open("/dev/com1",O_RDWR);

如下图
请添加图片描述
struct inode使用struct cdev中的file_operations,struct file也指向struct cdev中的file_operations,当对com0或者com1操作时,直接调用struct file的file_operations。

如何将file_operations里面定义的操作和struct cdev结构绑定到一起呢?我们可以使用cdev_init函数,将struct cdev中的ops指向第二个参数指向的内容

void cdev_init(struct cdev *, const struct file_operations *)

3 分配和注册字符设备

字符设备在内核中表示为struct cdev的实例。在编写字符设备驱动程序时,目标是最终创建并注册与struct file_operations关联的结构实例,为用户空间提供一组可以在该设备上执行的操作函数,为了实现这个目标,必须执行以下几个步骤:

  1. 使用alloc_chardev_region()保留一个主设备号和一定范围的次设备号。
  2. 使用class_create()创建自己的设备类。
  3. 创建一个struct file_operations(传递给cdev_init),每一个设备都需要创建,并调用call_init和cdev_add注册这个设备。
  4. 调用device_create()创建每个设备,并给它们一个合适的名字,这样,就可以在/dev目录下创建出设备。

class_create

宏class_create()用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进Linux内核系统中。此函数的执行效果就是在目录/sys/class下创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数,但此文件夹是空的。宏class_create()在实现时,调用了函数__class_create(),作用和函数__class_create()基本相同。
class_create在include/linux/device.h中被定义

#define class_create(owner, name)                     \
({                                                    \static struct lock_class_key __key;               \__class_create(owner, name, &__key);              \
})
  • 参数owner是一个struct module结构体类型的指针,指向函数__class_create()即将创建的struct class类型对象的拥有者,一般赋值为THIS_MODULE,此结构体的详细定义见文件include/linux/module.h。
  • 参数name是char类型的指针,代表即将创建的struct class变量的名字,用于给struct class的name字段赋值。

返回值为创建的逻辑类。

此宏需要与函数class_destroy()配对使用,不能单独使用,当单独使用时,第一次不会出现错误,但当第二次插入模块时就会出现错误。

cdev_add

函数cdev_add()用于向Linux内核系统中添加一个新的cdev结构体变量所描述的字符设备,并且使这个设备立即可用。
在文件linux/cdev.h中定义:

int cdev_add(struct cdev *, dev_t, unsigned)

函数 cdev_add()有三个输入参数,第一个输入参数代表即将被添加入Linux内核系统的字符设备;第二个输入参数是dev_t类型的变量,此变量代表设备的设备号,其中包括主设备号和次设备号;第三个输入参数是无符号的整型变量,代表想注册设备的设备号的范围,用于给struct cdev中的字段count赋值

device_create

函数device_create()用于动态地创建逻辑设备,并对新的逻辑设备类进行相应的初始化,将其与此函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到Linux内核系统的设备驱动程序模型中。函数能够自动地在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建与逻辑类对应的设备文件。

该函数定义在linux/device.h中

struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
  • 函数device_create()的第一个输入参数代表与即将创建的逻辑设备相关的逻辑类,也就是class_create

  • 第二个输入参数代表即将创建的逻辑设备的父设备的指针,子设备与父设备的关系是:当父设备不可用时,子设备不可用,子设备依赖父设备,父设备不依赖子设备。

  • 第三个输入参数是逻辑设备的设备号

  • 第四个输入参数是void类型的指针,代表回调函数的输入参数。

  • 第五个输入参数是逻辑设备的设备名,即在目录/sys/devices/virtual创建的逻辑设备目录的目录名。

返回值是struct device结构体类型的指针,指向新创建的逻辑设备,

device_create创建了设备文件,我们就可以根据该设备文件来和驱动或设备交互了。

注意:函数device_create()必须和函数device_destroy()配对使用,这样才不会出现错误

4 字符设备驱动程序

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/device.h>
#include <linux/cdev.h>static unsigned int major; /* major number for device */
static struct class *dummy_class;
static struct cdev dummy_cdev;int dummy_open(struct inode * inode, struct file * filp)
{pr_info("Someone tried to open me\n");return 0;
}int dummy_release(struct inode * inode, struct file * filp)
{pr_info("Someone closed me\n");return 0;
}ssize_t dummy_read (struct file *filp, char __user * buf, size_t count,loff_t * offset)
{pr_info("Nothing to read guy\n");return 0;
}ssize_t dummy_write(struct file * filp, const char __user * buf, size_t count,loff_t * offset)
{pr_info("Can't accept any data guy\n");return count;
}struct file_operations dummy_fops = {open:       dummy_open,release:    dummy_release,read:       dummy_read,write:      dummy_write,
};static int __init dummy_char_init_module(void)
{struct device *dummy_device;int error;dev_t devt = 0;/* Get a range of minor numbers (starting with 0) to work with */error = alloc_chrdev_region(&devt, 0, 1, "dummy_char");if (error < 0) {pr_err("Can't get major number\n");return error;}major = MAJOR(devt);pr_info("dummy_char major number = %d\n",major);/* Create device class, visible in /sys/class */dummy_class = class_create(THIS_MODULE, "dummy_char_class");if (IS_ERR(dummy_class)) {pr_err("Error creating dummy char class.\n");unregister_chrdev_region(MKDEV(major, 0), 1);return PTR_ERR(dummy_class);}/* Initialize the char device and tie a file_operations to it */cdev_init(&dummy_cdev, &dummy_fops);dummy_cdev.owner = THIS_MODULE;/* Now make the device live for the users to access */cdev_add(&dummy_cdev, devt, 1);dummy_device = device_create(dummy_class,NULL,   /* no parent device */devt,    /* associated dev_t */NULL,   /* no additional data */"dummy_char");  /* device name */if (IS_ERR(dummy_device)) {pr_err("Error creating dummy char device.\n");class_destroy(dummy_class);unregister_chrdev_region(devt, 1);return -1;}pr_info("dummy char module loaded\n");return 0;
}static void __exit dummy_char_cleanup_module(void)
{unregister_chrdev_region(MKDEV(major, 0), 1);device_destroy(dummy_class, MKDEV(major, 0));cdev_del(&dummy_cdev);class_destroy(dummy_class);pr_info("dummy char module Unloaded\n");
}module_init(dummy_char_init_module);
module_exit(dummy_char_cleanup_module);MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_DESCRIPTION("Dummy character driver");
MODULE_LICENSE("GPL");

在/dev目录下创建了字符设备

在这里插入图片描述
在/sys/class下创建了目录

在这里插入图片描述
设备的详细信息
在这里插入图片描述

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

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

相关文章

Java中的异常栈轨迹和异常链

Java中允许对异常进行再次抛出&#xff0c;以提交给上一层进行处理&#xff0c;最为明显的例子为Java的常规异常。 常规异常&#xff1a;有Java所定义的异常&#xff0c;不需要异常声明&#xff0c;在未被try-catch的情况下&#xff0c;会被默认上报到main()方法。 Example: pu…

同步---信号量

信号量1 信号量2 驱动程序和测试程序3 内核的具体实现总结1 信号量 Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已经被占用的信号量时&#xff0c;信号量会将其放到一个等待队列&#xff0c;然后让其睡眠&#xff0c;这时处理器去执行其他代码。当持有信号量的进…

算法---KMP算法

字符串1 KMP算法状态机概述构建状态转移1 KMP算法 原文链接&#xff1a;https://zhuanlan.zhihu.com/p/83334559 先约定&#xff0c;本文用pat表示模式串&#xff0c;长度为M&#xff0c;txt表示文本串&#xff0c;长度为N&#xff0c;KMP算法是在txt中查找子串pat&#xff0…

文件上传 带进度条(多种风格)

文件上传 带进度条 多种风格 非常漂亮&#xff01; 友好的提示 以及上传验证&#xff01; 部分代码&#xff1a; <form id"form1" runat"server"><asp:ScriptManager ID"scriptManager" runat"server" EnablePageMethods&quo…

同步---自旋锁

1 自旋锁的基本概念 自旋锁最多只能被一个可执行线程持有&#xff0c;如果一个执行线程试图获得一个已经被使用的自旋锁&#xff0c;那么该线程就会一直进行自旋&#xff0c;等待锁重新可用。在任何时刻&#xff0c;自旋锁都可以防止多余一个的执行线程同时进入临界区。 Linu…

实习日志----4.播放时段参数设置

由于客户在下发广告时&#xff0c;一则广告可在多个时段播放&#xff0c;这就需要设置多个播放时段的参数。 但在这种情况下&#xff0c;我并不知道用户每次需要下发几个时段&#xff0c;所以前台不能设定死。 因此我要实现这么一个功能&#xff0c;让用户根据自己的需要来动态…

linux系统编程---线程总结

线程总结1 线程的实现线程创建线程退出线程等待线程清理2 线程的属性线程的分离线程的栈地址线程栈大小线程的调度策略线程优先级3 线程的同步互斥锁读写锁条件变量信号量线程是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该进程中的全部系统资源&#xff0c;例…

如何给Linux操作系统(CentOS 7为例)云服务器配置环境等一系列东西

1.首先&#xff0c;你得去购买一个云服务器&#xff08;这里以阿里云学生服务器为例&#xff0c;学生必须实名认证&#xff09; 打开阿里云&#xff0c;搜索学生服务器点击进入即可 公网ip为连接云服务器的主机 自定义密码为连接云服务器是需要输入的密码 购买即可 点击云服…

Linux系统编程---I/O多路复用

文章目录1 什么是IO多路复用2 解决什么问题说在前面I/O模型阻塞I/O非阻塞I/OIO多路复用信号驱动IO异步IO3 目前有哪些IO多路复用的方案解决方案总览常见软件的IO多路复用方案4 具体怎么用selectpollepolllevel-triggered and edge-triggered状态变化通知(edge-triggered)模式下…

c#中textbox属性_C#.Net中的TextBox.MaxLength属性与示例

c#中textbox属性Here we are demonstrating use of MaxLength property of TextBox. 在这里&#xff0c;我们演示了TextBox的MaxLength属性的使用。 MaxLength property of TextBox is used to set maximum number of character that we can input into a TextBox. Limit of M…

IIS7 MVC网站生成、发布

(1)生成。 确保System.Web.Mvc.dll在bin目录下 (2)发布网站到文件系统 (3)在IIS中为网站添加应用程序池&#xff08;一个虚拟目录&#xff0c;一个应用程序池&#xff09; (4)添加在默认网站下添加虚拟目录 &#xff08;5&#xff09;转换为应用程序 至此&#xff0c;部署完毕 …

C语言多维数组

文章目录多维数组数组名下标指向数组的指针作为函数参数的多维数组指针数组小结多维数组 如果某个数组的维数超过1&#xff0c;它就被称为多维数组&#xff0c;例如&#xff0c;下面这个声明&#xff1a; int matrix[6][10]创建了一个包含60个元素的矩阵。但是&#xff0c;它…

fwrite函数的用法示例_C语言中的fwrite()函数(带有示例)

fwrite函数的用法示例C中的fwrite()函数 (fwrite() function in C) Prototype: 原型&#xff1a; size_t fwrite(void *buffer, size_t length, size_t count, FILE *filename);Parameters: 参数&#xff1a; void *buffer, size_t length, size_t count, FILE *filenameRetu…

伙伴算法、slab机制、内存管理函数

文章目录1 伙伴算法页框操作alloc_pages()2 slabslab机制要解决的问题使用高速缓存3 内存管理函数kmallockzallocvmallocvzalloc区别参考文章内核使用struct page结构体描述每个物理页&#xff0c;也叫页框。内核在很多情况下&#xff0c;需要申请连续的页框&#xff0c;而且数…

Javaweb---监听器

1.什么是监听器 监听器就是监听某个对象的状态变化的组件。 事件源&#xff1a;被监听的对象 ----- 三个域对象 request session servletContext 监听器&#xff1a;监听事件源对象 事件源对象的状态的变化都会触发监听器 ---- 62 注册监听器&#xff1a;将监听器与事件源进行…

Linux中的Ramdisk和Initrd

Ramdisk简介先简单介绍一下ramdisk&#xff0c;Ramdisk是虚拟于RAM中的盘(Disk)。对于用户来说&#xff0c;能把RAM disk和通常的硬盘分区&#xff08;如/dev/hda1&#xff09;同等对待来使用&#xff0c;例如&#xff1a;redice # mkfs.ext2 /dev/ram0mke2fs 1.38 (30-Jun-200…

slab下kmalloc内核函数实现

文章目录kmalloc的整体实现获取高速缓存高速缓存获取index总结https://blog.csdn.net/qq_41683305/article/details/124554490&#xff0c;在这篇文章中&#xff0c;我们介绍了伙伴算法、slab机制和常见的内存管理函数&#xff0c;接下来&#xff0c;我们看看kmalloc内核函数的…

标题:三羊献瑞

标题&#xff1a;观察下面的加法算式&#xff1a; 其中&#xff0c;相同的汉字代表相同的数字&#xff0c;不同的汉字代表不同的数字。 请你填写“三羊献瑞”所代表的4位数字&#xff08;答案唯一&#xff09;&#xff0c;不要填写任何多余内容。 思路分析&#xff1a; 首先…

进程虚拟地址管理

文章目录1 地址分布实际使用中的内存区域2 进程的虚拟地址描述用户空间mmap线程之间共享内存地址的实现机制1 地址分布 现在采用虚拟内存的操作系统通常都使用平坦地址空间&#xff0c;平坦地址空间是指地址空间范围是一个独立的连续空间&#xff08;比如&#xff0c;地址从0扩…

标题:加法变乘法

标题&#xff1a;我们都知道&#xff1a;123 … 49 1225 现在要求你把其中两个不相邻的加号变成乘号&#xff0c;使得结果为2015 比如&#xff1a; 123…10*1112…27*2829…49 2015 就是符合要求的答案。 请你寻找另外一个可能的答案&#xff0c;并把位置靠前的那个乘号左…