I want to be a complete engineer - technical genius and sensitive humanist all in one!

Sunday, September 13, 2009

识人

一个人让对方不放心,怎样做都是徒劳。

Thursday, September 3, 2009

Linux 虚拟文件系统概述

[翻译说明:Richard Gooch 的这份 Overview of the Linux Virtual File System 位于内核
源代码的 Documentation/filesystems/下, 文件名是 vfs.txt。 该文档在 Understanding
the Linux Kernel 中被推荐, 从 2.6.26 官方内核中拷贝出来翻译,详细情况看原文。

介绍
====

虚拟文件系统(也被称做虚拟文件系统切换)是内核中为应用程序提供文件系统接口的一个软件层。
同时, 它也为不同的文件系统共存, 提供了一种抽象。

open(2),stat(2),read(2),write(2),chmod(2)等VFS系统调用, 是在进程上下文中被
调用的。 文件锁的描述在 Documnetation/filesystems/Locking中。


目录项缓存(dcache)
-----------------

VFS实现了诸如 open(2),stat(2),chmod(2)等系统调用。 路径名作为参数传递给这些系统
调用, 然后VFS使用路径名, 在目录项缓存(也叫 dentry cache 或 dcache)中查找相应的目
录项(dentry)。 通过目录项缓存, 路径名(文件名)能迅速得转换成相应的目录项。 目录项只存
在于内存中, 不会保存到磁盘上:它们存在的意义在于性能。

目录项缓存(dcache)旨在维护整个文件空间的一个视图。 但大多数计算机无法将文件系统的整
个目录结构缓存在内存中, 因此有些文件没有被缓存。 为了把路径名解析成相应目录项(dentry),
VFS得一步步的将路径名分解,创建目录项(dentry),读取相应的Inode。 这个过程是通过
Inode中的查找方法做到的。

Inode 对象
---------

一个目录项 (dentry) 通常包含指向 inode 的指针。 Inode是文件系统中的对象, 诸如常规文件,
目录, 管道(FIFO) 和其他的文件系统对象。 它们要么保存在于磁盘上(块设备上的文件系统), 要么
存在于内存中(伪文件系统)。 当需要时, 在磁盘上的 inode会被读取到内存中; 对inode的修改也会
写回到磁盘上。 一个 Inode可以被多个目录项(dentry)指向(例如硬链接就是这样)。

查找一个文件的Inode, VFS需要调用它的父目录对应Inode的lookup()方法。 该方法由Inode所在
的文件系统实现来定义。 一旦 VFS找到了dentry(因此也就知道了inode), open(2)系统调用就能
打开文件、 stat(2)就能查看inode内的数据。 Stat(2)操作非常简单: 一旦 VFS找到了dentry,
它就查看相关的inode并把部分数据返回给用户空间。

File 对象
--------

打开文件时还需要一个操作: 分配一个file结构(就是内核对文件描述符的实现)。 初始化这个新分配的
file结构时, 其中的一个指针会指向目录项(dentry),另一个指针会指向文件操作成员函数表──这是
从 inode中取来的。 然后函数表中的open()方法会被调用, 这样, 就调用了文件系统自己的open方法。
File结构被放置在进程的文件描述符表中。

读、写和关闭文件(以及其他与 VFS有关的操作)时, 首先使用用户空间提供的的文件描述符找到相应的
file结构, 然后调用file结构指向的函数表中的相应函数, 去执行相应的操作。只要文件还在打开着, 目录
项(dentry)就处于使用状态, 也就意味着inode也处在使用状态。


注册和挂载一个文件系统
=====================

注册或注销一个文件系统, 使用如下的 API 函数:

#include

extern int register_filesystem(struct file_system_type *);
extern int unregister_filesystem(struct file_system_type *);

传递进来的 file_system_type 结构描述了一个文件系统。 当有将把某个设备挂载到文件空间的某个目录
上的请求时, VFS会调用对应文件系统(由 file_system_type 结构代表)的 get_sb()方法。 挂载点的
目录项(dentry)将被更新, 指向inode的指针将指向新挂载的文件系统的根目录inode。
( 这里更新挂载点d_inode的描述是错的 )

在/proc/filesystems文件中, 你可以看到内核中所有已注册的文件系统。

file_system_type 结构
---------------------

该结构描述了文件系统。 例如 2.6.22 内核中的代码, 该结构具有下列成员:

struct file_system_type {
const char *name;
int fs_flags;
int (*get_sb) (struct file_system_type *, int,
const char *, void *, struct vfsmount *);
void (*kill_sb) (struct super_block *);
struct module *owner;
struct file_system_type * next;
struct list_head fs_supers;
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
};


name: 文件系统的名字, 例如"ext2"、"iso9660"、"msdos"等

fs_flags: 各种标志(亦即: FS_REQUIRES_DEV, FS_NO_DCACHE 等)

get_sb: 每当该类型的文件系统被挂载时, 调用该方法

kill_sb: 每当该类型的文件系统被卸载时, 调用该方法

owner: VFS 内部使用:多数情况下该被赋值为 THIS_MODULE

next: VFS 内部使用: 多数情况下该被赋值为 NULL

s_lock_key, s_umount_key: 处理锁依赖相关

get_sb()方法有如下参数:

struct file_system_type *fs_type: 描述文件系统, 由特定文件系统初始化

int flags: 挂载标志

const char *dev_name:要挂载的设备名

void *data: 任意的挂载选项, 通常是 ASCII 字符串的形式

struct vfsmount *mnt: 挂载点的VFS内部呈现

get_sb()方法必须探测 dev_name指定的块设备所包含的文件系统类型, 判断该方法是否支持fs_type
指定的文件系统。 如果dev_name指定的块设备被成功打开, 它将为块设备中的文件系统初始化一个
super_block结构, 如果失败, 将返回错误代码。

由get_sb()方法填充的 super_block结构的一些域中, s_op 最值得关注。 它指向是一个super_operations结构,
这个结构描述了文件系统的下一层的实现。

通常, 一个文件系统会使用通用的 get_sb()实现并自己提供一个 fill_super()方法。 通用的 get_sb()实现有:

get_sb_bdev: 挂载一个基于块设备的文件系统

get_sb_nodev: 挂载不存在于磁盘上的文件系统

get_sb_single: 挂载所有挂载点共享一个super_block结构的文件系统

fill_super()方法具有下列参数:

struct super_block *sb: superblock 结构, fill_super()方法必须初始化它

void *data: 任意的挂载选项, 通常由 ASCII 字符串组成

int silent: 出错时是否打印错误信息


Superblock 对象
==============

一个 superblock 对象代表一个挂载的文件系统。

super_operations 结构
---------------------

该结构描述了 VFS 如何操作文件系统上的 superblock, 以 2.6.22 为例, 它具有下列成员:

struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);

