PPT内容

这是linux设备驱动开发详解讲座ppt下载,主要介绍了设备驱动简介;建立和运行模块;字符驱动;调试技术;并发和竞争;分配内存;硬件通讯;中断处理;块设备驱动,欢迎点击下载。

嵌入式Linux驱动开发及内核原理

内容提要

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

分配内存

与硬件通讯

中断处理

块设备驱动

设备驱动简介

驱动是什么

Driver is a software layer that lies between the applications and the actual device

驱动程序的角色

提供机制(方法), 而不是策略(怎么用)

隐藏在UNIX中的哲学

mechanism: What capabilities are provided.

policy: How these capabilities can be used.

Kernel的作用

Kernel可划分为下列功能单元

进程管理: 进程调度, 资源分配, 进程间通信.

内存管理: 其实也算是资源分配的一部分

文件系统: 管理, 组织物理媒介上数据的方法

设备控制: 设备驱动(linux驱动开发所关注的)

网络: 实质上是进程间通信. 但它不局限于一个特定的进程. 它关注收/发packets, 路由, 地址解析...

Kernel的结构

模块

可加载模块(lodable modules)

module: 可实时加载到内核中的代码, 它可动态连接到内核(insmod, rmmod)

设备驱动就是module的代表, 但module还包括文件系统等等.

设备和模块的分类

模块分为这些类型,每种类型的模块驱动对应类型的设备

character module,

block module,

network interface

other module

字符设备和块设备

字符设备: 以字节流的形式被访问的设备。e.g: /dev/console : 文本控制台. /dev/ttyS0 : 串口

它通过文件系统节点被访问. e.g: /dev/tty1, /dev/lp0

字符设备与一般文件(regular file)的区别

可以在一般文件中前后移动(lseek), 但只能顺序访问字符设备.

当然, 也有特例: frame grabbers.

块设备: 能支持文件系统的设备

传统的UNIX: 只能以block(512B)为单位访问块设备

Linux: 能以访问字符设备的方式访问块设备, 即以字节文单位访问块设备.

Linux中字符设备与块设备的区别

内核内部对数据的组织和管理不同, 对驱动开发者来说透明

接口不同: 使用两套不同的interface

网络设备

网络接口: 能与其他主机通信的设备

它可以是硬件设备, 也可以是软件设备, 比如lo. (参考TCP/IP详解p26)

网络接口只管收发数据包, 而不管这些数据包被什么协议所使用

不同于字符设备和块设备, 网络接口没有对应的文件系统节点. 虽然可以通过类似eth0这样的"文件名"来访问网络接口, 但文件系统节点中却没有针对网络接口的节点

内核与网络接口之间的通信也不同于内核与字符/块设备之间的通信(read, write), 它们之间使用特定的传输数据包的函数调用

其他设备

也有一些module不能严格地划分类型.

USB module: 它工作在内核的USB子系统之上

实际的USB设备可以是字符设备, 块设备, 也可以是网络接口

在设备驱动之外, 别的功能, 不论硬件和软件, 在内核中都是模块化的

例如文件系统

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

分配内存

与硬件通讯

中断处理

块设备驱动

建立和运行模块

建立开发环境

ldd3例子开发环境linux2.6.10

2.6驱动开发需要预先安装内核源码

源码需要从官方下载kernel.org

或者其他发行版的官方下载

直接解压到/usr/src目录下

版本影响

内核官方版本注意kernel.org

注意发行版的内部版本

最新内核版本linux2.6.20/21工作队列接口变化

小版本变动不会对驱动的架构造成太大影响

对于不同发行版,不同内核版本要做少量移植和测试

内核模块VS应用程序

执行机制不同

模块初始化

模块退出

类似事件编程

使用库不一样

无法使用标准库

只能调用内核提供的函数

用户空间VS内核空间

用户空间VS内核空间

应用程序运行在用户空间

设备模块运行在内核空间

运行模式不一样

内存地址映射也不一样

用户空间和内核空间的转换

可能发生在进程中的系统调用时或者硬件中断

系统调用虽然在内核中执行,但是依然是在进程的上下文中进行的,所以可以访问到进程中的数据。

中断处理和进程是异步的了,而且不和任何进程有关系

模块跨越两个空间,有两个触发入口

一些函数作为系统调用的一部分执行

一些函数负责中断处理

内核中的并发

应用程序很多时候是按照顺序来执行的

内核处于并发的执行环境当中

内核当中有并发的进程

中断需要响应和处理

内核中的服务也在运行

对称多处理器导致并行

模块的加载卸载和查看

加载使用insmod

卸载使用rmmod

查看使用lsmod

模块代码

static int __init initialization_function(void)

{

/*initialization code here*/

}

module_init(initialization_function);

模块代码

static void __exit cleanup_function(void)

{

/* Cleanup code here*/

}

module_exit(cleanup_function);

如何处理加载中的失败

int __init my_init_function(void)

{

int err;

/* registration takes a pointer and a name */

err = register_this(ptr1, "skull");

if (err)

goto fail_this;

err = register_that(ptr2, "skull");

if (err)

goto fail_that;

err = register_those(ptr3, "skull");

if (err)

goto fail_those;

return 0; /* success */

fail_those:

unregister_that(ptr2, "skull");

fail_that:

unregister_this(ptr1, "skull");

fail_this:

return err; /* propagate the error */

}

