字符设备驱动框架

  • 1、字符设备驱动简介
  • 2、file_operations
  • 3、驱动模块的加载和卸载
  • 4、字符设备的注册与注销
  • 5、实现设备具体操作函数
  • 6、添加LICENSE和作者信息
  • 7、linux设备号
    • 7.1、静态分配设备号
    • 7.2、动态分配设备号
  • 8、程序编写
    • 8.1、驱动程序
    • 8.2、应用程序编写
  • 9、实验测试

第一个驱动学习。
        框架框架框架,本节最重要的就是字符设备框架。

1、字符设备驱动简介

1、字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

2、我们在应用层打开某一个文件(节点),然后调用open、read、write、close,最终会进入到内核调用驱动中的open、read、write、release函数。
        其实就是file_operations结构体的成员变量实现。

2、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 *);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 (*mremap)(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_MMUunsigned (*mmap_capabilities)(struct file *);#endif};

成员介绍:
        1、owner,一般设置为THIS_MODULE。
        2、llseek函数用于修改文件当前的读写位置。
        3、read函数用于读取设备文件。
        4、write函数用于向色被写入数据。
        5、poll是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
        6、open函数打开设备文件。
        7、release函数用于关闭设备文件,与应用程序中的close函数对应。

3、驱动模块的加载和卸载

1、linux驱动有两种运行方式。一种是编译进内核;另一种是将驱动编成模块(ko文件),在内核启动后用insmod命令加载模块。
        2、模块有加载和卸载两种操作,我们编写驱动时候需要注册这两种操作函数,如下注册函数:

 module_init(xxx_init); //注册模块加载函数module_exit(xxx_exit); //注册模块卸载函数

3、module_init函数用来向内核注册一个模块加载函数,参数xxx_init就是需要注册的函数,当insmod加载驱动的时候,xxx_init函数就会被调用。同理rmmod的时候xxx_exit函数调用。

4、字符设备的注册与注销

1、对于字符设备驱动而言,当驱动模块加载成功后需要注册字符设备,卸载时要注销掉字符设备。
        2、字符设备的注册和注销函数原型如下:

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major,  const char *name)

参数介绍:
        major:主设备号,Linux下每个设备都有设备号,分为主次设备号。
        name:设备名字。
        fops:结构体file_operations类型指针。

一般字符设备的注册在驱动模块入口函数xxx_init中进行;注销在xxx_exit中进行。

5、实现设备具体操作函数

file_operations变量的定义,open read write的实现。

static int chrtest_open(struct inode *inode, struct file *filp)
static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
static ssize_t chrtest_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
static int chrtest_release(struct inode *inode, struct file *filp)

6、添加LICENSE和作者信息

 MODULE_LICENSE("GPL") //添加模块 LICENSE 信息MODULE_AUTHOR("abc") //添加模块作者信息

7、linux设备号

1、Llinux提供了一个名为dev_t的数据类型表示设备号,dev_t定义在文件include/linux/types.h中,如下:

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

2、综上dev_t就是一个unsigned int型数据,32位。这32位组成主设备号和次设备号两部分:
                1. 高12位代表主设备号,范围0~4095,
                2. 低20位代表次设备号。

3、设备号操作函数:

 #define MINORBITS 20#define MINORMASK ((1U << MINORBITS) - 1)#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

MINORBITS,表示次设备号位数,一共20位
        MAJOR:用于从dev_t中获取主设备号
        MINOR:用于从dev_t中获取次设备号
        MKDEV,用于将给定的主设备号和次设备号合成dev_t类型的设备号。

7.1、静态分配设备号

静态分配需要我们使用 cat /proc/devices命令来查看当前系统已经使用的设备号,然后确定设备号,比较繁琐,且容易带来冲突问题。

7.2、动态分配设备号

1、为了解决冲突,Linux社区推荐使用动态分配设备号。设备号的申请函数如下:

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

2、参数:
        dev:保存申请到的设备号
        baseminor:次设备号起始地址,alloc_chrdev_region一次可以申请多个设备号,这些设备的主设备号都一样,但次设备号不一样,从0开始。
        count:要申请的设备号数量。
        name:名字。
        3、注销字符设备之后要释放掉设备号,函数如下:

 void unregister_chrdev_region(dev_t from, unsigned count)

4、参数:
        from:要释放的设备号。
        count:要释放的设备号数量。

