文件系统概述

Linux内核的各种真实文件系统、块设备和字符设备统一在虚拟文件系统的框架中,虚拟文件系统为应用提供了一组抽象的文件输入输出接口。

虚拟文件系统是对各种真实文件系统的抽象,在虚拟文件系统中定义了抽象的超级块、i节点和目录,它为真实文件系统提供了一种统一的框架接口。真实文件系统通过这些接口与虚拟文件系统相连接,真实文件系统是这些抽象接口的具体实现。

虚拟文件系统存在于内存中,在系统启动时产生,随着系统关闭而消失。

文件操作常用的头文件

C POSIX library是C语言的POSIX系统下的标准库。包含了一些在C语言标准库之外的函数。

#include <unistd.h>  //多种必要的POSIX函数与常量
#include <fcntl.h>   //文件打开、创建、加锁等操作
#include <sys/stat.h>//文件信息(stat (Unix)等)
#include <sys/types.h>//不同的数据类型
#include <dirent.h>   //打开与列出目录内容
//此外,还有C标注库中的
#include <stdio.h>	  //标准缓存输入输出

文件基本输入输出

文件输入输出涉及到以下函数:

  • open、creat 在<fcntl.h>
  • read、write、lseek、close 在<unistd.h>

案例:复制文件

//cp.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#define PMODE 0644	//权限定义为rw-r--r--
#define BUFSIZE 200

int main(int argc,char *argv[])
{
    int fdin,fdout,n;
    char buf[BUFSIZ];
    if(argc !=3)
    {
        fprintf(stderr,"Usage:%s filein fileout\n",argv[0]);
        return 1;
    }
    if((fdin = open(argv[1],O_RDONLY)) == -1)
    {
        perror(argv[1]);
        return 2;
    }
    if((fdout = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,PMODE)) == -1)
    {
        perror(argv[2]);
        return 3;
    }

    while((n = read(fdin,buf,BUFSIZE)) > 0)
        write(fdout,buf,n);

    close(fdin);
    close(fdout);
    
    return 0;
}

###文件属性操作 文件的属性信息存放在文件对应的i节点中,对于不同类型的物理文件系统,文件属性的组织形式不尽相同,为了获得统一的文件属性格式,Linux定义了struct stat这个数据结构,类型定义如下:

struct stat {
             dev_t     st_dev;         /* 文件设备编号*/
             ino_t     st_ino;         /* i节点号 */
             mode_t    st_mode;        /* 文件类型和存储权限 */
             nlink_t   st_nlink;       /* 硬链接 */
             uid_t     st_uid;         /* 用户ID */
             gid_t     st_gid;         /* 用户组ID */
             dev_t     st_rdev;        /* Device ID (if special file)*/
             off_t     st_size;        /* 文件字节数bytes */
             blksize_t st_blksize;     /* 块大小 */
             blkcnt_t  st_blocks;      /* 以512bytes为单位的块数 */
             struct timespec st_atim;  /* 文件最后一次访问时间 */
             struct timespec st_mtim;  /* 文件最后一次修改时间 */
             struct timespec st_ctim;  /* 文件属性最后一次改变时间 */
          };

以之前的cp.c为例:

cp

