1. 关于unix文件结构
在unix/linux文件系统中,一切皆是文件,目录是文件,设备是文件,文件是文件......文件需要有文件的各项属性,在unix中,可以使用stat函数族来获取文件属性。使用stat函数获取的节点文件存储在struct stat *buf指向的内存中。
#include <sys/stat.h>
int
fstat(int fildes, struct stat *buf);
int
lstat(const char *restrict path, struct stat *restrict buf);
int
stat(const char *restrict path, struct stat *restrict buf);
int
fstatat(int fd, const char *path, struct stat *buf, int flag);
struct stat { /* when _DARWIN_FEATURE_64_BIT_INODE is NOT defined */
dev_t st_dev; /* device inode resides on */
ino_t st_ino; /* inode's number */
mode_t st_mode; /* inode protection mode */
nlink_t st_nlink; /* number of hard links to the file */
uid_t st_uid; /* user-id of owner */
gid_t st_gid; /* group-id of owner */
dev_t st_rdev; /* device type, for special file inode */
struct timespec st_atimespec; /* time of last access */
struct timespec st_mtimespec; /* time of last data modification */
struct timespec st_ctimespec; /* time of last file status change */
off_t st_size; /* file size, in bytes */
quad_t st_blocks; /* blocks allocated for file */
u_long st_blksize;/* optimal file sys I/O ops blocksize */
u_long st_flags; /* user defined flags for file */
u_long st_gen; /* file generation number */
};
这里需要简单介绍以下几个struct stat元素和相关函数,实际使用时可以使用man查看具体使用方法,为了节约时间和保持思维连贯性,在此不做赘述。
a. st_mode
表示文件类型和访问权限
umask 设置创建文件时默认的permission
chmod
fchmod
chown
fchown
lchown
b. st_ino
st_ino表示一个inode节点号,在unix/linux系统中,我们为每一个文件分配一个inode指针,这个inode指针指向文件存储的一组内存块blocks,如果一个block是4k,而我们文件中存了7k的文件,那么操作系统就会给这个文件分配2个block和一个inode,inode就是这两个block的索引。
c. st_nlink
关于链接计数需要具体分文件和目录来讨论。对于文件来说,st_nlink表示指向该文件inode的目录项数,也可以称之为硬链接数(hard link),这意味着什么呢?假设我在/usr/bin/目录下有一个文件f_nlink,f_nlink的inode号是1553,这个时候/usr/bin目录项中必然有一条目录项是(1553,f_nlink),此时f_nlink的st_nlink就是1;如果使用ln命令给f_nlink创建一个硬链接到/usr/目录下,则在/usr/目录中也必然有一条目录项是(1553,f_link),/usr/和/usr/bin/都有目录想指向1553这个inode,此时f_nlink的st_nlink是2(硬链接数是2);此时如果在/usr/bin/目录下删除f_nlink(使用unlink或者rm),则f_nlink的链接数减少1,但是inode仍然存在,且从/usr/目录下也可以访问f_nlink文件,只有当链接数减为0时才会删除f_nlink的inode,彻底删除文件释放内存。这里又需要扩展开来聊聊软链接了,对于软连接来说,如果本体被删除,则副本就不能访问本体。
对于目录来说,一个目录项下有几个文件或者子目录,则链接数就是几(包括.和..)。
硬链接有两个限制:(1)不允许给目录创建硬链接,防止产生环路,遍历目录时陷入死循环;(2)只有在同一个文件系统中的文件之间才能创建硬链接,这也很好理解,只有在同一个文件系统中才能访问inode
2. 关于unix文件i/o流
在unix系统调用层面我们使用read和write来读写文件。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
参数:
fd: 将要读取的数据的文件描述符
buf: 所要读到数据的内存缓冲的缓冲区指针
nbytes: 读取的数据大小
返回值:
返回读取的数据大小,如果读到文件末尾则返回0;读取失败返回-1。
ssize_t write(int fd, const void *buf, size_t nbytes);
返回值:
写入文件成功则返回写的字节数;失败返回-1。
当频繁的使用read和write系统调用读写文件,进程就需要不断的在内核态和用户态之间转换,系统开销太大;因此流(stream)就出现了,流的结构FILE了几个部分:文件描述符fd,指向该流缓冲区指针,缓冲区大小,当前缓冲区中的字符数,出错标志等。我们通常使用文件指针FILE*来表示流,个人认为FILE*的使用是对文件系统调用函数的又一层封装。总结几个有关流的unix函数如下:
a. 打开流
#include <stdio.h>
FILE *
fdopen(int fildes, const char *mode);
FILE *
fopen(const char *restrict filename, const char *restrict mode);
FILE *
freopen(const char *restrict filename, const char *restrict mode,
FILE *restrict stream);
b. 关闭流
#include <stdio.h>
int
fclose(FILE *stream);
void
fcloseall(void);
c. 读写流
读写流也分几种方法:
无缓冲,即每次只读或者写一个字符的i/o流。
#include <stdio.h>
int
fgetc(FILE *stream);
int
getc(FILE *stream);
int
getc_unlocked(FILE *stream);
int
getchar(void);
int
getchar_unlocked(void);
int
getw(FILE *stream);
#include <stdio.h>
int
fputc(int c, FILE *stream);
int
putc(int c, FILE *stream);
int
putc_unlocked(int c, FILE *stream);
int
putchar(int c);
int
putchar_unlocked(int c);
int
putw(int w, FILE *stream);
行缓冲,即每次读或者写一行字符的i/o流,每行以一个换行符\r终止。
#include <stdio.h>
char *
fgets(char * restrict str, int size, FILE * restrict stream);
char *
gets(char *str);
#include <stdio.h>
int
fputs(const char *restrict s, FILE *restrict stream);
int
puts(const char *s);
全缓冲,也称直接i/o,每次i/o操作读或者写某个数量的对象。
#include <stdio.h>
size_t
fread(void *restrict ptr, size_t size, size_t nitems,
FILE *restrict stream);
size_t
fwrite(const void *restrict ptr, size_t size, size_t nitems,
FILE *restrict stream);
与文件描述符0,1,2分别指标准输入,标准输出,标准错误文件相对,流也有stdin,stdout,stderr
3. 进程与文件
在一个进程中打开文件,则进程表项中有一个分配一个对应的文件描述符fd和其指针映射;指针指向一个文件表,每当打开一个文件就会分配一个文件表,文件表中存储了文件状态标志(只读,只写,可读可写),当前文件偏移量,v节点指针;v节点指针指向v节点信息,v节点又包含了inode。
a. 使用dup/dup2可以复制fd或者改变fd,这样可以创造进程表中两个fd指向同一个打开文件的情况;
b. 可以打开一个文件两次,则有两个进程表项中分别有fd指向文件表,每个文件表都有其自己的文件偏移量,涉及到同步问题;
c. 一个进程表中,两个fd分别指向不同的文件。