《linux设备驱动开发详解》读书笔记

合集下载

第9章 Linux驱动程序设计详述

第9章  Linux驱动程序设计详述

(1)字符设备
字符设备是指存取时没有缓存的设备。典型的字 符设备包括鼠标,键盘,串行口等。
字符设备在I/O传输过程中以字符为单位的,但是 不一定是以字节为单位,因为一个字符展16bit(2个字 节)。它是通过文件系统节点来存储,在Linux系统中 ,字符设备以特别的文件方式在文件目录树中占据位置 并拥有自己的结点,并且指明了文件类型,但是,操作 (包括打开、关闭、读、写操作)起来却和普通文件一 样。大部分字符设备仅仅是数据通道,只能顺序存取。
(1)驱动程序注册
向系统增加一个驱动程序意味着要赋予它一个主 设备号,这可以通过在驱动程序的初始化过程中调用 register_chrdev()或者register_blkdev()来完成。
(2)设备的打开
打开设备是通过调用file_operations结构中的函 数open()来完成的,它是驱动程序用来为今后的操作完 成初始化准备工作的。在大部分驱动程序中,open()通 常需要完成下列工作:
(7)驱动程序注销
向系统增加一个驱动程序意味着要赋予它一个主 设备号,这可以通过在驱动程序的初始化过程中调用 register_chrdev()或者register_blkdev()来完成。而在关 闭字符设备或者块设备时,则需要通过调用 unregister_chrdev()或unregister_blkdev()从内核中注 销设备,同时释放占用的主设备号。
(2)modprobe命令的功能是自动处理可载入模块。 语法:modprobe [-acdlrtvV][--help][模块文件][符号名称 = 符号值]
(3)insmod(install module)挂载模块 。 功能说明:载入模块。 语法:insmod [-fkmpsvxX][-o <模块名称>][模块文件][符

Linux 2.6 字符设备驱动笔记(很好)

Linux 2.6 字符设备驱动笔记(很好)

Linux 2.6 字符设备驱动程序*笔记○、说明笔记适用于Linux的2.6.10以后的内核。

笔记以Linux DeviceDriver3提供的scull程序(scull目录中的main.c和scull.h)为记录主线,并以该驱动程序中的各种系统调用和函数调用流程为记录顺序。

比如,module_init( )和module_exit()为相对应的一对系统调用,一般书籍中都会放在一起讨论,但是本笔记却不会这样,而是在需要调用的时候才会涉及,因此module_init()会放在笔记开始时,也就是刚加载module时讨论,而module_exit( )则会放在笔记结束前,也就是要卸载module时再加以讨论。

该笔记的的目的是为了对Linux Device Drvier3中提到的各个知识点作一下整理,理清一下头绪,从而能让我对Linux驱动程序加深整体或者全局上的理解。

注:个人理解,有误难免!*******************************************驱动程序module的工作流程主要分为四个部分:1、用Linux提供的命令加载驱动module2、驱动module的初始化(初始化结束后即进入“潜伏”状态,直到有系统调用)3、当操作设备时,即有系统调用时,调用驱动module提供的各个服务函数4、卸载驱动module一、驱动程序的加载Linux驱动程序分为两种形式:一种是直接编译进内核,另一种是编译成module形式,然后在需要该驱动module时手动加载。

对于前者,还有待学习。

Module形式的驱动,Linux提供了两个命令用来加载:modprobe和insmod。

其中modprobe可以解决驱动module的依赖性,即假如正加载的驱动module若引用了其他module提供的内核符号或者其他资源,则modprobe就会自动加载那些module,不过,使用modprobe时,必须把要加载的驱动module放在当前模块搜索路径中。

LINUX设备驱动开发详解

LINUX设备驱动开发详解

LINUX设备驱动开发详解概述LINUX设备驱动开发是一项非常重要的任务,它使得硬件设备能够与操作系统进行有效地交互。

本文将详细介绍LINUX设备驱动开发的基本概念、流程和常用工具,帮助读者了解设备驱动开发的要点和技巧。

设备驱动的基本概念设备驱动是连接硬件设备和操作系统的桥梁,它负责处理硬件设备的输入和输出,并提供相应的接口供操作系统调用。

设备驱动一般由设备驱动程序和设备配置信息组成。

设备驱动程序是编写解决设备驱动的代码,它负责完成设备初始化、IO操作、中断处理、设备状态管理等任务。

设备驱动程序一般由C语言编写,使用Linux内核提供的API函数进行开发。

设备配置信息是定义硬件设备的相关参数和寄存器配置的文件,它告诉操作系统如何与硬件设备进行交互。

设备配置信息一般以设备树或者直接编码在设备驱动程序中。

设备驱动的开发流程设备驱动的开发流程包括设备初始化、设备注册、设备操作函数编写和设备驱动注册等几个主要步骤。

下面将详细介绍这些步骤。

设备初始化设备初始化是设备驱动开发的第一步,它包括硬件初始化和内存分配两个主要任务。

硬件初始化是对硬件设备进行基本的初始化工作,包括寄存器配置、中断初始化等。

通过操作设备的寄存器,将设备设置为所需的状态。

内存分配是为设备驱动程序分配内存空间以便于执行。

在设备初始化阶段,通常需要为设备驱动程序分配一块连续的物理内存空间。

设备注册设备注册是将设备驱动程序与设备对象进行关联的过程,它使得操作系统能够正确地管理设备。

设备注册包括设备号分配、设备文件创建等操作。

设备号是设备在系统中的唯一标识符,通过设备号可以找到设备对象对应的设备驱动程序。

设备号分配通常由操作系统负责,设备驱动程序通过注册函数来获取设备号。

设备文件是用户通过应用程序访问设备的接口,它是操作系统中的一个特殊文件。

设备文件的创建需要通过设备号和驱动程序的注册函数来完成。

设备操作函数编写设备操作函数是设备驱动程序的核心部分,它包括设备打开、设备关闭、读和写等操作。

Linux系统字符设备驱动框架笔记

Linux系统字符设备驱动框架笔记

Linux系统字符设备驱动框架笔记字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标、键盘、显示器、串口等等,当我们执行 ls -l /dev 的时候,就能看到大量的设备文件, c 就是字符设备, b 就是块设备,网络设备没有对应的设备文件。

编写一个外部模块的字符设备驱动,除了要实现编写一个模块所需要的代码之外,还需要编写作为一个字符设备的代码。

驱动模型Linux一切皆文件,那么作为一个设备文件,它的操作方法接口封装在struct file_operations ,当我们写一个驱动的时候,一定要实现相应的接口,这样才能使这个驱动可用,Linux的内核中大量使用"注册+回调"机制进行驱动程序的编写,所谓注册回调,简单的理解,就是当我们open一个设备文件的时候,其实是通过VFS找到相应的inode,并执行此前创建这个设备文件时注册在inode 中的'open函数,其他函数也是如此,所以,为了让我们写的驱动能够正常的被应用程序操作,首先要做的就是实现相应的方法,然后再创建相应的设备文件。

#include //for struct cdev#include //for struct file#include //for copy_to_user#include //for error number/* 准备操作方法集 *//*struct file_operations {struct module *owner; //THIS_MODULE//读设备ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//写设备ssize_t (*write) (struct file *, const char __user *, size_t,loff_t *);//映射内核空间到用户空间int (*mmap) (struct file *, struct vm_area_struct *);//读写设备参数、读设备状态、控制设备long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//打开设备int (*open) (struct inode *, struct file *);//关闭设备int (*release) (struct inode *, struct file *);//刷新设备int (*flush) (struct file *, fl_owner_t id);//文件定位loff_t (*llseek) (struct file *, loff_t, int);//异步通知int (*fasync) (int, struct file *, int);//POLL机制unsigned int (*poll) (struct file *, struct poll_table_struct *);。

linux设备驱动程序开发总结

linux设备驱动程序开发总结

不管我们学习什么编程语言,和我们见面的第一个程序就是“hello world!” 相信各位道上的朋友都遇到过这种个程序!!学习驱动程序也不例外,我学的第一个驱动程序就是“hello world!!”具体的程序代码如下:#include <linux/init.h>#include <linux/module.h>MODULE_LICENSE("Dual BSD/GPL");static int hello_init(void){printk(KERN_ALERT"Hello, world!\n");return 0;}static void hello_exit(void){printk(KERN_ALERT"byby FriendyARM mini2440!\n");}module_init(hello_init);module_exit(hello_exit);将其复制到工作目录下,并编写一个简单的Makefile文件:由于每个人使用的Linux系统不一样且每个人内核源代码所存放的位置也不是一样的。

所以编写Makefile文件的时候,参考别人的进行修改是一个很不错的的学习Makefile文件的方法。

当然你能把Linux内核的Makefile文件了解一下,对你了解Linux内核有很大的帮助的。

学习心得:1、驱动模块运行在内核空间,运行是不能依赖任何函数库和模块连接,所以在写驱动程序的时候所调用的函数只能是作为内核一部分的函数。

2、驱动模块和应用程序的一个重要不同是:应用程序退出时可不管资源释放或者其他的清除工作,但模块的退出啊哈念书必须仔细撤销初始化函数所做的一切,否则,在系统想重新引导之前某些东西就会残留在系统中。

3、处理器的多种工作模式其实就是为了操作系统的用户空间和内核空间设计的,在Unix类的操作系统中只是用到了两个级别:最高级别和最低级别。

Linux设备驱动第五章(并发和竞争)读书笔记

Linux设备驱动第五章(并发和竞争)读书笔记
void downgrade_write(struct rw_semaphore *sem);
down_write, down_write_trylock, 和 up_write 全部就像它们的读者对应部分, 除了, 当然, 它们提供写存取. 如果你处于这样的情况, 需要一个写者锁来做一个快速改变, 接着一个长时间的只读存取, 你可以使用 downgrade_write 在一旦你已完成改变后允许其他读者进入.
3)函数如下:
需要只读存取的代码的接口是:
void down_read(struct rw_semaphore *sem);
int down_read_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem);
在scull_write 代码中我们可以看到:
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
注意对 down_interruptible 返回值的检查; 如果它返回非零, 操作被打断了. 在这个情况下通常要做的是返回 -ERESTARTSYS. 看到这个返回值后, 内核的高层要么从头重启这个调用要么返回这个错误给用户. 如果你返回 -ERESTARTSYS, 你必须首先恢复任何用户可见的已经做了的改变, 以保证当重试系统调用时正确的事情发生. 如果你不能以这个方式恢复, 你应当替之返回 -EINTR.
读者/写者旗标实现的结果:我们可以并发的读,但是只能互斥的写。提高了效率。只读的任务可以并行进行它们的工作而不必等待其他读者退出临界区.
1)使用rwsem要包含<linux/rwsem.h>.