void (*read_inode) (struct inode *);

void (*dirty_inode) (struct inode *);
int (*write_inode) (struct inode *, int);
void (*put_inode) (struct inode *);
void (*drop_inode) (struct inode *);
void (*delete_inode) (struct inode *);
void (*put_super) (struct super_block *);
void (*write_super) (struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait);
void (*write_super_lockfs) (struct super_block *);
void (*unlockfs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*clear_inode) (struct inode *);
void (*umount_begin) (struct super_block *);

int (*show_options)(struct seq_file *, struct vfsmount *);

ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
};

除非特别说明, 所有这些方法都在不持有锁的情况下调用, 这意味着这些方法可以安全的阻塞,
这些方法都必须在进程上下文调用(亦即:不是在中断处理函数或者 boottom half 中)。

alloc_inode: 由 inode_alloc()调用, 来为 inode分配空间并初始化它。 如果不定义该
方法,则分配的空间只够inode结构使用。 而通常情况下,alloc_inode会分配一个更大
的数据结构, 其中包含一个inode结构"。

destroy_inode: 由 destroy_inode()调用, 撤消为 inode分配的资源。 该方法只有在
alloc_inode被定义的时候才有效,它撤消alloc_inode做的所有事。

read_inode: 从某个挂载的文件系统中读取相关的 inode。 inode 结构的 i_ino 成员由 VFS
初始化, 来指定要读取的 inode; 其他成员由本方法填充。
你可以将该字段设置为NULL,这样就不能使用iget了, 而要使用iget5_locked来读取inode。
对于不能只通过inode号找到相应inode的文件系统, 则必需使用这种方法。

dirty_inode: 由 VFS 调用, 来把一个 inode 标记为脏。

write_inode: 当 VFS 需要把某个 inode 写入到磁盘上时, 调用本方法。 第二个参数指定
了写操作是否需要同步, 并非所有的文件系统都会检查这个标志。

put_inode: 当inode被从inode缓存(inode cache)中移除时调用

drop_inode: 当所有对inode的引用都被移除时调用, 调用时必须持有 inode_lock自旋锁。
该方法或者为 NULL(正常的 Unix 文件系统语义), 或者为 "generic_delete_inode"
(那些不想 缓存inode的文件系统。 这会导致不管 inode的引用计数的值是多少, delete_inode
总会被调用)"generic_delete_inode"的行为, 与 put_inode()中使用"force_delete"是等价的, 但
不会象后者那样会引发竞争情形。

