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

10bf132f7acae6bb07bbb5ddff0cbf92.png

哈喽,我是老吴,继续记录我的学习心得。

一、保持专注的几个技巧

  • 将最重要的事放在早上做。

  • 待在无干扰环境下,比如图书馆。

  • 意识到刚坐下开始投入工作前,有点负面小情绪是特别正常的现象。

  • 让“开心一刻”成为计划的一部分。

  • 拥有合情合理的日计划和周计划。


二、Linux 字符设备驱动内幕 (1)

正文目录:

1. 什么是字符设备驱动?
2. 快速体验字符设备驱动和应用程序 (超简单的 demo)
3. 字符设备在内核里的抽象
    3.1 字符设备核心代码概览
    3.2 对字符设备进行抽象: struct cdev
    3.3 对字符设备的操作进行抽象:struct file_operations
4. 更多值得学习的知识点
5. 相关参考

写作目的:

  • 探索 Linux 字符设备驱动。

测试环境:

  • Ubuntu 16.04
  • Gcc 5.4.0

1. 什么是字符设备驱动?

  • 现实世界中存在着成千上万的硬件设备,这些设备在硬件特性和使用方式上都各不相同。Linux 系统的大牛们从这些形形色色的设备中提取出共性,将它们抽象为三大类:字符设备、块设备和网络设备

    e4e0925c069f6ec36a378bd5047ce415.png

  • 基于代码质量和复用性的考虑,Linux 内核针对每一类硬件设备都提供了对应的驱动模型框架,一般包括基本的内核设施和文件系统接口。开发人员在写某类设备驱动程序时,能一套完整的驱动模型框架可以使用,从而将精力放在硬件设备本身的控制上。
  • 简单的 Linux 设备驱动程序结构图:

    aaa63f9e1d1f5e8c885304520f04da56.png

  • 详细一点的 Linux 设备驱动程序结构图:

dcc4ca856ea88f18587de186355ba78c.png

2. 快速体验字符设备驱动和应用程序 (超简单的 demo)

1) 字符设备驱动 (chrdev_drv.c):

字符设备的打开和读函数:

static struct cdev chr_dev; // 字符设备抽象
static dev_t ndev;          // 设备号
static int chr_open(struct inode *nd, struct file *filp){
    printk("chr_open, major=%d, minor=%d\n", MAJOR(nd->i_rdev), MINOR(nd->i_rdev));
    return 0;
}

static ssize_t chr_read(struct file *filp, char __user *u, size_t sz, loff_t *off){
    printk("In chr_read()\n");
    return 0;
}

static int chr_release(struct inode *nd, struct file *filp){
    printk("In chr_release()\n");
    return 0;
}

文件操作函数集:

struct file_operations chr_ops =
{
    .owner = THIS_MODULE,
    .open = chr_open,
    .read = chr_read,
    .release = chr_release,
};

模块加载和卸载:

static int demo_init(void){
    int ret;
    cdev_init(&chr_dev, &chr_ops);
    ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev");
    if(ret 0)
        return ret;

    printk("demo_init():major=%d, minor=%d\n", MAJOR(ndev), MINOR(ndev));

    ret = cdev_add(&chr_dev, ndev, 1);
    if(ret 0)
        return ret;

    return 0;
}
static void demo_exit(void){
    printk("demo_exit...\n");

    cdev_del(&chr_dev);
    unregister_chrdev_region(ndev, 1);
}

2) 访问字符设备的应用程序:

int main(){
    int ret;
    char buf[32];
    int fd = open("/dev/chr_dev", O_RDONLY|O_NDELAY);
    if(fd 0)
    {
        printf("open file %s failed!\n", CHR_DEV_NAME);
        return -1;
    }
    read(fd, buf, 32);
    close(fd);

    return 0;
}

3) 不用完全理解代码的含义,直接看运行效果:

# 编译驱动程序
$ make KERNELDIR=XXX/linux ARCH=arm CROSS_COMPILE=arm-linux-

# 编译应用程序
$ arm-linux-gcc chrdev_app.c -o chrdev_app

# 加载驱动模块
$ insmod chrdev_drv.ko
demo_init():major=242, minor=0

# 手动创建字符设备文件节点
$ mknod /dev/chr_dev c 242 0

# 运行应用程序
$ ./chrdev_app
chr_open, major=242, minor=0
In chr_read()
In chr_release()