Llnux设备驱动之我见

Llnux设备驱动之我见
瞄回囫图口囫注目丽UN  ̄ U J3 IU .
… mc a at u P
信 息 技 术
Ln x 备 驱动之我 见 iu 设
关 宝 金 .
( 木 斯 技 师 学 院 , 龙 江 佳木 斯 14 0 ) 佳 黑 5 04
摘 要: 主要论 述 l u 操 作 系统及 如何 编 写 l u 操 作 系统设备 驱动程 序 。 i x 一个 开放 的操 作 系统 , 望它 能增加 开发者 本文 ix n ix n Ln 是 u 希

之 间的 交流 , 促进 l u i x的 更加 开放 。 n 关 键 词 : e c r esl u ;iu enl D v edi r ;n x Ln x K re i v i
( 这是一 种比较 好的做法 )否则 , P I ; 把 C 配置空 D A操作完成 时 , 被 自动置 1由此看来 , M 该位 , 美 国微软件公司的黑屏事件使 中国 ^ 盗 间中 br 寄存器 中的 内存地址进行重 映射 , al 只 这是与 D A控 制器之 间的握手操作。 M 这里要明 数据 包由网络层传递 给驱动程序 版软件中清醒, 同时也提出来了一个问题 , 我们 有 经过重映射 ,驱动程 序才能够 访问该 内存 区 确指 出的是 , 除了使用微 软的产品就没有 其它 的选择 了吗 。 域 , 这里涉及到页表项 的添加 。 并放置 在 D MA传 输缓 冲区的操作 由上面谈到 26复位芯片 . 的 r83 ttx i t 19s r mt 1 a _ 函数负责 ,中断处理 函数 当然不是的 , 还有苹果操作系统 ,众多版本 的 2 . 7初始 化 nt eie 构 中的部分 回掉 中仅仅是对 已发送的数据包的确认 ,同时记录 e dv 结 _ c l u 操作系统。随着使用 l u ix n i x操作系统的人

