3.1 Linux中的文件IO (7、8节)

来源:互联网 发布:复合vb 编辑:程序博客网 时间:2024/06/05 21:14

3.1.7.linux系统如何管理文件

3.1.7.1、硬盘中的静态文件和inode(i节点)

(1)文件平时都在存放在硬盘中的,硬盘中存储的文件以一种固定的形式存放的,我们叫静态文件。
(2)一块硬盘中可以分为两大区域:一个是硬盘内容管理表项,另一个是真正存储内容的区域。操作系统访问硬盘时是先去读取硬盘内容管理表,从中找到我们要访问的那个文件的扇区级别的信息,然后再用这个信息去查询真正存储内容的区域,最后得到我们要的文件。
(3)操作系统最初拿到的信息是文件名,最终得到的是文件内容。第一步就是去查询硬盘内容管理表,这个管理表中以文件为单位记录了各个文件的各种信息,每一个文件有一个信息列表(我们叫inode,i节点,其实质是一个结构体,这个结构体有很多元素,每个元素记录了这个文件的一些信息,其中就包括文件名、文件在硬盘上对应的扇区号、块号那些东西·····)
强调:硬盘管理的时候是以文件为单位的,每个文件一个inode,每个inode有一个数字编号,对应一个结构体,结构体中记录了各种信息。
(4)联系平时实践,大家格式化硬盘(U盘)时发现有:快速格式化和底层格式化。快速格式化非常快,格式化一个32GB的U盘只要1秒钟,普通格式化格式化速度慢。这两个的差异?其实快速格式化就是只删除了U盘中的硬盘内容管理表(其实就是inode),真正存储的内容没有动。这种格式化的内容是有可能被找回的。

3.1.7.2、内存中被打开的文件和vnode(v节点)

(1)一个程序的运行就是一个进程,我们在程序中打开的文件就属于某个进程。每个进程都有一个数据结构用来记录这个进程的所有信息(叫进程信息表),表中有一个指针会指向一个文件管理表,文件管理表中记录了当前进程打开的所有文件及其相关信息。文件管理表中用来索引各个打开的文件的index就是文件描述符fd,我们最终找到的就是一个已经被打开的文件的管理结构体vnode
(2)一个vnode中就记录了一个被打开的文件的各种信息,而且我们只要知道这个文件的fd,就可以很容易的找到这个文件的vnode进而对这个文件进行各种操作。

3.1.7.3、文件与流的概念

(1)流(stream)对应自然界的水流。文件操作中,文件类似是一个大包裹,里面装了一堆字符,但是文件被读出/写入时都只能一个字符一个字符的进行,而不能一股脑儿的读写,那么一个文件中N多的个字符被挨个一次读出/写入时,这些字符就构成了一个字符流。
(2)流这个概念是动态的,不是静态的。

(3)编程中提到流这个概念,一般都是IO相关的。所以经常叫IO流。文件操作时就构成了一个IO流。

3.1.8.lseek详解

3.1.8.1、lseek函数介绍