8、程序编写

8.1、驱动程序

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>#define CHRDEVBASE_MAJOR 200             /* 主设备号 */
#define CHRDEVBASE_NAME     "chrdevbase"  /* 设备名     */static char readbuf[100];      /* 读缓冲区 */
static char writebuf[100];      /* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};/*
return  0 成功;其他 失败
*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{//printk("chrdevbase open!\r\n");return 0;
}/** @description      : 从设备读取数据 * @param - filp  : 要打开的设备文件(文件描述符)* @param - buf    : 返回给用户空间的数据缓冲区* @param - cnt  : 要读取的数据长度* @param - offt  : 相对于文件首地址的偏移* @return             : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 向用户空间发送数据 */memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("NM kernel senddata ok!\r\n");}else{printk("NM kernel senddata failed!\r\n");}return 0;
}static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 接收用户空间传递给内核的数据并且打印出来 */retvalue = copy_from_user(writebuf, buf, cnt);if(retvalue == 0){printk("NM kernel recevdata:%s\r\n", writebuf);}else{printk("NM  kernel recevdata failed!\r\n");}return 0;
}static int chrdevbase_release(struct inode *inode, struct file *filp)
{printk("NM  chrdevbase release!\r\n");return 0;
}/** 设备操作函数结构体*/
static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,    .open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,
};/*
@description: 驱动入口函数
*/
static int __init chrdevbase_init(void)
{int retvalue = 0;/* 注册字符设备驱动 */retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if(retvalue < 0){printk("NM chrdevbase driver register failed\r\n");}printk("NM chrdevbase init!\r\n");return 0;
}/*
@description: 驱动出口函数
*/
static void __exit chrdevbase_exit(void)
{/* 注销字符设备驱动 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("NM chrdevbase exit!\r\n");
}/* * 将上面两个函数指定为驱动的入口和出口函数 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);/* * LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("NM");

程序介绍:入口函数chrdevbase_init:注册字符设备驱动,主设备号为200,名字为chardevbase。字符设备操作集合:chrdevbase_fops:实现了open、read、write、release函数,供应该层调用。

8.2、应用程序编写

应用程序打开设备节点,调用open、read、write函数。

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"// 使用方法:./chrdevbase /dev/chrdevbase <1>|<2>static char usrdata[] = {"usr data!"};int main(int argc, char *argv[])
{int fd, retvalue;char *filename;char readbuf[100], writebuf[100];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开驱动文件 */fd  = open(filename, O_RDWR);if(fd < 0){printf("Can't open file %s\r\n", filename);return -1;}if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */retvalue = read(fd, readbuf, 50);if(retvalue < 0){printf("read file %s failed!\r\n", filename);}else{/*  读取成功,打印出读取成功的数据 */printf("read data:%s\r\n",readbuf);}}if(atoi(argv[2]) == 2){/* 向设备驱动写数据 */memcpy(writebuf, usrdata, sizeof(usrdata));retvalue = write(fd, writebuf, 50);if(retvalue < 0){printf("write file %s failed!\r\n", filename);}}/* 关闭设备 */retvalue = close(fd);if(retvalue < 0){printf("Can't close file %s\r\n", filename);return -1;}return 0;
}

程序介绍:打开/dev下chrdevbase节点,根据第三个参数确定是读取数据还是写入数据。
        1、读取则读取内核中的字符串,存入readbuf中并打印出来。
        2、写入则将usrdata写入内核中。

9、实验测试

1、编译将ko拷贝到文件系统中:

        2、加载驱动并测试

成功读取和写入数据,实验完成。

2022.10.30

