1.直接IO的宏观思想:
直接I/O,它是指数据可以直接在磁盘和应用程序地址空间进行传输,而不用路由内核提供的数据缓冲区.宏观框架示意图如下:
2.直接IO出现的原因:
标准I/O即缓冲I/O,大多数文件系统的默认操作就是标准I/O.它的存在,缓冲了应用程序到实质物理设备的数据,内核会根据一定的时机更新到实质的物理设备和应用程序空间里面去.像一些应用程序,它有自己更加灵活的缓冲机制,因此,它并不需要内核提供的缓存I/O.
3.使用直接IO:
针对上述所说的情况,有些应用有自身缓冲,不需要内核提供的默认的标准I/O,这时候我们会选择摒弃内核的标准IO而使用直接IO.我们要摒弃内核提供的标准IO,只需要在open()函数里面指定O_DIRECT标识即可.
操作系统内核中处理 open()系统调用的内核函数是 sys_open(),sys_open() 会调用 do_sys_open() 去处理主要的打开操作.它主要做了三件事情:首先,它调用getname()从进程地址空间中读取文件的路径名;接着,do_sys_open() 调用 get_unused_fd() 从进程的文件表中找到一个空闲的文件表指针,相应的新文件描述符就存放在本地变量 fd 中;之后,函数do_filp_open()会根据传入的参数去执行相应的打开操作.如下:
sys_open()
|-----do_sys_open()
|---------getname()
|---------get_unused_fd()
|---------do_filp_open()
|--------nameidata_to_filp()
|----------__dentry_open()
函数do_flip_open()在执行的过程中会调用函数 nameidata_to_filp(),而 nameidata_to_filp() 最终会调用 __dentry_open() 函数,若进程指定了 O_DIRECT 标识符,则该函数会检查直接I/O操作是否可以作用于该文件.如下:
if (f->f_flags & O_DIRECT) {
if (!f->f_mapping->a_ops ||
((!f->f_mapping->a_ops->direct_IO) &&
(!f->f_mapping->a_ops->get_xip_page))) {
fput(f);
f = ERR_PTR(-EINVAL);
}
}
4.内核驱动对直接IO的支撑:
尽管实际的驱动编程中,我们基本上不会去关注到直接I/O的实现问题.像块设备或网络设备中执行直接I/O其已经在内核中高层代码已经实现了的.而对于字符设备来说,执行直接I/O基本是不可行的.但是内核还是为我们实现直接I/O提供了API:
int get_user_pages(struct task_struct *tsk, struct mm_struct *mm,unsigned long start, int nr_pages, int write, int force, struct page **pages, struct vm_area_struct **vmas)
各参数说明如下:
tsk:
一个指向进行
I/O
的任务的指针
;
它的主要目的是告知内核哪个进程应当负责任何一个当设置缓冲时导致的页错
.
这个参数几乎一直作为
current
传递
.
mm:
一个内存管理结构的指针
,
描述被映射的地址空间
. mm_struct
结构是捆绑一个进程的虚拟地址空间所有的部分在一起的
.
对于驱动的使用
,
这个参数应当一直是
current-
>
mm.
start:
start
是
(
页对齐的
)
用户空间缓冲的地址
.
nr_pages:
以页为单位的用户空间缓冲的长度
.
write:
如果
write
是非零
,
这些页被映射来写
(
当然
,
隐含着用户空间在进行一个读操作
).
force:
force
标志告知
get_user_pages
来覆盖在给定页上的保护
,
来提供要求的权限
;
驱动应当一直传递
0
在这里
.
pages、vmas:
输出参数
.
在成功完成后
,
页包含一系列指向
struct page
结构的指针来描述用户空间缓冲
,
并且
vmas
包含指向被关联的
VMA
的指针
.
这些参数应当
,
显然
,
指向能够持有至少
len
个指针的数组
.
任一个参数可能是
NULL,
但是你需要
,
至少
,struct page
指针来实际对缓冲操作
.
get_user_pages()实际使用过程中,还要求给这个地址空间的 mmap 读者/写者旗标在调用前被以读模式获得.因此,实际使用中的情景如下:
down_read(¤t->mm->mmap_sem);
result = get_user_pages(current, current->mm, ...);
up_read(¤t->mm->mmap_sem);
5. 块设备中执行直接 I/O:
要在块设备中执行直接 I/O,进程必须在打开文件的时候设置对文件的访问模式为O_DIRECT,这样就等于告诉操作系统进程在接下来使用read()或者write()系统调用去读写文件的时候使用的是直接 I/O方式,所传输的数据均不经过操作系统内核缓存空间.使用直接 I/O 读写数据必须要注意缓冲区对齐(buffer alignment )以及缓冲区的大小的问题,即对应read()以及 write()系统调用的第二个和第三个参数.这里边说的对齐指的是文件系统块大小的对齐,缓冲区的大小也必须是该块大小的整数倍.
因此,块设备中执行直接I/O要注意两点:
1).Open的标志为O_DIRECT;
2).对齐.