从上面测运行结果可知,应用程序调用 chrdev_app.c / open() 会导致驱动程序 chrdev_drv.c / struct file_operations chr_ops->open() 被调用,read 操作也是类似。

内核是如何实现上述功能的?

带着这个困惑来了解字符设备驱动的框架,才不会迷失在内核里各种复杂的代码细节里。

3 字符设备在内核里的抽象

3.1 字符设备核心代码概览

在深入阅读各种代码之前,先整体地概览一遍将会涉及到的程序文件,找出核心主干,能有效地避免陷入繁杂的代码细节中。

1) 分解 C source 文件,fs/char_dev.c (Linux-4.14):

作用:
char_dev.c 是字符设备驱动框架的核心实现文件,它位于 fs 目录中,说明了字符设备驱动和文件系统是紧密联系在一起的。

内容(以重要性排序):1> public 函数:

// 1. 字符设备子系统初始化
void __init chrdev_init(void) // 2. struct cdev 管理相关void chrdev_show(struct seq_file *f, off_t offset) void cdev_put(struct cdev *p) void cd_forget(struct inode *inode) int cdev_add(struct cdev *p, dev_t dev, unsigned count) void cdev_set_parent(struct cdev *p, struct kobject *kobj) int cdev_device_add(struct cdev *cdev, struct device *dev) void cdev_device_del(struct cdev *cdev, struct device *dev) void cdev_del(struct cdev *p) 
struct cdev *cdev_alloc(void) void cdev_init(struct cdev *cdev, const struct file_operations *fops)// 3. 设备号管理相关
__register_chrdev_region(unsigned int major, unsigned int baseminor, 
__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct) int register_chrdev_region(dev_t from, unsigned count, const char *name) int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, int __register_chrdev(unsigned int major, unsigned int baseminor, void unregister_chrdev_region(dev_t from, unsigned count) void __unregister_chrdev(unsigned int major, unsigned int baseminor,
  • 在 Linux 代码中,双下划线(__)开始的函数名表示这是一个低层接口, 应当小心使用。如果你调用这个函数,确信你知道你在做什么。也就是说,阅读代码时,双下划线开头的函数可以暂时放一边。

  • struct cdev 管理相关:cdev_add() / cdev_del() / cdev_init(),需重点关注。

  • 设备号管理相关:alloc_chrdev_region() / register_chrdev_region(),需重点关注。

2> public 变量:

const struct file_operations def_chr_fops = {
 .open = chrdev_open,
 .llseek = noop_llseek,
};
  • 类似于高级语言的多态机制,在 Linux 各个子系统的驱动框架中一般用一个统一的 file_operations->open 函数关联到各个具体的硬件驱动的 struct file_operations,需要重点关注。

3> private 变量:

static struct char_device_struct {
 struct char_device_struct *next;
 unsigned int major;
 unsigned int baseminor;
 int minorct;
 char name[64];
 struct cdev *cdev;  /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

static struct kobj_map *cdev_map; 
static struct kobj_type ktype_cdev_default;
static struct kobj_type ktype_cdev_dynamic;
  • struct char_device_struct *chrdevs[] 数组是字符设备驱动框架的核心数据结构,需重点关注。

4> private 函数:

static inline int major_to_index(unsigned major) static int find_dynamic_major(void) static struct kobject *cdev_get(struct cdev *p) static int chrdev_open(struct inode *inode, struct file *filp) static void cdev_purge(struct cdev *cdev) static struct kobject *exact_match(dev_t dev, int *part, void *data) static int exact_lock(dev_t dev, void *data) static void cdev_unmap(dev_t dev, unsigned count) static void cdev_default_release(struct kobject *kobj) static void cdev_dynamic_release(struct kobject *kobj) static struct kobject *base_probe(dev_t dev, int *part, void *data)
  • helper 类函数,不太重要。

2) 分解 C header 文件,include/linux/cdev.h (Linux-4.14):

作用:
包含字符设备驱动相关结构体的定义、以及一些字符设备核心 API 的声明。

内容:1> struct cdev 结构体:

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;
  • struct cdev 是字符设备驱动的核心抽象,需重点关注。

3.2 对字符设备进行抽象: struct cdev

编写字符设备驱动程序就是为了管理和控制字符设备,Linux 内核将字符设备抽象为一 个数据结构:struct cdev。这个结构体似乎从 Linux-2.6 到现在 Linux-5.8 都没有发生变化。

1) struct cdev 成员简介:

目前没必要完全理解这些成员的作用,有个大概印象就好:

  • struct kobject kobj: 内嵌的内核对象,与 Linux 设备驱动模型相关,后续需专门写一篇文章来介绍。

  • struct module *owner: 指向拥有这个结构体的模块的指针,这个成员用来在它的操作还在被使用时阻止模块被卸载,一般初始化为 THIS_MODULE;

  • struct file_operations *ops: 在Linux通用文件模型下,字符设备的文件操作函数集,后续详解。

  • struct list_head list:用于管理 struct cdev 的链表,后续需专门写一篇文章来介绍。

  • 字符设备的设备号,由主设备号和次设备号构成,后续需专门写一篇文章来介绍。

2) 两种方式创建 struct cdev 对象:

