Linux应用编程和网络编程(1、文件IO_文件管理)

三、Linux系统之文件管理

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

2、内存中被打开的文件和vnode(v节点)
(1)一个程序的运行就是一个进程,我们在程序中打开的文件就属于某个进程。每个进程都有一个数据结构用来记录这个进程的所有信息,叫做进程信息表。表中有一个指针会指向一个文件管理表,文件管理表中记录了当前进程打开的所有文件及其相关信息,文件管理表中用来索引各个打开的文件的index就是文件描述符fd,我们最终找到的就是一个已经被打开的文件的管理结构体vnode。
(2)一个vnode中就记录了一个被打开的文件的各种信息,我们只要知道这个文件的fd,就可以很容易的找到这个文件的vnode进而对这个文件进行各种操作。
(3)open在打开文件的时候在文件管理表中增加了一个表象,表象里的内容就是一个结构体vnode,反馈一个数字作为文件描述符,供我们使用。

3、文件与流的概念
(1)流(stream)对应自然界的水流。文件操作中,文件类似是一个大包裹,里面装了一堆字符,但是文件被读出/写入时都只能一个字符一个字符的进行。那么一个文件中N多的个字符被挨个一次读出/写入时,这些字符就构成了一个字符流。
(2)流这个概念是动态的,不是静态的。
(3)编程中提到流这个概念,一般都是IO相关的。所以经常叫IO流。文件操作时就构成了一个IO流。

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

off_t lseek(int fd, off_t offset, int whence);
       SEEK_SET :The offset is set to offset bytes.
       SEEK_CUR : The offset is set to its current location plus offset bytes.
       SEEK_END :The offset is set to the size of the file plus offset bytes.

(5)用lseek计算文件长度

#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;
	int ret = -1;
	char writebuf[] = "woainiy";
	
	if (argc != 2)
	{
		printf("usage: %s filename\n", argv[0]);
		_exit(-1);
	}
	
	fd = open(argv[1], O_RDWR);
	
	if (-1 == fd)		
	{
		perror("文件打开错误");
		return -1;
	}
	
	ret = lseek(fd, 0, SEEK_END);// 文件末尾偏移0字节

	printf("文件长度是:%d字节\n", ret);
	
	return 0;
}

//输出:文件长度是:8字节

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

#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;		
	char buf[100] = {0};
	char writebuf[20] = "abcd";
	int ret = -1;
	
	fd = open("123.txt", O_RDWR | O_CREAT);
	if (fd < 0)		
	{
		perror("文件打开错误");
		_exit(-1);
	}
	else
	{
		printf("文件打开成功,fd = %d.\n", fd);
	}
	
	ret = lseek(fd, 10, SEEK_SET);// 文件开始偏移10字节
	printf("lseek, ret = %d.\n", ret);
	
	ret = write(fd, writebuf, strlen(writebuf));
	if (ret < 0)
	{
		perror("write失败");
		_exit(-1);
	}
	else
	{
		printf("write成功,写入了%d个字符\n", ret);
	}
	close(fd);
	return 0;
}

//输出:
//文件打开成功, fd=3
//Iseek, ret=10
//write成功, 写入了4个字符

5、多次打开同一文件与O_APPEND
(1)重复打开同一文件读取,会分别读。使用open两次打开同一个文件时,fd1和fd2对应的文件指针是2个独立的指针。文件指针是包含在动态文件的文件管理表中。
先建立一个文件a.txt,写入abcdefghijklmn

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

int main(void)
{
	int fd1 = -1;
	int fd2 = -1;
	int ret = -1;
	char buf[100] = {0};
	
	fd1 = open("a.txt", O_RDWR);
	fd2 = open("a.txt", O_RDWR);
	if ((fd1 < 0) || (fd2 < 0))
	{
		printf("error\n");
		return -1;
	}

	while (1)
	{
		memset(buf, 0, sizeof(buf));
		ret = read(fd1, buf, 2);
		if (ret < 0)
		{
			printf("error\n");
			return -1;
		}
		else
		{
			printf("%s\n", buf);
		}
		
		sleep(1);	//睡眠一段时间
		
		memset(buf, 0, sizeof(buf));
		ret = read(fd2, buf, 2);
			if (ret < 0)
		{
			printf("error\n");
			return -1;
		}
		else
		{
			printf("%s\n", buf);
		}
	}
	close(fd1);
	close(fd2);
	return 0;
}
//输出:
//ab
//ab
//cd
//cd
//ef
//ef
//gh

(2)重复打开同一文件写入
默认结果是分别写,fd2的内容会覆盖掉fd1

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