delete_inode: 当 VFS 想删除一个 inode 时调用

put_super: 当 VFS 想释放 superblock(比如卸载时)时调用。 调用前应先调用lock_super锁住superblock

wirte_super: 当 VFS 需要将superblock写入到磁盘时调用, 该方法是可选的。

sysc_fs: 当 VFS需要把有隶属于 superblock的脏数据写入到磁盘上时调用。 第二个参 数指示了该方法是否需要
一直等待写操作的完成。 可选。

write_super_lockfs: 当 VFS需要锁定一个文件系统, 强制使它进入一致状态时调用该方法。
该方法目前用于逻辑卷管理 (Logical Volume Manager, LVM)

unlockfs: 当 VFS解锁一个文件系统, 并标记它为可写的, 此时调用本方法。

statfs: 当 VFS 想获得文件系统的一些统计数据时调用。 调用时需要持有内核锁(翻译疑 问:看 2.6.16 的 vfs_statfs 函数调用 sb->s_op->statfs 时并没有持有锁, 不知道作者指的是哪把锁?)

remount_fs: 当文件系统被 remount 时调用, 调用需持有内核锁

clear_inode: 当 VFS 清除 inode 时调用。 可选。

umount_begin: 当 VFS 卸载一个文件系统时调用

show_options: VFS 需要在/proc//mounts 显示挂载选项时调用

quota_read: VFS 想读取文件系统的磁盘配额文件时调用

quota_write: VFS 想写入文件系统的磁盘配额文件时调用

read_inode()方法负责填充"i_op"域, 它是一个指向"struct inode_operations"的指针, 该
结构描述了那些操作于每个 inode 的方法。

Inode 对象
==========

一个 inode 对象代表了文件系统内的一个对象。

inode_operations 结构
---------------------

描述了 VFS 如何操作你的文件系统中的一个 inode。 例如在 2.6.22 内核中, 有如下的成员:

struct inode_operations {
int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*unlink) (struct inode *,struct dentry *);
int (*symlink) (struct inode *,struct dentry *,const char *);
int (*mkdir) (struct inode *,struct dentry *,int);
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,int,dev_t);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *);
int (*readlink) (struct dentry *, char __user *,int);
void * (*follow_link) (struct dentry *, struct nameidata *);
void (*put_link) (struct dentry *, struct nameidata *, void *);
void (*truncate) (struct inode *);
int (*permission) (struct inode *, int, struct nameidata *);
int (*setattr) (struct dentry *, struct iattr *);
int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*removexattr) (struct dentry *, const char *);
void (*truncate_range)(struct inode *, loff_t, loff_t);
};

正如 super_operations 的方法一样, inode_operations 的成员也是在无锁情形下调用──
除非有特别说明。

create: 由 open(2)和 creat(2)系统调用来调用。 只有你想在文件系统中支持正规文件时,
才需要提供该方法的实现。 该方法参数中的 dentry应该还没有跟 inode 相关连(亦即:是一个负的
dentry)。 该方法中一般会调用d_instantiate()将 dentry于inode相关联。

lookup: VFS 需要在父目录中寻找一个 inode 时调用。 要查找的文件名在 dentry 中。 该方
法必须调用 d_add()来将dentry与找到的inode相关联 , 该 inode 的"i_count"域随之增
加。 如果该名字的 inode 未找到, 则 dentry 与 NULL 关联(亦即:是一个负的 dentry)。
本方法只有在真正遇到不可恢复的错误时才返回错误, 否则的话, 那些会创建 inode 的系
统调用, 如 create(2)、mknod(2)、mkdir(2)等将无法工作。 如果你想重载 dentry 的方法,
那么就初始化 dentry 的"d_dop"域, 它是一个指向"struct dentry_operations"的指针。

link: 由系统调用 link(2)来调用。 只有你想在自己的文件系统内支持硬链接时, 才需要提供该方法的实现。
多数情形下, 需要象 create 方法那样调用 d_instantiate()函数。

unlink: 由 unlink(2)系统调用来调用。 只有你想支持删除 inode 时才提供。

symlink: 由 symlink(2)系统调用来调用。 只有你想在自己的文件系统里支持符号链接时,
才需要提供该方法的实现。 多数情形下, 需要象 create 方法那样调用 d_instantiate()函数。