如何编写清理函数

void __exit my_cleanup_function(void)

{

unregister_those(ptr3, "skull");

unregister_that(ptr2, "skull");

unregister_this(ptr1, "skull");

return;

}

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

分配内存

与硬件通讯

中断处理

块设备驱动

主次设备号

字符设备可以通过文件系统来存取

字符设备一般位于/dev下

有c标志的是字符设备

有b标志的是块设备

设备号文档Documentation/devices.txt

主设备号决定驱动的种类

次设备号决定使用哪个设备

设备编号的内部表达

dev_t 类型(在 中定义)用来持有设备编号 -- 主次部分都包括

获得一个 dev_t 的主或者次编号, 使用

MAJOR(dev_t dev);

MINOR(dev_t dev);

转换为一个 dev_t, 使用:

MKDEV(int major, int minor);

分配和释放设备编号

分配指定的主设备号

int register_chrdev_region(dev_t first, unsigned int count, char *name);

动态分配主设备号

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);

释放

void unregister_chrdev_region(dev_t first, unsigned int count);

字符驱动中重要的数据结构

file_operations

file

inode

字符设备的注册

在Linux 2.6下使用“struct cdev”记录字符设备的信息。结构定义如下:  struct cdev {   …   struct module *owner;   struct file_operations *ops;   dev_t dev;   …  };

void cdev_init(struct cdev *, struct file_operations *);

struct cdev *cdev_alloc(void);

int cdev_add(struct cdev *, dev_t, unsigned)

void cdev_del(struct cdev *);  …

Open方法

检查设备的特定错误

如果设备是首次打开,则对其进行初始化

如有必要,更新f_op指针

分配且填写filp->private_data里的数据结构

Release方法

释放由open分配的, 保存在filp->private中的所有内容

在最后一次关闭操作时关闭设备

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

分配内存

与硬件通讯

中断处理

块设备驱动

通过打印调试

通过宏可以定义日志级别

KERN_EMERG  用于紧急消息, 常常是那些崩溃前的消息.

KERN_ALERT  需要立刻动作的情形.

KERN_CRIT  严重情况, 常常与严重的硬件或者软件失效有关.

KERN_ERR  用来报告错误情况; 设备驱动常常使用 KERN_ERR 来报告硬件故障.

KERN_WARNING  有问题的情况的警告, 这些情况自己不会引起系统的严重问题.

KERN_NOTICE  正常情况, 但是仍然值得注意. 在这个级别一些安全相关的情况会报告.

KERN_INFO  信息型消息. 在这个级别, 很多驱动在启动时打印它们发现的硬件的信息.

KERN_DEBUG  用作调试消息.

printk 语句缺省是 DEFAULT_MESSAGE_LOGLEVEL (KERN_WARNING )

通过打印调试

如果开启Klogd及Syslogd则输出到日志

日志文件参考/var/log/message

在printk当中打印设备编号

Print_dev_t

Format_dev_t

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

分配内存

与硬件通讯

中断处理

块设备驱动

并发和管理

并发源很多

多个进程运行

SMP多个CPU并行

设备中断

延迟机制(工作队列,定时器,Tasklet)

并发和竞争

两个或多个进程读写某些共享数据,而最后的结果取决于进程运行的精确时序,就称为竞争条件(Race Conditions)。

竞争情况来自对资源的共享存取的结果.

存取管理的常用技术是加锁或者互斥

其次常用的技术是引用计数

临界区

把对共享内存进行访问的程序片段称作临界区(critical region),或临界段(critical section)。如果我们能够适当地安排使得两个进程不可能同时处于临界区,则就能够避免竞争条件。

临界区四要素

任何两个进程不能同时处于临界区

临界区外的进程不能阻塞其他进程

不能使进程在临界区外无限等待

不应对CPU的速度和数目做假设

PV操作解决同步互斥

PV原语的含义  P操作和V操作是不可中断的程序段,称为原语。PV原语及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量sem是一整数,sem大于等于零时代表可供并发进程使用的资源实体数,但sem小于零时则表示正在等待使用临界区的进程数。  P原语操作的动作是:  (1)sem减1;  (2)若sem减1后仍大于或等于零,则进程继续执行;  (3)若sem减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。  V原语操作的动作是:  (1)sem加1;  (2)若相加结果大于零,则进程继续执行;  (3)若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。  PV操作对于每一个进程来说,都只能进行一次,而且必须成对使用。在PV原语执行期间不允许有中断的发生。

解决互斥

用PV原语实现进程的互斥

由于用于互斥的信号量sem与所有的并发进程有关,所以称之为公有信号量。公有信号量的值反映了公有资源的数量。

只要把临界区置于P(sem)和V(sem)之间,即可实现进程间的互斥。就象火车中的每节车厢只有一个卫生间,该车厢的所有旅客共享这个公有资源:卫生间,所以旅客间必须互斥进入卫生间,只要把卫生间放在P(sem)和V(sem)之间,就可以到达互斥的效果。

解决同步