LINUX设备驱动读书笔记新版

LINUX设备驱动读书笔记新版

嵌入式Linux设备驱动开发读书笔记经过第十二章的学习,我大致了解了linux设备驱动开发的意义所在,在linux 系统中设备被抽象化,系统通过调用标准的驱动接口来实现各种硬件的操作,同时,系统上层对于硬件设备的调用无需关心具体的硬件细节,系统调用设备使用文件的方式进行操作,通过这样的屏蔽,系统以标准和统一的方式去完成硬件的操作,对于硬件设备更新速度越来越快的今天,硬件的更新,只需要去更改驱动就能够实现设备的升级,而不需要去改变上层的应用软件,同时也利于上层和软件和硬件之间开发的分工和合作。

设备驱动最终被添加到内核里,与系统成为一个整体,ARM处理器的指令集与我们通常使用的PC机的X86指令集不同,ARM 的RISC指令集也可以说是一个精简版本的X86指令集,虽然没有X86指令集那么强大的功能,但是同样可以支持LINUX系统的运算,因此基于ARM的LINUX系统需要使用支持ARM指令集的编译器来进行编译,也就是ARM-GCC,在宿主机,也就是X86指令集下使用ARM-GCC编译出来的LINUX系统能够在ARM硬件系统上运行。

设备驱动程序是内核的一部分,因此驱动的稳定对系统的稳定非常重要,设备驱动程序必须为内核提供一个标准的接口,并且为了防止系统过于臃肿,对于某些系统设备驱动要求能够实时的进行加载,对于设备的更新也更加的方便,以安装的方式进行加载。

以下列举我对几种设备驱动编写的理解1字符设备:一般应用程序都有一个main入口点,而设备驱动却没有main函数,其实模块在调用insmod命令时会进入设备驱动的module_init函数,在该函数中注册设备,在调用rmmod函数时驱动被卸载,此时入口点为module_exit函数,他们的关系如图设备被加载之后,系统便能够在内核中调用驱动,在LINUX内核中,dev_t 类型用来保存主设备号和次设备号,dev_t四个字节,其中12bit用于表示主设备号,其余20bit表示次设备号,使用系统函数给设备分配一个设备号,获得这个设备号之后,便能够通过特定的系统函数来对设备进行注册,当设备成功注册之后,系统就能够通过open和release操作来打开设备和卸载设备。

Linux设备驱动开发入门-Read

Linux设备驱动开发入门-Read

Linux设备驱动开发入门本文以快捷而简单的方式讲解如何像一个内核开发者那样开发linux设备驱动源作者: Xavier Calbet版权:GNU Free Documentation License 翻译: 顾宏军()中文版权:创作共用.署名-非商业用途-保持一致知识准备要开发Linux 设备驱动,需要掌握以下知识:•C 编程 需要掌握深入一些的C 语言知识,比如,指针的使用,位处理函数,等。

•微处理器编程 需要理解微机的内部工作原理:存贮器地址,中断,等。

这些内容对一个汇编程序员应该比较熟悉。

Linux 下有好几种不同的设备。

为简单起见,本文只涉及以模块形式加载的字符设备。

使用2.6.x 的内核。

(特别是Debian Sarge 使用的2.6.8内核。

)用户空间和内核空间当你开发设备驱动时,需要理解“用户空间”和内核空间之间的区别。

1:2:3:4:5:6:7:8:9:10:11:12:13:14:15:16:17:18:19:20:21:22:23:24:25:•内核空间 :Linux 操作系统,特别是它的内核,用一种简单而有效的方法管理机器的硬件,给用户提供一个简捷而统一的编程接口。

