建设班级网站首页,施工合同电子版,wordpress ico图标,网络运营学校前言
1#xff09;在Linux下面#xff0c;一切皆文件#xff0c;文件文件内容文件属性
2#xff09;在访问文件是#xff0c;都得先将文件打开#xff0c;修改文件的本质其实还是通过执行代码的形式修改。
3#xff09;文件是被进程打开的#xff0c;一个进程可以打…前言
1在Linux下面一切皆文件文件文件内容文件属性
2在访问文件是都得先将文件打开修改文件的本质其实还是通过执行代码的形式修改。
3文件是被进程打开的一个进程可以打开多个文件操作系统会将打开的文件进行管理被打开的文件被加载到内存中称为内存文件未被打开的文件存放在磁盘上称为磁盘文件。
本文对进程和文件之间的关系以及操作系统如何对文件进行管理
一、C语言接口与重定向 在C语言中提供了打开文件接口fopen通过查询man手册其参数有两个第一个为想要打开的文件名第二个为打开方式打开方式有以下12中其中r表示读w表示写a表示追加b表示二进制表示读写只要带r的打开方式文件不存在就报错只要带w的打开方式文件不存在就创建。以w形式打开文件文件会被自动清空以a方式打开文件向文件内写入内容内容追加到文件末尾
打开方式含义文件不存在处理方式r只读报错w只写创建新文件a只写打开后文件指针指向末尾创建新文件rb只读打开二进制文件报错wb只写打开二进制文件创建新文件ab只写打开二进制文件后指针指向末尾报错r读写出错w读写创建新文件a读写打开后文件指针指向末尾创建新文件rb读写打开一个二进制文件报错wb读写打开一个二进制文件创建新文件ab读写打开二进制文件后指针指向末尾创建新文件
验证重定向的文件打开方式
[roothcss-ecs-e53a test5]# cat log.txt
hello world
[roothcss-ecs-e53a test5]# echo hellolog.txt
[roothcss-ecs-e53a test5]# cat log.txt
hello
[roothcss-ecs-e53a test5]# log.txt
[roothcss-ecs-e53a test5]# cat log.txt 使用重定向后发现原来的内容被覆盖如果左边为空重定向结果是文件内容被清空所以可以推断出重定向的打开文件方式是w
[roothcss-ecs-e53a test5]# echo hello world log.txt
[roothcss-ecs-e53a test5]# cat log.txt
hello world
[roothcss-ecs-e53a test5]# echo hello world log.txt
[roothcss-ecs-e53a test5]# echo hello world log.txt
[roothcss-ecs-e53a test5]# echo hello world log.txt
[roothcss-ecs-e53a test5]# echo hello world log.txt
[roothcss-ecs-e53a test5]# cat log.txt
hello world
hello world
hello world
hello world
hello world使用对文件重定向发现原来的内容没有被覆盖新的内容追加到了文件末尾所以推断出重定向的打开方式是a
二、C语言文件读写接口
fgets(char *s, int size, FILE *stream),整行读取,遇到回车换行或结尾停止.在文本方式时使用。且会在末尾追加一个\0占据一个字符所以实际读取字符数为size-1。
fread(void *ptr, size_t size, size_t nmemb, FILE *stream),读取二进制流参数为写入的指针读取单元字符大小字符数量目标流。末尾会插入一个
fputs(const char*str,FILE*stream)写入字符串流参数为字符串和需要写入的文件流。
fwrite(const void*ptr,size_t size,size_t nmemb,FILE* stream)写入二进制流参数为需要写入的内容基本单元的大小需要写入几个基本单元以及需要写入的文件流。返回值为写入了多少个基本单位
三、Linux下文件I/O接口
1.当前路径
进程在启动的时候会自动记录当前所在的路径可以在/proc/PID/cwd查询到当前进程所对应的当前路径 2.程序默认打开的文件流
在开发操作过程中开发人员
stdin:标准输入--键盘设备
stdout:标准输出--显示器设备
stderr:标准错误--显示器设备 所以可以对stdout和stderr进行写入后显示屏显示指定内容。例如使用printf对stdout进行写入使用fprintf对指定流进行写入等。可以通过读取stdin内容读取键盘输入的数据例如使用scanf和fread读取标准输入流数据。
3.文件操作
1.通过C语言操作文件 2.通过系统调用操作文件
1open
open(const char * filename,int flags),以特定的方式打开文件返回值为文件打开状态。返回值为文件描述符返回-1表示文件打开失败。 flags含义O_RDONLY只读打开O_WRONLY只写打开O_RDWR读写打开O_CREAT若文件不存在则创建它。需要使用mode选项来指明新文件的访问权限O_APPEND追加写O_TRUNC清空文件内容
在open的flags参数前三个宏必须存在且仅存在一个后续选项如有需要直接与前三者进行按位或操作即可。如果文件不存在则需要在flags参数后面追加mode参数以表示该文件的权限。
#include stdio.h
#include string.h
#include stdlib.h
#include unistd.h
#include fcntl.h
int main(){int fp open(log11.txt,O_RDONLY|O_CREAT,0777);if(fp -1){perror(open error\n);return 1;}return 0;
} 通过观察创建后文件权限位可以看到指定的文件权限为0777但是创建后文件的权限为0755这是因为在设置文件的权限位的时候不仅受参数影响同时会受到umask的影响。如果不想收到umask的影响可以使用系统接口umask将该进程下umask设置为0但是不影响其他地方的umask。 设置前 设置后 2close
close(int fd)关闭打开的文件 3write
ssize_t write(int fd, const void *buf, size_t count);向指定的文件写入内容,参数fd(目标文件的文件描述符)、buf需要写入的内容、count需要写入的字符数。如果文件原来有内容则直接进行覆盖而不是将内容清空。
#include stdio.h
#include string.h
#include stdlib.h
#include unistd.h
#include fcntl.h
#include sys/stat.h
int main(){int fd open(log.txt,O_WRONLY);if(fd -1){perror(open error\n);return -1;}const char* str hahaha\n;write(fd,str,strlen(str));close(fd);int fd1 open(text.txt,O_WRONLY);if(fd1 -1){perror(open error\n);return -1;}write(fd1,str,strlen(str));close(fd1);return 0;
}4read
ssize_t read(int fd, void *buf, size_t count);读取指定的文件参数fd目标文件描述符buf内容保存位置count表示读取的字符个数
int main(){int fd open(log.txt,O_RDONLY);if(fd -1){perror(error\n);return -1;}char buffer[1024];ssize_t s read(fd,buffer,sizeof(buffer));if(s0){buffer[s]0;printf(%s\n,buffer);}close(fd);return 0;
}四、Linux下的文件I\O
1fd文件描述符
open的返回值是一个int类型的数字表示文件描述符 -1表示打开失败。打开5个文件观察他们的fd
int main(){int fd1 open(log1.txt,O_WRONLY|O_CREAT);int fd2 open(log2.txt,O_WRONLY|O_CREAT);int fd3 open(log3.txt,O_WRONLY|O_CREAT);int fd4 open(log4.txt,O_WRONLY|O_CREAT);int fd5 open(log5.txt,O_WRONLY|O_CREAT);printf(fd1 is %d\n,fd1);printf(fd2 is %d\n,fd2);printf(fd3 is %d\n,fd3);printf(fd4 is %d\n,fd4);printf(fd5 is %d\n,fd5);
}发现他们的fd从3开始依次递增。因为0表示标准输入1表示标准输出2表示标准错误。 2Linux进程文件描述 在Linux系统下面有一个结构体叫做file 的结构体将打开的文件进行描述管理结构体中包括了文件的属性、方法集和缓冲区。操作系统将所需要打开的文件从磁盘中读取出来然后以链表的形式串起来进行管理。 在进程控制块结构体中存在一个files_strucet指针指向一个files_struct结构体对象这个对象的作用是将进程打开的文件统一管理起来。在file_struct里面存在一个叫做struct file*fd_array[]的数组文件描述符表用于存放文件指针即将系统打开的文件列表中的指针。而文件对应的下标就是fd文件描述符所以可以通过文件描述符对文件进行操作。 在C语言中FILE结构体本质就是对struct file进行封装后的结果。
3文件fd的文件分配规则
在Linux下面fd为012分别为标准输入标准输出和标准错误所以在打开文件时默认是从3开始依次递增所以下面代码运行结果为3,4。
int main(){int fd1 open(log1.txt,O_RDONLY|O_CREAT);int fd2 open(log2.txt,O_RDONLY|O_CREAT);printf(fd1 is:%d\nfd2 is:%d\n,fd1,fd2);return 0;
}如果将标准输入关闭再打开新的文件结果变成了03分析其原因是stdin打开时0被占用当将stdin关闭后
int main(){close(0);int fd1 open(log1.txt,O_RDONLY|O_CREAT);int fd2 open(log2.txt,O_RDONLY|O_CREAT);printf(fd1 is:%d\nfd2 is:%d\n,fd1,fd2);return 0;
}所以得出结论fd的分配规则按照文件打开的顺序从0开始依次分配未使用的数组下标即为fd。
4文件重定向
在上面的例子中可以通过fd关闭标准文件流再打开文件时原文件流的fd会被赋给其他文件该现象就称为文件重定向。
int main(){close(0);close(1);int fd1 open(log1.txt,O_RDONLY);int fd2 open(log2.txt,O_WRONLY);char str[1024];scanf(%s,str);printf(%s\n,str);return 0;
}在代码中关闭了标准输入标准输出流再打开了文件log1.txt和log2.txt此时这两个文件的fd分别为0和1再调用scanf和printf函数发现可以通过这连个函数进行从文件中读和向文件中写。 重定向的底层原理在操作系统层将进程对应的文件描述列表对应下标替换为目标文件而scanf和printf的底层其实是向下标为0和1的文件读取/写入内容。如果以只写的方式打开文件但是不进行写操作目标文件会被清空。
5dup2接口 在上面已经可以实现输出和输入重定向但是存在一点问题需要将原文件关闭再按照顺序打开文件。在linux下面有一个接口dup2可以直接将文件的fd进行替换从而实现文件重定向。
dup2(int oldfd,int newfd),oldfd需要拷贝的目标文件的fdnewfd(需要被替换的文件fd)。
所以上面的代码就可以进行以下修改
int main(){int fd1 open(log1.txt,O_RDONLY);int fd2 open(log2.txt,O_WRONLY);dup2(fd1,0);dup2(fd2,1);char str[1024];scanf(%s,str);printf(%s\n,str);return 0;
}五、C语言缓冲区
1什么是缓冲区 文件缓冲区其实就是内存里面的一块空间在C语言中其意义是文件操作只需要将需要读写的文件交给缓冲区再由缓冲区与系统调用直接进行交互减少了C语言接口调用时间同时通过暂存的方式减少了系统调用的次数从而提高读写效率。
2缓冲区在哪里 在打开文件时调用fopen接口会返回一个FILE*指针FILE是一个结构体里面包含了fd和缓冲区指针。即每一个打开的文件都有一个·独立的缓冲区。在调用C语言接口对文件进行读取的时候有一个FILE*的参数其实质就是将文件里的内容拷贝到缓冲区里面再内部决定何时进行缓冲区的刷新。
3缓冲区刷新策略
1、无刷新
无刷新就是读取一个数据就即时将内容交给系统
2、行刷新
当数据遇到\n或者\r时就进行刷新
3、全刷新
等数据完全完成读写后再对缓冲区进行刷新
4缓冲区现象观察 由于标准输出默认是输出到显示屏属于行刷新不便于观察所以在输出的时候将其重定向到log.txt文件中。
int main(){const char* s1111111111write\n;const char* s2222222222fprintf\n;const char* s3333333333fwrite\n;write(1,s1,strlen(s1));fprintf(stdout,%s,s2);fwrite(s3,strlen(s3),1,stdout);
}到目前正常按照顺序将字符串内容写入到log.txt当中现在在test.c文件末尾加上fork()再次运行观察现象。
int main(){const char* s1111111111write\n;const char* s2222222222fprintf\n;const char* s3333333333fwrite\n;write(1,s1,strlen(s1));fprintf(stdout,%s,s2);fwrite(s3,strlen(s3),1,stdout);fork();
}发现后面两个字符串被写入了两次而第一个字符串只写入一次第一个字符串使用的是系统接口调用而后面两个是使用的C语言函数接口。其原因是使用C语言文件接口后文件内容其实是被写到了文件的缓冲区当中而并没有直接写入到log.txt文件当中当fork后发生写时拷贝缓冲区以及里面的内容也被子进程拷贝在进程结束时。子进程和父进程都需要刷新缓冲区此时就将缓冲区里面的内容通过系统调用写入到了log.txt文件当中而write系统调用直接就将文件内容写入到了log.txt当中。