用PV原语实现进程的同步

与进程互斥不同,进程同步时的信号量只与制约进程及被制约进程有关而不是与整组并发进程有关,所以称该信号量为私有信号量。

利用PV原语实现进程同步的方法是:首先判断进程间的关系为同步的,且为各并发进程设置私有信号量,然后为私有信号量赋初值,最后利用PV原语和私有信号量规定各进程的执行顺序。

Linux 信号量实现

void sema_init(struct semaphore *sem, int val);

DECLARE_MUTEX(name);

DECLARE_MUTEX_LOCKED(name);

void init_MUTEX(struct semaphore *sem);

void init_MUTEX_LOCKED(struct semaphore *sem);

void down(struct semaphore *sem);

int down_interruptible(struct semaphore *sem);

int down_trylock(struct semaphore *sem);

void up(struct semaphore *sem);

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

分配内存

与硬件通讯

中断处理

块设备驱动

ioctl 接口

大部分驱动需要通过设备驱动进行各种硬件控制的能力.

大部分设备可进行超出简单的数据传输之外的操作;

例如, 设备锁上它的门, 弹出它的介质, 报告错误信息, 改变波特率, 或者自我销毁.

这些操作常常通过 ioctl 方法来支持, 它通过相同名字的系统调用来实现.

阻塞 I/O

数据操作可能会遇到

read 的调用时可能没有数据时

Write的调用时设备没有准备好接受数据

当驱动不能立刻满足要求怎么办

程序员希望调用 read 或 write 并且使调用返回

驱动应当(缺省地)阻塞进程, 使它进入睡眠直到请求可继续.

进程的休眠

进程被置为睡眠, 从调度器的运行队列移除

睡眠的进程被搁置一边, 等待以后发生事件

睡眠注意安全编程

在原子上下文时不能睡眠

休眠醒来,无法确定休眠时间和时序

休眠的进程必须有时机被唤醒

与休眠相关的数据结构和函数

等待队列

等待-唤醒函数

wait_event(queue, condition)

wait_event_interruptible(queue, condition)

wait_event_timeout(queue, condition, timeout)

wait_event_interruptible_timeout(queue,condition, timeout)

void wake_up(wait_queue_head_t *queue);

void wake_up_interruptible(wait_queue_head_t *queue);

阻塞操作的推荐用法

阻塞操作标准语法:

如果一个进程调用 read 但是没有数据可用(尚未), 这个进程必须阻塞. 这个进程在有数据达到时被立刻唤醒, 并且那个数据被返回给调用者, 即便小于在给方法的 count 参数中请求的数量.

如果一个进程调用 write 并且在缓冲中没有空间, 这个进程必须阻塞, 并且它必须在一个与用作 read 的不同的等待队列中. 当一些数据被写入硬件设备, 并且在输出缓冲中的空间变空闲, 这个进程被唤醒并且写调用成功, 尽管数据可能只被部分写入,这时缓冲内没有足够空间给被请求的 count 字节.

非阻塞I/O,poll 和 select

可以实现非阻塞读写多个文件

三者的区别和联系

select 在 BSD Unix 中引入

poll 是 System V 的解决方案

epoll扩展到几千个文件描述符,提高了性能

内部实现

unsigned int (*poll) (struct file *filp, poll_table *wait);

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

分配内存

与硬件通讯

中断处理

块设备驱动

测量时间流失

内核通过定时器中断来跟踪时间的流动

定时器中断由系统定时硬件以规律地间隔产生

每次发生一个时钟中断, 一个内核计数器的值递增.

这个计数器在系统启动时初始化为 0, 因此它代表从最后一次启动以来的时钟嘀哒的数目

这个计数器是一个 64-位 变量( 即便在 32-位的体系上)并且称为 jiffies_64

获知当前时间

void do_gettimeofday(struct timeval *tv);

延后执行

长延时技术

忙等待

让出处理器

超时

短延时技术

void ndelay(unsigned long nsecs);

void udelay(unsigned long usecs);

void mdelay(unsigned long msecs);

内核定时器

struct timer_list { /* ... */ unsigned long expires;

void (*function)(unsigned long); unsigned long data; };

void init_timer(struct timer_list *timer);

struct timer_list TIMER_INITIALIZER(_function, _expires, _data);

void add_timer(struct timer_list * timer);

int del_timer(struct timer_list * timer);

Tasklets 机制

struct tasklet_struct { /* ... */ void (*func)(unsigned long); unsigned long data; };

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

DECLARE_TASKLET(name, func, data);

DECLARE_TASKLET_DISABLED(name, func, data);

Tasklet特性

一个 tasklet 能够被禁止并且之后被重新使能; 它不会执行直到它被使能与被禁止相同的的次数.

如同定时器, 一个 tasklet 可以注册它自己.

一个 tasklet 能被调度来执行以正常的优先级或者高优先级. 后一组一直是首先执行.

taslet 可能立刻运行, 如果系统不在重载下, 但是从不会晚于下一个时钟嘀哒.

一个 tasklet 可能和其他 tasklet 并发, 但是对它自己是严格地串行的 -- 同样的 tasklet 从不同时运行在超过一个处理器上. 同样, 如已经提到的, 一个 tasklet 常常在调度它的同一个 CPU 上运行.