这里用对象一词,是为了引导大家用面向对象的思维来看待 Linux 内核的设计

面向过程还是面向对象,不应该和语言绑定在一起,应该理解为 2 种不同的编程思维。人脑是很美妙的,在不同的场景,只要你愿意,它就能应用不同的思维方式来解决问题。设计 Linux 内核代码的神牛们,可谓是各个都是面向对象编程的大牛,练习编程就应该练习对事物的抽象能力,C 程序员如果觉得自己缺乏这方面的能力,不如学习一下 Java 编程。

静态定义:

static struct cdev chr_dev;

动态分配:

struct cdev *my_cdev = cdev_alloc();

cdev_alloc() 不仅会为struct cdev对象分配内存空间,还会对该对象进行必要的初始化:

struct cdev *cdev_alloc(void){
 struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
 if (p) {
  INIT_LIST_HEAD(&p->list);
  kobject_init(&p->kobj, &ktype_cdev_dynamic);
 }
 return p;
}

一个值得注意的点:
我搜索了一下内核源码,发现大多数驱动都选择静态定义 struct cdev,这样更简单省事。

3) 某个真实字符硬件的抽象:

数据结构 struct cdev 作为字符设备的抽象,仅仅是为了满足 Linux 内核对字符设备驱动程序框架结构设计的需要

现实中,一个具体的字符硬件设备的数据结构的抽象往往要复杂得多,在这种情况下 struct cdev 常常作为一种内嵌的成员变量出现在实际设备的数据结构中。

例如 drvier/watchdog/watchdog_dev.c:

struct watchdog_core_data {
 struct kref kref;
 struct cdev cdev;
 struct watchdog_device *wdd;
 struct mutex lock;
 unsigned long last_keepalive;
 unsigned long last_hw_keepalive;
 struct delayed_work work;
 unsigned long status;  /* Internal status bits */
};

4) 初始化 cdev 对象:

void cdev_init(struct cdev *cdev, const struct file_operations *fops){
 memset(cdev, 0, sizeof *cdev);
 INIT_LIST_HEAD(&cdev->list);
 kobject_init(&cdev->kobj, &ktype_cdev_default);
 cdev->ops = fops;
}

cdev_init() 最重要的作用就是将 struct cdev 对象和 struct file_operations 对象绑定在一起。

一些值得注意的点

  • cdev_init() 和 cdev_alloc() 中有一部分功能是重叠的(例如 kobject_init()),所以 cdev_init() 只能搭配静态定义 struct cdev 的方式来使用。

  • 如果采用 cdev_alloc() 动态分配 struct cdev 对象,则需自行 cdev->ops = fops 。

3.3 对字符设备的操作进行抽象:struct file_operations

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 (*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 *);

 [...];

} __randomize_layout;

  • 目前没必要完全理解这些成员的作用,有个大概印象就好。

  • 字符设备驱动程序中一个极其关键的数据结构,字符设备驱动程序的编写,就是是围绕着如何实现 struct file_operations 中的那些函数指针成员而展开的。

  • 通过内核文件系统组件在其间的穿针引线,应用程序中对文件类函数(open、read、write)的调用,将最终被转接到 struct file_operations 中对应函数指针的具体实现上,后续需专门写一篇文章来介绍。

鉴于大多数人的注意力无法在一篇文章里上集中太久,更多的内容将放在后面的文章里。建议大家可以先自行阅读相关书籍,不是自己理解到的东西是消化不了的。