可以看到cp.c文件实际大小640bytes,在磁盘上占用了一个4096bytes的块,也就是8个512bytes的块。`

文件属性操作常用函数:

  • stat 获取文件属性信息
  • chmod 设置文件权限
  • chown 设置文件属主
  • utime 获取时间

案例:改变文件读写权限

#include <stdio.h>
#include <sys/stat.h>

int main()
{
    mode_t fdmode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
    if(chmod("cp.c",fdmode) == -1)
    {
        printf("error\n");
        return 1;
    }
    return 0;
}

编译运行,结果如下,可见文件权限已经被修改。

niuhe@niuhe-ubuntu:~/Linux$ gcc -o chmod chmod.c 
niuhe@niuhe-ubuntu:~/Linux$ ls
chmod  chmod.c  cp  cp.c
niuhe@niuhe-ubuntu:~/Linux$ ll cp.c 
-rw-rw-r-- 1 niuhe niuhe 640 10月  6 16:55 cp.c
niuhe@niuhe-ubuntu:~/Linux$ ./chmod 
niuhe@niuhe-ubuntu:~/Linux$ ll cp.c 
-rw-r--r-- 1 niuhe niuhe 640 10月  6 16:55 cp.c

###目录操作 目录是一种特殊的文件,其内容由若干目录项组成,一个目录项包括文件名和i节点号。为了便于管理,每个目录中都包含当前目录".“和父目录”..",当前目录项指向当前目录的i节点编号,父目录项记录了父目录对应的i节点编号。 常用库函数及头文件如下:

#include <sys/stat.h>
	//在某目录中创建一个目录项,分配一个i节点和目录项相链接
	//分配一个逻辑块用来存放目录内容,并在其中建立当前目录和父目录两个目录项
	int mkdir(const char *pathname,mode_t mode);
#include <unistd.h>
	//删除空目录
	int rmdir(const char *pathname);
	//改变工作目录;在每个进程的进程控制块中保存着当前工作目录的i节点
	//初始工作目录继承自父进程,进程运行过程可以改变工作目录
	int chdir(const char *pathname);
	//获得调用者进程的当前工作目录,buf存放路径,size路径包含字节数
	char *getcwd(char *buf, size_t size);
#include <dirent.h>
	//打开目录,成功返回目录流(字符串)
	DIR *opendir(const char *pahtname);
	//读目录,成功返回下一个目录项
	struct dirent *readdir(DIR *dp);
	//关闭目录
	int closedir(DIR *dp);

目录是一种特殊的文件,存放着很多目录项,每一个目录项是一个结构体。

struct dirent
	{
  	long d_ino;					//i节点号
  	char d_name[NAME_MAX+1];	//文件名
  	off_t d_off;				//在目录文件中偏移量
  	unsigned short d_reclen;	//文件名长度
}

案例:改变当前进程工作目录

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

int main(void)
{
    if(chdir("/tmp") < 0)
        printf("chdir failed");
    printf("chdir to /tmp succeeded\n");
    return 0;
}

案例:浏览目录中所有文件名

#include <stdio.h>
#include <dirent.h>

int main(int argc,char *argv[])
{
    DIR *dirp;
    struct dirent *direntp;
    if((dirp = opendir(argv[1])) == NULL)
    {
        printf("cannot open the %s directory",argv[1]);
        return 1;
    }
    while((direntp = readdir(dirp)) != NULL)
        printf("%s\n",direntp->d_name);
    closedir(dirp);
    return 0;
}

###C标准I/O库

运用read、write等系统底层函数进行输入输出时,需要在用户态和内核态之间来回切换。若每次读取或写入数据较少,将导致频繁的I/O操作,降低了程序运行效率。 标准I/O库对底层I/O系统调用进行了封装,提供了带格式转换的I/O操作,并在用户空间增加了缓冲管理,可减少程序和输出设备之间I/O次数。函数原型定义在<stdio.h>中。 细节略

I/O重定向

文件描述符

Linux系统中,进程拥有各自打开文件的描述符。文件描述符按生成的顺序存放在文件描述符表中,Linux内核将文件描述符表用一维数组表述,每个打开的文件占用一个单元,用来存放操作文件的必要信息,如读写操作当前位置、文件打开方式、文件操作集等。 进程在打开一个文件时,返回的是文件描述符所在数组的下标,称为文件描述符。 通常,创建子进程时,子进程从父进程继承文件描述符表,前3个描述符0,1,2分别对应标准输入、标准输出、标准错误输出,与进程的控制终端设备对应。通常已经被打开,进行读写操作时无需重新打开。 一个文件可以同时被多个进程打开,它在不同进程中对应的文件描述符以及操作状态也未必相同。

####I/O重定向

程序根据打开文件的描述符对文件进行读写操作,真正完成读写操作的是进程描述符表相应位置中的内容。以输出重定向为例,若将进程描述符表中1号单元的内容替换为打开的文件test,则进程在向标准输出文件输出信息时,原本数据应显示在终端显示器上,但现在这些数据将被输出至文件test。 实现I/O重定向可以通过:

  • open close open方法
  • 系统函数调用:dupdup2

#####open close open方法

Linux在为进程新打开文件分配描述符时,从下表0开始扫描进程文件描述符表,将打开的文件信息放在找到的第一个空闲单元,并将该下表作为打开文件的描述符。 以标准输入0为例,将标准输入关闭,使得文件描述符表第0号单元成为空闲单元,此时,进程新打开另一个文件,内核将文件描述符表0号单元分配给新打开的文件,并返回描述符0,也就实现了输入重定向。

#####dup和dup2函数

使用dup和dup2函数,只是复制文件描述符,使两个文件描述符指向同一个file结构体,并且file结构体引用计数是2。此时,打开文件的状态保存在同一个file结构体中。而使用open函数两次打开一个文件会存在两份file结构体,分别有各自的状态。

#include <unistd.h>
	//从进程文件描述符表中寻找一个可用的最小描述符,返回此描述符
	//并复制oldfd对应的File结构指针到新的最小描述符
	int dup(int oldfd);
	//oldfd需要复制的文件描述符,newfd是复制后oldfd在文件描述符表中新的序号。
	//成功返回一个新描述符,否则返回-1
	//若newfd已经打开,则先关闭newfd,然后复制oldfd到newfd,使newfd也指向oldfd,此时oldfd和newfd两个描述符共享同一个文件。
	int dump2(int oldfd, int newfd);

案例:输出重定向

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
    int fileID;
    fileID = creat("ls.tst",0640);
    if(fileID < 0)
    {
        fprintf(stderr,"error creating ls.tst\n");
        return 1;
    }
    dup2(fileID,1);
    close(fileID);
    execl("/bin/ls","ls",NULL);
    return 0;
}

参考