工作队列

工作队列表面类似于 taskets

tasklet 在软件中断上下文中运行的结果是所有的 tasklet 代码必须是原子的. 相反, 工作队列函数在一个特殊内核进程上下文运行; 结果, 它们有更多的灵活性. 特别地, 工作队列函数能够睡眠.

tasklet 常常在它们最初被提交的处理器上运行. 工作队列以相同地方式工作

内核代码可以请求工作队列函数被延后一个明确的时间间隔.

工作队列

struct workqueue_struct *create_workqueue(const char *name);

struct workqueue_struct *create_singlethread_workqueue(const char *name);

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

内存管理

与硬件通讯

中断处理

块设备驱动

内存分配

内存分配的最常用接口.

#include

void *kmalloc(size_t size, int flags);

void kfree(void *obj);

内存管理功能

程序在寻址过程中使用的是虚拟地址,该地址由段和偏移值该地址并不能直接用。为了能寻址物理内存,就需要一种地址变换机制将虚拟地址映射或变换到物理内存中,这种地址变换机制就是内存管理的主要功能之一(内存管理的另外一个主要功能是内存的寻址保护机制。由于时间有限限,本次不对其进行讨论)。

内存管理机制

1.无内存管理,直接寻址,也称为实地址模式(内存不受保护)

2.段式管理,也称为保护模式(可分段记录内存的使用情况)

3.分页式管理,目前操作系统使用的模式(可提高管理效率)

分页式管理(一)

内存分页管理是通过页目录表和内存页表所组成的二级表进行的。其中页目录表和页表的结构是一样的,表项结构也相同。页目录表中的每个表项(简称页目录项)(4 字节)用来寻址一个页表,而每个页表项(4 字节)用来指定一页物理内存页。因此,当指定了一个页目录项和一个页表项,我们就可以唯一地确定所对应的物理内存页。页目录表占用一页内存,因此最

分页式管理(二)

页目录表有1024个表项,每个表项占用4个字节,所以一个页目录表刚好占用一页内存(4X1024),因此最多可以寻址1024 个页表。而每个页表也同样占用一页内存,因此一个页表可以寻址最多1024 个物理内存页面。这样一个页目录表所寻址的所有页表共可以寻址1024 X 1024 X 4096 = 4G 的内存空间

页目录表和页表结构

线性地址

内存分配所获取的都是线性地址;

一个32 位的线性地址被分成了三个部分

第一部分(10位):指定一个页目录项

第二部分(10位):指定一个页表项

第三部分 (12位)   :指定物理内存页上的偏移地址

线性地址结构(一)

Typeded struct{

unsigned int dir:10    /*用作页面表目录中的下标,该目录项指向一个页面表 */

unsigned int page:10    /*用作页面表中的下标,该表项指向一个物理页面 */

unsigned int offset:12    /*用作4k字节物理页面内的偏移量 */

}线性地址

线性地址结构(二)

在页面目录中共有210 =1024个目录项,每个目录项指向一个页面表,每个页面表共有1024个页面描述项。线性地址的位31-22 共10 个比特用来确定页目录中的目录项,位21-12 用来寻址页目录项指定的页

表中的页表项,最后的12 个比特正好用作页表项指定的一页物理内存中的偏移地址

线性地址到物理地址映射(一)

有专门的寄存器CR3存储当前页面目录的地址

1.从寄存器CR3中取得页面目录的基地址

2.以线性地址中的dir位段为下标,在目录中取得相应也表的基地址

3.以线性地址中的page位段为下标,在所得到的页面表中取得相应的页面描述项

4.将页面描述项中给出的页面基地址与线性地址中的offset位段相加得到实际的物理地址

线性地址到物理地址映射(二)

线性地址到物理地址映射(三)

线性地址到物理地址映射(四)

表项结构(一)

表项结构(二)

表项结构(三)

表项结构(四)

页框地址(PAGE FRAME ADDRESS)指定了一页内存的物理起始地址

存在位(PRESENT – P)确定了一个页表项是否可以用于地址转换。P=1 可用,P=0 不能用于地址转换,相应的页面或页面表不在内存.CPU会产生缺页中断,内核中

表项结构(五)

P=0表示相应的页面或页面表不在内存.CPU会产生缺页中断,内核中的有关异常服务程序就可根据从磁盘上的页面交换区将相应的页面读入内存,并且相应的设置表现中的基地址,并将P位设置为1,供后续访问使用。相反也可将内存中暂时不使用的内存页面写入到磁盘的交换区,然后将相应的页面表项的P位设置为0。这就实现虚拟内存功能

表项结构(六)

当P=0时,表项中的其余各位均无意义,所以可以被用来临时存储其他信息,如被换出的页面在磁盘上的位置等等信息

PS=0时表示包含在由该目录项所指的页面面表中所有页面的大小都是4K字节,这也是目前在Linux内核中所采用的页面大小。但从Pentium处理器开始,intel引入了PSE页面大小扩充机制。即当PS=1,页面的大小就成了4M字节,而页面表就不足使用。

表项结构(七)

