电商网站设计公司排名,wordpress分城市访问,开通网站主机,wordpress网站标签logo哈喽#xff0c;我是老吴#xff0c;继续记录我的学习心得。一、保持专注的几个技巧将最重要的事放在早上做。待在无干扰环境下#xff0c;比如图书馆。意识到刚坐下开始投入工作前#xff0c;有点负面小情绪是特别正常的现象。让“开心一刻”成为计划的一部分。拥有合情合… 哈喽我是老吴继续记录我的学习心得。一、保持专注的几个技巧将最重要的事放在早上做。待在无干扰环境下比如图书馆。意识到刚坐下开始投入工作前有点负面小情绪是特别正常的现象。让“开心一刻”成为计划的一部分。拥有合情合理的日计划和周计划。二、Linux 字符设备驱动内幕 (1)正文目录1. 什么是字符设备驱动2. 快速体验字符设备驱动和应用程序 (超简单的 demo)3. 字符设备在内核里的抽象 3.1 字符设备核心代码概览 3.2 对字符设备进行抽象: struct cdev 3.3 对字符设备的操作进行抽象struct file_operations4. 更多值得学习的知识点5. 相关参考写作目的探索 Linux 字符设备驱动。测试环境Ubuntu 16.04Gcc 5.4.01. 什么是字符设备驱动 现实世界中存在着成千上万的硬件设备这些设备在硬件特性和使用方式上都各不相同。Linux 系统的大牛们从这些形形色色的设备中提取出共性将它们抽象为三大类字符设备、块设备和网络设备。基于代码质量和复用性的考虑Linux 内核针对每一类硬件设备都提供了对应的驱动模型框架一般包括基本的内核设施和文件系统接口。开发人员在写某类设备驱动程序时能一套完整的驱动模型框架可以使用从而将精力放在硬件设备本身的控制上。简单的 Linux 设备驱动程序结构图详细一点的 Linux 设备驱动程序结构图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 KERNELDIRXXX/linux ARCHarm CROSS_COMPILEarm-linux-# 编译应用程序$ arm-linux-gcc chrdev_app.c -o chrdev_app# 加载驱动模块$ insmod chrdev_drv.kodemo_init():major242, minor0# 手动创建字符设备文件节点$ mknod /dev/chr_dev c 242 0# 运行应用程序$ ./chrdev_appchr_open, major242, minor0In 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.cstruct 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_operationsstruct 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 章节《嵌入式应用开发完全手册》三、思考技术也思考人生学习技术更要学习如何生活。你和我各有一个苹果如果我们交换苹果的话我们还是只有一个苹果。但当你和我各有一个想法我们交换想法的话我们就都有两个想法了。对 嵌入式系统 (Linux、RTOS、OpenWrt、Android) 和 开源软件 感兴趣想和更多人互相交流学习请关注公众号嵌入式Hacker一起来学习吧。无论是关注或转发还是打赏都是对作者莫大的支持。觉得文章对你有价值的话不妨点个 在看和点赞 哦。欢迎加入我的微信群先加我我拉你进群暗号(我最棒)。祝各位工作顺利家庭幸福财源滚滚~~~