mkdir: 由系统调用 mkdir(2)来调用。 只有你想在文件系统中支持创建子目录时, 才需要提供其实现。
需要象 create 方法那样调用 d_instantiate()函数

rmdir: 由系统调用 rmdir(2)来调用。 只有想支持删除子目录时, 才需要。

mknod: 由系统调用 mknod(2)来调用, 以创建设备文件或者命名管道(FIFO)或者
本地socket。 只有你想支持创建这些类型的 inode 时, 你的文件系统才需要提供该方法的实现。
需要象 create 方法那样调用 d_instantiate()方法。

rename: 由系统调用 rename(2)来调用, 以重命名一个对象, 使之具有由第二个inode和 denrty
给定的父目录和名字。


readlink: 由系统调用readlink(2)来调用. 只有在文件系统支持符号链接时才需提供

follow_link: 由 VFS 调用, 以跟踪符号链接到它所指向的 inode。 只有你想支持符号链接
时才需要提供。 该方法返回了一个 void 型指针 cookie 传递给 put_link(), 参考下面的 put_link()方法。

put_link: 由 VFS 调用, 来释放由 follow_link 方法申请的临时性资源。 由follow_link()返回的
cookie 作为最后一个参数传递给本方法。 由一些诸如 NFS 这样的文件系统使用, 因为在这样的文
件系统中, page cache 是不稳定的(亦即, 随着跟踪符号链接的过程中建立起的 page cache 可能在
跟踪的最后已经不存在了)。

truncate: 由 VFS 调用以改变文件的大小。 在调用本方法之前, VFS 先把 inode 的 i_size
域设为期望的值。 本方法主要由 truncate(2)系统调用等使用。

permission: 由 VFS 调用, 来检查 POSIX 类文件系统的访问权限。

setattr: 由 VFS 调用, 来设定文件的属性, 该方法主要由 chmod(2)等使用

truncate_range: 该方法由文件系统提供, 用于截去文件中一个连续区域中块. (例如在文件中制造一个空洞)

getattr: 由 VFS 调用, 来设定文件的属性, 主要由 stat(2)等系统调用使用

setxattr: 由 VFS 调用, 来设置文件的扩展属性。 所谓扩展属性,就是和 inode 相关的一 对「名称-值」,
它是在inode 分配的时候与之关联的。 由 setxattr(2)系统调用使用。

getxattr: 由 VFS 调用, 获取根据扩展属性的名称, 获取其值。 由 getxattr(2)系统调用使用。

listxattr: 由 VFS 调用, 来列出给定文件的所有扩展属性。 给 listxattr(2)系统调用使用。

removexattr: 由 VFS 调用, 移除给定文件的扩展属性。 给 removexattr(2)系统调用使用。

truncate_range: 由文件系统实现提供的方法, 用于截去指定范围内的块 (例如: 在文件中制造一个空洞)

地址空间对象(The Address Space Object)
======================================

地址空间对象用来对Page Cache中的页进行分组、管理。 它可以用来跟踪文件中的(或其他地方的)
页面,也可以用来跟踪文件映射到进程地址空间的映射区。

地址空间对象有多种用处,其关系有时并不紧密。 这些用处包括:动态调整内存使用情况
根据地址来查找页面,跟踪那些标记为Dirty和Writeback的页面。

在这用处中,第一项独立于其他项。VM可以把脏页写入磁盘,从而使得它变为clean;也可以释放clean的页,
以便重新使用它。重新使用页前, 脏页需要调用->writepage方法;clean的页, 如果有PagePrivate标记,
则需要调用->releasepage方法; 没有PagePrivate标记的Clean页,如果又没有外部的引用,可以直
接释放,而不用通知它所属的地址空间对象。

为了做到这点,需要将页面放入一个"最近最少使用"链表,每当页面被使用,就调用lru_cache_add和
mark_page_active。

通常情况下地址空间中的页面要位于基树(radix tree)中,由page结构的->index成员来索引。该基树为每个
页面维护了PG_Dirty和PG_Writeback的信息,这样,设置了这些标志的页面可以通过基树很快 找到。

Dirty标签主要是由mpage_writeages──默认的->writepages方法──使用的,用来寻找那么需要调用
->writepages方法的页面。 如果未使用mpage_writepages函数(亦即,地址空间提供了自己定义的
->writepages方法),PAGECACHE_TAG_DIRTY标签就基本没用了。 write_inode_now和sync_inode函
数通过该标签,来检查->writepages方法是否成功把地址空间对象里的所有脏页都写出去了。
/* "PAGECACHE_TAG_DIRTY标签就基本没用了", 这句肯定是不对的 */

