前言

  • 以野火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. export html to canvas image

    export html to canvas image canvas.toDataURL https://developer.mozilla.org/en-US/docs/Web/API/HTMLCa ...

  2. config file language All In One

    config file language All In One YAML YAML Ain't Markup Language .yaml / .yml https://yaml.org/ https ...

  3. Azure 计费 & 成本管理 & 取消 Azure 订阅

    Azure 计费 & 成本管理 & 取消 Azure 订阅 https://docs.microsoft.com/zh-cn/azure/cost-management-billing ...

  4. Scalability & Scale-up & Scale-out

    Scalability & Scale-up & Scale-out 架构,弹性,伸缩性 Scalability 可扩展性 https://en.wikipedia.org/wiki/ ...

  5. React & Calendar

    React & Calendar 日历 https://github.com/YutHelloWorld/calendar/blob/master/src/Calendar.js // 国际化 ...

  6. Dart: path库

    path库pub地址 安装: dependencies: path: 使用: import 'dart:io'; import 'package:path/path.dart' as path; ma ...

  7. 使用 Tye 辅助开发 k8s 应用竟如此简单(三)

    续上篇,这篇我们来进一步探索 Tye 更多的使用方法.本篇我们来了解一下如何在 Tye 中如何对数据库进行链接. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如果您是首次 ...

  8. React Context 理解和使用

    写在前面 ​ 鉴于笔者学习此内容章节 React官方文档 时感到阅读理解抽象困难,所以决定根据文档理解写一篇自己对Context的理解,文章附带示例,以为更易于理解学习.更多内容请参考 React官方 ...

  9. Mysql 高可用(MHA)-读写分离(Atlas)-分布式架构(Mycat)

    Mysql 高可用(MHA)-读写分离(Atlas) 1. 搭建主从复制(一主两从) 1.1 准备环境 1 主库:10.0.0.51/db01 2 从库:10.0.0.52/db02,10.0.0.5 ...

  10. finally会执行吗:try/catch的测试

    翻译练习 原博客地址:Will it finally: a try/catch quiz 你知道try和catch是怎么工作的,但是你知道finally是怎么工作的吗?它是在抛出异常后执行还是在ret ...