5.1.1、虚拟文件系统
第一层、虚拟文件系统(VFS)
第二层、各种不同的具体的文件系统
VFS就是把各种具体的文件系统的公共部分抽取出来,形成一个抽象层,是系统内核的一部分。
查看系统支持哪些文件系统:
# cat /proc/filesystems
5.1.2、通用文件模型
VFS的主要目的在于引入了一个通用的文件模型(Common File Model),这个模型的核心是4个对象类型,即超级块对象(Superblock Object)、索引节点对象(Inode Object)、文件对象(File Object)和目录项对象(Dentry Object)。
- 超级块:Super Block,存放系统中已安装文件系统的有关信息。
- 索引节点:Inode,对应于存放在磁盘上的文件控制块(File Control Block)。文件->索引节点->索引节点号->唯一标识
- 文件对象:File Object,存放打开文件与进程之间进行交互的有关信息,是进程与文件系统的桥梁。
- 目录项:Dentry,存放目录项与对应文件链接的信息。
1、超级块对象
用来描述整个文件系统信息。VFS超级块是由各种具体的文件系统在安装时建立的,只存在于内存中。
超级块对象通过alloc_super()函数创建并初始化。在文件系统安装时,内核会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存的超级块对象中。
2、索引节点对象
文件系统中处理文件所需要的信息都放在索引节点对象里。文件名可以随时更改,但是索引节点是唯一的。
类型:
- 磁盘文件:狭义的磁盘上存储的文件、数据文件、进程文件
- 设备文件:
- 特殊节点
inode有一个唯一的i_ino节点号。每个文件都有一个文件主,指文件的创建者,可改变。每个用户都有用户组,因此inode结构中应有i_uid、i_gid,指明身份权限。
inode中还有两个设备号i_dev和i_rdev,分别代表主设备号和从设备号。
节点的管理:
- 未使用索引节点的链表:用变量inode_unused表示。
- 已使用索引节点的链表
- 脏索引节点列表:Hash表。
5.1.3、Linux下的设备文件
- 字符设备
- 块设备
字符设备:以字节为单位逐个进行I/O操作的设备。不支持随机访问。
块设备:利用一块系统内存作为缓冲区。先缓冲区,后实际I/O操作。
5.2.1、不带缓存的文件I/O操作
1、文件描述符,非负整数。
2、open 和 close
#include <fcntl.h>
int open(const char *pathname, /*被打开的文件名*/
const char flags, /*文件打开的方式*/
int perms) /*被打开文件的存取权限,八进制*/
// 成功:返回文件描述符
// 失败:-1
#include <unistd.h>
int close(int fd); /*fd为文件描述符*/
// 成功:0
// 出错:-1
// 调用示例
int fd = open("/tmp/hello.c", O_WRONLY | O_CREATE | O_TRUNC, 0666);
close(fd);
3、read、write和lseek
#include <unistd.h>
size_t read/write (int fd, /*文件描述符*/
void *buf, /*缓冲区*/
size_t count); /*字节数*/
// 成功:实际字节数
// 已达文件尾(读):0
// 出错:1
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fd, /*描述符*/
off_t offset, /*偏移量,单位是字节,可正可负*/
int whence); /*基点*/
// 成功:文件的当前位置
// 出错:-1
char buf_write[] = "abcdefg";
char buf_read[10];
int fd = open("/tmp/hello.c", O_CREAT | O_TRUNC | O_RDWR, 0666);
int len = strlen(buf_write) + 1;
int size = write(fd, buf_write, len);
lseek(fd, 0, SEEK_SET);
size = read(fd, buf_read, len);
4、fcntl
复制现有描述符,获得/设置文件描述符标记,获得/设置文件状态标记。获得/设置异步I/O所有权及获得/设置记录锁。
文件锁包括建议性锁和强制性锁。
锁函数,lockf 和 fcntl,lockf施加建议性锁,而fcntl不仅可以施加建议性锁,还可以施加强制性锁。还可以实现记录锁。
记录锁:
- 读取锁(共享锁)
- 写入锁(排斥锁)
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, /*文件描述符*/
int cmd, /*不同的命令*/
struct flock *lock); /*设置记录锁的具体状态*/
// 成功:0
// 出错:-1
/*lock结构*/
struct flock
{
short l_type;
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
}
加锁整个文件常用的方法是将l_start说明为0,l_where说明为SEEK_SET,l_len说明为0。
5、select
fcntl函数解决了文件的共享问题,接下来处理I/O多路复用。
I/O处理模型:
- 阻塞I/O模型:进程睡眠
- 非阻塞模型:不睡眠,返回一个错误。通常采用轮询处理多路I/O,耗费CPU资源。
- I/O多路复用模型:把需要处理的多路I/O交由内核监控后睡眠。就绪时,被唤醒处理。
- 信号驱动I/O模型:不轮询也不阻塞。I/O就绪时,内核发送SIGIO信号给进程,进程在信号处理函数中完成操作。
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
int select(int numfds, /*需要检查的最大的文件描述符+1*/
fd_set * readfds, /*监视的读文件描述符集合*/
fd_set * writefds, /*监视的写文件描述符集合*/
fd_set * exeptfds, /*监视的异常处理文件描述符集合*/
struct timeval *timeout); /*等待时间*/
// 成功:已就绪的文件描述符的数量
// 出错:-1
struct timeval
{
long tv_sec;
long tv_usec;
}
5.2.2 标准I/O
前述文件的I/O都是基于文件描述符的不带缓存的操作。标准I/O都是基于流的,符合ANSI C的操作。
又称高级磁盘I/O,是在文件I/O的基础上进行了封装。
- 全缓冲
- 行缓冲
- 无缓冲
1、FILE指针
标准I/O为每个打开的文件在内存中开辟一个区域,用来存放文件的相关信息。这些信息被保存在一个由系统定义的结构体类型FILE中。在标准I/O中,流(stream)用FILE *来描述,所有的操作都是围绕流来进行的。
in <stdio.h>
typedef struct _IO_FILE FILE;
in <libio.h>
struct _IO_FILE
{
int _flags;
...
int _fileno;
...
};
标准I/O库中预定义了3个流:标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。当一个程序执行时,系统自动打开这3个流,并且可以在程序中直接使用。
2、打开流
打开文件有3个标准函数,分别为fopen、fdopen和freopen。
fopen指定打开文件的路径和模式;fdopen指定打开的文件描述符和模式;freopen还可指定特定的I/O流。
#include <stdio.h>
FILE * fopen(const char * path,
const char * mode);
FILE * fdopen(int fd,
const char * mode);
FILE * freopen(const char * path,
const char * mode,
FILE * stream);
// 成功:指向FILE的指针
// 失败:NULL
#include <stdio.h>
int fclose(FILE * stream);
// 成功:0
// 失败:EOF(-1)
4、按字符读/写文件
#include <stdio.h>
int fgetc(FILE * stream);
int getc(FILE * stream);
int getchar(void);
int fputc(int c, FILE * stream);
int putc(int c, FILE * stream);
int putchar(int c);
// 成功:字符
// 失败:EOF
5、按行读/写文件
#include <stdio.h>
char *fgets(char *s, int n, FILE *stream);
int fputs(const char *s, FILE *stream);
int puts(const char *s);
// fgets 成功:缓冲区地址,失败:NULL
// fputs 成功:字符串长度,失败:EOF
// puts 成功:字符串长度+1,失败:EOF
6、按指定格式读/写文件
通常处理二进制文件。
#include <stdio.h>
size_t fread/fwrite(void * ptr, /*缓冲区*/
size_t size, /*读取的记录大小*/
size_t nmemb, /*读取的记录数*/
FILE * stream); /*文件流*/
// 成功:实际对象数量
// 失败:EOF
7、刷新流
#include <stdio.h>
int fflush(FILE * stream);
// 成功:0
// 失败:-1
8、文件定位
#include <stdio.h>
void rewind(FILE * stream);
int fseek(FILE * stream,
long offset,
int whence);
long ftell(FILE * stream);
// fseek 成功:0,失败:-1
// ftell 成功:文件当前位置,失败:-1L
5.3、Linux下对文件和目录的操作
5.3.1、文件类型
- 目录文件(directory file):
- 普通文件(regular file):
- 字符设备文件(character device file):
- 块设备文件(block device file):
- FIFO:进程间通信
- 套接字(socket):
- 符号连接(symbolic link):
5.3.3、获取文件属性
stat/fstat/lstat函数
#include <sys/types/h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *filename, struct stat *buf); /*支持符号链接*/
struct stat
{
dev_t st_dev; // 文件所在的设备名称
ino_v 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; // 设备类型
off_t st_size; // 文件大小
blksize_t st_blksize; // 块大小
blkcnt_t st_blocks; // 块个数
time_t st_atime; // 上次访问时间
time_t st_mtime; // 上次修改时间
time_t st_ctime; // 上次改变状态时间
}
st_mode
// 成功:0
// 失败:-1,并设置errno
struct stat buf;
fstat("./data", &buf);
switch(buf.st_mode & S_IFMT)
{
case S_IFREG: printf("regular file\n"); break;
case S_IFDIR: printf("directory\n"); break;
default: printf("other file types\n); break;
}
5.3.4、修改文件访问权限
chmod/fchmod函数
#include <sys/types.h>
#include <sys/stat.h>
int chmod(const char *filename, mod_t mode);
int fchmod(int fd, mode_t mode);
// 成功:0
// 失败:-1,并设置errno
chmod("./data", S_IRWXU|S_IRGRP|S_IWGRP|S_IROTH);
5.3.5、创建目录
mkdir函数
#include <sys/stat.h>
int mkdir(const char *filename, mode_t mode);
// 成功:0
// 失败:-1,并设置errno
mkdir("mydir", 0755);
5.3.6、创建链接文件
#include <unistd.h>
int link(const char *path1, const char *path2); /*创建硬链接path2*/
int symlink(const char *path1, const char *path2); /*创建符号链接path2*/
// 成功:0
// 失败:-1,并设置errno
link("./data", "./data1");
symlink("./data", "./data2");
5.3.7、删除文件
unlink/remove函数
#include <unistd.h>
int unlink(const char *filename);
int remove(const char *filename);
// 成功:0
// 失败:-1,并设置errno
unlink("./data");
5.3.8、重命名文件
#include <unistd.h>
int rename(const char *old, const char *new);
// 成功:0
// 失败:-1,并设置errno
rename("./data_old", "./data_new");