Writeback标签由 filemap*_wait* 和 sync_page* 等函数使用(通过wait_on_page_writeback_range),
以等待写回操作完成。 在等待的时候,会针对每个在写回的页面调用->sync_page方法(如果定义了)。

地址空间对象的操作可能会给页面附加一些信息,通常会使用'struct page'的'private'。 如果附加了这样
的信息,那么就应该设置页面的PG_Private标志,这样,各个VM子系统就会在相应的场合调用地址空
间对象的处理函数来处理这些附加信息。

地址空间对象是联系应用程序和磁盘存储的媒介。 数据是以页为单位,从存储读入地址空间的;再提供
给应用程序时,要么是拷贝该页中的数据到程序空间,要么是通过内存映射。同样的, 程序将 数据写入到地址空间中,
然后写回到磁盘存储--通常也是以页为单位,但实际上地址空间会的精确的控制写回操作大小。

读的过程很简单,只需要'readpage'方法;相对来说,写的过程要复杂的多,使用prepare_write/
commit_write或set_page_dirty来把数据写入到地址空间中,再使用writepage,sync_page和
writepages来把数据从地址空间中写入到磁盘存储上。

从地址空间中添加和删除页面,都必须得持有inode的i_mutex互斥锁。

当有数据写入到页面,就应设置该页面的PG_Dirty标志。 该标志会一直保持着,直到writepage请求把该页写回存
储--这会清除PG_Dirty标志,而设置PG_Writeback标志。 写回操作实际上是发生在PG_Dirty标志清除后的任意时刻。
当页成功写回到存储后,PG_Writeback标志会被清除。

Writeback使用了一个writeback_control结构。


address_space_operations结构
----------------------------

该结构描述了 VFS 如何把文件映射到 page cache 中。 例如在 2.6.22 内核中, 它有以下成
员:

struct address_space_operations {
int (*writepage)(struct page *page, struct writeback_control *wbc);
int (*readpage)(struct file *, struct page *);
int (*sync_page)(struct page *);
int (*writepages)(struct address_space *, struct writeback_control *);
int (*set_page_dirty)(struct page *page);
int (*readpages)(struct file *filp, struct address_space *mapping,
struct list_head *pages, unsigned nr_pages);
int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
sector_t (*bmap)(struct address_space *, sector_t);
int (*invalidatepage) (struct page *, unsigned long);
int (*releasepage) (struct page *, int);
ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,
loff_t offset, unsigned long nr_segs);
struct page* (*get_xip_page)(struct address_space *, sector_t,
int);
/* migrate the contents of a page to the specified target */
int (*migratepage) (struct page *, struct page *);
int (*launder_page) (struct page *);
};

writepage: 由 VM 用来把脏页写到磁盘上。 这可能是为了数据的完整性(亦即,'sync'),
或者是为了释放内存(flush)。二者的区别见wbc->sync_mode。
writepage方法调用前应保证PG_Dirty标志已清除,PageLocaked已设置。该方法执行回写操作,
并在写操作执行前设置PG_Writeback标志,清楚页面的PageLocaked标记。

如果wbc->syn_mode的值为WB_SYNC_NONE, ->writepage方法就不必费力的完成工作,
它可以选择容易写的其他页面(例如,由于内部依赖的问题)。 如果它决定不回写页面,则应
当返回AOP_WRITEPAGE_ACTIVE,以便VM不再对该页调用->writepage方法了。

更多细节可以参考"Locking"文件。

readpage: 由 VM 用来从磁盘上读取页面。
调用readpage方法前应保证在已经对页面加锁, 当读操作完成时会解锁该页,并将页面标记为最新。
如果出于某种原因,->readpage方法需要解锁该页,它倒也可以这么做,然后返回AOP_TRUNCATED_PAGE。
这种情况下,该页将被重定位、重新加锁,都成功了后VFS会重新调用->readpage方法。

sync_page: VM 调用它来通知存储设备, 执行所有与某一页有关的正等待的 I/O 操作。 同一
address_space 中的其他页的 I/O 也可能被执行。

该方法是可选的,只有在等待writeback完成时才会调用,而且只针对那些设置了PG_Writeback的页调用。

writepages; VM 调用, 把该 address_space中的"脏"页回写到磁盘。 如果wbc->sync_mode
为WBC_SYNC_ALL,那么writeback_control将指定需要回写页面的范围。如果是WBC_SYNC_NONE,
就要给一个nr_to_write值, writepages应该尽可能的回写这么多个的页面到存储设备。
如果未提供->writepages方法,那么就使用mpage_writepages函数。它会选择地址空间
对象中那些标记为脏的页面,并传给->write_page方法。
/* 没提供writepages, VFS会使用generic_writepages */

