声振论坛

 找回密码
 我要加入

QQ登录

只需一步,快速开始

查看: 2362|回复: 1

[其他相关] 文件系统和不同格式文件转换编程(转贴)

[复制链接]
发表于 2007-3-4 04:31 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?我要加入

x
一、Linux 的文件系统概述
 
   Linux 的一个最重要特点就是它支持许多不同的文件系统。这使Linux 非常灵活,能够与许多其他的操作系统共存。到目前为止,Linux 共支持1 5 种文件系统:ext、ext2xia、minix、umsdos、msdos、vfat、proc、smb、ncp、iso9660、sysv、 hpfs、affs和ufs。无疑随着时间的推移,Linux支持的文件系统数还会增加。
   正如其它的UNIX操作系统一样,Linux系统可用的独立文件系统不是通过设备标识来访问的,而是把它们链接到一个单独的树形层次结构中。该树形层次结构把文件系统表示成一个整个的独立实体。Linux 以装配的形式把每个新的文件系统加入到这个单独的文件系统树中。无论什么类型的文件系统,都被装配到某个目录上,由被装配的文件系统的文件覆盖该目录原有的内容。该个目录被称为装配目录或装配点。在文件系统卸载时,装配目录中原有的文件才会显露出来。
  在硬盘初始化时,硬盘上的分区结构把物理硬盘化分成若干个逻辑分区。每个分区可以包含一个像EXT2那样的一个独立的文件系统。文件系统用目录将文件按照逻辑层次结构组织起来。目录是记录在物理设备块中的软链接信息。包含文件系统的设备被称为块设备。例如IDE硬盘分区/dev/hda1-系统中第一个 IDE硬盘驱动器的第一个分区,就是一个块设备。
   Linux文件系统把这些块设备当作简单的线性块的集合,它不知道也不在意下层物理硬盘的实际结构。每个块设备驱动程序的任务就是把系统读该设备上某一块的请求映射成对本设备有意义的术语,如该块所在的磁道、扇区或柱面号。无论文件系统存在在何种设备上,它都可以按相同的方式进行操作。而且在使用文件系统时,由不同的硬件控制器控制的不同物理介质上的不同文件系统对系统用户是透明的。文件系统既可能在本地系统上的,也可能是通过网络链接装配的远程文件系统。
  文件是数据的集合,一个文件系统不仅包括该文件系统中所有文件的数据,还包括文件系统的结构信息。它记录下所有Linux用户和进程当作文件看待的信息、目录软链接信息以及文件保护信息等等。而且文件系统必需保证这些信息的安全性,因为操作系统的基本完整性就取决于它的文件系统。没有人会使用一个随时会丢失数据和文件的操作系统。由于现代的数据库系统需要更大的文件长度,在1993年几乎是为LINUX度身定做的扩展文件系统-EXT2诞生了。在EXT文件系统加入到Linux中时,Linux系统发生了一个重大的发展。真实文件系统从操作系统中分离出来,而由一个接口层提供的真实文件系统的系统服务被称为虚拟文件系统 (VFS)。VFS使得Linux可以支持许多种不同的文件系统,而这些文件系统都向VFS提供相同的软件接口。由于所有的Linux文件系统的细节都是由软件进行转换的,所有对Linux系统的其余部分和在系统中运行的程序来说,这些文件系统是完全相同的。Linux的虚拟文件系统层使得你可以同时透明地装配很多不同的文件系统。
  实现Linux虚拟文件系统要使得它对文件的访问要尽可能地快、尽可能地高效,而且一定要确保文件和数据的正确性。这两个要求彼此是不对称的。在每个文件系统被装配使用后,Linux的VFS会在内存中缓存来自于这些文件系统的信息。因此由于对文件或目录的创建、写、删除操作而改变了Linux缓存中的数据时,对文件系统的更新操作要格外小心。如果你能在运行的内核中看到文件系统的数据结构,就会看到那些文件系统读/写的数据块。代表被访问的文件和目录的数据结构可能被创建或删除,而设备驱动程序不停地工作,读取数据、保存数据。这些缓存中最重要的一个是缓冲区缓存,它把独立文件系统访问下层块设备的方法集成起来。每个被访问的块都被放到缓冲区缓存中,并根据它们的状态放在相应的队列中。缓冲区缓存不仅缓存数据缓冲区,它还有助于管理块设备驱动程序的异步接口。

