深入理解Linux文件系统编程(一)   
转载请表明出处:http://blog.csdn.net/u012637501 (嵌入式_小J的天空)
   文件系统是linux操作系统组织系统资源的一种方式,操作系统的可用性取决于对文件系统的支持,操作系统就是通过文件系统接口提供各种功能。对于Linux操作系统来说,文件是对系统资源的一个抽象,是对系统资源进行访问的一个通用接口,诸如内存、硬盘、一般设备以及进程间通信的通道等资源都被表示为文件。对这些资源的操作,就相当于对一个文件进行相关的操作。
一、基本概念
1.系统调用(I/O函数库)
    系统编程是指直接利用系统的底层接口进行编程。操作系统通过给用户提供一组特殊"接口",以便用户程序可以通过这组特殊接口来获取操作系统的内核提供的服务,包括进程控制(进程间通信)、文件系统控制、系统控制、存储管理、网络管理、socket管理、用户管理等,这种调用接口的方法也称之为"系统调用""系统调用接口",即一般通过CPU的软中断指令实现从用户态到内核态的调用,这是一种由应用程序主动发起的模式切换,让软中断处理完毕返回后重新切换到用户态,实现系统调用的返回。
2.标准I/O函数库
    C标准库提供了文件的标准I/O库,与系统调用的区别在于实现了一个垮平台的用户态缓冲的解决方案。标准I/O库使用简单,与系统调用I/O相似,也包括打开、读写、关闭这些操作。
3.API-用户编程接口
    是一些预先定义的函数(即C标准库提供的编程接口),目的是为开发人员提供编写应用程序的接口,从而使我们开发的应用程序无需访问源码。注意:API和系统调用并不是一一对应的关系,因为C标准库提供的有些接口根本不需要请求内核服务就可以完成,比如内存的复制、数学上的计算;或者有些API可能需要经过多次的系统调用才能完成其功能。

博主笔记1:系统I/O函数和标准I/O函数的区别?
    I/O操作-对文件进行输入/输出操作。虽然系统调用和标准I/O函数库均能实现对文件操作,但是两者性质是不同的:       
(1)系统调用是直接利用底层接口获取内核提供的服务,实现直接对文件的操作;而标准I/O函数库实际上是对底层系统调用的包装,其对设备或文件的操作先是经过一个缓存区然后再调用系统I/O函数来操作文件(有些API接口甚至不需要实现系统调用)。
(2)系统调用通过文件描述符对文件进行操作;标志I/O库则通过文件指针对文件进行操作
(3)系统进行I/O操作时:系统调用要请求内核的服务,会引发CPu模式的切换,期间会有大量的堆栈数据保存操作导致系统开销较大,如果频繁的进行系统调用会降低应用程序的运行效率;标准I/O函数库通过引入缓冲机制,多个读写操作可以合并为依次系统调用,通过减少系统调用的次数,将大大提高系统的运行效率

4.文件描述符fd和索引节点
   一般来说,使用与管理文件我们都是通过文件名实现的。但是,对于应用编程而言,文件描述符更有用,而系统中的文件在本质上是通过其索引节点进行管理的。
(1)文件描述符在系统调用中,我们主要是通过文件描述符实现文件的相应操作。文件描述符是应用程序打开一个文件返回的一个整数,它也就代表这个文件。就像体育比赛一样,每个运动员都会被分配一个编号,当我们为某个号码加油时,实际就是其对应的运动员加油。在linux系统中,所有打开的文件都对应一个文件描述符,其本质是一个非负整数,由系统分配且范围为0~OPEN_MAX(1024).每个进程在启动后默认有三个打开的文件描述符0、1、2,如果启动时没有进行重定向,则文件描述符0关联到标准输入文件,1关联到标准输出,2关联到标准错误输出,即C库中表示:
#define STDIN_FILENO 0    //标准输入
#define STDOUT_FILENO 0    //标准输出
#define STDERR_FILENO 0    //标准错误输出
(2)索引节点:从系统的角度来看,文件的索引节点(inode)是文件的唯一标识。一个文件的inode包含文件系统处理文件所需要的全部信息,如访问权限、当前大小等。包含两种类型inode:
    a.内核inode:保存在内存中,当系统中每个打开的文件都对应着一个内核inode,即与具体的文件系统类型无关;
    b.磁盘inode:保存在磁盘上,在文件系统中的每一个文件都有一个磁盘inode,它所保存的具体信息与文件系统的类型有关。