set_page_dirty: VM调用它把某页标记为脏。
如果一个地址空间对象给页面附加了数据,且当页面变脏时那些数据需要更新,那么就需要
该本方法了。 例如,当一个内存映射的页被修改了,本方法就被调用。它需要设置页的PageDirty标志,
在基树中为该页加上PAGECACHE_TAG_DIRTY标签。

readpages: VM 调用, 从磁盘上读取跟该 address_space 有关的页。
基本可以说本方法就是readpage方法的向量化版本(a vector version of readpage)。
readpage方法只读一个页面,而本方法读多个页面。
readpages方法只用与预读(read-ahead),所以其错误可以被忽略,不管什么地方出错
了,只管放弃,没问题的。

prepare_write: 在通常的写操作路径中, VM调用它来设置跟页有关的写请求。 它表示页面中的一定区域将会
写入数据。 地址空间应该检查该写操作请求是否能成功完成, 如果需要的话,分配磁盘空间, 做其他需要的处理。
如果改写操作只修改一个文件系统中数据块中的部分数据, 这些被部分修改的数据块中的数据需要预先读入 (如果还
没有被读入)。 只有这样, 这些被部分修改的数据块才能正确的更新。 和readpage一样, 如果prepare_write
需要解锁页面, 它也可以这么做,然后返回AOP_TRUNCATED_PAGE.

提示: 不要将页面标记为最新, 除非页面真的为最新。 如果标记为最新, 其他的并发的读操作可能会读到错误的数据。


commit_write: 如果prepare_write成功, 写入的数据将拷贝到页面中, 然后commit_write会被调用。 一般情况下,
它会更新文件的大小、 将inode标记为脏、 作一些相关的处理。 如果可能, 该函数应该避免返回错误, 错误应该在 prepare_write发现


bmap: VFS 调用它, 把对象内的逻辑块号映射到物理块号上。 传统的 FIBMAP ioctl 系统调和激活交换文件时
使用该函数。 为了将页面换出到交换文件中, 交换文件必须直接映射到块设备上。 Swap子系统读写操作不会通过
文件系统, 而是使用bmap将文件的逻辑块映射到设备的物理块上,读写操作时直接使用物理块号。

invalidatepage: 如果一个页面有PagePrivate标记, 当要把页面中部分或全部数据从地址空间移出时, 需要调用该方法。在截断(truncate)地址空间时和使整个地址空间无效时会将页面数据从地址空间移出(在后一种情况, 传递给该函数offset
参数的值是0)。 页面的私有数据也应该依照截断(truncate)的情况更新, 如果offset参数的值为0, 则应该释放私有数据。
这是因为, 当offset为0时, 整个页面的数据都会被丢弃, 这有点象releasepage的功能, 但在这种情况下, invalidatepage必须成功的释放页面。

releasepage: 该方法在想要释放一个有PagePrivate标记的页时被调用。->releasepage应该移除页面的私有数据, 清除PagePrivate标记。 它也可能会将页面从地址空间中移出。 如果出于某种原因, 操作失败, 则返回0。 该函数在以下两种情况下被调用。 第一种情况:虚拟内存子系统(VM)找到一个干净(clean)的页面 , 并且该页面没有活动用户使用时, 虚拟内存子系统会尝试回收该页面。如果releasepage中的操作都成功, 页面会从地址空间中移出, 变为空闲。第二中情况: 收到将地址空间中的部分或全部页面变为无效的请求时。 invalidate_inode_pages2处理这种请求, 它可能由 fadvice(2)
系统调用发出, 也可能由一些文件系统显示的发出,比如nfs, 9fs (当文件系统发现地址空间中的数据已经过期)。 如果是文件系统发出这种请求, 并且所有指定的页面都要变为无效, 则需要通过->releasepage来保证页面都要变为无效。 当某些页面的私有数据不能清除时, ->releasepage可能会清除页面的Update标记。

direct_IO: VM 为直接 I/O 的读/写操作调用它。 直接 I/O是指跳过页面缓存(page cache), 直接在用程序地址空间和存储之间传输数据。

get_xip_page: VM 调用它, 把块号转换成页。 在相关的文件系统卸载之前, 该页保持有效。 那些想实现「适当执行」
(execute-in-place,XIP)的文件系统需要提供该方法的实现。在 fs/ext2/xip.c 文件中可以找到例子。
/* 感觉将XIP翻译成适当执行不是很贴切 */