二、LINUX文件系统代码的位置

   EXT2文件系统的代码都在fs/ext2/目录下,其数据结构定义在include/linux/ex2_fs.h、ext2_fs_i.h和 ext2_fs_sb.h中;虚文件系统(Virtual File System)数据结构在include/linux/fs.h中,代码在fs/*中;缓冲区缓存代码在fs/buffer.c中。

三、与文件的输入/输出有关的函数

3.1 引言

   本节开始讨论LINUX系统,先说明常用的文件I/O函数:例如打开文件、读文件、写文件等等。
   大多数LINUX文件I/O只需用到5个函数:open、read、write、lseek 以及close。然后说明不同缓存器长度对read 和write函数的影响。本节所说明的函数经常被称之为不带缓存的I/O(unbuffered I/O)。不带缓存指的是每个read 和write都调用内核中的一个系统调用。这些不带缓存的I/O函数不是ANSI C 的组成部分,但是是POSIX.1和XPG3的组成部分。只要涉及在多个进程间共享资源,原子操作的概念就变成非常重要。我们将通过文件I/O和传送给 open函数的参数来讨论此概念。并进一步讨论在多个进程间如何共享文件,并涉及内核的有关数据结构。在讨论了这些特征后,将说明dup、fcntl和 ioctl函数。

3.2 文件描述符
 
  对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。按照惯例,UNIX shell使文件描述符0与进程的标准输入相结合,文件描述符1与标准输出相结合,文件描述符2与标准出错输出相结合。这是UNIX shell以及很多应用程序使用的惯例,而与内核无关。尽管如此,如果不遵照这种惯例,那么很多LINUX应用程序就不能工作。在POSIX.1应用程序中,幻数0、1、2应被代换成符号常数STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO。这些常数都定义在头文 <unistd.h>中。文件描述符的范围是0~OPEN_MAX(见表2-7)。早期的LINUX版本采用的上限值是19(允许每个进程打开20个文件),现在很多系统则将其增加至63 。

3.3 open 函数

   调用open函数可以打开或创建一个文件。例如:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname,int oflag,.../*,mode_t mode*/) ;
返回:若成功为文件描述符,若出错为-1

   我们将第三个参数写为. . .,这是ANSI C 说明余下参数的数目和类型可以变化的方法。对于open 函数而言,仅当创建新文件时才使用第三个参数。在函数原型中此参数放置在注释中。pathname是要打开或创建的文件的名字。oflag参数可用来说明此函数的多个选择项。用下列一个或多个常数进行或运算构成oflag参数(这些常数定义在<fcntl.h>头文件中):
o O_RDONLY 只读打开。
o O_WRONLY 只写打开。
o O_RDWR 读、写打开。

3.4 creat 函数

   creat函数用来创建一个新文件。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char *pathname, mode_t mode) ;
返回:若成功为只写打开的文件描述符,若出错为-1

   此函数等效于:open(pathname,O_WRONLY |O_CREAT|O_TRUNC,mode) ;在早期的UNIX版本中,open的第二个参数只能是0、1或2。没有办法打开一个尚未存在的文件,因此需要另一个系统调用creat以创建新文件。现在,open函数提供了选择项O_CREAT和O_TRUNC,于是也就不再需要creat函数了。
   creat的一个不足之处是它以只写方式打开所创建的文件。在提供open的新版本之前,如果要创建一个临时文件,并要先写该文件,然后又读该文件,则必须先调用creat,close,然后再调用open。现在则可用下列方式调用open:
   open(pathname,O_RDWR |O_CREAT|O_TRUNC, mode) ;

