前言

  • 以野火i.M 6U为例

3. 字符设备驱动

需要明确的是模块驱动是两回事。

本笔记开始记录驱动的相关知识。

3.1 Linux设备分类

Linux设备可分为三:字符设备、块设备和网络设备。

网络设备:是一种特殊设备,它并不存在于/dev下面,主要用于网络数据的收发。

Linux系统将设备分别抽象为 struct cdev, struct block_device,struct net_devce 三个对象,具体的设备都可以包含着三种对象从而继承和三种对象属性和操作, 并通过各自的对象添加到相应的驱动模型中,从而进行统一的管理和操作。

3.2 设备相关概念

3.2.1 设备号

设备号分 主设备号次设备号

主设备号区别设备类型。

次设备号表示具体的设备。、

设备号用 dev_t 来表示,dev_t 是32 bit的数,其中高 12 bit 表示主设备号,低 20 bit 表示次设备号。

但是需要注意的是,在内核源码中会设定一个宏 CHRDEV_MAJOR_MAX,主设备号限定在 0-CHRDEV_MAJOR_MAX 中

三个函数来处理设备号:

  • MAJOR(dev):使用该函数来获得设备号中的主设备号。
  • MINOR(dev):使用该函数来获得设备号中的次设备号。
  • MKDEV(ma,mi):使用该函数来获得设备号中的设备号。

3.2.2 设备节点

设备节点(设备文件):Linux设备节点是通过 mknod 命令来创建的;也可以通过代码函数来创建(如:device_creat)。

一般的数据文件称为普通文件,而设备节点的文件称为设备文件。

设备节点被创建在 /dev 路径下,通过设备节点就可以操作对应的设备了。

3.2.3 APP open 文件理解 **

APP 每打开一个文件时,都会获得一个文件句柄(一个整数),每一个句柄在内核中都有与之对应的 struct file打开文件时创建的)。

APP 关闭一个文件时,句柄对应的 struct file 会被内核释放,句柄值也会交回系统。

同样的道理,APP 打开设备节点,即是设备文件时,内核也会创建一个 struct file 的数据结构。

但是需要注意的是该结构体里面的 struct file_operations *f_op 是由驱动程序提供的。

内核使用 inode 结构体在内核内部表示一个文件,

3.3 数据结构

主要介绍几个数据结构:struct file、sruct file_operations、struct inode

3.3.1 struct file

看节点 《3.2.3 APP open 文件理解》 即可

struct file 源码在 内核源码/include/linux

其中包含 sruct file_operations ,就是让 APP 能够操作该文件。

struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op; /*
¦* Protects f_ep_links, f_flags.
¦* Must not be taken from IRQ context.
¦*/
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra; u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data; #ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
errseq_t f_wb_err;
} __randomize_layout

3.3.2 struct file_operations

file_operations 就是把系统调用和驱动程序关联起来,提供文件操作函数(在这里即是驱动函数)。

在编写驱动程序的时候,创建一个该类型的结构体,然后编写部分数,填充该结构体部分内容即可。

struct file 源码在 内核源码/include/linux

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 *);
__poll_t (*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 *);
unsigned long mmap_supported_flags;
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_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_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);
int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