4. 更多值得学习的知识点

  • 字符设备号的构成与管理

  • 字符设备的注册

  • 生成字符设备文件的方式有哪些?

  • 创建字符设备文件是发生了什么?

  • 字符设备文件是如何关联到字符设备驱动的?

  • 分析一些真实的字符设备驱动

5. 相关参考

  • 《Linux 内核文档》

  • 《Linux设备驱动程序》(ldd) / 第 3 章节

  • 《深入Linux设备驱动程序内核机制》(ildd) / 第 2 章节

  • 《精通Linux设备驱动程序开发》(eldd) / 第 2 章节

  • 《Linux设备驱动开发详解》(ldds) / 第 6 章节

  • 《深入Linux内核架构》(plka) / 第 6 章节

  • 《嵌入式应用开发完全手册》


三、思考技术,也思考人生

学习技术,更要学习如何生活

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。

0e62daae23d25cf9a8c0e0aa75320441.png

嵌入式系统 (Linux、RTOS、OpenWrt、Android) 和 开源软件 感兴趣,想和更多人互相交流学习,请关注公众号:嵌入式Hacker,一起来学习吧。

无论是关注或转发,还是打赏,都是对作者莫大的支持。觉得文章对你有价值的话,不妨点个 在看和点赞 哦。

欢迎加入我的微信群:先加我,我拉你进群,暗号(我最棒)。

祝各位工作顺利,家庭幸福,财源滚滚~~~

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

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

相关文章

github删除文件_github 仓库中删除历史大文件