博主笔记2:如何理解上述两种索引节点?
    当进程打开一个文件时,文件的磁盘inode中的信息将会被载入内存,并建立一个内核inode。
    当内核inode被修改后,系统负责将其同步到磁盘上。磁盘inode与对应内核inode所保存的信息并不完全相同的。
    内核inode记录的是关于文件的更通用的一些信息;而忽略掉与具体文件系统类型相关的信息。
    一般而言,一个文件inode应当记录如下信息:
    a.文件类型;
    b.与文件相关的硬链接的个数;
    c.以字节为单位的文件的长度;
    d.设备标识符
    e.各种时间截,包括文件状态的改变时间、文件的最后访问时间和最后修改时间等。

5.文件指针与流
    在标准I/O函数中,并不直接操作文件描述符,而是使用文件指针。文件指针与文件描述符是一一对应关系,这种对应关系由标准I/O库自己内部维护。
(1)文件指针
    文件指针指向的数据类型为FILE型,但应用程序无需关心它的具体内容。在应用程序调用文件时,只需要提供文件指针即可。
(2)流
    在标准I/O中,一个打开的文件称之为"流"(stream),流可用于读(输入流)、写(输出流)或者读写(输入输出流)。每个进程在启动时,都会打开三个流,与打开的三个文件相对应:sdtin代表标准输入流、stdout代表标准输出流、stderr代表标准错误输出流,他们三个都是(FILE*)型的指针
注意:标准错误输出流不进行缓冲,输出的内容会马上同步到文件(即控制台设备)。
博主笔记3:FILE型指针(FILE *fp)?
    FILE是在stdio.h定义的保存文件流信息的一个结构体类型实际是C语言定义的标准数据结构,用于文件。 
    FILE *fp:是声明一个FILE类型名为fp的指针,fp为指向FILE类型的对象,也可以理解成为FILE结构体的一个指针对象。在应用程序访问文件时,一般使用FILE*类型变量表示文件句柄,通过它来访问FILE结构体,对文件进行操作。每打开一个文件就会返回一个文件指针(为FILE类型),然后通过这个指针对象访问结构体的成员变量从而获得文件信息
如fopen函数,FILE* fp=fopen("1.txt","r+");
struct _iobuf 
 {
         char *_ptr; //文件输入的下一个位置
         int _cnt; //当前缓冲区的相对位置
         char *_base; //指基础位置(应该是文件的其始位置)
         int _flag; //文件标志
         int _file; //文件的有效性验证
         int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取
         int _bufsiz; //文件的大小
         char *_tmpfname; //临时文件名
};
typedef struct _iobuf FILE;
总结:FILE是C运行库中的结构体,文件描述符是posix系统级的。一切关于FILE的操作最终都将调用系统级的函数。

二、头文件
#include <stdio.h> 
    程序中用到系统提供的标准函数库中的输入输出函数,即提供标准输入输出信息
#include <stdlib.h>
    stdlib 头文件里包含了C、C++语言的最常用的系统函数。stdlib.h里面定义了五种类型、一些宏和通用工具函数。 类型例如size_t、wchar_t、div_t、ldiv_t和lldiv_t; 宏例如EXIT_FAILURE、EXIT_SUCCESS、RAND_MAX和MB_CUR_MAX等等; 常用的函数如malloc()、calloc()、realloc()、free()、system()、atoi()、atol()、rand()、srand()、exit()等等。
#include<sys/types.h> 
    基本系统数据类型,是Unix/Linux系统的基本系统数据类型的头文件,含有size_t(反映内存中对象的大小-以字节为单位)、time_t(以秒为单位计时),pid_t(进程ID和进程组ID)、dev_t(设备号)、ssize_t(供返回字节计数或错误提示的函数使用)等类型
