级别: 初级 杨爱 林, Linux 研发工程师
2005 年 9 月 01 日 本篇文章主要介绍了 Linux 平台下的虚拟文件系统概念以及 KDE 桌面环境下 KIO 机制。通过对这些概念的掌握,最后详细分析并实现一个"我的共享"应用的形式来展示 KIO强大的网络透明性和虚拟文件系统的功能。
一. 虚拟文件系统
1. Linux 文件系统结构
Linux 系统的文件系统在架构上跟 UNIX 文件系统是一样的。它们都是采用反转过来的树结构。最上层是根目录,也就是 "/"。根目录底下可以是目录也可以是文件,目录里也可以包含子目录,文件等。在Windows系统中,如果有两个分区,一个叫C分区,另一个叫D分区,当我们要用到D分区时,只要打"D:"就可以了。但是在Linux里是不能这样。要去读取另一个分区的内容必须要经过mount的动作。如:
mount -t ext2 /dev/hda3 /mnt/home
|
就会将硬盘第三个分区挂在 /mnt/home 这个目录底下。Mount 完之后,/mnt/home 原本的内容会看不到,只会看到 hda3 里的内容。其中 /mnt/home 称为 hda3 的挂载点。而/mnt/home 这个目录则是被 hda3 所覆盖。经过 mount 以后,我们就可以通过 /mnt/home去读取 hda3 的内容,就好象hda3的内容本来就放在/mnt/home底下一样。不管怎么挂载,Linux会保持其文件系统为一个树的形状。这样mount下去,我们很容易可以推想到,从根目录开始的这个tree很有可能包含多种文件系统,可能挂在/mnt上的是Ext2文件系统,挂在/home上的是FAT,而挂在/cdrom上的则是iso9660文件系统。我们知道,当使用者去读取这些目录里的内容时,用户或应用程序本身是不用去考虑这个目录挂的文件系统是那种类型的。同是使用者也不会感到在使用这些文件系统时有什么本质的不同。而就程序员的观点来看,也不会说去读/mnt/home里的文件和去读/home里的文件要加不同的参数。而这种方便性正是Linux利用了VFS来做到。
2. 虚拟文件系统架构
Linux文件系统分为三个部分,第一部分叫Virtual File System Switch,简称VFS。这是Linux文件系统对外的接口。任何要使用文件系统的程序都必须经由这层接口来使用它。另外两部分是属于文件系统的内部结构。其中一个是cache,另一个就是真正最底层的文件系统,像Ext2,VFAT等文件系统,在运行程序时我们会知道Kernel使用文件系统时都是经过VFS这层接口来使用。而使用者或程序设计师去读取一个文件的内容时,它不会因为这个文件位于不同的文件系统就需要使用不同的方式来读取。因为VFS已经帮我们做了。当我们要读取的文件位于CDROM时,VFS就自动帮我们把这个读取的要求交由iso9660文件系统来做,当我们要读取的文件在FAT里时,VFS则自动呼叫FAT的函数来帮我们做到。当然,有需要时,VFS也可以会直接透过Disk driver去读取信息。但是当我们要求读写文件时,iso9660或FAT文件系统不会直接透过driver去读写。就像PC上除了内存之外,还有一层的cache来加快速度。在Linux文件系统中其实也有一个Cache的机制以加快速度。叫做Buffer Cache。底层的文件系统要读写磁盘上的资源时都要经过Buffer Cache。如果资源在Buffer Cache里有的话,就直接读取,如果没有,才透过Buffer Cache要求driver去读写。除了Buffer Cache之外,其实,Linux文件系统里还有一个Cache,叫Directory Cache。使用它系统整体的速度就会往上提升。Directory Cache的功能就在此。Linux文件系统里还有一个Cache,叫Inode Cache。故名思义,它是针对Inode做的Cache。Directory Cache跟Inode Cache关系是很密切的。
3. 文件的表示
从使用者的观点来看,可以用文件的绝对路径来代表某个文件而不会出错。在 VFS 里,它并不是用路径来代表文件的。它是用一个叫节点(Inode)的东东来代表的。基本上文件系统里的每一个文件,系统都会给它一个 Inode,只要 Inode 不一样,就表示这二个文件不是同一个,如果两个文件的 Inode 一样,就表示它们是同一个文件。Inode是VFS所定义的,而我们知道 VFS 里包含了好几种的文件系统,并不是每一个文件系统都会有Inode的这种概念。就像 FAT,事实上它跟本没有所的 Inode 概念。但是当 VFS 要求 FAT 去读取某个文件时,它是把那个文件的Inode传给FAT去读。所以,在 VFS来讲,每一个文件都有其对应的Inode,但是在底层的文件系统并不是这种情形。因此,VFS跟底层的文件系统沟通也是经过一层的接口。比如说, VFS要open一个位于FAT的文件时,VFS 会配置一个 Inode,并把这个 Inode 传给 FAT,FAT 要负责填入一些信息到 Inode 里,必要时,也可以在 Inode 里加入自己所需要的信息。
二. KIO 机制
1. KDE
在谈 kio 机制之前,我们有必要对 KDE 做一个简单的介绍。
KDE是新一代透明的网络桌面环境,它的目标是为 Unix 工作站提供一个类似于 Mac OS 或者 Windows 9x/NT 的简单、易用的操作环境,它由一个窗口管理器、文件管理器、面板、控制中心以及其它组成,它已经发展成为一个成熟的桌面操作环境了,KDE 拥有大量的为 Unix 工作站开发的应用软件,办公套件 KOffice 等重量级软件,还有我们平时常用的应用软件,以及与 Windows 的"控制面板"类似的系统管理工具。更体贴用户的是,他们还推出了大量 GUI 设定软件,来帮助用户设置 Unix/Linux 上的服务器(如 Samba、电源管理等)。
KDE 是功能强大、模块化的桌面环境,KDE 3.4 更加直观、强大以及友好。它使 Linux用户享受到标准兼容性和大量新技术带来的快乐,像全功能的网络浏览器和网络管理器-KOnqueror;综合的办公套件- KOffice;同时,KDE3.4 也增强了可用性,像 KDE 扩展的桌面主题功能、配置功能以及新的 KDE 帮助中心。KDE 3.4 同样也为开发者提供了功能强大的新工具 - 来自 KPart 的 KDE 组件对象技术,来自 KIO 用于开发自由或者专有软件的 KDE 透明网络 I/O(输入/ 输出)体系结构。
2. KIO
桌面环境 KDE 提供了一个完整的异步虚拟文件系统库 KIO,使用 kio 可以用相同语法的扩展 URL 来访问资源,所有的文件操作都是通过外部的 kioslave 来完成。而且这些kioslave 是异步的。Kioslave 通过 Unix domain sockets 和 custom protocols 来与 the master process 通信。其中的 KIO 库自动执行 encoding/decoding 或者其他一些协议的实现,当他完成这些动作后,他通过 QT 的 signal 来通知应用程序,例如,在 KDE 桌面环境中已经实现的 kioslave:smb、system、tftp、ftp、file 等协议,对于这些协议或者说是 KIO 的使用,用户只要使用统一的 URL 就可以了,如在 URL 地址中输入
系统会根据其中的协议来调用相应的 KIOSLAVE 来处理对应的请求。
还比如,在 URL 地址输入框中输入:
系统会根据你输入的 URL 来判断这个协议要使用 file 协议,所以系统就会去调用 file协议相应的 kioslave 来处理这个请求,结果输出了该目录下的资源。
对于其他协议的使用方式都是一样的,只要开发人员已经实现并安装了该模块,用户就可以方便的使用该协议来访问具体的资源。如,我们已经在 KDE 环境下实现的"system 协议",那么我们就可以用给定的 URL 来访问该协议下的资源,例如我们在 URL 地址中输入
结果"system协议"相对应的kioslave 就会被调用来响应这种请求,最后结果如图所示,
如果我们从来没有实现某一个协议,而是使用了一个已存在的文件系统,那么我们也可以通过输入文件系统的名字来实现这个功能,但要注意,这是 konqeror 特有的功能,其实现原理也是通过 file 协议来工作的,比如,我们在 konqeror 的地址输入框中输入:
其结果就是"我的电脑"目录下的内容,它实际上是一个连接的转换。切记他不是一个协议。如果我们输入如下 URL
其结果如图所示:
使用 KIO 编程有如下好处:
1) 对文件的操作变的非常简单,KIO 允许程序员下载和上传整个文件和对文件的一些简单操作,但是没有提供所有的象 Linux 系统提供的那么多的功能强大的函数接口。对每个文件的操作需要一个额外的进程,kio 使用起来没有线程好。
2) KIO 网络透明,KIO 网络透明为以下特性提供了无缝支持:GNU/Linux、NFS 共享、MS Windows SMB 共享、HTTP 页面、FTP 目录和 LDAP 目录的文件访问和浏览。KDE 文件结构的模块化、插件化特性让给 KDE 添加额外的协议更加简单(比如 IPX 或 WebDAV 协议),这些协议自动对所有的 KDE 程序可用。
KIO 是 KDE 体系结构中的一部分,它提供了访问文件,web页面的能力。和其他资源的一致性接口应用程序使用这种接口来正确操作远程文件就象是操作本地的文件一样方便。KIO slaves 是提供这种方便性的核心程序。
3) List的行为
当涉及到一个目录的 URL 请求时(比如,以"/"结尾的请求),slave 的行为 ListDir被激活,如果 KIO 不能决定操作对象是一个文件还是目录时,slave 的另一个行为 stat()传递该信息,ListDir() 返回所有的目录 entries 给应用程序,为了降低这种 overhead 的沟通手段,这些 entries 被缓存起来。
目录的信息会以 UDSEntries 的表现形式来表达,这里 UDS 代表通用目录服务,尽管这个名字暗示着灵活。但一个 UDSEntry 包含了一个 UDSAtoms 列表,这些所列举的内容是由 UDS_ID 号、类型和相应的值组成的。
下面的命令将一个目录添加到给定的列表中:UDSEntry entry;
UDSAtom atom;
atom.m_uds = KIO::UDS_NAME;
atom.m_str = "FileName";
entry.append(atom);
|
从应用程序的角度看,在 KfileItem 的文件视图里列出每个文件或是对话框都要求一个UDSEntry。列出一个目录的内容,我们使用类似的方法创建一个ListJob来列举文件或目录并且使用自定义的 slots 与他们的 signal 连接。
data( KIO::Job*, const KIO::UDSEntryList& )
|
函数 data 在给定的目录里包含了所有的文件和子目录,最后这个目录被传递给UDSEntryList。
列举一个目录是经常要操作的工作,UDSEntry 的常用 API 使用起来不是很方便,KDE 应用程序可以使用类 KfileItem, KfileItem 提供了一个构造函数,通过这个构造函数可以获取 UDSEntry 的所有属性,这里也有一个 accessor 方法,他可以返回 URL、协议类型、文件大小、访问权限或者是关联小图表,除此之外,函数 run() 可以执行一个文件,也可以启动一个关联程序,
应用程序开发人员都不希望通过使用类 KdirLster 来操作 UDSEntry,而是传递 URL 的方法来列举,打开并且连接一个槽到一个信号 newItems( const KFileItemList& ),这个列举函数将会立即收到一个 KfileItems 列表,KdirLister 的另外一个优点是自动通知已被列举的目录的改变状况,比如说,文件的删除,重命名,添加等。
4) KIO Actions
下面我们将一些常用的 KIO 函数做一个简单的介绍:
设置所有连接 host 的信息,所有以下的行为都依赖这些信息,通常,这个函数将立即连接 host,而那些行为函数不是必需的。
当数据到达时,我们可以通过调用函数 data() 读文件并返回数据块,当然数据块可以是任意大小。
写文件,如果是块数据,可以通过调用readDate()来读。
找出文件或目录的属性,这些可用的信息与所使用的协议有关系,最后通过 Atoms(UDSEntry)列表返回。
通过 mimetype 可以发现URL或文件的协议类型。进而调用相应的处理程序。
读出目录内容,通过 listEntry() 函数可以返回 entries,为了能够给用户进程一个feedback,我们可以通过函数totalFiles()的反复调用来通知已知目录的文件个数。
创建一个目录
重命名一个对象
创建一个符号连接
设置一个对象的访问权限
拷贝一个文件,如果是目录文件,将分成几个过程来拷贝。
当删除一个对象时,一个标志符号会作为一个文件或目录是否被删除而被传递,如果是目录,就假定他是空目录。
为专门的操作提供协议,比如说,当挂载文件系统时,有相应的文件操作协议在工作。
三.KIO 应用示例分析
我们都知道,在使用 Linux 桌面环境时,浏览文件资源首选的工具就是 Konqeror,其次是打开,保存对话框,其中实现了资源共享,资源管理等功能,如文件或目录的共享。那么对于最终用户来说,可能对浏览和管理共享资源不是很方便,比如说,没有一个文件目录来浏览整个系统的共享资源,并且在文件系统中也不存在一个"我的共享"目录,但是我们又不想在文件系统中创建一个真正的我的共享目录节点,那么要使用我的共享来访问共享资源,一个选择就是 KIO。我们把它叫做 kio_myshare,其中要实现的模块称为 kioslave。KDE 桌面环境提供了这种实现接口,如,我们可以用开发工具 Kdevelop 来生成一个缺省的kioslave 工程项目,然后根据需要做相应的功能实现。Kdevelop 开发 kioslave 环境如图所示:
根据开发环境的向导我们可以生成一个工程文件,然后我们实现如下函数:
class kio_myshareProtocol : public KIO::SlaveBase
{
private:
KURL myshare_current_url;
struct stat *myshare_file;
public:
kio_myshareProtocol(const QCString &pool_socket, const QCString &app_socket);
virtual ~kio_myshareProtocol();
virtual void listDir( const KURL& url );
virtual void stat( const KURL& url );
protected:
KURL kio_myshareProtocol::checkURL(const KURL& kurl) const
};
|
上面这个类是一个最基本的kioslave实现的类,它继承了KIO的基类SlaveBase。
kio_myshareProtocol::kio_myshareProtocol(const QCString &pool_socket, const QCString &app_socket)
: SlaveBase("kio_myshare", pool_socket, app_socket)
{
kdDebug() << "kio_myshareProtocol::kio_myshareProtocol()" << endl;
}
|
kio_myshareProtocol是构造函数,在其中可以实现一些初始化工作,在kioslave的具体实现过程中,通常不做任何其它的工作。
kio_myshareProtocol::~kio_myshareProtocol()
{
kdDebug() << "kio_myshareProtocol::~kio_myshareProtocol()" << endl;
}
|
析构函数。
KURL kio_myshareProtocol::checkURL(const KURL& kurl) const {
QString surl = kurl.url();
if (surl.startsWith("myshare:/")) {
if (surl.length() == 9) // just the above
return kurl; // unchanged
if (surl.at(9) != '/') {
surl = "myshare://" + surl.mid(9);
kdDebug() << "checkURL return1 " << surl << " "
<<KURL(surl) << endl;
return KURL(surl);
}
}
// no emtpy path
QString path = kurl.path();
if (path.isEmpty())
{
KURL url(kurl);
url.setPath("/");
kdDebug() << "checkURL return3 " << url << endl;
return url;
}
kdDebug() << "checkURL return3 " << kurl << endl;
return kurl;
}
|
这个函数主要用来检查输入的URL是否正确,然后做进一步的重定向工作 。其前提条件是你必须输入关键字:myshare:/。
void kio_myshareProtocol::listDir( const KURL& url )
{
KURL url = checkURL(kurl);
if (url != kurl)
{
redirection(url);
finished();
return;
}
myshare_current_url = kurl;
UDSEntry udsentry;
UDSAtom atom;
if (( kurl.url() == QString("myshare:/") ) /*&& ( dirfd < 0 )*/){
QStringList list = Samba::self()->listShare();
QString path;
for ( QStringList::Iterator it = list.begin(); it != list.end();++it){
path = Samba::self()->getUrlOfShare(*it);
if( stat(path,&myshare_stat)==-1)
{
kdDebug() << "stat err return " << endl;
exit(1);
}
for(i=0,it.toFirst();i<count;++it,++i)
{
QFileInfo* pList=*it;
QString strName=pList->fileName();
//kdDebug(7101)<<"file:"<<strName<<endl;
if(strName.isEmpty() || strName=="." || strName=="..")
continue;
struct stat st;
if(lstat(pList->absFilePath().local8Bit(),&st))
continue;
entry.clear();
UDSAtom atom;
atom.m_uds = KIO::UDS_NAME;
atom.m_str = strName;
entry.append( atom );
atom.m_uds = KIO::UDS_SIZE;
atom.m_long = pList->size();
entry.append(atom);
atom.m_uds = KIO::UDS_MODIFICATION_TIME;
atom.m_long = st.st_mtime;
entry.append( atom );
atom.m_uds = KIO::UDS_ACCESS;
atom.m_long=st.st_mode;
entry.append( atom );
atom.m_uds = KIO::UDS_FILE_TYPE;
atom.m_long =(pList->isDir()?S_IFDIR:S_IFREG);
entry.append( atom );
nTotal++;
listEntry(entry,false);
}
totalSize(nTotal);
listEntry(entry,true);
finished();
}
|
这个kio行为是kioslave实现过程中关键的一种行为,在这里特别想说明的就是KDE环境中已经实现了共享机制smba,因此我们只要去读smba下的共享文件就可以获取系统中所有已经共享的资源,从而实现了kio_myshare的直接数据源,然后根据所获得的文件和目录信息。得到kioslave中想要的UDSAtom。
void kio_myshareProtocol::stat(const KURL& url)
{
KURL url(_url);
QString path(KURL::decode_string(url.path()));
if ((path.isEmpty()) || (path=="/"))
{
url.setPath("/a/");
redirection(url);
finished();
return;
};
struct stat st;
UDSEntry entry;
entry.clear();
UDSAtom atom;
atom.m_uds = KIO::UDS_NAME;
atom.m_str = KURL::decode_string(url.filename());
entry.append( atom );
//kdDebug(7101)<<"size:::"<<st.st_size<<endl;
atom.m_uds = KIO::UDS_SIZE;
atom.m_long =st.st_size;
entry.append(atom);
atom.m_uds = KIO::UDS_MODIFICATION_TIME;
atom.m_long =st.st_mtime;
entry.append( atom );
atom.m_uds = KIO::UDS_ACCESS;
atom.m_long=st.st_mode;
entry.append( atom );
atom.m_uds = KIO::UDS_FILE_TYPE;
atom.m_long =(S_ISDIR(st.st_mode)?S_IFDIR:S_IFREG);
entry.append( atom );
statEntry( entry );
finished();
}
|
使用这个KIO行为可以获取所有的UDSAtom属性,也就是我们通常要求的文件类型,文件大小,创建日期等。
如果我们完成了以上几个KIO行为,那我们就可以实现一个简单的myshare协议,从而可以在konqeror或者是打开,保存对话框中的地址栏中输入:
myshare:/
就可以访问到系统中所有已经共享的文件资源,最终以文件系统的形式展现在用户面前。
不过,我们实现myshare协议以后,还要在系统中注册它,否则,系统不会认识这种协议,注册的方法就是把如下格式的配置文件安装到系统指定的位置,只有这样,系统才能够根据用户的请求找到响应的kioslave。Myshare协议的配置文件如下:
[Protocol]
exec=kio_myshare
protocol=myshare
input=none
output=filesystem
listing=Name,Size,Type,Date,Access,Owner,Group,
reading=true
writing=true
deleting=true
Icon=mime_empty
Description=A kioslave for myshare
|
在作者的系统中,这个配置文件放到目录/usr/share/services下。
四. 小结
通过以上对虚拟文件系统的浅层的描述,我们能够理解它并且为进一步学习KIO编程有个初步的知识框架,除此之外,我们对KDE环境下KIO编程进行了深入的探讨,尤其是对kio
下的常用action做了详细的说明,从而为编写kioslave打下了坚实的基础,当然,通过kio_myshare的实现分析,我们已经掌握了kio编程的基础知识,但是对于kio编程的更多内容我们还需努力。
关于作者  | |  | 杨爱林,信息管理学士,毕业与中国农业大学信息管理与信息系统专业,目前主要从事 Linux的开发和性能测试工作。 |
对本文的评价
|