问题如果git中提交了大文件,而且保存到了版本库中,那在下载或者克隆git包的时候,速度会非常慢。再加上github在国内访问本来就很慢,可能会导致包无法下载(克隆)。为了提升下载(克隆)速度,可以永久的删除这些文件(包括该…

linux plc编程软件,基于Linux平台的可编程控制器软PLC设计

实例下面以一个简单的对3并口通道循环控制为例,说明软PLC 的工作流程。(1)梯形图编程。从软PLC 主界面进入后,启动梯形图编程,调用梯形图编程的主程序。梯形图编程共需要调用梯形图界面模块、关闭模块、IO 模块,这些均在配置文件中…

insert into语句_入门MySQL——DML语句篇

前言:在上篇文章中,主要为大家介绍的是DDL语句的用法,可能细心的同学已经发现了。本篇文章将主要聚焦于DML语句,为大家讲解表数据相关操作。这里说明下DDL与DML语句的分类,可能有的同学还不太清楚。DDL(Data Definitio…

linux不重启换root密码是什么原因,在Linux下修改和重置root密码的方法(超简单)

刚开始接触linux的人,忘记了root密码可能会不知所措。想找回自己的root密码,但是又不知道方法。其实,只需要简单的几步就可以重置自己的root密码了(找回密码我也不会)1.开机HcQBEm上敲击e,然后编辑选项2.在linux16这一行&#xff…

SCP-bzoj-1019

项目编号:bzoj-1019 项目等级:Safe 项目描述: 戳这里 特殊收容措施: 对于一个hanoi,知道了各种移动操作的优先级,也就确定了方案。可以证明对于盘子数为N的hanoi,任意移动方案都等价于将数目为N…

一键分享手机代码_通过广告路由器指定手机浏览器自动认证WIFI上网 附代码

说说应用过程,下面用手机QQ浏览器为例。在路由器搭建免费WIFI,用户连接免费WIFI后,使用手机QQ浏览器点击打开任意网页即可自动通过认证并上网,有的手机会自动打开认证网页,如果使用其他手机浏览器则自动跳转到引导认证…

netbeans7.4_NetBeans 7.1:创建自定义提示

netbeans7.4我已经在帖子中对我最喜欢的NetBeans提示进行了讨论,这些帖子中包含用于现代化Java代码的七个NetBeans提示和七个不可或缺的NetBeans Java提示 。 这两个帖子中涉及的十四个提示仅占NetBeans支持的“即开即用”提示总数的一小部分。 但是,由于…

linux uboot启动流程分析,uboot启动流程分析

uboot版本为NXP维护的2016.03版本下载地址为http://git.freescale.com/git/...分析uboot的启动流程,需要编译一下uboot,然后打开链接脚本u-boot.lds在u-boot.lds中1 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf…

JavaOne 2015 –第二十版十大收获

我们刚刚在旧金山有了JavaOne的第二十版。 这将是我自2004年以来第十二次参加不间断的系列活动。最大的教训是什么,可以揭示Java的未来。 模块化斗争 自从Java 2007首次提到模块以来,已经花费了将近9年的时间,或者说,直到2016年9…

IOS--文件管理NSFileManager

iOS的沙盒机制。应用仅仅能訪问自己应用文件夹下的文件。iOS不像android。没有SD 卡概念。不能直接訪问图像、视频等内容。iOS应用产生的内容,如图像、文件、缓存内容等都必须存储在自己的沙盒内。默认情况下,每一个沙盒含有3个文件 夹:Docum…

linux修改文件内容_详解5种实用方法---Linux系统清空或删除大文件内容

概述有时我们在处理Linux终端中的文件时,可能要去清除文件的内容,而无需使用任何Linux命令行编辑器打开它。怎么才能实现呢?下面通过几种不同的方式教大家清空文件内容。1.通过重定向到空来清空文件内容使用shell重定向null(不存在的对象)清空…

凯撒密码c语言小写字母,凯撒密码c(c语言编程凯撒密码)

凯撒密码c(c语言编程凯撒密码)2020-05-15 13:09:51共10个回答#include#includeintmain(){charsave[10][30];inta,b,i,j;scanf("%d",&a);for(i0;i能不能说清楚一点,是加密吗?#include#include#defineMAXSIZE81intmain(){charstr[MAXSIZE];inti;intof…

Java正则表达式库基准测试– 2015年

在尝试使Java在计算机语言基准测试游戏的regexdna挑战中排名第一时,我正在研究Java正则表达式库的性能。 我可以找到的最新网站是2010年的tusker.org 。因此,我决定使用Java Microbenchmarking Harness重做测试并发布结果(破坏性警告&#xf…

C语言写程序注意,单片机C语言编程应注意的若干问题

作为一种结构化的程序设计语言,C语言的特点就是可以使你尽量少地对硬件进行操作,具有很强的功能性、结构性和可移植性,常常被优选作为单片机系统的编程语言。但是基于单片机的C语言和标准C语言有很大区别,如何结合单片机的系统资源…

Java实现将日志信息存到TXT中

在java文件操作的时候,思考将日志信息存到txt中,现在很多项目都是通过log4j来做的,同样也会用到将日志存到txt中. package FileOperation;import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Calenda…

接口返回的类型是html页面_1.10 PhalApi 2.x 接口文档

接口文档在线接口文档PhalApi提供一些非常实用而又贴心的功能特性,其中最具特色的就是自动生成的在线可视化文档。在线接口文档主要分为两大类,分别是: 在线接口列表文档在线接口详情文档当客户端不知道有哪些接口服务,或者需要查…

jmeter数据库负载测试_JMeter:负载测试关系数据库

jmeter数据库负载测试Apache JMeter是完全使用Java编写的性能测试工具。 可以在请求/响应模型上运行的任何应用程序都可以使用JMeter进行负载测试。 关系数据库也不例外:接收sql查询,执行查询并返回执行结果。 我将向您展示使用JMeter的图形用户界面设置…

具有Couchbase,Java EE和WildFly的CRUD Java应用程序

Couchbase是一个开源的NoSQL文档数据库。 它允许访问,索引和查询JSON文档,同时利用集成的分布式缓存来实现高性能的数据访问。 开发人员可以使用不同的语言(Java,Go,.NET,Node,PHP,…

c语言10个人 三向成绩,C语言入门学习精华:这样学习C语言最有效

C语言入门学习精华:这样学习C语言最有效c语言死了吗?本材料描述了使用C语言的高级技能,并努力将您的C语言能力从“基本”提升到“高级”。然而,学习态度比学习方法要好。在正式学习之前,有一个问题是不能抱怨的。也就是…

jQuery UI全教程之一(dialog的使用教程)

jQuery UI目前的版本已经更新到了1.8.7。个人感觉和easyui相比起来,jQuery UI在界面的美观程度和可定制型更强一些。所以再次将一些jQuery UI组件的用法说明一下,方便日后查阅。也方面没接触jQuery UI的人能早日使用jQuery UI套件 (一)首先来说jQuery UI…