migrate_page: 用于压缩内存的使用。 如果虚拟内存子系统(VM)希望重定位一个页面(比如一个内存即将失效), 它将传递新页面和被替换的页面的描述符 给该函数。 该函数应该将被替换的页面的私有数据转移到新页面上, 并且更新所有对被替换的页面的引用。
launder_page: 释放一个页面之间调用, 它将脏页面回写到存储设备。 它可以防止页面再次被标记为脏, 整个操作在持有页面锁的情况下进行。



文件对象(The File Object)
=========================

一个文件对象, 代表了进程的一个打开文件。

file_operations 结构
--------------------

该结构描述了 VFS 如何操作一个打开的文件。 例如在内核 2.6.13 中, 它有如下成员:

struct file_operations {
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t,
loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned
long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long,
loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long,
loff_t *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void
*);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *,
int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned
long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
};

除非特别说明, 否则这些方法都可以在不加锁的情况下调用。

llseek: VFS 想移动文件的读写位置指针时调用

read: 由 read(2)及其它相关系统调用调用

aio_read: 由 io_submit(2)及其他异步 I/O 操作调用

write: 由 write(2)及相关系统调用调用

aio_write: 由 io_submit(2)及其他异步 I/O 操作调用

readdir: VFS 想读取目录内容时调用

poll: VFS 调用。 调用的时机为: 当进程想检查某一文件上是否出现特定特征, 并且
(可选地)阻塞, 直到所等待特征出现。 给 select(2)和 poll(2)系统调用使用。

ioctl: 由 ioctl(2)调用

unlocked_ioctl: 由 ioctl(2)调用。 那些并不获取 BKL(译注:Big Kernel Lock, 大内核
锁,一种同一时刻只允许一个 CPU 在内核态、允许递归获取的锁,详见 lib/kernel_lock.c 代码注释)

compat_ioctl: 由 ioctl(2)调用。 调用时机为: 在 64 位内核上执行 32 位的 ioctl 系统调用。

mmap: 由 mmap(2)调用

open: 当 VFS 想打开一个 inode 时调用。 VFS 打开文件时, 先创建一个新的 struct file, 然后
调用该 file 结构的 open 方法。 嗯, 你可能会想:open 方法为什么不放在
struct inode_operations 里呢? 可能这种想法也有道理, 但我觉得象内核这样设计, 可以简
化文件系统的实现。 并且, 该 open()方法适合初始化 file 结构的"private_data"成员──如
果你想让该成员指向某个设备的数据结构。

flush: 由 close(2)调用, 来冲刷文件。

release: 当最后一个对 file 结构的指向也被关闭时调用

fsync: 由 fsync(2)调用

fasync: 由 fcntl(2)调用, 前提是该 file 的异步(非阻塞)模式已被激活

lock: 由带 F_GETLK,F_SETLK 和 F_SETLKW 命令的 fcntl(2)调用

readv: 由 readv(2)调用

writev: 由 writev(2)调用

sendfile: 由 sendfile(2)调用

get_unmapped_aera: 由 mmap(2)调用

check_flags: 由带 F_SETFL 命令的 fcntl(2)调用

dir_notify: 由带 F_NOTIFY 命令的 fcntl(2)调用

flock: 由 flock(2)调用

注意, 文件操作的这些方法, 是由其 inode 所在的分区的文件系统来实现的。 当打开一
个设备文件(字符设备或块设备特殊文件)时, 多数文件系统会调用 VFS 的一些例程来定
位该设备所属的驱动程序信息。 这些例程将用设备驱动程序中实现的的 file operations
替换文件系统中实现的的那个, 并继续调用新的 open 方法, 这是「为什么打开文件系统
中的设备文件,会最终导致调用设备驱动中的 open()方法」的原因。


目录项 Cache(Directory Entry Cache, dcache)
===========================================

dentry_operations 结构
----------------------

该结构描述了一个文件系统如何重载标准的 dentry 操作集。 Dentry 和 dcache 是 VFS 和具
体文件系统实现的概念, 设备驱动程序就和他们不搭边了。 这些方法可以被置为 NULL,
因为它们是可选的, 如果你不实现, VFS 就使用默认的。 例如 2.6.13 内核中, 该结构有
如下成员:

struct dentry_operations {
int (*d_revalidate)(struct dentry *, struct nameidata *);
int (*d_hash) (struct dentry *, struct qstr *);
int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
int (*d_delete)(struct dentry *);
void (*d_release)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
};