#include<sys/stat.h>
    包含的头文件stat.h在系统目录的sys目录下,获取文件属性。
       struct stat{
mode_t st_mode; //文件类型和权限信息
ino_t st_ino; //i结点标识
dev_t st_dev; //device number (file system)
dev_t st_rdev; //device number for special files
nlink_t st_nlink; //符号链接数
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
off_t st_size; //size in bytes,for regular files
time_t st_st_atime; //最后一次访问的时间
time_t st_mtime; //文件内容最后一次被更改的时间
time_t st_ctime; //文件结构最后一次被更改的时间
blksize_t st_blksize; //best I/O block size
blkcnt_t st_blocks; //number of disk blocks allocated
};
#include <sys/fcntl.h>
    fcntl.h定义了很多宏和open,fcntl函数原型
 #include<unistd.h>
    unistd.h定义了更多的函数原型
 
   
三、系统调用API接口
1.创建文件
(1)函数
   #include<sys/types.h>
   #include<sys/stat.h>
  int creat(const char *filename,mode_t mode)
(2)说明
filename:创建文件名(包含路径、缺省为当前目录);
mode:文件属性(0-无任何权限;S_IRUSR或1->可读;S_IWUSR或2->可写;S_IXUSR或3->可执行;S_IRWXU或7->均可)
(3)返回值
创建成功返回文件描述符fd;错误返回-1。
注释:
    (1)mode_t表示unsigned int,所以mode_t 实际上也就是一种无符号整数
    (2)const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程度上可以提高程序的安全性和可靠性。
2.打开文件
(1)函数:
#include<sys/types.h>
#include<sys/stat.h>
int open(const char *pathname,int flags);                     //文件已创建
int open(const char *pathname,int flags,mode_t mode); //文件没有创建
(2)说明:
flags:打开标识。
O_RDONLY->只读打开方式;O_WRONLY->只写打开方式;O_RDWR->可读可写O_APPEND->追加方式打开 O_CREAT->创建一个文件;O_NOBLOCK->非阻塞方式打开
(3)返回值:打开成功返回文件描述符fd;错误返回-1,并把错误代码设给err。
                                    if(fd=open(argv[1],O_CREAT|O_RDONLY,0755))
注释:非阻塞方式,即当文件打开失败时,进程不阻塞,跳过该部分执行进程剩余部分或者结束进程。
3.读一个文件
(1)函数       
   #include<unistd.h>
   int read(int fd,const *buf,size_t length)
(2)说明
            从文件描述符fd指向的文件中,读取length个字节到buf所指向的缓存区中(文件内容->缓存Buf)。
(3)返回值
返回为实际读取的字节个数length
4.写一个文件
(1)函数:
    #include<unistd.h>
    int write(int fd,const *buf,size_t length)
(2)说明
        向文件描述符fd指向的文件,写入length个字节到buf所指向的缓存区中
(3)返回值
    实际写入的字节个数length
注释:(1)size_t实际为unsigned int类型,设计 size_t 就是为了适应多个平台的 ,size_t的引入增强了程序在不同平台上的可移植性。 在32位系统中size_t是4字节的,而在64位系统中,size_t是8字节的,这样利用该类型可以增强程序的可移植性。
   
5.发送控制命令
(1)函数
    #includ<sys/ioctl.h>
    int ioctl(int fd,int request,...)
(2)说明
        ioctl操作用于向文件发送控制命令,那些不能被抽象为读和写的文件操作统一由ioctl操作代表。对于Linux系统,ioctl操作常用与修改设备的参数。
    fd:要操作的文件的描述符;
    request:代表要进行的操作,不同的(设备)文件有不同的定义;
    可变参数:取决于request参数,通常是一个指向变量或者结构体的指针;
    返回值:成功返回0,错误返回-1   
(3)返回值
        返回文件指针相对于文件头的位置