int main(void)
{
	int fd1 = -1;
	int fd2 = -1;
	int ret = -1;
	
	fd1 = open("a.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
	fd2 = open("a.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);

	if ((fd1 < 0) || (fd2 < 0))
	{
		printf("error\n");
		return -1;
	}

	while (1)
	{
		ret = write(fd1, "ab", 2);
		if (ret < 0)
		{
			printf("error\n");
			return -1;
		}
		sleep(1);
		ret = write(fd2, "cd", 2);
		if (ret < 0)
		{
			printf("error\n");
			return -1;
		}
	}
	close(fd1);
	close(fd2);
	return 0;
}

//输出:
//cdcdcdab

若想实现接续读,在open时加O_APPEND标志即可
fd1 = open(“a.txt”, O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0666);
fd2 = open(“a.txt”, O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0666);
结果为:

abcdabcdabcdbcdab

(3)O_APPEND为什么能够将分别写改为接续写?
核心的东西是文件指针,分别写的内部原理就是2个fd拥有不同的文件指针,并且彼此只考虑自己的位移。但是O_APPEND标志可以让write和read函数内部多做一件事情,就是移动自己的文件指针的同时也去把别人的文件指针同时移动。(即使加了O_APPEND,fd1和fd2还是各自拥有一个独立的文件指针,但是这两个文件指针关联起来了,一个动了会通知另一个跟着动)
(4)O_APPEND对文件指针的影响,对文件的读写是原子的。
原子操作的含义是:整个操作一旦开始是不会被打断的,必须直到操作结束其他代码才能得以调度运行,这就叫原子操作。每种操作系统中都有一些机制来实现原子操作,以保证那些需要原子操作的任务可以运行。
6、文件共享的3种实现方式
(1)文件共享的核心是实现多个文件描述符指向同一个文件。
(2)常见的有3种文件共享的情况:
第一种是同一个进程中多次使用open打开同一个文件
第二种是在不同进程中去分别使用open打开同一个文件(这时候因为两个fd在不同的进程中,所以两个fd的数字可以相同也可以不同)
第三种情况是后面要学的,linux系统提供了dup和dup2两个API来让进程复制文件描述符。
(3)我们分析文件共享时的核心关注点在于:分别写/读还是接续写/读

7、dup和dup2函数介绍

	int dup(int oldfd);
    int dup2(int oldfd, int newfd);

(1)dup系统调用对fd进行复制,会返回一个新的文件描述符(譬如原来的fd是3,返回的就是4)
(2)dup系统调用特点:不能指定复制后得到的fd的数字是多少,而是由操作系统内部自动分配的,分配原则为现有最小的fd。
(3)dup返回的fd和原来的oldfd都指向oldfd打开的那个动态文件,操作这两个fd实际操作的都是oldfd打开的那个文件。实际上构成了文件共享。
(4)dup和close配合进行文件的重定向

	fd1 = open("a.txt", O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0666);

	if ((fd1 > 0))
	{
		close(1);
		fd2 = dup(fd1);
		printf("error\n");
		return -1;
	}

让第三行代码出错,错误信息会重定向到a.txt中。
的实现原理,其实就是利用open+close+dup,open打开一个文件2.txt,然后close关闭stdout,然后dup将1和2.txt文件关联起来即可。
(5)使用dup2复制的新的符号描述符,在对同一文件交叉写入的时候可以实现接续写。
8、fcntl函数的简单用法
int fcntl(int fd, int cmd, … /* arg */ );
不同的cmd使fcntl具有不同的功能,比如F_DUPFD这个cmd的作用就是复制复制文件描述符,作用和dup/dup2相似。和dup2的区别在于,F_DUPFD不能任意分配fd,分配的文件描述符必须大于等于 arg。

9、标准IO库
(1)标准IO是C库函数,而文件IO是linux系统的API。
C语言库函数是由API封装而来的,库函数内部也是通过调用API来完成操作的,但是库函数因为多了一层封装,所以比API要更加好用一些。
API在不同的操作系统之间是不能通用的,但是C库函数在不同操作系统中几乎是一样的。所以C库函数具有可移植性而API不具有可移植性。
从性能上和易用性上看,C库函数一般要好一些。譬如IO,文件IO是不带缓存的,而标准IO是带缓存的,因此标准IO比文件IO性能要更高。
(2)常用的标准IO库函数有:fopen、fclose、fwrite、fread、ffulsh、fseek
(3)一个简单的标准IO读写文件实例

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	FILE *fp;
	int len;
	int buf[100] = {0};
	
	// 打开并创建一个文件
	//fp = fopen("1.txt", "w+");
	fp = fopen("1.txt", "r+");
	if (fp == NULL)
	{
		perror("fopen");
		exit(-1);
	}

#if 0	
	// 写文件
	len = fwrite("12345", sizeof(char), 2, fp);
	if (0 == len)
	{
		perror("fwrite");
		exit(-1);
	}
	printf("write the len is %d\n", len);
#endif	

#if 1	
	//  读文件
	len = fread(buf, sizeof(char), 8, fp);
	if (0 == len)
	{
		perror("fread");
		exit(-1);
	}
	
	printf("read the len is %d\n", len);
#endif	
	// 关闭文件
	fclose(fp);
	
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值