Ps=1时线性地址中的低22位就全部用在4M字节的页面中的位移,这样总的寻址能力不变1024X4M=4G,但映射过程减少了一层。提高访问速度(主要是由于内存容量和磁盘的容量的日益增加,磁盘访问速度的显著提高,以及对图像处理要求的日益增加,4M页面可能成为主流)

表项结构(八)

已访问(Accessed – A)和已修改(Dirty – D)比特位用于提供有关页使用的信息。除了页目录项中的已修改位,这些比特位将由硬件置位,但不复位。在对一页内存进行读或写操作之前,CPU 将设置相关的目录和二级页表项的已访问位。在向一个二级页表项所涵盖的地址进行写操作之前,处理器将设置该二级页表项的已修改位,而页目录项中的已修改位是不用的。当所需求的内存超出实际物理内存量时,内存管理程序就可以使用这些位来确定那些页可以从内存中取走,以腾出空间。内存管理程序还需负责检测和复位这些比特位

表项结构(九)

读/写位(Read/Write – R/W)和用户/超级用户位(User/Supervisor – U/S)并不用于地址转换,但用于分页级的保护机制,是由CPU 在地址转换过程中同时操作的。

Linux 中物理内存的管理和分配

物理地址0 处存放了一个页目录表,紧随其后是4 个页表。这4 个页表将被用于任务0,其它的派生进程将在主内存区申请内存页来存放自己的页表

控制内存分配如何进行的标志, 从最少限制的到最多的. GFP_USER 和 GFP_KERNEL 优先级允许当前进程被置为睡眠来满足请求. GFP_NOFS 和 GFP_NOIO 禁止文件系统操作和所有的 I/O 操作, 分别地, 而 GFP_ATOMIC 分配根本不能睡眠.

#include

GFP_USER

GFP_KERNEL

GFP_NOFS

GFP_NOIO

GFP_ATOMIC

内存分配标志

这些标志分配内存时修改内核的行为

__GFP_DMA

__GFP_HIGHMEM

__GFP_COLD

__GFP_NOWARN

__GFP_HIGH

__GFP_REPEAT

__GFP_NOFAIL

__GFP_NORETRY

slab 缓存

创建和销毁一个 slab 缓存. 这个缓存可被用来分配几个相同大小的对象.

#include

kmem_cache_t *kmem_cache_create(char *name, size_t size, size_t offset, unsigned long flags, constructor(), destructor( ));

int kmem_cache_destroy(kmem_cache_t *cache);

缓存标志

在创建一个缓存时可指定的标志.

SLAB_CTOR_ATOMIC

SLAB_CTOR_CONSTRUCTOR

缓存中分配释放单个对象

从缓存中分配和释放一个单个对象. /proc/slabinfo 一个包含对 slab 缓存使用情况统计的虚拟文件.

void *kmem_cache_alloc(kmem_cache_t *cache, int flags);

void kmem_cache_free(kmem_cache_t *cache, const void *obj);

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

分配内存

与硬件通讯

中断处理

块设备驱动

硬件读写屏障

硬件内存屏障. 它们请求 CPU(和编译器)来检查所有的跨这个指令的内存读, 写

#include

void rmb(void);

void read_barrier_depends(void);

void wmb(void);

void mb(void);

I/O读写

用来读和写 I/O 端口的函数. 它们还可以被用户空间程序调用, 如果它们有正当的权限来存取端口.

#include

unsigned inb(unsigned port);

void outb(unsigned char byte, unsigned port);

unsigned inw(unsigned port);

void outw(unsigned short word, unsigned port);

unsigned inl(unsigned port);

void outl(unsigned doubleword, unsigned port);

延时读写函数

如果在一次 I/O 操作后需要一个小延时, 你可以使用在前一项中介绍的这些函数的 6 个暂停对应部分; 这些暂停函数以 _p 结尾

unsigned inb_p(unsigned port);

字串函数

这些"字串函数"被优化为传送数据从一个输入端口到一个内存区, 或者其他的方式. 这些传送通过读或写到同一端口 count 次来完成.

void insb(unsigned port, void *addr, unsigned long count);

void outsb(unsigned port, void *addr, unsigned long count);

void insw(unsigned port, void *addr, unsigned long count);

void outsw(unsigned port, void *addr, unsigned long count);

void insl(unsigned port, void *addr, unsigned long count);

void outsl(unsigned port, void *addr, unsigned long count);

I/O 端口资源分配

I/O 端口的资源分配器. 这个检查函数成功返回 0 并且在错误时小于 0

#include

struct resource *request_region(unsigned long start, unsigned long len, char *name);

void release_region(unsigned long start, unsigned long len);

int check_region(unsigned long start, unsigned long len);

I/O地址映射

ioremap 重映射一个物理地址范围到处理器的虚拟地址空间, 使它对内核可用. iounmap 释放映射当不再需要它时.

#include

void *ioremap(unsigned long phys_addr, unsigned long size);

void *ioremap_nocache(unsigned long phys_addr, unsigned long size);

void iounmap(void *virt_addr);

内存区处理资源分配

为内存区处理资源分配的函数

struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

void release_mem_region(unsigned long start, unsigned long len);

int check_mem_region(unsigned long start, unsigned long len);

