顺序与随机读写
1.每个打开的文件都有一个与其相关的文件读写位置保存在文件表项中,用以记录从文件头开始计算的字节偏移量,文件读写位置通常是一个非负的整数,用off_t类型表示,在32位系统上被定义为long int,而在64位系统上则被定义为long long int
2.打开一个文件时,除非制定了O_APPEND(追加)标志,自动把读写位置偏移到文件尾,否则文件读写位置一律被设为0,即文件首字节的位置
3.每一次读写操作都从当前的文件读写位置开始,并根据所读写的字节数,同步增加文件读写位置,为下一次读写做好准备
4.因为文件读写位置是保存在文件表项而不是v节点中的,因此通过多次打开同一个文件得到多个文件描述符,各自拥有各自的文件读写位置
#include
off_t lseek(int fd,off_t offset,int whence);
功能:人为调整文件读写位置
参数:fd:文件描述符
offset:文件读写位置偏移字节数
whence:offset参数的偏移起点,可以如下取值
SEEK_SET -从文件头(首字节)开始
SEEK_CUR - 从当前位置(最后被读写字节的下一个字节)开始
SEEK_END - 从文件尾(最后一个字节的下一个字节)开始
返回值:成功返回调整后的读写位置,失败返回-1
5.lseek函数的功能仅仅是修改保存在文件表项中的文件读写位置,并不实际引发任何I/O动作
lseek(fd,-7,SEEK_CUR); //从当前位置向文件头偏移7字节
lseek(fd,0,SEEK_CUR); //返回当前文件读写位置
lseek(fd,0,SEEK_END); //返回文件总字节数,不是字节个数,比如,int a[10];返回的是字节个数不是元素个数,字节数是8,元素是2,写入位置其实是不记录\0的,比如write(fd,"hello world!",15)写入后,其读写位置在12而不是13,可用off_t pos = lseek(fd,0,SEEK_CUR);来看。
6.可以通过lseek函数将文件读写位置设到文件尾之后,在超过文件尾的位置上写入数据,将在文件中形成空洞,位于文件中但没有被写过的字节都被设置为0,数字0对应的字符是\0 文件空洞不占磁盘空间,但被计算在文件大小之内
//文件读写位置调整
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
int main(void){
//打开一个文件 此时文件读写位置为0
int fd = open("./lseek.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
//向文件中写入 hello world! 此时文件读写位置为 12
char* buf = "hello world!";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
off_t pos = lseek(fd,0,SEEK_CUR);
if(pos == -1){
perror("lseek");
return -1;
}
printf("%ld\n",pos);
//调整文件读写位置,到空格之后,会覆盖掉world!
if(lseek(fd,-6,SEEK_END) == -1){
perror("lseek");
return -1;
}
//再次向文件写入数据 linux!
buf = "linux!";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
//再再次调整读写位置
if(lseek(fd,8,SEEK_END) == -1){
perror("lseek");
return -1;
}
//再再次写入数据
buf = "<--this is a hole";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
close(fd);
return 0;
}
系统I/O与标准I/O
1,当系统调用函数被执行时,需要在用户态和内核态之间来回切换,因此频繁执行系统调用函数会严重影响性能
2.标准库做了必要的优化,内部维护一个缓冲区,只在满足特定条件时才将缓冲区与系统内核同步,借此降低执行系统调用的频率,减少进程在用户态和内核态之间来回切换的次数,提高运行性能
标准库函数:fopen open
fread read
fwrite write
fclose close
相同的内容 重复1000次写入,是用标准库快还是系统调用的快
vim stdio.c
//标准库
#include<stdio.h>
int main(void){
FILE* fp = fopen("./std.txt","w"); //返回的是文件指针
if(fp == NULL){
perror("fopen");
return -1;
}
for(int i = 0;i < 100000;i++){
fwrite(&i,sizeof(i),1,fp);
} //i地址,i大小,一次操作多大的存储区,1,往fp写
fclose(fp);
return 0;
}
vim sysio.h
//系统调用
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
int main(void){
int fd = open("./sys.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
for(int i = 0;i < 100000;i++){
write(fd,&i,sizeof(i));
}
close(fd);
return 0;
}
time ./stdio
time ./sysio
不是调用一次fwrite就要去调用底层的write,而是先写入一个临时缓冲区,写满或者写完了才会掉一次write,
文件描述符的复制
#include
int dup(int oldfd);
功能:复制文件描述符表的特定条目到最小可用项
参数:oldfd:源文件描述符
返回值:成功返回目标文件描述符,失败返回-1
1.dup函数将oldfd参数所对应的文件描述符表项复制到文件描述符第一个空闲项中,同时返回该参数对应的文件描述符。dup函数返回的文件描述符一定是调用进程当前未使用的最小文件描述符
2.dup函数只复制文件描述符表项,不复制文件表项和v节点,因此该函数所返回的文件描述符可以看做是参数文件描述符oldfd的副本,它们标识同一个文件表项
3.注意,当关闭文件时,即使是由dup函数产生的文件描述符副本,也应该通过close函数关闭,因为只有当关联与一个文件表项的所有文件描述符都被关闭了,该文件表项才会被摧毁,所有关联与一个v节点的所有文件表项都被销毁了,v节点才会被从内存中删除,因此从资源合理利用的角度讲,凡是明确不再继续使用的文件描述符,都应该尽可能及时地使用close函数关闭
4.dup函数究竟会把oldfd参数所对应地文件描述符表项,复制到文件描述符地什么位置,程序员是无法控制的,这完全由调用该函数时文件描述符表的使用情况决定,因此对该函数的返回值做任何约束性假设都是不严谨的
5.由dup函数返回的文件描述符与作为参数传递给该函数的文件描述符标识的是同一个文件表项,而文件读写位置是保存在文件表项中的,因此通过这些文件描述符中的任何一个,对文件进行读写或随机访问,都会影响通过其它文件描述符操作的文件读写位置。这与多次通过open函数打开同一个文件不同。
//文件描述符的复制
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
int main(void){
int fd = open("./dup.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
printf("fd == %d\n",fd);
//文件描述符的复制
int newfd = dup(fd);
if(newfd == -1){
perror("dup");
return -1;
}
printf("newfd == %d\n",newfd);
//通过fd 向文件写入hello world!
char* buf = "hello world!";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
//通多newfd修改文件读写位置
if(lseek(newfd,-6,SEEK_END) == -1){
perror("lseek");
return -1;
}
//再次通过fd写入linux!
buf = "linux!";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
close(fd);
close(newfd);
return 0;
}
#include
int dup2(int oldfd,int newfd);
功能:复制文件描述符表的特定条目到指定项
参数:oldfd:源文件描述符
newfd:目标文件描述符
返回值:成功返回目标文件描述符(newfd),失败返回-1.
dup2函数在复制由oldfd参数所标识的源文件描述符表项时,会先检查由newfd参数所标识的目标文件描述符表项是否空闲,若空闲则直接将前者复制给后者,否则会先将目标文件描述符newfd关闭,使之成为空闲项,再行复制
//文件描述符的复制
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
int main(void){
int fd = open("./dup.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
printf("fd == %d\n",fd);
//文件描述符的复制
//int newfd = dup(fd);
int newfd = dup2(fd,1);
//int newfd = fcntl(fd,F_DUPFD,1);
if(newfd == -1){
perror("dup");
return -1;
}
printf("newfd == %d\n",newfd);
//通过fd 向文件写入hello world!
char* buf = "hello world!";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
//通多newfd修改文件读写位置
if(lseek(newfd,-6,SEEK_END) == -1){
perror("lseek");
return -1;
}
//再次通过fd写入linux!
buf = "linux!";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
close(fd);
close(newfd);
return 0;
}
使用dup2后,由于文件描述符1是非常特殊的,输出,因此此时调用printf,是写入到dup.txt的,因此上文中的printf(newfd == 1);在dup.txt中,如果同时还想要printf,那么就把原来的1复制到5上,然后write
其实这也是输出重定向的一种方式,如果使用文件描述0作为newfd,那么键盘输入就是把输入字符串写入文件中了。
注意!!!!!!!!!!!!!!!!!!!!!!!
这里如果你修改一下代码,把第一条printf注释掉,你会发现,我的printf("newfd == ")这一条语句没有出现在txt中,这是因为输出缓冲区的问题,printf会先放放入缓冲区中,但是程序执行到close后,会自动关闭缓冲区清楚其中数据,因此,在开头加上setbuf(stdout,NULL)或者干脆close(1);都有关闭缓冲区的效果,此时再度运行则txt中又出现 newfd == 1
文件控制
fcnt:垃圾箱函数,有很多边角功能,
#include
int fcnt(int oldfd,F_DUPFD,int newfd);
功能:复制文件描述符
参数:oldfd:源文件描述符
newfd:目标文件描述符
返回值:成功返回目标文件描述符(可能是newfd),失败返回-1
fcntl函数在复制由oldfd参数所标识的源文件描述符表项时,会先检查由newfd参数所标识的目标文件描述符表项是否空闲,所空闲则直接将前者复制给后者,否则并不会将其关闭,而是另外找一个空闲的文件描述符作为复制目标。
cmd接不同的宏,有不同功能; ... 是不定长参数
//文件描述符的复制
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
int main(void){
int fd = open("./dup.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
printf("fd == %d\n",fd);
//文件描述符的复制
//int newfd = dup(fd);
//int newfd = dup2(fd,1);
int newfd = fcntl(fd,F_DUPFD,1);
if(newfd == -1){
perror("dup");
return -1;
}
printf("newfd == %d\n",newfd);
//通过fd 向文件写入hello world!
char* buf = "hello world!";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
//通多newfd修改文件读写位置
if(lseek(newfd,-6,SEEK_END) == -1){
perror("lseek");
return -1;
}
//再次通过fd写入linux!
buf = "linux!";
if(write(fd,buf,strlen(buf)) == -1){
perror("write");
return -1;
}
close(fd);
close(newfd);
return 0;
}