d_revalidate: 当 VFS 想重新使一个 dentry 有效时调用, 这一般发生在某次查找中在dcache
中找到了 dentry。 多数文件系统会把这个方法置为 NULL, 因为它们留在 dache 中的 dentry 还是有效的。

d_hash: 当 VFS 把一个 dentry 加入到哈希表中时调用

d_compare: 比较两个 dentry 时调用

d_delete: 当 dentry 的最后一个引用被删除时调用。 这意味着没有人在使用这个 dentry
了, 但它依然是有效的, 并且在 dcache 中。

d_release: 当 dentry 真正被销毁时调用。

d_input: 当一个 denrty 失去了它所属的 inode 时(正好在 dentry 被销毁之前)调用。
如果这个方法置为 NULL, VFS 就会调用 iput(); 如果你自己定义了该方法, 必须在自己
的实现中调用 iput()。

每个 dentry 含有一个指向父 dentry 的指针, 还有一个所有子 dentries 的哈希链表。 基
本上, 子 dentries 就象目录中的文件一样。



Dcache API
----------

内核中定义了许多函数, 供文件系统来操作 dentries:

dget: 打开一个已存在的 dentry 的句柄(在这里,只是增加引用计数而已)

dput: 关闭 dentry 的一个句柄(减少引用计数)。 如果引用计数减到了 0, 就调用

d_delete 方法, 把该 dentry 置入「未使用」队列。 「把 dentry 置入未使用队列」意味着,
如果内存不够用了, 将遍历「未使用队列」并调用 deallocates 方法来销毁 dentries,
以腾出内存。 如果 dentry 已经是「unhashed」(译注:指不在父 dentry 的 hash 链中)且
引用计数为 0, 这时候调用 d_delete 方法然后销毁它。

d_drop: 该方法把一个 dentry 从它的父 dentry 的 hash 链中脱链。 如果它的引用计数变为
0, 随后的调用 dput()将销毁该 dentry。

d_delete: 删除一个 dentry。 如果该 dentry 没有其他的引用了, 则变为「负的 dentry」
并调用 d_iput()方法; 如果还有其他引用, 就不走这些而调用 d_drop()。

d_add: 把一个 dentry 放入它的父 dentry 的哈希链表, 并调用 d_instantiate()。

d_instantiate: 把一个 dentry 链入 inode 的「别名哈希链表」并更新 d_inode 域。 inode
结构的i_count 域应该被设置/增加。 如果 dentry 不和任何 inode 关联, 则它就是一个
「负的 dentry」。 该函数一般在为负的 dentry 新创建一个 inode 时调用。

d_lookup: 给出父 dentry 和名字等信息, 在 dcache 哈希表中查找一个 dentry。 如果找到,
增加其引用计数并返回其地址。 调用者在使用完毕时, 必须调用 d_put()方法来释放
dentry。

关于访问 dentry 时加锁的更多信息, 请参考文档 Documentation/filesystems/dentry-locking.txt。

Wednesday, September 2, 2009

ubuntu上使用scim和gajim技巧两则

Howto run Gajim with root privileges

Gajim 0.12.1 says that he can’t run with root privileges. This is new feature that appeared in Fedora 10. In earlier versions it was normaly running under root. Anyway let’s turn off this feature in case that you need to run Gajim under root.

Open /usr/bin/gajim with your favorite text editor, find 24th line and just comment out this part of code:

if test $(id -u) -eq 0; then
echo "You must not launch Gajim as root, it is INSECURE"
exit 1
fi

After commenting it should look like

#if test $(id -u) -eq 0; then
# echo "You must not launch Gajim as root, it is INSECURE"
# exit 1
#fi

Save file. Thats it.

-------------------------------------------------------------------------------

root 下的中文显示问题解决方法如下

9.04中选择root进入系统,无法显示中文桌面,而且选择中文后也没有作用, 而在/etc/environment中,"LANGUAGE=zh_CN:zh:en_US:en"是不用修改的,改后有没问题还不清楚。

解决方法:

在root下打开隐藏文件.profile。
修改root下隐藏文件.profile,把最后的两行  LANG=C 和LANGUAGE=C改为如下所示后重启。
以下是修改过的。

# ~/.profile: executed by Bourne-compatible login shells.

if [ "$BASH" ]; then
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
fi

mesg n
# Installed by Debian Installer:
# no localization for root because zh_CN.UTF-8
# cannot be properly displayed at the Linux console
LANG=C #修改此行为:LANG="zh_CN.UTF-8"
LANGUAGE=C #修改此行为:LANGUAGE="zh_CN:zh"

然后重启,选择以root进入系统,即可显示中文。

Labels

Followers