I/O 内存存取函数

用来使用 I/O 内存的存取者函数.

#include

unsigned int ioread8(void *addr);

unsigned int ioread16(void *addr);

unsigned int ioread32(void *addr);

void iowrite8(u8 value, void *addr);

void iowrite16(u16 value, void *addr);

void iowrite32(u32 value, void *addr);

I/O 内存函数.

旧的, 类型不安全的存取 I/O 内存的函数.

unsigned readb(address);

unsigned readw(address);

unsigned readl(address);

void writeb(unsigned value, address);

void writew(unsigned value, address);

void writel(unsigned value, address);

memset_io(address, value, count);

memcpy_fromio(dest, source, nbytes);

memcpy_toio(dest, source, nbytes);

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

分配内存

与硬件通讯

中断处理

块设备驱动

注册注销中断处理

调用这个注册和注销一个中断处理.

#include

int request_irq(

unsigned int irq, 请求中断号

irqreturn_t (*handler)( ), 中断服务程序

unsigned long flags, 是否共享

const char *dev_name,设备名称/proc/interrupts 来显示中断的拥有者

void *dev_id);用户数据

void free_irq(unsigned int irq, void *dev_id);

中断介绍

硬中断是外部设备对CPU的中断,软中断通常是硬中断服务程序对内核的中断,信号则是由内核(或其他进程)对某个进程的中断。软中断的一种典型应用就是所谓的“下半部”(bottom half),它的得名来自于将硬件中断处理分离成“上半部”和“下半部”两个阶段的机制:上半部在屏蔽中断的上下文中运行,用于完成关键性的处理动作;而下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行

软中断,用软件方式进行模拟,实现宏观上的异步执行效果。很多情况下,软中断和"信号"有些类似

注册注销中断处理

调用这个注册和注销一个中断处理.

#include

int request_irq(

unsigned int irq, 请求中断号

irqreturn_t (*handler)( ), 中断服务程序

unsigned long flags, 是否共享

const char *dev_name,设备名称/proc/interrupts 来显示中断的拥有者

void *dev_id);用户数据

void free_irq(unsigned int irq, void *dev_id);

中断申请标志

给 request_irq 的标志.

SA_INTERRUPT 当置位了, 这表示一个"快速"中断处理. 快速处理在当前处理器上禁止中断来执行

SA_SHIRQ 安装一个共享的处理者

#include

SA_INTERRUPT

SA_SHIRQ

SA_SAMPLE_RANDOM

中断的文件系统节点

报告硬件中断和安装的处理者的文件系统节点. 可以获取当前系统得中断号和每个中断产生的中断次数等信息

/proc/interrupts

/proc/stat

驱动使用探测函数

驱动使用的函数, 探测决定哪个中断线被设备在使用.

probe_irq_on这个函数返回一个未安排的中断的位掩码. 驱动必须保留返回的位掩码, 并且在后面传递给 probe_irq_off. 在这个调用之后, 驱动应当安排它的设备产生至少一次中断 供探测使用

probe_irq_off 的返回值是被探测的中断号.

中断处理

汇编语言文件处理很多机器级别的工作., 汇编代码被安排到每个可能的中断. 在每个情况下, 这个代码将中断号压栈并且跳转到一个通用段, 称为 do_IRQ, 在 irq.c 中定义.do_IRQ 做的第一件事是确认中断以便中断控制器能够继续其他事情. 它接着获取给定 IRQ 号的一个自旋锁, 因此阻止任何其他 CPU 处理这个 IRQ. 它清除几个状态位(包括称为 IRQ_WAITING 的一个, 我们很快会看到它)并且接着查看这个特殊 IRQ 的处理者. 如果没有处理者, 什么不作; 自旋锁释放, 任何挂起的软件中断被处理, 最后 do_IRQ 返回.

使能和禁止中断

可以使能和禁止中断。共享处理不使用这个函数.

void disable_irq(int irq);

void disable_irq_nosync(int irq);

void enable_irq(int irq);

禁止中断

使用 local_irq_save 来禁止本地处理器的中断并且记住它们之前的状态

void local_irq_save(unsigned long flags);

void local_irq_restore(unsigned long flags);

使能和禁止中断

在当前处理器无条件禁止和使能中断的函数.

void local_irq_disable(void);

void local_irq_enable(void);

内容提要

设备驱动简介

建立和运行模块

字符驱动

调试技术

并发和竞争

高级字符驱动操作

时间,延时和延后工作

分配内存

与硬件通讯

中断处理

块设备驱动

块设备注册

register_blkdev 注册一个块驱动到内核, 并且, 可选地, 获得一个主编号. 一个驱动可被注销, 使用 unregister_blkdev.

#include

int register_blkdev(unsigned int major, const char *name);

int unregister_blkdev(unsigned int major, const char *name);

块设备相关数据结构

块设备驱动的数据结构.

struct block_device_operations

描述内核中单个块设备的结构.

#include

struct gendisk;

分配 gendisk 结构的函数, 并且返回它们到系统.

struct gendisk *alloc_disk(int minors);

void add_disk(struct gendisk *gd);

块设备相关函数

void set_capacity(struct gendisk *gd, sector_t sectors);