3.5 close 函数

   close函数用于关闭一个打开文件:
#include <unistd.h>
int close (int filedes);
返回:若成功为0 ,若出错为-1
   关闭一个文件时也会释放该进程加在该文件上的所有记录锁。当一个进程终止时,它所有的打开文件都由内核自动关闭。很多程序都使用这一功能而不显式地用close关闭打开的文件。

3.6 lseek 函数
 
  每个打开文件都有一个与其相关联的当前文件位移量。它是一个非负整数,用以度量从文件开始处计算的字节数。(本节稍后将对非负这一修饰词的某些例外进行说明。)通常,读、写操作都从当前文件位移量处开始,并使位移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND选择项,否则该位移量被设置为0。
可以调用l s e e k 显式地定位一个打开文件。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int f i l e d e s, off_t o f f s e t, int w h e n c e) ;
返回:若成功为新的文件位移,若出错为-1
   对参数offset 的解释与参数w h e n c e 的值有关:
o 若whence是SEEK_SET,则将该文件的位移量设置为距文件开始处offset 个字节。
o 若whence是SEEK_CUR,则将该文件的位移量设置为其当前值加offset, offset 可为正或负。
o 若whence是SEEK_END,则将该文件的位移量设置为文件长度加offset, offset 可为正或负。
   若lseek成功执行,则返回新的文件位移量,为此可以用下列方式确定一个打开文件的当前位移量:
off_t currpos;
currpos = lseek(fd,0,SEEK_CUR);
   这种方法也可用来确定所涉及的文件是否可以设置位移量。如果文件描述符引用的是一个管道或FIFO,则lseek 返回-1,并将errno设置为EPIPE。

3.7 read 函数

   用read函数从打开文件中读数据。
#include <unistd.h>
ssize_t read(int filedes, void *buff, size_tnbytes) ;
返回:读到的字节数,若已到文件尾为0 ,若出错为-1
   如read成功,则返回读到的字节数。如已到达文件的尾端,则返回0。
   有多种情况可使实际读到的字节数少于要求读字节数:
o 读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前还有30个字节,而要求读100个字节,则read返回30,下一次再调用read时,它将返回0(文件尾端)。
o 当从终端设备读时,通常一次最多读一行(第11 章将介绍如何改变这一点)。
o 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
o 某些面向记录的设备,例如磁带,一次最多返回一个记录。
   读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读得的字节数。

3.8 write 函数

   用write函数向打开文件写数据。
#include <unistd.h>
ssize_t write(int filedes, const void *buff, size_tnbytes) ;
返回:若成功为已写的字节数,若出错为-1
  其返回值通常与参数nbytes的值不同,否则表示出错。write出错的一个常见原因是:磁盘已写满,或者超过了对一个给定进程的文件长度限制。对于普通文件,写操作从文件的当前位移量处开始。如果在打开该文件时,指定了O_A PPEND选择项,则在每次写操作之前,将文件位移量设置在文件的当前结尾处。在一次成功写之后,该文件位移量增加实际写的字节数。
回复
分享到:

使用道具 举报

 楼主| 发表于 2007-3-4 04:32 | 显示全部楼层
四、LINUX文件系统编程源代码分析
 
   本节中我们将通过一起设计、编写一个UNIX格式文件和DOS/WINDOWS格式文件相互转换的工具软件,通过实践来充分理解如何吃透LINUX文件系统的内核,进行较为深入的文件系统应用程序设计。
   整个工具软件包括四个程序:
   1) unix2dos.c程序模块;
   2) dos2unix.c程序模块;
   3) 主程序file_transfer.c;
   4) file_transfer.h包含文件。

4.1 unix2dos.c程序模块

   unix2dos.c程序模块:用来把UNIX格式的文件转换为DOS/WINDOWS格式的文件。
   unix2dos.c程序模块源代码如下所示:
