关闭

Unix系统编程(2) - 文件系统

标签: unix编程文件系统
1168人阅读 评论(0) 收藏 举报
分类:

文件系统需要解答以下两个问题:

  • 1 文件系统在内核中时如何实现的?如何呈现给用户一个树状的目录结构?如何处理用户的文件和目录操作请求?

  • 2 磁盘是一种顺序的存储介质,一个树状的目录结构如何存到磁盘上?怎样设计文件系统的存储格式使访问磁盘的效率最高?各种文件和目录操作在磁盘上的实际效果是什么?

在Windows系统中一般我们使用的是NTFS文件系统(早期的windows中使用的时FAT32系统)。目前U盘的文件系统也大多时FAT32。但是Linux的文件系统是完全不一样的。Linux文件系统有ext2, ext3, ext4。ext3和ext4也只是在原来的文件系统上作了一些扩展。所以学习的角度来看一般都学习ext2文件系统。

0. 从设计文件系统来看

假设现在我们有一个名字为hello的文件需要存储到磁盘中,那么我们应该怎样去设计文件系统来存储这个文件呢?
用最简单的方式考虑,很自然的我们会想到使用一种类似键值对的形式进行存储,每个文件存储他的文件名,文件存储的起始位置以及文件的大小。乍一看好像这样确实能将我们的文件存到磁盘中。随之的问题又来了,存储了这个文件之后,假设我们现在想修改这个文件。由于该文件的随后的磁盘位置很可能已经被占用了,我们就不能把追加的内容直接写在原文件的后面。
从设计文件系统的角度来看,文件系统设计的目的就是方便我们将文件存储到磁盘,方便我们从磁盘中去取得文件。FAT32,NTFS,ext2等各种文件系统都是基于着一个目标的。

1. ext2文件系统

ext2文件系统中磁盘的按照下图的方式进行分割:

ext2文件系统
详细描述

1.1 Boot Block(启动块)

所有的文件系统都有这个块(而且这个块的大小都是一样的(1KB),肯定要一样,不然怎么读取磁盘的内容),这个块存储着磁盘的分区信息和启动信息,任何文件都不能使用启动块。windows系统安装的时候有个MBR,其实就是这个块。

1.2 Super Block (超级块)

超级块描述整个分区的文件系统信息,例如:块大小、文件系统版本号,上次mount时间等等。
超级块在每个块组的开头都有一份拷贝

1.3 Group Description Table (GDT, 块组描述符表)

块组描述符由很多的块组描述符组成,整个分区分成多少个块组就对应有多少块组描述符。每个块组描述符存储一个块组的描述信息,例如这个块组从哪里开始是inode表,从哪里开始是数据块,空闲的inode和数据块还有多少个等等。
和超级块类似,块组描述表在每个块组的开头都有一个拷贝,这些信息非常的重要,一旦超级块意外损坏就会丢失整个分区的数据,一旦块组描述符意外损坏就会丢失整个块组的数据,因此他们都有多份拷贝。
通常,内核只用地第0个块组中的拷贝,当执行e2fsck检查文件系统一致性时,第0个块组中的超级块组中的超级块和块组描述符就会拷贝到其他块组,这样当第0个块组的开头意外损坏的时候就可以从用其他的拷贝来恢复,从而减少损失。

1.4 Block Bitmap (块位图)

用来标注那些块使用了,哪些快没有使用。在文件删除的时候就是在此操作的,文件删除的时候并不是去写掉数据块的内容,而是将块位图写为未用,这样下次有数据要写入的时候就可以使用了,而且这样“删除”的速度很快。这也就是磁盘数据恢复的原理。

1.5 inode Bitmap(inode位图)

用来标注inode的使用情况。

1.6 inode Table (inode表)

由若干个inode组成,文件属性都是在inode里面。

1.7 数据块

保存文件的数据。前面说到一个数据块的大小是固定的,那么如果有一个非常大的文件想要存储到磁盘中该怎么解决呢?这就涉及到了数据的寻址,ext2文件系统inode中的数据指针共有15个,前面的12个位直接寻址指针,后面3个分别为一级,二级和三级间接寻址:


数据块寻址

那按照这样的放射能不能存大文件呢?可以计算假设块大小为1KB,那么最大的文件大小可以达到16GB。
这里就有一个问题:在磁盘的分区的时候如何分配合适的块大小来满足我们的使用?也就是说当我们系统需要存的都是很小的文件,那么我们就需要更多的文件描述符,块大小就可以设置较小;当我们系统存储大多数都是大文件的时候我们就可以将块设置较大。

数据块根据不同的文件类型有以下几种情况:
- 1 对于常规文件,文件的数据存储在数据块中。
- 2 于目录,该目录下的所有文件名和目录名存储在数据块中,注意文件名保存在它所在目录的数据块中,除文件名之外, ls -l 命令看到的其它信息都保存在该文件的inode中。
注意: 目录也是一种文件,是一种特殊类型的文件。
- 3 对于符号链接,如果目标路径名较短则直接保存在inode中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。
- 4 设备文件、FIFO和socket等特殊文件没有数据块,设备文件的主设备号和次设备号保存在inode中。

2 文件和目录访问相关API函数

2.1 stat, fstat, lstat, fstatat文件相关信息

NAME
       stat, fstat, lstat, fstatat - get file status

SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>
       #include <unistd.h>

       int stat(const char *pathname, struct stat *buf);
       int fstat(int fd, struct stat *buf);
       int lstat(const char *pathname, struct stat *buf);

       #include <fcntl.h>           /* Definition of AT_* constants */
       #include <sys/stat.h>

       int fstatat(int dirfd, const char *pathname, struct stat *buf,
                   int flags);

使用时一般带入文件路径或者文件描述就可以获取到该文件的属性,获得的结果以结构体stat的形式给出。

系统内核中给struct stat 结构体定义了:

       struct stat 
           {
               dev_t     st_dev;         /* ID of device containing file */
               ino_t     st_ino;         /* inode number */
               mode_t    st_mode;        /* protection */
               nlink_t   st_nlink;       /* number of hard links */
               uid_t     st_uid;         /* user ID of owner */
               gid_t     st_gid;         /* group ID of owner */
               dev_t     st_rdev;        /* device ID (if special file) */
               off_t     st_size;        /* total size, in bytes */
               blksize_t st_blksize;     /* blocksize for filesystem I/O */
               blkcnt_t  st_blocks;      /* number of 512B blocks allocated */

               /* Since Linux 2.6, the kernel supports nanosecond
                  precision for the following timestamp fields.
                  For the details before Linux 2.6, see NOTES. */

               struct timespec st_atim;  /* time of last access */
               struct timespec st_mtim;  /* time of last modification */
               struct timespec st_ctim;  /* time of last status change */

               #define st_atime st_atim.tv_sec      /* Backward compatibility */
               #define st_mtime st_mtim.tv_sec
               #define st_ctime st_ctim.tv_sec
           };

mode_t 文件类型

           S_IFSOCK   0140000   socket
           S_IFLNK    0120000   symbolic link
           S_IFREG    0100000   regular file
           S_IFBLK    0060000   block device
           S_IFDIR    0040000   directory
           S_IFCHR    0020000   character device
           S_IFIFO    0010000   FIFO


      使用下述函数来判断文件类型,实际上就是判断某位上的值是否为1.

           S_ISREG(m)  is it a regular file?
           S_ISDIR(m)  directory?
           S_ISCHR(m)  character device?
           S_ISBLK(m)  block device?
           S_ISFIFO(m) FIFO (named pipe)?
           S_ISLNK(m)  symbolic link?  (Not in POSIX.1-1996.)
           S_ISSOCK(m) socket?  (Not in POSIX.1-1996.)
  • example: 使用stat函数查看文件属性
/*************************************************************************
    > File Name: stat.c
    > Author: Robin
    > Mail: chou_robin@163.com 
    > Created Time: 2016年06月01日 星期三 14时56分55秒
 ************************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
    struct stat sb;
    if(argc != 2)
    {
        fprintf(stderr, "Usage: %s <pathname>\n",argv[0]);
        exit(1);
    }

    if(stat(argv[1], &sb) == -1)
    {
        perror("stat");
        exit(1);
    }

    printf("File type:       ");

    switch(sb.st_mode & S_IFMT)
    {
        case S_IFBLK:  printf("block device\n"); break;
        case S_IFCHR:  printf("character device\n"); break;
        case S_IFDIR:  printf("dirctory\n"); break;
        case S_IFIFO:  printf("FIFO/pipe\n"); break;
        case S_IFLNK:  printf("symlink\n"); break;
        case S_IFREG:  printf("regular file\n"); break;
        case S_IFSOCK: printf("socket\n"); break;
        default: printf("unkonwn\n"); break;
    }

    printf("I-node number:%ld\n", (long) sb.st_ino);
    printf("Mode: %lo (octal)\n",  (unsigned long) sb.st_mode);
    printf("Link count:  %ld\n", (long) sb.st_nlink);
    printf("Ownership: UID=%ld   GID=%ld\n",(long) sb.st_uid, (long) sb.st_gid);
    printf("Preferred I/O block size: %ld bytes\n",(long) sb.st_blksize);
    printf("File size: %lld bytes\n",(long long) sb.st_size);
    printf("Blocks allocated: %lld\n",(long long) sb.st_blocks);
    printf("Last status change:       %s", ctime(&sb.st_ctime));
    printf("Last file access:         %s", ctime(&sb.st_atime));
    printf("Last file modification:   %s", ctime(&sb.st_mtime));
    exit(EXIT_SUCCESS);                 
}


运行结果

2.2 access, faccessat 检查用户对文件的操作

NAME
       access, faccessat - check user's permissions for a file

SYNOPSIS
       #include <unistd.h>

       int access(const char *pathname, int mode);

       #include <fcntl.h>           /* Definition of AT_* constants */
       #include <unistd.h>

       int faccessat(int dirfd, const char *pathname, int mode, int flags);

mode 说明
R_OK 测试读权限
W_OK 测试写权限
X_OK 测试执行权限
F_OK 文件是否存在

example

/*************************************************************************
    > File Name: access.c
    > Author: Robin
    > Mail: chou_robin@163.com 
    > Created Time: 2016年06月01日 星期三 15时52分50秒
 ************************************************************************/
#include<stdio.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        fprintf(stderr,"args num: %d error\n", argc);
        exit(1);
    }
    int rt;
    if((rt = access(argv[1], R_OK)) == -1)
    {
        fprintf(stderr,"Error: ", perror);
        exit(1);
    }
    printf("Read:%d\n",rt);
    return 0;
}
2
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:176698次
    • 积分:3036
    • 等级:
    • 排名:第11965名
    • 原创:108篇
    • 转载:10篇
    • 译文:0篇
    • 评论:93条
    博客专栏
    最新评论