存储设备能力(以 512-字节)在 gendisk 结构中.

void add_disk(struct gendisk *gd);

添加一个磁盘到内核. 一旦调用这个函数, 你的磁盘的方法可被内核调用.

int check_disk_change(struct block_device *bdev);

一个内核函数, 检查在给定磁盘驱动器中的介质改变, 并且采取要求的清理动作当检测到这样一个改变.

请求队列相关函数

#include

request_queue_t blk_init_queue(request_fn_proc *request, spinlock_t *lock);

void blk_cleanup_queue(request_queue_t *);

处理块请求队列的创建和删除的函数.

struct request *elv_next_request(request_queue_t *queue);

void end_request(struct request *req, int success);

elv_next_request 从一个请求队列中获得下一个请求;

end_request 可用在每个简单驱动器中来标识一个(或部分)请求完成.

void blkdev_dequeue_request(struct request *req);

void elv_requeue_request(request_queue_t *queue, struct request *req);

从队列中除去一个请求, 并且放回它的函数如果需要.

void blk_stop_queue(request_queue_t *queue);

void blk_start_queue(request_queue_t *queue);

如果你需要阻止对你的请求函数的进一步调用, 调用 blk_stop_queue 来完成. 调用 blk_start_queue 来使你的请求方法被再次调用.

请求队列参数控制函数

设置各种队列参数的函数, 来控制请求如何被创建给一个特殊设备

void blk_queue_bounce_limit(request_queue_t *queue, u64 dma_addr);

void blk_queue_max_sectors(request_queue_t *queue, unsigned short max);

void blk_queue_max_phys_segments(request_queue_t *queue, unsigned short max);

void blk_queue_max_hw_segments(request_queue_t *queue, unsigned short max);

void blk_queue_max_segment_size(request_queue_t *queue, unsigned int max);

blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask);

void blk_queue_dma_alignment(request_queue_t *queue, int mask);

void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max);

块I/O请求

#include

struct bio;

低级函数, 表示一个块 I/O 请求的一部分.

bio_sectors(struct bio *bio);

bio_data_dir(struct bio *bio);

2 个宏定义, 表示一个由 bio 结构描述的传送的大小和方向.

bio_for_each_segment(bvec, bio, segno);

一个伪控制结构, 用来循环组成一个 bio 结构的各个段.

char *__bio_kmap_atomic(struct bio *bio, int i, enum km_type type);

void __bio_kunmap_atomic(char *buffer, enum km_type type);

__bio_kmap_atomic 可用来创建一个内核虚拟地址给一个在 bio 结构中的给定的段. 映射必须使用 __bio_kunmap_atomic 来恢复.

块I/O请求

int end_that_request_first(struct request *req, int success, int count);

void end_that_request_last(struct request *req);

使用 end_that_request_firest 来指示一个块 I/O 请求的一部分完成. 当那个函数返回 0, 请求完成并且应当被传递给 end_that_request_last.

rq_for_each_bio(bio, request)

另一个用宏定义来实现的控制结构; 它步入构成一个请求的每个 bio.

int blk_rq_map_sg(request_queue_t *queue, struct request *req, struct scatterlist *list);

为一次 DMA 传送填充给定的散布表, 用需要来映射给定请求中的缓冲的信息

typedef int (make_request_fn) (request_queue_t *q, struct bio *bio);

make_request 函数的原型.

void bio_endio(struct bio *bio, unsigned int bytes, int error);

指示一个给定 bio 的完成. 这个函数应当只用在你的驱动直接获取 bio , 通过 make_request 函数从块层.

request_queue_t *blk_alloc_queue(int flags);

void blk_queue_make_request(request_queue_t *queue, make_request_fn *func);

使用 blk_alloc_queue 来分配由定制的 make_request 函数使用的请求队列, . 那个函数应当使用 blk_queue_make_request 来设置.

文件系统原理

文件系统

操作系统通过文件系统管理设备、组织数据

用户通过I/O函数操作文件

文件分为逻辑结构和物理结构

文件系统内置安全特性

嵌入式文件系统的层次结构

根文件系统

分区文件系统

底层硬件

常见文件系统

JFFS2

CRAMFS

FAT16/32

EXT3

文件系统

超级块

目录和文件

Inode结构

文件系统数据结构

文件系统分析

EXT3文件系统分析

FAT16/32文件系统分析

谢谢大家

问题

建议

反馈

相关PPT

2013年人教地理选修2课件:第五章第四节海洋空间的开发利用PPT:这是2013年人教地理选修2课件:第五章第四节海洋空间的开发利用PPT下载,主要介绍了课标领航;基础自主梳理;海洋空间开发利用的特点和意义;海洋空间开发利用的主要方式;海底储藏;

《燃料的合理利用与开发》燃料及其利用PPT课件7:这是一个关于《燃料的合理利用与开发》燃料及其利用PPT课件7,主要介绍了生活中经常使用的燃料有煤、石油和天然气,我们常称它们为化石燃料。因为它们都是由古代生物的遗骸经一系列复杂变化而形成得等等内容。欢迎点击下载哦!

