嵌入式 linux 驱动高级开发及内核原理

Post on 19-Jan-2016

101 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

嵌入式 Linux 驱动高级开发及内核原理. 陈应刚 chenyg@miiceic.org.cn. 日程安排. 设备驱动简介 建立和运行模块 字符驱动 调试技术 并发和竞争 高级字符驱动操作 时间,延时和延后工作 分配内存 与硬件通讯 中断处理 块设备驱动. 日程安排. 设备驱动简介. 设备驱动简介. 驱动是什么 Driver is a software layer that lies between the applications and the actual device 驱动程序的角色 提供机制 , 而不是策略 - PowerPoint PPT Presentation

TRANSCRIPT

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

陈应刚 chenyg@miiceic.org.cn

日程安排 设备驱动简介 建立和运行模块 字符驱动 调试技术 并发和竞争 高级字符驱动操作 时间,延时和延后工作 分配内存 与硬件通讯 中断处理 块设备驱动

日程安排

设备驱动简介

设备驱动简介

驱动是什么 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 可划分为下列功能单元 进程管理 : 进程调度 , 资源分配 , 进程间通信 . 内存管理 : 其实也算是资源分配的一部分 文件系统 : 管理 , 组织物理媒介上数据的方法 设备控制 : 设备驱动 (ldd3 所关注的 ) 网络 : 实质上是进程间通信 . 但它不局限于一个

特定的进程 . 它关注收 / 发 packets, 路由 , 地址解析 ...

Kernel 的结构

模块

可加载模块 (lodable modules) module: 可实时加载到内核中的代码 , 它可动态

连接到内核 (insmod, rmmod) 设备驱动就是 module 的代表 , 但 module 还包

括文件系统等等 .

设备和模块的分类

模块分为这些类型,每种类型的模块驱动对应类型的设备 character module, block module, network interface other module

字符设备和块设备 字符设备 : 以字节流的形式被访问的设备。 e.g: /dev/con

sole : 文本控制台 . /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 类型 ( 在 <linux/types.h> 中定义 )用来持有设备编号 -- 主次部分都包括获得一个 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 in

t firstminor, unsigned int count, char *name);

释放 void unregister_chrdev_region(dev_t first, unsi

gned int count);

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

file_operations fileinode

字符设备的注册 在 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中的所有内容

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

日程安排

设备驱动简介建立和运行模块字符驱动调试技术

通过打印调试

通过宏可以定义日志级别 参考 P79

如果开启 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 *s

em); 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,conditio

n, 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 d

ata; }; 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, f

unc, 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 <linux/slab.h>void *kmalloc(size_t size, int flags);void kfree(void *obj);

内存分配标志 控制内存分配如何进行的标志 , 从最少限制的到

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

#include <linux/mm.h> 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 <linux/malloc.h>kmem_cache_t *kmem_cache_create(cha

r *name, size_t size, size_t offset, unsigned long flags, constructor(), destructor( ));

int kmem_cache_destroy(kmem_cache_t *cache);

缓存标志

在创建一个缓存时可指定的标志 . SLAB_CTOR_ATOMICSLAB_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);

日程安排 设备驱动简介 建立和运行模块 字符驱动 调试技术 并发和竞争 高级字符驱动操作 时间,延时和延后工作 分配内存 I/O读写

硬件读写屏障

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

#include <asm/system.h>void rmb(void);void read_barrier_depends(void);void wmb(void);void mb(void);

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

#include <asm/io.h> 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 coun

t); void insw(unsigned port, void *addr, unsigned long coun

t); void outsw(unsigned port, void *addr, unsigned long cou

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

t);

I/O 端口资源分配

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

#include <linux/ioport.h>struct resource *request_region(unsigned l

ong start, unsigned long len, char *name);void release_region(unsigned long start, u

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

gned long len);

I/O 地址映射

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

#include <asm/io.h>void *ioremap(unsigned long phys_addr, u

nsigned long size);void *ioremap_nocache(unsigned long phy

s_addr, unsigned long size);void iounmap(void *virt_addr);

内存区处理资源分配

为内存区处理资源分配的函数 struct resource *request_mem_region(unsi

gned 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 <asm/io.h> 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 <linux/interrupt.h>int request_irq(unsigned int irq, irqreturn_t

(*handler)( ), unsigned long flags, const char *dev_name, void *dev_id);

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

中断申请标志

给 request_irq 的标志 . SA_INTERRUPT 请求安装一个快速处理

者 ( 相反是一个慢速的 ).SA_SHIRQ 安装一个共享的处理者#include <asm/signal.h>SA_INTERRUPTSA_SHIRQSA_SAMPLE_RANDOM

中断的文件系统节点

报告硬件中断和安装的处理者的文件系统节点 .

/proc/interrupts/proc/stat

驱动使用探测函数

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

probe_irq_on 的结果必须传回给 probe_irq_off 在中断产生之后 . probe_irq_off 的返回值是被探测的中断号 .

unsigned long probe_irq_on(void);int probe_irq_off(unsigned long);

中断处理返回

从一个中断处理返回的可能值 , 指示是否一个来自设备的真正的中断出现了 .

IRQ_NONEIRQ_HANDLEDIRQ_RETVAL(int x)

使能和禁止中断

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

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 <linux/fs.h>int register_blkdev(unsigned int major, con

st char *name);int unregister_blkdev(unsigned int major, c

onst char *name);

块设备相关数据结构

块设备驱动的数据结构 . struct block_device_operations

描述内核中单个块设备的结构 . #include <linux/genhd.h> struct gendisk;

分配 gendisk 结构的函数 , 并且返回它们到系统 . struct gendisk *alloc_disk(int minors); void add_disk(struct gendisk *gd);

块设备相关函数 void set_capacity(struct gendisk *gd, sector_t se

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

中 . void add_disk(struct gendisk *gd);添加一个磁盘到内核 . 一旦调用这个函数 , 你的磁盘的方法可被内核调用 .

int check_disk_change(struct block_device *bdev);

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

请求队列相关函数 #include <linux/blkdev.h> 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 ma

x); void blk_queue_max_phys_segments(request_queue_t *queue, unsigned s

hort max); void blk_queue_max_hw_segments(request_queue_t *queue, unsigned sho

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

max); blk_queue_segment_boundary(request_queue_t *queue, unsigned long ma

sk); void blk_queue_dma_alignment(request_queue_t *queue, int mask); void blk_queue_hardsect_size(request_queue_t *queue, unsigned short ma

x);

块 I/O请求 #include <linux/bio.h> 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 函数操作文件 文件分为逻辑结构和物理结构 文件系统内置安全特性

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

根文件系统分区文件系统底层硬件

常见文件系统

JFFS2CRAMFSFAT16/32EXT3

文件系统

超级块目录和文件Inode 结构文件系统数据结构

文件系统分析

EXT3 文件系统分析FAT16/32 文件系统分析

谢谢大家

问题建议反馈后续资源

top related