同样的,内核,特别是它的设备驱动程序,是连接最终用户/程序员和硬件的一坐桥或者说是接口。

任何子程序或者函数只要是内核的一部分(例如:模块,和设备驱动),那它也就是内核空间的一部分。

•用户空间. 最终用户的应用程序,像UNIX 的shell 或者其它的GUI 的程序(例如,gedit),都是用户空间的一部分。

很显然,这些应用程序需要和系统的硬件进行交互。

但是,他们不是直接进行,而是通过内核支持的函数进行。

它们的关系可以通过下图表示:图1: 应用程序驻留在用户空间, 模块和设备驱动驻留在内核空间26:27:28:29:30:31:32:33:34:35:36:37:38:39:40:用户空间和内核空间之间的接口函数内核在用户空间提供了很多子程序或者函数,它们允许用户应用程序员和硬件进行交互。

Linux驱动笔记--基础

Linux驱动笔记--基础

【说明】:该系列笔记均是看韦东山视频教程的记录。

Linux驱动学习笔记(1)====================================================================================当我们着手写一个驱动的时候,并不需要从0开始,而是可以在内核源码里面找到别人成熟的类似的驱动进行修改,移植。

这也是一个非常高效的过程。

首先,我们要明白自己的目标,要的是什么.从上到下,一个软件系统可分为:应用程序、库、操作系统(内核)、驱动程序。

以点灯程序为例:现在的内核都有一个VFS文件系统,会根据用户空间(应用程序)里面的设备提供的设备属性、设备号来进行填充,然后根据填充的内容注册进内核。

实现流程如下:1,写出要实现的功能:led_open、led_write2, 怎么告诉内核 file_operation结构体a,定义fileoperation并填充b,把这个结构告诉内核 register_chrdev(...)3,谁来调用驱动入口:int_led_drv_init(void)4, 对驱动进行修饰 module_init(led_drv_init);【驱动程序】#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <asm/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>static struct class *leddrv_class;static struct class_device *leddrv_class_dev;volatile unsigned long *gpfcon = NULL;volatile unsigned long *gpfdat = NULL;static int led_drv_open(struct inode *inode, struct file *file){printk("led_drv_open\n");gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)); //清零gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)); //输出return 0;}ssize_t led_drv_write(struct file *file, char __user *buf,size_t count, loff_t * ppos){int val;copy_from_user(&val, buf, count); //copy to userif (val == 1){//open_ledgpfdat &= ~((1<<4) | (1<<5) | (1<<6));}else{// close_ledgpfdat |= (1<<4) | (1<<5) | (1<<6);}printk("led_drv_write\n");return 0;}static struct file_operations led_drv_fops ={.owner = THIS_MODULE,.open = led_drv_open,.write = led_drv_write,};int major;int led_drv_init(void){major = register_chrdev(0,"led_drv",&led_drv_fops);// 调用register_chrdev向内核注册设备leddrv_class = class_create(THIS_MODULE, "leddrv");if(IS_ERR(leddrv_class)) return PTR_ERR(leddrv_class);leddrv_class_dev = class_device_creat(leddrv_class, NULL, MKDEV(major,0),NULL,"led");if(unlikely(IS_ERR(leddrv_class_dev))) return PTR_ERR(leddrv_class_dev);gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //将物理地址映射为虚拟地址gpfdat = gpfcon + 1; //加1是以(volatile unsigned long *)的长度为单位,即为4字节return 0;}void led_drv_exit(void){unregister_chrdev(major,"led_drv");class_device_unregister(leddrv_class_dev);class_destroy(leddrv_class);iounmap(gpfcon); //释放gpfcon的虚拟地址return 0;}module_init(led_drv_init);module_exit(led_drv_exit);MODULE_LICENSE("GPL");当然,以上代码并不是很完善,只是为了说明一些问题。

linux驱动开发学习笔记

linux驱动开发学习笔记

Linux系统驱动开发1:Linux 设备通常划分为三种:字符设备、块设备和网络接口设备。

字符设备是指:那些只能一个字节一个字节读写数据的设备,不能随即读取设备内存中的某一数据。

其读取数据需要按照先后顺序,从这点上看,字符设备是面向数据流的设备。

常用的字符设备有鼠标、键盘、串口、控制台和LED等设备。

块设备是指:可以从设备的任意位置读取一定长度数据的设备。

其读取数据不必按照先后的顺序,可以定位到设备的某一具体的位置,读取数据。

常见的块设备有硬盘,磁盘,U 盘和SD卡。

每一个字符设备或者块设备都在/dev目录下对应一个设备文件。

进入/dev目录下,执行ls –l 命令,以C开头的是字符设备,以b开头的是块设备。

2:主设备号和次设备号一个字符设备或者一个块设备都有一个主设备号和次设备号。

主设备号和次设备号统称为设备号。

主设备号用来表示一个特定的驱动程序。

此设备号用来表示使用该驱动程序的各设备。

例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。

那么可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2.这里次设备号为别表示两个LED灯。

2.1 主设备号和次设备号的表示:在linux内核中,设备号用dev_t类型来表示。

在linux 2.6.29.4中,dev_t定义为一个无符号长整型变量,如下:typedef u_long dev_t ;u_long 在32位机中占4个字节,在64位机中占8个字节,以32位机为例,其中高12位表示主设备号,低20位表示次设备号。