6.关闭文件
(1)函数
    #include<unistd.h>
    int close(int fd);
(2)说明:关闭文件,调用close()会让数据写回磁盘,并释放该文件所占的资源
    fd为文件描述符
(3)返回值
    返回正常为0,失败为-1

博主笔记4:API的错误处理?
    在系统编程中,对API调用的错误处理是重要的一个环节,主要有三种方法:
(1)errno变量是一个整型值,当调用API出现错误时用于表示错误的类型,然后我们就可以根据errno变量装载的值,来对错误进行相应的处理,其接口头文件以及定义为:
#include<errno.h>
extern int errno;
当调用返回-1且errno变量被设置为EINTR,这说明在读入/写出有效字节前收到一个信号,这种情况可以重新进行读/写操作;
当调用返回-1且errno变量被设置为EAGAIN,这说明是在非阻塞方式下读文件/写文件,并且没有可读的数据/不能写文件;
当调用返回-1且errno变量被设置为非EINTR或EAGAIN,表明有其他类型的错误发生,必须根据具体情况进行处理。
(2)errno可以用于输出不太直观,C标准库提供了一个函数用于 错误码转换为一个描述错误的字符串 ,原型如下:
#include<string.h>
char *strerror(int errnum);
其中,参数errnum是错误码,返回值是错误码的字符串。
(3) perror函数 :直接将 当前的errno变量对应的错误信息输出到标准错误输出(控制台设备文件) ,原型:
#include<stdio.h>
void perror(const char *s)
首先将字符串s输出,然后再输出对错误的描述 。其中,s字符串是人为设定的。

6.文件定位
(1)函数:   
 int lseek(int fd,offset,int whence)
(2)说明:将文件读取指针相对whence移动offset个字节。
whence:SEEK_SET->相对文件开头;SEEK_CUR->相对文件读写指针当前位置;SEEK_END->相对文件末尾
offset:可为负数(向前移动)
lseek(fd,0,SEED_END);//返回文件长度(大小)
(3)返回值:返回文件指针相对于文件头的位置。
7.访问判断
(1)函数:       
int access(const char *pathname,int mode)
(2)说明:判断文件是否可以进行某种操作(如读或写)
        R_OK、W_OK、X_OK、F_OK(文件存在)
(3)返回值:测试成功,函数返回为0;否者返回为-1.
四、标准I/O库API接口
    相对于系统I/O函数库,标准I/O库实现了一个垮平台的用户缓冲机制,这有利于提高系统进行I/O操作的效率。举个简单的I/O缓冲例子。当我们使用printf函数向标准输出写入多个字符时,其工作原理为:先将所写的字符依次放在一个用户缓冲区中,直到碰到一个换行符系统才会调用系统I/O函数write将缓存区中的数据写入标准输出。也就是说,在换行符之前写入的字符并不会立即出现在控制台屏幕上,需要write函数实现用户态和内核态的交互。
1.打开一个文件
(1)函数:       
 FILE *fopen(const char *filename,const char *mode);
(2)说明:
        打开一个文件流并为文件建立相应的信息区(来存放文件信息)和文件缓冲区用来暂时存放输入输出数据返回
        filename:被打开文件的名称(包含路径)
       mode:字符串,用于表示打开的模式
 (3)返回值:成功返回一个FILE类型的文件指针(即 fp指向文件信息区的起始地址 ),失败返回NULL。
 (4)举例:
FILE *fp;
if(fp=fopen("文件名","r")==NULL)
{
printf("cannot open this file\n");
exit(0);
}
2.关闭一个文件
(1)函数      
 int fclose(FILE *stream);
(2)说明:关闭打开的文件,关闭前系统会自动调用fflush函数更新数据即将文件流中的数据写入文件
        stream代表打开的文件