在编写 readwrite 函数时需要用到两个函数来 copy 数据的:

  • static inline long copy_from_user(void *to, const void __user * from, unsigned long n):用于 write 函数时 copy 来自APP的数据。(即是 APP-->内核
  • static inline long copy_to_user(void __user *to, const void *from, unsigned long n):用于 read 函数时 copy 数据到APP。(即是 内核-->APP

后源:一般都是前面目的地址,后面是源地址

3.3.2 struct inode

内核使用 inode 结构体在内核内部表示一个文件。

与文件描述符的结构体(struct file)不同。

可以使用多个文件结构体表示同一个文件的多个描述符,但是都得指向同一个 inode 结构体,因为同一个文件,对应同一个 inode 结构体

inode 结构体 包含很多文件相关的信息,但是对于字符设备文件,只需要关心两个域即可:

  • dev_t i_rdev:设备文件的结点,包含了设备号。
  • struct cdev *i_cdevstruct cdev 是内核的一个内部结构,它是用来表示字符设备的,当 inode 结点 指向一个字符设备文件时,此域为指向inode结构体

3.4 字符设备驱动程序框架 **

字符设备驱动程序框架(框架是一样的,只是这里介绍新函数):

  1. 实现设备驱动:填充 struct operations 结构体部分内容。
  2. 驱动初始化
    • 分配设备编号dev_t):

      • 静态分配:register_chrdev_region()
      • 动态分配:alloc_chrdev_region()
    • 初始化 cdev:创建并初始化一个 cdev 结构体cdev_init()
    • 注册 cdev:向内核注册一个 cdev 结构体cdev_add()
    • 新建一个设备节点:建立设备节点,绑定设备号和 cdev
  3. 驱动注销
    • 释放 cdevcdev_del()
    • 归还申请的主设备号unregister_chrdev_region()

3.4.1 实现设备驱动

实现设备驱动其实就是填充 struct operations 结构体部分内容,并关联到主设备号。

3.4.2 驱动初始化和注销

3.4.2.1 设备号的申请和归还

register_chrdev_region()

  • 该函数用于静态申请一个或多个设备号。
  • 注:使用该函数时最好去内核源码的 Documentation/devices.txt 查看一下使用规则
  • 函数原型:int register_chrdev_region(dev_t from, unsigned count, const char *name)
    • from:dev_t类型的变量,用于指定字符设备的起始设备号,如果要注册的设备号已经被其他的设备注册了,那么就会导致注册失败。
    • count:指定要申请的设备号个数,count的值不可以太大,否则会与下一个主设备号重叠。
    • name:用于指定该设备的名称,我们可以在/proc/devices中看到该设备。
    • 成功返回 0;失败返回错误码。

alloc_chrdev_region()

  • 该函数用于动态申请设备号。
  • 函数原型:int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
    • dev:dev_t类型的指针变量,用于存放分配到的设备编号的起始值。
    • baseminor:次设备号的起始值,通常情况下为 0。
    • count:次设备号的个数。
    • name:用于指定该设备的名称,我们可以在/proc/devices中看到该设备。
    • 成功返回 0;失败返回错误码。

unregister_chrdev_region()

  • 该函数用于注销设备号,归还给内核。
  • 函数原型:void unregister_chrdev_region(dev_t from, unsigned count)
    • from:指定需要注销的字符设备的设备编号起始值,我们一般将定义的dev_t变量作为实参。
    • count:指定需要注销的字符设备编号的个数,该值应与申请函数的count值相等,通常采用宏定义进行管理。
    • 成功返回 0;失败返回错误码。

以上三个函数都属于新的函数,当然也就旧版的,目前依旧兼容,主要区别是:

  1. 旧版的申请了主设备号后,跟着的256个次设备号会全部被注册,导致资源浪费。
  2. register_chrdev() 包含了 cdev_init()cdev_add(),而新版没有。**

register_chrdev()

  • 该函数用于申请设备号。该函数包含了 cdev_init()cdev_add()
  • 函数原型:
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
  • major:用于指定主设备号,若为0,则动态分配。
  • name:字符设备的名称。
  • fops:字符设备驱动,即是struct file_operations。
  • 返回主设备号。

unregister_chrdev()

  • 该函数用于注销设备号,归还给内核。该函数包含了 cdev_del()
  • 函数原型:
static inline void unregister_chrdev(unsigned int major, const char *name)
{
__unregister_chrdev(major, 0, 256, name);
}
  • major:用于指定主设备号。
  • name:字符设备的名称。
  • 无返回。
3.4.2.2 初始化 cdev

cdev 在内核,表示字符设备文件。(相当于其它文件的 inode

初始化 cdev 使用函数 void cdev_init(struct cdev *cdev, const struct file_operations *fops):

  • cdevstruct cdev 类型的指针变量,指向需要关联的字符设备结构体;
  • fopsfile_operations 类型的结构体指针变量,一般将实现操作该设备的结构体 file_operations 结构体作为实参。

3.4.3 设备的注册和注销(设备节点)

注册和注销设备都有两种方法

  • 代码
  • 命令

设备的注册(代码

  • cdev_add() 函数用于向内核的cdev_map散列表添加一个新的字符设备。
  • 函数原型:int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    • p:struct cdev类型的指针,用于指定需要添加的字符设备;
    • dev:dev_t类型变量,用于指定设备的起始编号;
    • count:指定注册多少个设备。

设备的注销(代码

  • cdev_del() 用于注销设备。

    • 函数原型:void cdev_del(struct cdev *p)

      • p:struct cdev类型的指针,用于指定需要删除的字符设备。
  • 从系统中删除cdev,cdev设备将无法再打开,但任何已经打开的cdev将保持不变, 即使在cdev_del返回后,它们的FOP仍然可以调用。因为 struct file 还在

设备的注册(命令

方法:mknod 设备名 设备类型 主设备号 次设备号

  • 类型

    • P:创建先进先出(FIFO)特殊文件。可不指定主设备号和次设备号(其它类型必须指定主次设备号);
    • b:创建(有缓冲的)区块特殊文件;
    • c:创建(没有缓冲区)字符特殊文件;
    • u:创建(没有缓冲区)字符特殊文件。

3.5 实战例程

例程源码地址:李柱明gitee

3.6 字符设备驱动框架总结

字符设备驱动程序框架(框架是一样的,只是这里介绍新函数):

  1. 实现设备驱动:填充 struct operations 结构体部分内容。
  2. 驱动初始化
    • 分配设备编号dev_t):

      • 静态分配:register_chrdev_region()
      • 动态分配:alloc_chrdev_region()
    • 初始化 cdev:创建并初始化一个 cdev 结构体cdev_init()
    • 注册 cdev:向内核注册一个 cdev 结构体cdev_add()
    • 新建一个设备节点:建立设备节点,绑定设备号和 cdev
  3. 驱动注销
    • 释放 cdevcdev_del()
    • 归还申请的主设备号unregister_chrdev_region()

驱动模块实现分点实现步骤

  1. 模块的实现:

    1. 实现入口函数;
    2. 实现出口函数;
    3. 标注协议等信息。
  2. 驱动的实现:
    1. 实现驱动内容,struct operations 结构体内容;
    2. 实现驱动框架:
      1. 向内核申请设备号
      2. 初始化内核设备文件结构体 cdev;(一个设备号对应一个结构体
      3. 把设备文件结构体 cdev 绑定设备号驱动内容 struct operation,然后注册到内核;
  3. 设备节点的实现:
    1. 创建一个设备类
    2. 在该类下创建设备节点并绑定设备号。(一个设备号对应一个设备节点
  4. 注销:
    1. 删除设备节点
    2. 删除设备文件结构体 cdev
    3. 归还设备号
    4. 删除设备类

参考

【linux】驱动-3-字符设备驱动的更多相关文章

  1. Linux驱动设计——字符设备驱动(一)

    Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...

  2. 【转】深入浅出:Linux设备驱动之字符设备驱动

    深入浅出:Linux设备驱动之字符设备驱动 一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据 ...

  3. 【Linux驱动】字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面 ...

  4. linux设备驱动之字符设备驱动模型(2)

    在上一篇中我们已经了解了字符设备驱动的原理,也了解了应用层调用内核函数的机制,但是我们每次操作设备,都必须首先通过mknod命令创建一个设备文件名,比如说我们要打开u盘,硬盘等这些设备,难道我们还要自 ...

  5. linux设备驱动之字符设备驱动模型(1)

    一:字符设备驱动 在linux下面,应用层看到的一切皆为文件(名字)所有的设备都是文件,都可以调用open,read,write来操作,而在内核中每个中每个设备有唯一的对应一个设备号: APP   ( ...

  6. 蜕变成蝶~Linux设备驱动之字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...

  7. 深入浅出:Linux设备驱动之字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...

  8. 第一个驱动之字符设备驱动(二)mdev

    mdev是busybox提供的一个工具,用在嵌入式系统中,相当于简化版的udev,作用是在系统启动和热插拔或动态加载驱动程序时, 自动创建设备节点.文件系统中的/dev目录下的设备节点都是由mdev创 ...

  9. Linux字符设备驱动实现

    Linux字符设备驱动实现 要求 编写一个字符设备驱动,并利用对字符设备的同步操作,设计实现一个聊天程序.可以有一个读,一个写进程共享该字符设备,进行聊天:也可以由多个读和多个写进程共享该字符设备,进 ...

  10. Linux字符设备驱动基本结构

    1.Linux字符设备驱动的基本结构 Linux系统下具有三种设备,分别是字符设备.块设备和网络设备,Linux下的字符设备是指只能一个字节一个字节读写的设备,不能随机读取设备内存中某一数据,读取数据 ...

随机推荐

  1. Adobe DreamWeaver CC 快捷键

    1 1 ADOBE DREAMWEAVER CC Shortcuts: DREAMWEAVER CC DOCUMENT EDITING SHORTCUTS Select Dreamweaver > ...

  2. HTML5 Canvas 2D library All In One

    HTML5 Canvas 2D library All In One https://github.com/search?q=Javascript+Canvas+Library https://git ...

  3. CSS3 & CSS var & :root

    CSS3 & CSS var & :root How to change CSS :root color variables in JavaScript https://stackov ...

  4. 彻底理解c++的隐式类型转换

    隐式类型转换可以说是我们的老朋友了,在代码里我们或多或少都会依赖c++的隐式类型转换. 然而不幸的是隐式类型转换也是c++的一大坑点,稍不注意很容易写出各种奇妙的bug. 因此我想借着本文来梳理一遍c ...

  5. java的单例模式小知识点

    单例模式 目的 为了让一个类有且仅有一个实例 优点 只允许一个,节省空间 不用频繁创建删除,提高性能 缺点 不容易扩展 长期不使用会被系统当作垃圾回收,造成系统状态的丢失 实现 要点 防止外界随意的创 ...

  6. JDK环境解析,安装和目的

    目录 1. JDK环境解析 1.1 JVM 1.2 JRE 1.3 JDK 2. JDK安装 2.1 为什么使用JDK8 2.1.1 更新 2.1.2 稳定 2.1.3 需求 2.2 安装JDK 2. ...

  7. css中的transform,transition,translate的关系

    transform 旋转(transform是没有动画效果,你改变了它的值,元素的样子就唰的改变了.其中的位移的函数名就叫translate,所以说,translate是transform的一部分.) ...

  8. Java自学第6期——Collection、Map、迭代器、泛型、可变参数、集合工具类、集合数据结构、Debug

    集合:集合是java中提供的一种容器,可以用来存储多个数据. 集合和数组既然都是容器,它们有啥区别呢? 数组的长度是固定的.集合的长度是可变的. 数组中存储的是同一类型的元素,可以存储基本数据类型值. ...

  9. 如何用python自动编写《赤壁赋》word文档

    目录 前言 安装-python-docx 一.自动编写<赤壁赋> 准备数据 新建文档 添加标题 添加作者 添加朝代 添加图片 添加段落 保存word文档 二.自动提取<赤壁赋> ...

  10. ajax缺点

    ajax请求在SEO中效率低,SEO就是关键字搜索的匹配度. 比如在百度搜索Java,一般来说内容中出现Java的次数越多排名越靠前,当使用ajax时,它的异步刷新导致必须是页面刷新出来才去刷新数据, ...