(1)文件指针:当我们要对一个文件进行读写时,一定需要先打开这个文件,所以我们读写的所有文件都是动态文件。动态文件在内存中的形态就是文件流的形式。
(2)文件流很长,里面有很多个字节。那我们当前正在操作的是哪个位置?GUI模式下的软件用光标来标识这个当前正在操作的位置,这是给人看的。
(3)在动态文件中,我们会通过文件指针来表征这个正在操作的位置。所谓文件指针,就是我们文件管理表这个结构体里面的一个指针。所以文件指针其实是vnode中的一个元素。这个指针表示当前我们正在操作文件流的哪个位置。这个指针不能被直接访问,linux系统用lseek函数来访问这个文件指针。
(4)当我们打开一个空文件时,默认情况下文件指针指向文件流的开始。所以这时候去write时写入就是从文件开头开始的。write和read函数本身自带移动文件指针的功能,所以当我write了n个字节后,文件指针会自动向后移动n位。如果需要人为的随意更改文件指针,那就只能通过lseek函数了
(5)read和write函数都是从当前文件指针处开始操作的,所以当我们用lseek显式的将文件指针移动后,那么再去read/write时就是从移动过后的位置开始的。
(6)回顾前面一节中我们从空文件,先write写了12字节,然后read时是空的(但是此时我们打开文件后发现12字节确实写进来了)。

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <string.h>int main(int argc, char *argv[]){int fd = -1;// fd 就是file descriptor,文件描述符char buf[100] = {0};char writebuf[20] = "l love linux";int ret = -1;// 第一步:打开文件fd = open("a.txt", O_RDWR);if (-1 == fd)// 有时候也写成: (fd < 0){perror("文件打开错误");_exit(-1);}else{printf("文件打开成功,fd = %d.\n", fd);}ret = lseek(fd, 3, SEEK_SET);printf("lseek, ret = %d.\n", ret);#if 0// 第二步:读写文件// 写文件ret = write(fd, writebuf, strlen(writebuf));if (ret < 0){perror("write失败");_exit(-1);}else{printf("write成功,写入了%d个字符\n", ret);}#endif#if 1// 读文件ret = read(fd, buf, 20);if (ret < 0){printf("read失败\n");_exit(-1);}else{printf("实际读取了%d字节.\n", ret);printf("文件内容是:[%s].\n", buf);}#endif// 第三步:关闭文件close(fd);_exit(0);}

3.1.8.2、用lseek计算文件长度

(1)linux中并没有一个函数可以直接返回一个文件的长度。但是我们做项目时经常会需要知道一个文件的长度,怎么办?自己利用lseek来写一个函数得到文件长度即可。

#include <string.h>int cal_len(const char *pathname){int fd = -1;// fd 就是file descriptor,文件描述符int ret = -1;// 第一步:打开文件fd = open(pathname, O_RDONLY);if (-1 == fd)// 有时候也写成: (fd < 0){perror("文件打开错误");return -1;}//else//{//printf("文件打开成功,fd = %d.\n", fd);//}// 此时文件指针指向文件开头// 我们用lseek将文件指针移动到末尾,然后返回值就是文件指针距离文件开头的偏移量,也就是文件的长度了ret = lseek(fd, 0, SEEK_END);return ret;}int main(int argc, char *argv[]){int fd = -1;// fd 就是file descriptor,文件描述符int ret = -1;if (argc != 2){printf("usage: %s filename\n", argv[0]);_exit(-1);}printf("文件长度是:%d字节\n", cal_len(argv[1]));return 0;}

3.1.8.3、用lseek构建空洞文件

(1)空洞文件就是这个文件中有一段是空的。
(2)普通文件中间是不能有空的,因为我们write时文件指针是依次从前到后去移动的,不可能绕过前面直接到后面。
(3)我们打开一个文件后,用lseek往后跳过一段,再write写入一段,就会构成一个空洞文件。
(4)空洞文件方法对多线程共同操作文件是及其有用的。有时候我们创建一个很大的文件,如果从头开始依次构建时间很长。有一种思路就是将文件分为多段,然后多线程来操作每个线程负责其中一段的写入。

#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <string.h>int main(int argc, char *argv[]){int fd = -1;// fd 就是file descriptor,文件描述符char buf[100] = {0};char writebuf[20] = "abcd";int ret = -1;// 第一步:打开文件fd = open("123.txt", O_RDWR | O_CREAT);if (-1 == fd)// 有时候也写成: (fd < 0){perror("文件打开错误");_exit(-1);}else{printf("文件打开成功,fd = %d.\n", fd);}ret = lseek(fd, 10, SEEK_SET);printf("lseek, ret = %d.\n", ret);#if 1// 第二步:写文件// 写文件ret = write(fd, writebuf, strlen(writebuf));if (ret < 0){perror("write失败");_exit(-1);}else{printf("write成功,写入了%d个字符\n", ret);}#endif#if 1// 读文件ret = read(fd, buf, 20);if (ret < 0){printf("read失败\n");_exit(-1);}else{printf("实际读取了%d字节.\n", ret);printf("文件内容是:[%s].\n", buf);}#endif// 第三步:关闭文件close(fd);_exit(0);}




原创粉丝点击