/* $Date: 2001/03/28 12:19:21 $
  *
  * UNIX to DOS text format conversion.
  *
  * $Log: unix2dos.c,v $
  * Revision 1.1 2001/03/28 12:19:21 vong
  * Initial revision
  * Written by Haowen Huang
*/
static const char rcsid[] =
  $Id: unix2dos.c,v 1.1 2001/03/28 12:19:21 vong Exp $;

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include file_transfer.h

/*
  * Convert file named by pathname to DOS
  * text format, on standard output:
*/
int
unix2dos(const char *pathname) {
   int ch;           /* Current input character */
   FILE *in = 0;        /* Input file */

   if ( !(in = fopen(pathname,r)) ) {
     fprintf(stderr,Cannot open input file.\n);
     return 2;
   }
   while ( (ch = fgetc(in)) != EOF ) {
     if ( ch == '\n' )
       putchar('\r');
     putchar(ch);
   }
   fclose(in);
   return 0;
}

4.2 dos2unix.c程序模块

   dos2unix.c程序模块:用来把DOS/WINDOWS格式的文件转换为UNIX格式的文件。
   dos2unix.c程序模块源代码如下所示:

/* $Date: 2001/03/28 12:29:37 $
  *
  * The DOS to UNIX text conversion:
  *
  * $Log: dos2unix.c,v $
  * Revision 1.1 2001/03/28 12:29:37 vong
  * Initial revision
  * Written by Haowen Huang
*/
static const char rcsid[] =
  $Id: dos2unix.c,v 1.1 2001/03/28 12:29:37 vong Exp $;

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include file_transfer.h

/*
  * Convert file named by pathname, to
  * UNIX text file format on standard output:
*/
int
dos2unix(const char *pathname) {
   int ch;           /* Current input character */
   int cr_flag;         /* True when CR prev. encountered */
   FILE *in = 0;        /* Input file */

   if ( !(in = fopen(pathname,r)) ) {
     fprintf(stderr,Cannot open input file.\n);
     return 2;
   }
   cr_flag = 0; /* No CR encountered yet */
   while ( (ch = fgetc(in)) != EOF ) {
     if ( cr_flag && ch != '\n' ) {
       /* This CR did not preceed LF */
       putchar('\r');
     }
     if ( !(cr_flag = ch == '\r') )
       putchar(ch);
   }
   fclose(in);
   return 0;
}

4.3 主程序file_transfer.c

  主程序file_transfer.c:用来获取需要转换的文件参数,包括文件名、文件路径;支持同时输入多个需要转换的文件。其中对文件路径的处理由函数Basename(const char *path)来完成。我们在这里留给各位读者一个锻炼和实践的机会:本程序的文件路径处理函数Basename(const char *path)是和主程序写在一个文件中的;但从可扩展考虑,如果能够把文件路径处理函数单独写成一个程序模块,程序将更加灵活,请读者自己试试。
   file_transfer.c程序模块源代码如下所示:

/* file_transfer.c */

static const char rcsid[] =
  $Id: file_transfer.c,v 1.8 2001/03/29 02:29:54 vong Exp $;

/* REVISION HISTORY:
  * $Log: file_transfer.c,v $
  * Revision 1.8 2001/03/29 02:29:54 vong
  * Separated out the text conversion
  * functions to make this project
  * more modular.
  *
  * Revision 1.7 2001/03/29 01:31:39 vong
  * Now handles multiple input files, plus
  * put basename code into its own Basename()
  * function.
  *
  * Revision 1.6 2001/03/29 01:01:28 vong
  * Separated conversions into unix2dos() and
  * dos2unix() functions.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include file_transfer.h

/*
  * Return the pointer to the basename component
  * of a pathname:
*/
static char *
Basename(const char *path) {
   char *base_name = 0;
   if ( ( base_name = strrchr(path,'/') ) != 0 )
     ++base_name;          /* Skip over '/' */
   else
     base_name = (char *) path;   /* No dir. */
   return base_name;
}