《地理信息技术的应用》国土开发与保护PPT课件:这是《地理信息技术的应用》国土开发与保护PPT课件下载,主要介绍了第一部分内容:地理信息系统,人口空间分布与发展预测,1、人口空间分布分析,2.人口发展趋势预测,地理信息系统的应用——城市规划与评价询,欢迎点击下载哦。

《linux设备驱动开发详解讲座ppt》是由用户恕己于2018-01-10上传,属于仪器设备PPT。

linux 设备驱动 ppt,linux设备驱动开发详解讲座ppt相关推荐

  1. 转:S3C2440上LCD驱动(FrameBuffer)实例开发详解

    1.S3C2440上LCD驱动 (FrameBuffer)实例开发讲解 其中的代码也可直接参考:drivers/video/s3c2410fb.c 以下为转载文章,文章原地址:http://blog. ...

  2. 《Linux设备驱动开发详解 A》一一2.3 接口与总线

    本节书摘来华章计算机出版社<Linux设备驱动开发详解 A>一书中的第2章,第2.3节,作者:宋宝华 更多章节内容可以访问云栖社区"华章计算机"公众号查看.1 2.3 ...

  3. linux设备驱动开发详解源码,linux设备驱动开发详解光盘源码.rar

    压缩包 : linux设备驱动开发详解光盘源码.rar 列表 19/busybox源代码/busybox-1.2.1.tar.bz2 19/MTD工具/mtd-utils-1.0.0.tar.gz 1 ...

  4. 《Linux设备驱动开发详解》学习笔记一

    Linux设备驱动开发详解学习笔记<一> 书名:<Linux设备驱动开发详解>第二版 主机环境:Linux version 2.6.25-14.fc9.i686@Fedora ...

  5. 《Linux 设备驱动开发详解(第2版)》——1.4 Linux设备驱动

    本节书摘来自异步社区<Linux 设备驱动开发详解(第2版)>一书中的第1章,第1.1节,作者:宋宝华著,更多章节内容可以访问云栖社区"异步社区"公众号查看 1.4 L ...

  6. Linux设备驱动开发详解 第3版 (即 Linux设备驱动开发详解 基于最新的Linux 4 0内核 )前言

    Linux从未停歇脚步.Linus Torvalds,世界上最伟大的程序员之一,Linux内核的创始人,Git的缔造者,仍然在没日没夜的合并补丁,升级内核.做技术,从来没有终南捷径,拼的就是坐冷板凳的 ...

  7. 《Linux设备驱动开发详解(第2版)》隆重出版

    Linux设备驱动开发详解(第2版)(前一版狂销3万册,畅销书最新升级) [新品] 点击看大图     基本信息 * 作者: 宋宝华       * 出版社:人民邮电出版社     * ISBN:97 ...

  8. 《linux设备驱动开发详解》笔记——15 linux i2c驱动

    <linux设备驱动开发详解>笔记--15 linux i2c驱动 15.1 总体结构 如下图,i2c驱动分为如下几个重要模块 核心层core,完成i2c总线.设备.驱动模型,对用户提供s ...

  9. 《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)网购链接

    <Linux设备驱动开发详解:基于最新的Linux 4.0内核> china-pub   天猫     dangdang   京东 China-pub 8月新书销售榜 推荐序一 技术日新月 ...

最新文章

  1. 浙江大学计算机科学与技术学院工业设计工程,浙江大学考研·2020年工业设计考研信息整理...
  2. 请求接口时params和data的区别
  3. 20175330 数据结构-排序(选做)
  4. Phonegap之内存问题
  5. 如何使用SAP Analytics Cloud统计C4C系统每天新建的Lead个数和预测趋势
  6. 优化if-else代码的八种方案
  7. 试分别简述udp和tcp的特点_技术帖:污水处理中曝气设备的分类及特点小结
  8. python核心语法题_《Python核心编程》第3章笔记
  9. 批量执行定时任务_执行批量维护任务安全策略
  10. 9.react 从入门到放弃
  11. 【Ansible】Ansible 连接主机显示报错的处理方案
  12. 数据库系统概论重点总结
  13. jszip 解压压缩包_React实现zip压缩解压
  14. java替换字符串_java string中的替换字符串
  15. 3 idiots的台词
  16. 发明了万维网的他,如今却想亲手推翻它
  17. IEEE论文免费下载
  18. 推荐十本C#编程的最佳书籍
  19. FastReport for Delphi2010 中文菜单显示不全或者乱码解决方法
  20. 安装prometheus遇到:Failed to execute operation:file existed解决办法

热门文章

  1. K-means聚类分析与python实现
  2. 安装Google Chrome OS 操作系统
  3. 数据库课程设计-在线图书馆
  4. 西北乱跑娃 --- windows下kill进程
  5. ESP8266+MQTT控制继电器到阿里云
  6. 华为硬件工程师手册_华为是怎样开发硬件的 之一——概述
  7. 编写一个简版的数据库维护框架04-代码实现(基本函数部分1)
  8. EMANet:Expectation-Maximization Attention Networks for Semantic Segmentation论文解读和代码解读
  9. 深入探访支付宝双11十年路,技术凿穿焦虑与想象极限 | CYZONE特写 1
  10. Linux——基础命令