2.2 动态分配设备号和静态分配设备号静态分配设备号,就是驱动程序开发者静态的指定一个设备号。

对于一部分成用的设备,内核开发者已经为其分配了设备号,这些设备号可以在内核源码documentation/divice.txt文件中找到,如果只有开发者自己使用这些设备驱动程序,那么可以选择一个尚未使用的一个设备号。

《LINUX设备驱动程序》阅读笔记全十八章

《LINUX设备驱动程序》阅读笔记全十八章

《LINUX设备驱动程序》阅读笔记目录第1章:设备驱动程序简介 (1)第2章:构造和运行模块 (1)第3章:字符设备驱动程序 (1)第4章:调试技术 (2)第5章:并发和竞态 (2)第6章:高级字符驱动程序操作 (3)第7章:时间、延迟及延缓操作 (3)第8章:分配内存 (3)第9章:与硬件通信 (4)第10章:中断处理 (4)第11章:内核的数据类型 (4)第12章:PCI 驱动程序 (5)第13章:USB 驱动程序 (5)第14章:Linux 设备模型 (5)第15章:内存映射和 DMA (5)第16章:块设备驱动程序 (6)第17章:网络驱动程序 (6)第18章:TTY 驱动程序 (6)第1章:设备驱动程序简介1、“通常,设备驱动程序就是这个进入Linux内核世界的大门”,“设备驱动程序在Linux 内核中扮演着特殊的角色,它们是一个个独立的黑盒子,使某个特定硬件响应一个定义良好的内部编程接口,这些接口完全隐藏了设备的工作细节。

用户的操作通过一组标准化的调用执行,而这些调用独立于特定的驱动程序”。

2、Linux系统将设备分成三种基本类型:字符设备、块设备和网络设备。

第2章:构造和运行模块1、“内核黑客通常拥有一个‘牺牲用的’系统,用于测试新的代码”。

2、模块在被使用之前需要注册,而退出时要仔细撤销初始化函数所做的一切。

驱动模块只能调用由内核导出的那些函数。

3、公共内核符号表中包含了所有的全局内核项(即函数和变量)的地址。

当模块被装入内核后,它所导出的任何符号都会变成内核符号表的一部分。

第3章:字符设备驱动程序第一节-主设备号和次设备号。

对字符设备的访问都是通过文件系统内的设备名称进行的。

通常而言,主设备号标识设备对应的驱动程序,而次设备号用于正确确定设备文件所指的设备。

对应的数据结构为dev_t 类型。

分配设备号使用函数alloc_chrdev_region() ,释放就使用unregister_chrdev_region() 函数。

