man 2 是系统调用 ; man 3 是库函数
一般是指设备,
无O_SYNC时,write将内容写入缓冲区即可返回;有O_SYNC时,write阻塞等待底层完成写入才返回。
errno=errnumber,linux的错误代码,表示函数哪里错了;errno是由OS来维护的一个全局变量,OS内部告诉上层调用者发生了一个什么样的错误;例如(-37);
注意:不是所有函数错误时都会返回errno,需要通过man查看“return value”的描述中是否包含“errno”
perror=print errorno,是一个函数
count表示想要读写的字节数,而返回值是实际完成读写的字节数。
count和阻塞+非阻塞结合起来:如果一个函数是阻塞式的,起始要读取30个字节,结果只有20个字节可读时函数就会被阻塞住,等待余下的10个可读。
当读取一个2MB的文件时,不可能把count设置成2*1024*1024,则需要把count设置成一个合适的值(2048或4096),然后通过多次读取来实现文件的完整读取。
文件IO是指open,close,read,write,等API函数构成的一套用来读写文件的体系,但这套体系的效率并不高。
应用层C语言库函数提供了一些文件读写的函数列表,称为标准IO(fopen,fclose,fread,fwrite),这些函数是由“文件IO”封装而来的,封装的目的是为了在应用层添加一个缓冲机制。
标准IO的buf==》文件IO的buf==》内核的buf==》硬盘
文件一般是以静态的形式存在硬盘(块设备)中;块(多个扇区组成)--扇区(512字节)--字节
硬盘分为两个区域:硬盘内容管理表区+数据存储区;操作系统访问硬盘的文件时,先查询硬盘内容管理表(文件信息列表,i节点,包括文件名+扇区号+块号);
每个文件对应一个iNode结构体,结构体中记录了文件信息。
快速格式化(快):只删除了硬盘内容管理表,但是真正的数据存储区并没有被删除,只是删了inode;
底层格式化(慢):内容管理表和数据存储区都被删除了;
一个运行的程序就是一个进程,在程序中打开一个文件就属于某个进程,每个进程都有一个数据结构用来记录该进程的所有信息(称为“进程信息表”),该进程信息表中有一个指针会指向一个“文件管理表”,文件管理表记录了当前进程打开的所有文件及其相关信息。文件管理表中用来索引各个打开的文件的index就是文件描述符fd,最终找到的就是一个已经被打开的文件的管理结构体vnode。
一个vnode记录了一个被打开的文件的各种信息,通过这个文件的fd就可以找到这个文件的vnode进而对这个文件进行各种操作。
流stream,文件是字符的合集,文件的读写只能一个一个字符的进行。文件读取或写入时,就形成了“字符流”。
编程中提到stream的概念,一般都是IO相关的,文件操作时就构成了一个IO流。
文件指针:当我们对一个文件进行读写时,一定操作的是动态文件!动态文件在内存中的形式是以“文件流”的形式存在的。
文件流很长,里面有很多字节,通过“文件指针”表示;文件指针是vnode中的一个元素,这个文件指针只能通过lseek这个函数进行操作。
打开一个空文件时,文件指针默认指向文件流的起始位置,通过lseek函数可以更改文件指针所指向文件流的位置。
read和write函数都是从当前文件指针处开始操作的,所以当我们用lseek将文件指针移动后,再去使用read或write操作时就是从移动后的位置开始的。
注:之前的例子,一个空文件先write,然后直接read读取;提示写成功了,但是读的内容确实空的。这正是因为write后文件指针后移的原故。
linux中没有一个库函数可以直接返回一个文件的长度,
空洞文件:一个文件的内容有一段是空的;我们打开一个文件后,用lseek
空洞文件的作用:多线程操作文件时特别有用。有时创建一个大文件(视频文件)时,将文件分成多段,多个线程同时分别操作其中的一段的写入。
一个进程中,两次打开同一个文件,然后分别进行读取,看结果会怎么样
使用open两次打开同一个文件时,fd1和fd2所对应的文件指针是不同的2个独立的指针。因为文件指针包含于动态文件的文件管理表中,因此可以看出linux系统的进程中不同的fd对应不同的文件管理表。
试验结果是:分别写,后边写的覆盖前边写的
加上O_APPEND后,重复打开同一个文件并写入,就变成了可以“接续写入”
O_APPEND为什么可以将分别写(覆盖)变成了接续写?
其核心在于文件指针:分别写时2个fd拥有不同的文件指针,并且独立位移。但是O_APPEND标志可以让write和read函数多做一件事就是移动自己文件指针的同时也移动别人的文件指针!
虽然加了O_APPEND,但是fd1和fd2还是拥有各自独立的文件指针,但是这两个文件指针相互关联了起来,一个动了另一个也会动。
原子操作:原子操作一旦开始是不会被打断的,必须直到操作结束其他代码才能运行。
每种操作系统中都有一些机制来实现原子操作。
O_APPEND对文件指针的影响就是其对文件的读写是“原子的”!
什么是同一个文件:是指静态文件是同一个,即同一个inode,同一个pathname;
同一个文件被多个独立的“读写体(可以理解为多个文件描述符)”去同时操作(一个打开后不关闭操作,另一个也打开操作)。
文件共享的意义:实现多线程同时操作一个大文件,以提升文件读写效率。
文件共享的核心就是怎么弄出来多个文件描述符指向同一个文件;
常见的3种文件共享情形:
第一种是同一个进程中多次使用open打开同一个文件。fd应该不同;
第二种是不同的进程中,分别open打开该文件(由于在不同的进程中fd可能相同也可能不同);
第三种是linux系统提供了dup和dup2两个API来让进程复制文件的描述符。
文件描述符的本质是一个数字,这个数字的本质是进程表中文件描述表的一个表项,进程通过文件描述符作为index去索引查表得到文件表指针,再间接访问得到这个文件对应的文件表。
文件描述符是操作系统按照一定规律自动分配的;
操作系统规定,fd依次从0开始增加,fd也是有最大限制的;
在早期的linux版本(0.1)中fd的最大值是20,表示一个进程最多允许打开20个文件;
linux中的“文件描述符表”是一个数组(不是链表),fd是index,文件表指针是value;
当我们去open时,内核会从文件描述符表中挑选一个未被“占用”的最小的一个fd返回给我们;文件描述符是循环使用的,被释放掉后可以继续使用。
fd中的0,1,2已经被操作系统占用了,用户程序 能够获得的最小的fd就是3;
fd0,fd1,fd2对应的三个文件分别是stdin,stdout,stderr,也就是标准输入,标准输出,标准错误;
标准输入一般对应的是键盘,fd0对应的一般是键盘;fd1一般对应的是显示器;
printf函数默认输出到stdout上了,stdio中还有一个函数叫做fprintf,这个函数就可以指定输出到哪个文件描述符中。
dup系统调用对fd进行复制,会返回一个新的文件描述符(比如原来的fd=3,返回的就是4);
dup系统调用有一个特点,就是自己不能指定复制后得到的fd的数字是多少,而是由操作系统内部自动分配的,分配的原则遵守fd分配的原则。
dup返回的fd和之前原来的fd,都指向之前原来的fd所指向的打开的动态文件,操作这两个fd实际操作的都是之前原来的fd打开的文件,以此构成了“文件共享”!
通过dup复制得到的fd和原来的fd同时向一个文件写入时,结果是“分别写”还是“接续写”呢???
dup不能指定复制回来得到的fd的数值,dup2系统调用可以解决这个问题;
之前说过fd0,fd1,fd2分别被标准输入(fd1),标准输出(fd2),标准错误(fd3)占用;
如果使用close(1)关闭标准输出,关闭后printf就无法输出内容到标准输出了,但是我们可以使用dup重新分配fd1,这是就把旧的fd所打开的文件和这个fd1标准输出通道绑定在了一起,这称为“标准输出的重定位”!
#include
#include
#include
#include
#include
#include
#include
#define FILENAME "1.txt"
int main(void)
{
int fd1=-1,fd2=-1;
fd1=open(FILENAME,O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd1<1)
{
perror("open");
return -1;
}
printf("fd1=%d.\n",fd1);
close(1); //1就是标注输出stdout,关闭了标准输出!
//复制文件描述符
fd2=dup(fd1); //因为close(1),所以这里fd2一定等于1
//这句话就是把1.txt文件和标准输出绑定起来了,所以以后输出到标准输出的信息可以在1.txt中查看
printf("fd2=%d.\n",fd2);
printf("可以看出我们配合使用close和dup进行文件的重定位操作!\n");
close(fd1);
return -1;
}
dup2和dup作用都是根据原来的文件描述符复制出一个新的文件描述符,但是dup2允许用户指定新的文件描述符的数字;
使用方法,看man手册原型即可。
dup2复制的文件描述符和原来的文件描述符,虽然数字不一样,但是这两个fd指向同一个打开的文件;
而且这两个文件描述符的write操作是“接续写”(aabb),不是“分别写”(bbbb);【实验验证】
#include
#include
#include
#include
#include
#include
#include
#define FILENAME "1.txt"
int main(void)
{
int fd1=-1,fd2=-1;
fd1=open(FILENAME,O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd1<1)
{
perror("open");
return -1;
}
printf("fd1=%d.\n",fd1);
/*
close(1); //1就是标注输出stdout,关闭了标准输出!
//复制文件描述符
fd2=dup(fd1); //因为close(1),所以这里fd2一定等于1
//这句话就是把1.txt文件和标准输出绑定起来了,所以以后输出到标准输出的信息可以在1.txt中查看
printf("fd2=%d.\n",fd2);
printf("可以看出我们配合使用close和dup进行文件的重定位操作!\n");
*/
fd2=dup2(fd1,16);
printf("fd2=%d.\n",fd2);
while(1)
{
write(fd1,"aa",2);
sleep(1);
write(fd2,"bb",2); //如果看到的是aabbaabb,则是接续写;
//如果看到的是bbbbbbbb,则是分别写!
}
close(fd1);
return -1;
}
linux中的shell命令执行后,打印结果都是默认进入到stdout的(本质上是这些命令ls,pwd的源码都是调用printf进行打印的),所以我们在linux的终端shell中直接看到命令执行的结果;
linux终端中一个重要的重定位符号“>”,该符号可以将ls,pwd等命令的结果重定位到一个文件中如2.txt;
“>”重定向的实现原理就是,利用open+close+dup实现的,open打开2.txt,然后close(1)关闭标准stdout,然后dup将fd=1与2.txt文件关联起来即可。
fcntl函数是一个多功能的文件管理工具,接收2个参数+1个变参。第一个参数是fd表示要操作哪个文件,第二个参数cmd表示要进行哪个命令操作,变参是用来传递参数的其配合cmd使用。
cmd的形式类似于F_XXX,不同的cmd的功能不同;不用熟悉全部的cmd的含义,只要弄明白一个案例就可以,其他以此类推,碰到不明白的fcntl的cmd时,查询man手册即可。
编程的思想:9分模仿+1分创新,第一次肯定是先看懂别人的代码然后去模仿。
F_DUPFD这个cmd的作用是复制文件描述符(作用类似与dup和dup2),其功能是从可用的fd数字列表中找一个比arg大或者和arg一样大的数字作为oldfd复制出来的newfd;F_DUPFD与dup2类似,但是不同的地方是dup2返回的是指定的newfd否则就会出错,但是F_DUPFD命令返回的是“大于等于”arg的那个数字。【arg为变参】
SYNOPSIS
#include
#include
int fcntl(int fd, int cmd, ... /* arg */ );
@fd:被复制的文件描述符,表示要操作哪个文件
@cmd:表示要进行什么操作
@arg:配合cmd使用
//fd2=dup2(fd1,16);
fd2=fcntl(fd1,F_DUPFD,6); //arg=6时,返回值是大于等于6的值
printf("fd2=%d.\n",fd2);
标准IO是C库函数,而文件IO是linux系统的API;
C语言库函数是由API封装而来的,库函数内部也是通过调用API来完成操作的;但是库函数多了一层封装,因此比API好用些。
库函数比API的一个优势是:API在不同的操作系统是不是通用的,但是C库函数在不同操作系统中作用几乎是一样的,所以C库函数具备可移植性而API不具备可移植性。
从性能和易用性上来看,C库函数一般要好一些,譬如文件IO是不带缓存的,但是标准IO带缓存,标准IO的性能要更高。
常见的标准IO库函数有:fopen, fclose, fread, fwrite, fflush;fseek!
#include
#include
#include
#define FILENAME "1.txt"
int main()
{
FILE *fp=NULL;
size_t len=-1;
int array[10]={1,2,3,4,5};
char buff[100]={0};
//fp=fopen(FILENAME,"W+");
fp=fopen(FILENAME,"r+");
if(NULL == fp)
{
perror("fopen");
exit(-1);
}
printf("fopen success.fp=%d.\n",fp);
//这里是写文件
//len=fwrite("abcde",1,5,fp);
//len=fwrite(array,sizeof(int),sizeof(array)/sizeof(arry[0]),fp);
//在这里读文件
memset(buff,0,sizeof(buff));
len=fread(buff,1,10,fp);
printf("len=%d.\n",len);
printf("buff is [%s].\n",buff);
fclose(fp);
return 0;
}