int
main(int argc,char **argv) {
   char *base_name = 0;      /* Basename of command */
   int rc = 0;          /* Command return code */
   int (*conv)(const char *pathname); /* Conv. Func. Ptr */
   int x;

/*
  * Determine the basename of the command name. This
  * is necessary since this command could be invoked
  * with a pathname. For example, argv[0] could be
  * /usr/local/bin/unix_cvrt if it was installed
  * in the system that way.
*/
base_name = Basename(argv[0]);

/*
  * Check for missing input file:
*/
if ( argc < 2 ) {
fprintf(stderr,Missing input file(s).\n);
return 1;
}

/*
* Now that we know the basename of the command
* name used, we can determine which function we
* must carry out here.
*/
if ( !strcmp(base_name,unix_cvrt) )
   conv = unix2dos;
else
   conv = dos2unix;

/*
  * Perform a text conversion
*/
for ( x=1; x<argc; ++x )
   if ( (rc = conv(argv[x])) != 0 )
     break;         /* An error occured */
   return rc;
}

4.4 MakeFile文件

# $Source: /vong/src/chap05/RCS/Makefile,v $
# $Revision: 1.1 $
# $Date: 2001/03/29 22:17:18 $

CC= gcc
STD= _GNU_SOURCE
OBJS= dos_cvrt.o unix2dos.o dos2unix.o

.c.o:
   $(CC) -c -Wall $(CFLAGS) -D$(STD) $<

all: dos_cvrt unix_cvrt

dos_cvrt: $(OBJS)
   $(CC) $(OBJS) -o dos_cvrt

unix_cvrt: dos_cvrt
   rm -f unix_cvrt
   ln dos_cvrt unix_cvrt

clean:
   rm -f *.o core

clobber: clean
   rm -f dos_cvrt unix_cvrt

# End Makefile
 
附录一 常用的LINUX资源Web 和FTP 站点

   下列是一些有用的World Wide Web 和ftp站点。
   http://www.postgoods.com/ 专业的电子商务应用技术网站。
   http://www.azstarnet.com/~axplinux:这是David Mosberger-Tang的Alpha AXP linux 网络站点,所有的Alpha AXP How To 均可在此找到,并且还包括大量的Linux的指示器和AlphaAXP的特殊信息,如CPU数据表格。
   http://www.redhat.com/Red Hat 的网络站点,包括大量的有用的指示器。
   ftp://sunsite.unc.edv:大量免费软件的主要汇集点。pub/linux目录下有linux专用软件。
   http://www.intel.com:Intel公司的网络站点,是查寻Intel芯片信息的好地方。
   http://www.ssc.com/lj/index.html:The Linux Journal 是一本非常好的Linux杂志,值得每年摘录其中优秀的文章。
   http://www.blackdown.org/java/linux.html:关于Java和Linux的基本站点。
   ftp://tsx-11.mit.edu/ftp/pub/Linux:MIT的Linux ftp站点。
   ftp://ftp.cs.helsinki.fi/pub/software/linux/kernel:Linux的核心资源。
   http://www.linux.org.uk:UF Linux Use Group 。
   http://sunsite.unc.edu/mdw/linux.html:Linux Documentation Project 主页。
   http://www.digital.com:Digital Equipment Corporation 的主要网页。
   http://altavista.digital.com:DIGITAL公司的Altavista的搜索引擎,是在网络和新闻群中搜寻信息的非常好的站点。
   http://www.Linuxhq.com:The Linux HQ 网络站点,存储最新的官方和非官方的patch,即帮助读者得到系统所需的最好的内核资源的建议和网络指示器。
   http://www.amd.com:The AMD网络站点。
   http://www.cyrix.com:Cyrix的网络站点。
您需要登录后才可以回帖 登录 | 我要加入

本版积分规则

QQ|小黑屋|Archiver|手机版|联系我们|声振论坛

GMT+8, 2024-5-27 18:26 , Processed in 0.055461 second(s), 17 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表