Linux设备驱动第五章(并发和竞争)读书笔记(Linux device drivers, fifth chapters (concurrency and compe

Linux设备驱动第五章(并发和竞争)读书笔记(Linux device drivers, fifth chapters (concurrency and compe

Linux设备驱动第五章(并发和竞争)读书笔记(Linux device drivers, fifth chapters (concurrency and competition),reading notes)The fifth chapter is concurrency and competitionReference article:/u1/34474/showart.php? Id=408682?5.3 flags and mutex1) flag is the core of a single integer value. Combined witha pair of functions, also known as PV operations".One wants to enter the critical section of the P process with the flag raised, if the flag value is greater than 0, this value decreased 1, and the process continues. If the flag is small than or equal to 0, indicating that the flag has been occupied by other processes, the process must wait until the flag is released.Unlock the flag, flag is released, to complete the operation by calling the V V operation, increasing the value of the flag, and the flag can wake up waiting process.2) mutex function is: mutual exclusion, to prevent multiple processes simultaneously in the same critical area. The mutex is initialized to 1 flags. Because that is initialized to 1, when the first process of P, 1>0, so the process can run at the same time, the flag value is 0, 1 decline, the second process to obtain the flag is used to access the critical section, butfound that the flag value is 0, so the process can not run until the 2 release of the flag process 1.5.3.1 implementation of the LINUX flag1) to use the flag, must type contains <asm/semaphore.h>. is related to the struct semaphore;2) the flag: create a flag, then use sema_init to set itSuch as:Struct semaphore *sem; / / define flagSema_init (SEM, Val); / / initialize Val to initialize the value of flag flag.3) usually, flag is used to model the mutex. As a result, the kernel provides a series of macros to initialize.DECLARE_MUTEX (name); / / initialize a mutex, a value of 1DECLARE_MUTEX_LOCKED (name); / / initialize a mutex, a value of 0 is: flag after initialization is not available.To use this flag any process must first unlock it again.When we need to initialize at runtime (that is, dynamic creation), we use the:Void init_MUTEX (struct semaphore *sem); / / initialize a mutex,a value of 1Void init_MUTEX_LOCKED (struct semaphore *sem); initializes a mutex with the value of 0For example, we want to create a mutex that is initialized to 0:Struct semaphore *sem; / / define flagECLARE_MUTEX_LOCKED (SEM); / / initialize flag value is 0.The above is the initialization flag, after initialization of course we must use the flag, or create it what is the significance?4) flag acquisition and release (can also be a mutex, because basically all flags are used to LINUX mutex)Get the flag also known as P, also called down. Down literally means "drop". The flag is minus 1.The P function is as follows:Void down (struct semaphore *sem); / * is not recommended, will establish not kill process * /Int down_interruptible (struct semaphore *sem); / * recommended the use of down_interruptible need to be careful, if the operation is interrupted, the function returns a nonzero value, and it will not have the call signal. The proper use ofdown_interruptible requires always checking the return value and responding accordingly. * /Int down_trylock (struct semaphore *sem); / * with "_trylock" never sleeps, if the signal is not available in the call, will return a nonzero value. * /We usually use the down_interruptible function. Recommended drops in the book.Once the flag, the flag of the obtaining process can access the critical area to protect the flag. What do you do when you use it? Of course is to control the discarded in the critical region, is also the release flag.The release flag also known as V, also called UP up, literally rise.The flag is 1. (down and up description of the image of the flag operation).The V function is as follows:Void up (struct, semaphore, *sem);Once called UP, will not have the flag in the process.5) use flags are easy to make mistakesGet the process to release the flag flag using the call to UP, but not many call UP, that is: a down for a UP.In the hold flag when an error is encountered, we must be in the return (error state) is to call UP release flag, otherwise the critical region has been the process of possession, but perhaps the process has been kill, and the other to the use of critical areas will be due process has not been suspended critical region.5.3.2. in scull using the flagThe key to the correct use of the lock primitive is to specify exactly what resources to protect and confirm that each access to these resources uses the correct locking methodFirst of all, look at a scull structure:Struct scull_dev {Struct scull_qset *data Pointer to first quantum set; / * * /Int quantum the current quantum size; / * * /Int qset the current array size; / * * /Unsigned long size amount of data stored here; / * * /Unsigned int access_key used by sculluid and scullpriv; / * * /Struct semaphore SEM mutual exclusion semaphore; / * * /Struct CDEV CDEV Char device structure; / * * /};Struct semaphore SEM is our flag, and this structure is the object we want to protect the.Flag must be initialized before use. Scull this initialization at load time, in this cycle:For (I = 0; I < scull_nr_devs; i++) {Scull_devices[i].quantum = scull_quantum;Scull_devices[i].qset = scull_qset;Init_MUTEX (&scull_devices[i].sem);Scull_setup_cdev (&scull_devices[i], I);}We define a scull_nr_devs character driven, so we set up a scull_nr_devs flag, people ask: why not set a global flag on it? The answer is that each character driver does not share resources. For efficiency, there is no reason for us to use one of the SCULL devices and other processes can not use other scull devices.This loop typically appears in the __init loading function that drives the initialization of variables and the loading ofcharacter devices.Init_MUTEX is called in scull_setup_cdev. In the opposite order of this operation may produce a competitive situation, the flag may be accessed before it is ready. (note).When the initialization finished, we want to use the driver, we must confirm that there is no access to the data structure of scull_dev in the absence of holding flag. In other words: only in the hold when the flag can access the data.In the scull_write code, we can see:If (down_interruptible (&dev->sem))Return -ERESTARTSYS;Note that the return value of the examination of thedown_interruptible; if it returns non-zero, the operation was interrupted. Usually do in this case is to return to-ERESTARTSYS. to see the return value, the kernel or restart the call from the top or return the error to the user. If you return -ERESTARTSYS, you must first restore any user visible has been done to change, to ensure that when the right things happen again when the system call. If you cannot recover in this way, you should be back for -EINTR.When we finished using the critical area, the wirte function must release the flag,Whether it succeeds or fails when we use the write function,for example, kmalloc fails to allocate memory, andcopy_form_uesr fails from user space copy data. Remember: be sure to release the flag.Code:Out:Up (&dev->sem);Return retval;When you encounter a different error, use the goto statement to jump to out.Also, you must make sure that you do not access the scull_dev structure when you don't have semaphores.5.3.3 reader / writer flagFlag for all caller exclusive, but sometimes we can think: what is the flag? In general, we used in the access to the critical section, because if there is no flag, can process 1 has just completed the revision of a process variable, 2 and modified the same variable, leading to the later data on the front of the data to cover. Just like the examples in 5.1. scull. But when we read only, we only allow one process to read, which makes it inefficient. Therefore, the reader / writer flag.The reader / write flag: we can realize the concurrent read exclusive write, but only. Improved efficiency. The read-onlytasks can work in parallel without waiting for other readers to exit the critical area1) use rwsem to include <linux/rwsem.h>.2) initialize a rwsem.Void init_rwsem (struct, rw_semaphore, *sem);3) function as follows:The interface that requires read-only access to the code is:Void down_read (struct, rw_semaphore, *sem);Int down_read_trylock (struct, rw_semaphore, *sem);Void up_read (struct, rw_semaphore, *sem);Provides read-only access to protected resources of the down_read call can access concurrently with other readers. Note that the down_read may be set to the calling process can not be interrupted sleep. If the down_read_trylock is not waiting for the read access is not available; if allowed to access it returns non-zero, otherwise it is 0. down_read_trylock convention different from most of the kernel function, a return value of 0 indicates success. The use of a down_read to obtain the rwsem must eventually use up_read release.The reader's interface is similar:Void down_write (struct, rw_semaphore, *sem);Int down_write_trylock (struct, rw_semaphore, *sem);Void up_write (struct, rw_semaphore, *sem);Void downgrade_write (struct, rw_semaphore, *sem);Down_write, down_write_trylock, and up_write all like their readers corresponding part, besides, of course, they provide write access. If you are in such a situation, the need for a write lock to do a quick change, then read-only access for a long time, you can use downgrade_write once you have completed the change in allow other readers to enter.4) rwsem uses relatively little in driving, but sometimes they are useful. Rwsem allows multiple readers to hold the flag, and write a priority, when a writer trying to get the flag, it does not allow readers to enter until the write completes the work. But it can lead to a large number of readers because of hunger, how to write to the competition flag, because the writer has priority, so the reader is long time refused. Thus: rwsem the best use of the few write and write a short time under the condition of occupation.5.4. Completions mechanismIt tells the exclusive use of the flag flag, but there is a way: synchronization. The meaning of synchronization is: sequential execution, event A running only when the event B is started. It's called synchronization.Such as:Struct semaphore sem;Init_MUTEX_LOCKED (&sem);The initialization flags are not available / 0Start_external_task (&sem); / / call on this task in UP.Down (&sem); / / get the flagBut the fact is that: this flag is not the best tool, so the Completions mechanism produces.1) to use the Completions mechanism, you must include<linux/completion.h>.2) 2 ways to create completions:1:DECLARE_COMPLETION (my_completion);2:struct completion my_completion; / / create dynamic* / / *...Init_completion (&my_completion);3) function:Void wait_for_completion (struct completion *c); / / wait for completionThis function is not an interrupt waiting, if called but no process to complete, will not be a killing process, so there must be a function to complete the condition, also is a completion event.Void complete (struct completion *c); / / only to wake up a waiting processVoid complete_all (struct completion *c); / / wait for wake up all the processThe above 2 functions are used to emit completion events.If we use the complete_all () function, then we must reuse it before calling INIT_COMPLETION (struct completion C) to quickly initialize the completion structure.4) instance programDECLARE_COMPLETION (COMP); / / initialize completionSsize_t complete_read (struct, file, *filp, char, __user, *buf, size_t, count, loff_t, *pos){Printk (KERN_DEBUG, process,%i (%s), going, to, sleep\n, current->pid, current->comm);Wait_for_completion (&comp); / / wait for the completion eventPrintk (KERN_DEBUG, awoken,%i (%s), \n, current->pid, current->comm);Return 0; / * * / EOF}Ssize_t complete_write (struct, file, *filp, const, char,__user, *buf, size_t, count, loff_t, *pos){Printk (KERN_DEBUG, process,%i (%s), awakening, the, readers, \n, current->pid, current->comm);Complete (&comp); / / a completion eventReturn count; succeed to avoid retrial * / / *.}The program implements: the write of the device enables the exact read to be completed, and may have multiple "read", but we do not know which reads".5) typical use of the completion mechanismTypical use of completion mechanism is terminated at the exitand the module of kernel threads together. In the prototype example, some internal work is driven by a kernel thread in a while (1) during the whole cycle. When the module is ready to clean up, and wait for the thread to exit the exit function to inform this end. To the kernel contains a special function to use thread:Void complete_and_exit (struct, completion, *c, long, retval);5.5. spin lock (to be continued)。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

除了需要阻塞之外,select 函数所提供的功能(异步阻塞 I/O)与 AIO 类似。不过,它是对通知事件进行阻塞,而不是对 I/O 调用进行阻塞。
4)Linux 上的 AIO
在异步非阻塞 I/O 中,我们可以同时发起多个传输操作。这需要每个传输操作都有惟一的上下文,这样我们才能在它们完成时区分到底是哪个传输操作完成了。在 AIO 中,这是一个 aiocb(AIO I/O Control Block)结构。这个结构包含了有关传输的所有信息,包括为数据准备的用户缓冲区。在产生 I/O (称为完成)通知时,aiocb 结构就被用来惟一标识所完成的 I/O 操作。这个 API 的展示显示了如何使用它。
AIO 接口的 API 非常简单,但是它为数据传输提供了必需的功能,并给出了两个不同的通知模型。表 1 给出了 AIO 的接口函数,本节稍后会更详细进行介绍。
表 1. AIO 接口 API
API 函数
说明
aio_read
请求异步读操作
aio_error
检查异步请求的状态
点击(此处)折叠或打开
struct aiocb {
int aio_fildes; // File Descriptor
int aio_lio_opcode; // Valid only for lio_listio (r/w/nop)
volatile void *aio_buf; // Data Buffer
size_t aio_nbytes; // Number of Bytes in Data Buffer
struct sigevent aio_sigevent; // Notification Structure
/* Internal fields */
...
};
sigevent 结构告诉 AIO 在 I/O 操作完成时应该执行什么操作。我们将在 AIO 的展示中对这个结构进行探索。现在我们将展示各个 AIO 的 API 函数是如何工作的,以及我们应该如何使用它们。
aio_read
aio_read 函数请求对一个有效的文件描述符进行异步读操作。这个文件描述符可以表示一个文件、套接字甚至管道。aio_read 函数的原型如下:
内核已经实现了两步,我们要实现一个简单的传参。由于FASYNC标志改变时,驱动程序中的fasync()函数得以执行,故在驱动中要实现fasync()函数。
1)定义结构体fasync_struct。
struct fasync_struct *async_queue;//异步结构体指针
2)实现XXX_fasync,把函数fasync_helper,fd,filp和定义的结构体传给内核。
/* Zero out the aiocb structure (recommended) */
bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
/* Allocate a data buffer for the aiocb request */
1、异步通知的概念
异步通知的意思是:一旦设备就绪,则主动通知应用程序。异步通知类似于“中断”的机制,而不像之前学的阻塞型I/O和poll。阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O使用poll意味着查询设备是否可访问,而异步通知则意味着设备通知自身可访问,实现了异步I/O。
图 4. 异步阻塞 I/O 模型的典型流程 (select)
select 调用的主要问题是它的效率不是非常高。尽管这是异步通知使用的一种方便模型,但是对于高性能的 I/O 操作来说不建议使用。
④ 异步非阻塞 I/O(AIO)
最后,异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会立即返回,说明 read 请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操作。当 read 的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。
每个 I/O 模型都有自己的使用模式,它们对于特定的应用程序都有自己的优点。本节将简要对其一一进行介绍。
① 同步阻塞 I/O
图 2 给出了传统的阻塞 I/O 模型,这也是目前应用程序中最为常用的一种模型。其行为非常容易理解,其用法对于典型的应用程序来说都非常有效。在调用 read 系统调用时,应用程序会阻塞并对内核进行上下文切换。然后会触发读操作,当响应返回时(从我们正在从中读取的设备中返回),数据就被移动到用户空间的缓冲区中。然后应用程序就会解除阻塞(read 调用返回)。
3)异步 I/O 的动机
从前面 I/O 模型的分类中,我们可以看出 AIO 的动机。这种阻塞模型需要在 I/O 操作开始时阻塞应用程序。这意味着不可能同时重叠进行处理和 I/O 操作。同步非阻塞模型允许处理和 I/O 操作重叠进行,但是这需要应用程序根据重现的规则来检查 I/O 操作的状态。这样就剩下异步非阻塞 I/O 了,它允许处理和 I/O 操作重叠进行,包括 I/O 操作完成的通知。
int XXX_fasync (int fd, struct file *filp, int mode)
{
struct XXX_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
③ 异步阻塞 I/O
另外一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。在这种模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系统调用来确定一个 I/O 描述符何时有操作。使 select 调用非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。对于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知。
aio_return
获得完成的异步请求的返回状态
aio_write
请求异步写操作
aio_suspend
挂起调用进程,直到一个或多个异步请求已经完成(或失败)
aio_cancel
取消异步 I/O 请求
lio_listio
发起一系列 I/O 操作
每个 API 函数都使用 aiocb 结构开始或检查。这个结构有很多元素,但是清单 1 仅仅给出了需要(或可以)使用的元素。
}
3)当设备可写时,调用函数kill_fasync发送信号SIGIO给内核。
if (dev->async_queue){
XXX_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
4)当设备关闭时,需要将fasync_struct从异步队列中删除:
图 5. 异步非阻塞 I/O 模型的典型流程
在一个进程中为了执行多个 I/O 请求而对计算操作和 I/O 处理进行重叠处理的能力利用了处理速度与 I/O 速度之间的差异。当一个或多个 I/O 请求挂起时,CPU 可以执行其他任务;或者更为常见的是,在发起其他 I/O 的同时对已经完成的 I/O 进行操作。
2)I/O 模型
在深入介绍 AIO API 之前,让我们先来探索一下 Linux 上可以使用的不同 I/O 模型。这并不是一个详尽的介绍,但是我们将试图介绍最常用的一些模型来解释它们与异步 I/O 之间的区别。图 1 给出了同步和异步模型,以及阻塞和非阻塞的模型。
图 1. 基本 Linux I/O 模型的简单矩阵
int aio_read( struct aiocb *aiocbp );
aio_read 函数在请求进行排队之后会立即返回。如果执行成功,返回值就为 0;如果出现错误,返回值就为 -1,并设置 errno 的值。
要执行读操作,应用程序必须对 aiocb 结构进行初始化。下面这个简短的例子就展示了如何填充 aiocb 请求结构,并使用 aio_read 来执行异步读请求(现在暂时忽略通知)操作。它还展示了 aio_error 的用法,不过我们将稍后再作解释。清单2使用 aio_read 进行异步读操作的例子。
图 3. 同步非阻塞 I/O 模型的典型流程
非阻塞的实现是 I/O 命令可能并不会立即满足,需要应用程序调用许多次来等待操作完成。这可能效率不高,因为在很多情况下,当内核执行这个命令时,应用程序必须要进行忙碌等待,直到数据可用为止,或者试图执行其他工作。正如图 3 所示的一样,这个方法可以引入 I/O 操作的延时,因为数据在内核中变为可用到用户调用 read 返回数据之间存在一定的间隔,这会导致整体数据吞吐量的降低。
2、应用层中启用异步通知机制的三个步骤
1)调用signal函数,让指定的信号SIGIO与处理函数sig_handler对应。
signal(SIGIO, sig_handler);
2)指定一个进程作为文件的“属主(filp->owner)”,这样内核才知道信号要发给哪个进程。
fcntl(fd, F_SET_OWN, getpid());
my_aiocb.aio_buf = malloc(BUFSIZE+1);
if (!my_aiocb.aio_buf) perror("malloc");
/* Initialize the necessary fields in the aiocb */
my_aiocb.aio_fildes = fd;
XXX_fasync(-1, filp, 0);
4、异步I/O
1)简介
Linux中最常用的输入/输出(I/O)模型是同步I/O。在这个模型中,当请求发出之后,应用程序就会阻塞,直到请求满足为止。这是很好的一种解决方案,因为调用应用程序在等待I/O请求完成时不需要使用任何中央处理单元(CPU)。但是在某些情况中,I/O请求可能需要与其他进程产生交叠。可移植操作系统接口(POSIX)异步I/O(AIO)应用程序接口(API)就提供了这种功能。
相关文档
最新文档