(3)返回值:关闭成功返回0,否者返回EOF并设置变量errno的值以指示错误。
3.文件写/读-以数据块为单位
(1)函数
size_t fread(void *buffer,size_t size,size_t count,FILE *stream):
a.说明:从stream文件指针所指向的文件流中所指向的文件读取count个数据块且大小为size字节缓存区
b.返回值:返回数据块个数,为size_t类型。
fwrite(buffer,size,count,fp):从缓冲区写入count个数据项到fp指向的文件
(2)写文件举例:
for(i=0;i
{
    if(fwrite(&stu[i],sizeof(struct Student),1,fp) !=1);//从数组stu中写入1个大小为sizeof(struct Student)字节数据
    printf("file write error");
}
读文件举例:
for(i=0;i
{
    fread(&stu[i],sizeof(struct Student),1,fp);
    printf("%s%d%s",stu[1].name.....);
}
注释:(1)sizeof(struct Student),求字节数,结构体大小字节数为所有变量类型大小字节之和。
            (2)&stu[i],参数&stud[i] :是一个指针,对fwrite来说,是要输出数据的地址。
4.从/向文件读写一个字符
(1)函数      
  int fget(FILE *stream):从指向的文件读出一个字符
     返回值:成功返回该字符;失败返回文件结束标志EOF(-1)
  int  fput(ch,FILE *stream):将一个字符ch写入一个文件中
     返回值:成功返回该字符;失败返回结束标识EOF(-1)。
  feof(FILE *stream):该函数检查文件读写位置是否移动文件末尾。
其中,putchar函数用于项标准输出写入一个字符,即int putchar(int c);等价于int fput(c,stdout);
(2)应用举例
while(!feof(fp1)) //fp1为文件描述符,当文件读写位置移动到文件末尾时,feof(fp1)=1
{
    ch=fgetc(fp1); //从文件1读取一个字符到ch
    fputc(ch,fp2);
    putchar(ch); //输出一个字符
}
while(ch!='#')
{
    fputc(ch,fp); //向一个文件中写入一个字符
    putchar(ch); //输出一个字符
    ch=getchar(); //获取一个字符
}
5.从/向文件读写字符串
(1)函数       
 char *fgets(char *s,int size,FILE *stream)
    a.说明:从fp指向文件读写一个长度(字节为单位)为(n-1)(最后一个为结束标志位'\0')的字符串,存放到字符数组str。
    b.返回值:成功返回数组str地址,失败返回NULL。
 char fputs(const char *s,FILE *stream)
    a.说明:把str所指向的字符串写入到文件指针变量fp所指向的文件中。
    b.返回值:非负数表示写入成功,有错误发生则返回EOF
(2)应用举例
while(fgets(str[i],10,fp)!=NULL)
{
    printf("%s",str[i]);
    i++;
}
6.文件定位
(1)函数:
  int fseek(FILE *stream,long offset,int whence)
(2)说明:用于移动文件流的读写位置
    whence:表示偏移量相对于文件开始位置 SEEK_SET 0
                   表示偏移量相对于 文件当前位置 SEEK_CUR 1 
                   表示偏移量相对于 文件末尾位置 SEEK_END 2
(3)返回值:0表示操作成功,-1表示操作失败并且errno变量的值被设置为错误码
7.其他文件定位函数
 (1)函数
    void rewind(FILE *stream);
  说明: 将读写位置移动到文件流的开头
 (2)函数
    long ftell(FILE *stream);
  说明: 得到文件流的读写位置,返回值为文件流的当前读写位置(相对于头文件)
(3)函数
    int fflush(FILE *stream);
  说明: 将文件流中的数据跟新到真实的文件中,成功返回0,否者将返回EOF并且设置变量errno的值以指示错误。
        在fclose关闭文件之前,系统会自动调用fflush更新数据。
8.标准I/O错误处理
    当标准I/O操作发生错误处理时,一般返回NULL指针或者EOF,我们可以通过errno变量得到错误码。
 (1)函数
   int ferror(FILE *stream);
   int feof(FILE *stream);
(2)说明
    ferror函数用于 判断文件流是否发生错误,若返回非0值则表示发生了错误
    feof函数用于 判断对文件流的读写是否已经达尾部,若返回非0值则表示已经达尾部。


Logo

更多推荐