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 LinkedHashMap getOrDefault()方法与示例

LinkedHashMap类的getOrDefault()方法 (LinkedHashMap Class getOrDefault() method) getOrDefault() method is available in java.util package. getOrDefault()方法在java.util包中可用。 getOrDefault() method is used to get the value associated with the given key el…

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

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

贪心算法---背包问题(物品可以分割问题)

问题背景&#xff1a; 有一天&#xff0c;阿里巴巴赶着一头毛驴上山砍柴。砍好柴准备下山时&#xff0c;远处突然出现一股烟尘&#xff0c;弥漫着直向上空飞扬&#xff0c;朝他这儿卷过来&#xff0c;而且越来越近。靠近以后&#xff0c;他才看清原来是一支马队&#xff0c;他…

同步---信号量

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

Java Float类floatToIntBits()方法与示例

Float类floatToIntBits()方法 (Float class floatToIntBits() method) floatToIntBits() method is available in java.lang package. floatToIntBits()方法在java.lang包中可用。 floatToIntBits() method follows IEEE 754 floating-point standards and according to standa…

解释三度带和六度带的概念以及各坐标系如何定义

★ 地形图坐标系&#xff1a;我国的地形图采用高斯&#xff0d;克吕格平面直角坐标系。在该坐标系中&#xff0c;横轴&#xff1a;赤道&#xff0c;用&#xff39;表示&#xff1b;纵轴&#xff1a;中央经线&#xff0c;用&#xff38;表示&#xff1b;坐标原点&#xff1a;中央…

0-1背包问题(物品不可分割)

问题背景&#xff1a; 所谓“钟点秘书”&#xff0c;是指年轻白领女性利用工余时间为客户提供秘书服务&#xff0c;并按钟点收取酬金。“钟点秘书”为客户提供有偿服务的方式一般是&#xff1a;采用电话、电传、上网等“遥控”式 服务&#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…

cache初接触,并利用了DataView

我们在写代码的时候,如果数据控件要获得数据,一般方法,Conn.Open();OleDbCommand cmd;cmd new OleDbCommand(sql, Conn);GridView1.DataSource dbcenter.accessGetDataSet(sql);GridView1.DataBind();Conn.close();但如果多个数据控件要绑定数据,则比较频繁打开数据库,效率一…

Java ByteArrayInputStream reset()方法及示例

ByteArrayInputStream类reset()方法 (ByteArrayInputStream Class reset() method) reset() method is available in java.util package. reset()方法在java.util包中可用。 reset() method is used to reset this ByteArrayInputStream to the last time marked position and …

回文数猜想

问题描述&#xff1a; 一个正整数&#xff0c;如果从左向右读&#xff08;称之为正序数&#xff09;和从右向左读&#xff08;称之为倒序数&#xff09;是一样的&#xff0c;这样的数就叫回文数。任取一个正整数&#xff0c;如果不是回文数&#xff0c;将该数与他的倒序数相加…

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

文件上传 带进度条 多种风格 非常漂亮&#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;让用户根据自己的需要来动态…

线性插值算法实现图像_C程序实现插值搜索算法

线性插值算法实现图像Problem: 问题&#xff1a; We are given an array arr[] with n elements and an element x to be searched amongst the elements of the array. 给定一个数组arr []&#xff0c;其中包含n个元素和一个要在该数组的元素中搜索的元素x 。 Solution: 解&…

hdu 1197

地址&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1197 题意&#xff1a;求一个数转换成10&#xff0c;12&#xff0c;16进制后各个位上的数的和是否相等。 mark&#xff1a;模拟进制转换。 代码&#xff1a; #include <stdio.h>int zh(int a, int n) {int su…

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

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

博客上一些项目相关源码链接

GitHub&#xff1a;https://github.com/beyondyanyu/Sayingyy

重新开启Ctrl+Alt+Backspace快捷键

UBUNTU老用户知道CtrlAltBackspace这个快捷键是用来快速重启X的在9.04中被默认关闭了&#xff0c;那如何来打开它呢&#xff1f;在终端中输入&#xff1a;sudo gedit /etc/X11/xorg.conf在其中加入&#xff1a;Section “ServerFlags”Option “DontZap” “false”EndSection退…

Java LocalDate类| 带示例的getDayOfYear()方法

LocalDate类的getDayOfYear()方法 (LocalDate Class getDayOfYear() method) getDayOfYear() method is available in java.time package. getDayOfYear()方法在java.time包中可用。 getDayOfYear() method is used to get the day-of-year field value of this LocalDate obje…