驱动学习----字符设备驱动框架相关推荐

  1. 设备驱动,字符设备驱动、(总线)设备驱动模型、sysfs文件系统、平台设备驱动

    以下内容转载于微信公众号:嵌入式企鹅圈.如有侵权,请告知删除. 学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sysfs等相关概念和技术. 对于初学者来说会非常 ...

  2. 【linux驱动之字符设备驱动基础】

    linux驱动之字符设备驱动基础 文章目录 linux驱动之字符设备驱动基础 前言 一.开启驱动学习之路 二.驱动预备知识 三.什么是驱动? 3.1 驱动概念 3.2 linux 体系架构 3.3 模 ...

  3. Linux驱动之字符设备驱动

    系列文章目录 第一章 Linux入门之驱动框架 第二章 Linux驱动之字符设备驱动 文章目录 系列文章目录 前言 一.认识字符设备驱动 1.基本概念 2.基本概念 二.字符设备旧框架 1.注册和注销 ...

  4. <Linux开发>--驱动开发-- 字符设备驱动(3) 过程详细记录

    <Linux开发>–驱动开发-- 字符设备驱动(3) 过程详细记录 驱动开发是建立再系统之上的,前面作者也记录了系统移植的过程记录,如果有兴趣,可进入博主的主页查看相关文章,这里就不添加链 ...

  5. linux的驱动开发——字符设备驱动

    1.字符设备驱动 \qquad字符设备驱动是最基本,最常用的设备.它将千差万别的硬件设备采用统一的接口封装起来,屏蔽了硬件的差异,简化了应用层的操作. 2.描述所有字符设备的结构体 \qquad描述所 ...

  6. linux PCI驱动调用字符设备驱动方式

    上一篇文章写了字符设备驱动的基本结构及访问方式,在实际应用时首先需要绑定自己的硬件设备.本篇主要描述字符设备驱动与PCI接口类型的设备访问方式(内核为2.6.24及以上的方法,测试内核为2.6.32) ...

  7. 正点原子-驱动开发-字符设备驱动

    Linux中的三大类驱动:字符设备.块和网络设备驱动 I2C.SPI.音频等都属于字符设备驱动 的类型 EMMC.NAND.SD卡和 U盘等存储都属于块设备 网卡,WIFI等都属于网络驱动 一个设备可 ...

  8. linux open函数_Linux驱动开发 / 字符设备驱动内幕 (1)

    哈喽,我是老吴,继续记录我的学习心得. 一.保持专注的几个技巧 将最重要的事放在早上做. 待在无干扰环境下,比如图书馆. 意识到刚坐下开始投入工作前,有点负面小情绪是特别正常的现象. 让"开 ...

  9. _Linux驱动开发 / 字符设备驱动内幕 (1)

    哈喽,我是老吴,继续记录我的学习心得. 一.保持专注的几个技巧 将最重要的事放在早上做. 待在无干扰环境下,比如图书馆. 意识到刚坐下开始投入工作前,有点负面小情绪是特别正常的现象. 让"开 ...

最新文章

  1. java中在做除法操作时,对有余数的结果进行取整
  2. go语言的书籍的淘宝调查
  3. sql string转number_少用 string.Format
  4. 【Vue2.0】—常用的内置指令(九)
  5. Unicode详解zz
  6. Flutter动画系列之SizeTransition
  7. java实现网络连接_Java 网络编程 | 菜鸟教程
  8. 什么是informatic?
  9. abb机器人伺服电机报闸是什么_abb伺服电机究竟怎么样呢?
  10. vscode通过扩展插件实现流程图绘制
  11. 三次方在python中如何表示_python中计算三次方怎么表示
  12. iOS UI 自动化测试原理以及在 Trip.com 的应用实践
  13. iPhone 14来了,苹果13和14区别,值不值得买
  14. 计算机自动隐藏桌面图标,Windows 8.1 右下角图标莫名自动隐藏
  15. 服务器显示raid报警,Raid为什么会出现故障?RAID/服务器恢复
  16. matlab 读取midi,matlab miditoolbox中的midi文件生成函数的改进
  17. Arduino自制打地鼠游戏机详细教程
  18. (OK) dnf - Fedora23——删除多余不用的内核
  19. 【每日更新】百度新闻!最新,最快的资讯速递!
  20. 编程经验NO.1 from月光博客

热门文章

  1. 记录使用 Lib.Harmony 时注入过程中遇到的一个坑
  2. java计算机毕业设计外卖点餐系统源代码+数据库+系统+lw文档
  3. 从职场新人到职场达人——职业规划
  4. Redis:发布订阅机制
  5. 借助数据库生成订单号
  6. HTML5七夕情人节表白网页制作【浪漫的空中散落的花瓣3D相册】HTML+CSS+JavaScript 3D动态相册源码素材 html生日快乐祝福网页制作
  7. EasyNVR通过国标级联到上级云服务器,视频无法播放的原因是什么?
  8. SMBIOS Table
  9. 1.6 x86读取smbios信息
  10. 轻量级 TCP 端口转发工具:rinetd