硬盘最小单元是扇区(如512字节),然后扇区又组成块(如4k),一个扇区中不可存在两个文件。
一个硬盘分成两大区域:一个是硬盘内容管理表,另一个是真正存储的内容。操作系统访问硬盘时,先读取硬盘内容管理表,找到扇区级别的信息,然后找到真正存储内容的区域。
硬盘内容管理表——为每个文件的提供了一个inode结构体用于记录该文件的信息(如文件名、块号、扇区号等)。所谓快速格式化其实就是删除硬盘内容管理表,真正存储内容没动。
一个进程有一个进程信息表用来记录该进程的所有信息,这个进程信息表中有一个文件管理表(文件描述符表),该文件管理表中存储了文件描述符fd,我们可以通过该fd最终找到被打开文件的结构体vnode。
所以静态文件对应的是inode,动态文件对应的是vnode。
流是动态的概念,一般都是IO相关的,所以文件操作时就构成了一个IO流。一个进程的进程表中有一个文件描述符表,每个fd对应一个不同的文件表,每个文件表中有一个操作指针,该指针即是用于指示操作vnode的io流的位置。
当write read操作时,该指针会自动加1,要想手动修改该指针位置可以使用lseek函数。
由于每个fd对应一个独立的文件指针,所以在一个进程中打开同一个文件两次,然后往里写数据是分别写的,会出现数据覆盖的现象。解决办法是open时添加一个O_APPEND标志,该标志可以让一个文件指针移动的时候,通知其它指针同步移动。这样可以实现接续写文件的功能。
文件共享核心是多个文件描述符指向同一个文件。实现方式有:
1.同一个进程多次open同一个文件,此时fd不同,且指向不同的文件表。
2.不同进程open同一个文件,此时fd可能相同。该fd指向不同的文件表。
3.使用dup或dup2系统函数来复制fd。此时两个fd指向同一个文件表。
off_t lseek(int fd, off_t offset, int whence);
offset是偏移值,whence是相对位置(SEEK_SET文件头 SEEK_CUR当前指针 SEEK_END文件尾),返回值是实际移动的偏移值。
lseek函数构建空洞文件(文件有一段是空的),打开一个文件后用lseek往后跳一段然后写内容即可,该空洞文件的大小包括前面空洞的部分。在多线程共同操作一个文件中经常用到lseek函数。
int dup(int oldfd);
int dup2(int oldfd, int newfd);
dup由系统自动分配一个描述符(返回一个新的描述符),dup2可以由用户指定一个描述符(该值如果被占用,或者超出范围,则返回-1报错;如果正确则返回newfd的值)。
int fcntl(int fd, int cmd, ... /* arg */ );
fcntl函数时一个多功能文件管理的工具箱,cmd表示要进行哪个命令操作(使用哪个工具)。变参arg是给cmd命令传递参数的。
1.fcntl实现dup功能
F_DUPFD这个cmd类似于dup2,该命令可以分配一个arg描述符,不同处在于当arg被占用时,可以分配一个比arg大的值而不报错。
fcntl(fd, F_DUPFD, 6);
2.fcntl对已经打开的文件添加非阻塞标志
如果在open之初忘了加O_NONBLOCK标志,则可以使用fcntl来添加该标志
flag = fcntl(fd, F_GETFL); //先获取标志
fcntl(fd, F_SETFL, flag | O_NONBLOCK); //再设置标志
注意:linux中不能修改文件权限,如O_RDWR O_RDONLY O_APPEND O_TRUNC等。
进程的进程表中的文件描述符表其实是一个数组,文件描述符对应该数组的下标(见上图),其值即为指向文件表的指针值。文件描述符是open时由操作系统自动分配的,一个进程在创建之初 “0 1 2”是固定的,分别对应“标准输入 标准输出 标准错误”。
举例:
int printf(const char *format, …);
int fprintf(FILE *stream, const char *format, …);
printf 其实就是输出到1标准输出的流中。
fprintf多了一个数据流参数,即可以指定输出到哪个描述符对应的流中。
我们可以通过close一个标准文件描述符如1(stdout),然后dup一个打开的文件描述符实现1描述符的重定位。此时,输出到stdout的信息会写到打开的文件中。
举例:
fd = open(“a.txt”, O_RDWR);
close(1); //关闭1描述符
fd2 = dup(fd); //此时fd2即为1描述符,该描述符指向a.txt
//...之后print输出的结果会显示到a.txt中