文件描述符:
0.复习文件操作(C语言)
0.1由一段C语言文件操作产出几个问题
#include <stdio.h>int main(){ FILE *fp = fopen("log.txt","w");//写入 if(fp == NULL) { perror("fopen"); return 1; } const char* msg = "hello file"; int cnt = 1; while(cnt < 20) { fprintf(fp,"%s:%d\n",msg,cnt++); } fclose(fp); return 0;}运行结果:
这是我们写的一段最简单的C语言代码,这段代码可以产出几个问题:
0.1.1 log.txt生成时没有带路径,默认这个文件会在哪里形成呢?
我们都知道是在当前路径。那么什么是当前路径呢?当前路径是源代码所在的路径吗?其实这种说法是一种感性的认识,其实当前路径是进程所在的路径。为了验证这一结论。我们使用命令来查看一下。
#include <stdio.h>#include <unistd.h>#include <sys/types.h>int main(){ FILE *fp = fopen("log.txt","w");//写入 if(fp == NULL) { perror("fopen"); return 1; } printf("pid:%d\n",getpid());//获取当前进程的pid while(1) { sleep(1); } const char* msg = "hello file"; int cnt = 1; while(cnt < 20) { fprintf(fp,"%s:%d\n",msg,cnt++); } fclose(fp); return 0;}使用指令查看
ls /proc/pid值 -l我们发现有一个cwd(current working directory)--当前工作路径
因此我们验证了:当前路径是当前进程所处的工作路径。
此时我们如果对当前工作路径进行修改,我们仍然可以得到想要的结果(此时我们更改当前的工作路径到/home/Lxy)
#include <stdio.h>#include <unistd.h>#include <sys/types.h>int main(){ chdir("/home/Lxy");//更改当前进程的工作路径 FILE *fp = fopen("log.txt","w");//写入 if(fp == NULL) { perror("fopen"); return 1; } printf("pid:%d\n",getpid()); while(1) { sleep(1); } const char* msg = "hello file"; int cnt = 1; while(cnt < 20) { fprintf(fp,"%s:%d\n",msg,cnt++); } fclose(fp); return 0;}我们继续使用指令来进行查看
ls /proc/pid值 -l0.1.2 复习a选项
a 选项是一个写入操作,写入到文件的结尾。也就是追加操作。我们赶紧有C语言来看看效果
#include <stdio.h>#include <unistd.h>#include <sys/types.h>int main(){ FILE *fp = fopen("log.txt","a");//写入 if(fp == NULL) { perror("fopen"); return 1; } const char* msg = "hello file"; int cnt = 1; while(cnt <= 5) { fprintf(fp,"%s:%d\n",msg,cnt++); } fclose(fp); return 0;}0.1.3 复习w选项
我们看到w操作解释的第一句话是Truncate file to zero length or create text file for writing. (将文件截断为零长度或创建文本文件进行写入).意思就是如果文件不存在则创建之;如果文件已经存在则从头开始写入。我们首先验证一下存在不写会有什么结果。
#include <stdio.h>#include <unistd.h>#include <sys/types.h>int main(){ FILE *fp = fopen("log.txt","w");//写入 if(fp == NULL) { perror("fopen"); return 1; } fclose(fp); return 0;}我们看到了文件被清空了。因此我们得到当我们以'w'方式打开文件,准备写入时,其实文件已经被清空了。这是w和a的一个最大的区别!
0.1.4 复习读操作
fgets
fgets是从特定的文件流(FILE * stream)中读取特定的数据(char *s),大小是size.返回值成功了就是读取的起始地址。我们也用C语言来实现一下
#include <stdio.h>#include <unistd.h>#include <sys/types.h>int main(){ //chdir("/home/Lxy");//更改当前进程的工作路径 FILE *fp = fopen("log.txt","r");//写入 if(fp == NULL) { perror("fopen"); return 1; } char buffer[64]; while(fgets(buffer,sizeof(buffer),fp) != NULL) { printf("echo:%s",buffer); } fclose(fp); return 0;}我们根据读操作写一个小程序玩一玩,我们的想法是./myfile filename 可以打印出文件的内容
#include <stdio.h>#include <unistd.h>#include <sys/types.h>int main(int argc,char* argv[]){ if(argc != 2) { printf("Usage:%s filename\n",argv[0]); return 1; } FILE * fp = fopen(argv[1],"r"); if(fp == NULL) { perror("fopen"); return 1; } char buffer[64]; while(fgets(buffer,sizeof(buffer),fp) != NULL) { printf("%s",buffer); } fclose(fp); return 0;}我们来一起看看吧~我们发现我们所写的和系统提供的cat指令大差不大啦~(感兴趣的小伙伴自己也尝试一下吧)
至此,我们文件基本操作(C语言)就复习到这里~
1.认识文件相关系统调用接口
我们回归理论:当我们像文件写入的时候,最终是不是像磁盘写入?肯定是,那么操作系统是硬件,只有操作系统有资格像硬件写入。那么能绕开操作系统吗?答案是不能。所有的上层访问文件的操作都必须贯穿操作系统。操作系统是如何被上层使用的呢?答案肯定是使用操作系统提供的相关系统调用!
那么这里有两个问题?
那么为什么要做封装?
1.1 见一见系统接口
1.1.1 open
open是文件系统接口最重要的接口,没有之一。因此我们先来看看这个接口。
第一个参数(const char* pathname):带路径的文件名
第二个参数(flags):打开文件传递的选项(下面会重点介绍)
第三个参数(mode):设置权限
返回值:int , -1表示出现错误 (C语言FILE*)
第一个参数介绍:
带路径的文件名,如果只写文件名默认在当前路径下创建。
第二个参数介绍:
flags标志位有很多选项,其中这些选项都是宏。其中系统传递标记位时,是用位图结构来进行传递的。因此每一个宏标记只需要有一个比特位是1,并且有其他宏对应的值不能重叠。在这里我们讲自己写一个接口来验证一下
#include <stdio.h>#define PRINT_A 0x1 // 0000 00001#define PRINT_B 0x2 // 0000 00010#define PRINT_C 0x4 // 0000 00100#define PRINT_D 0x8 // 0000 01000#define PRINT_DFL 0x0void Show(int flags){ if(flags & PRINT_A) printf("Hello A\n"); if(flags & PRINT_B) printf("Hello B\n"); if(flags & PRINT_C) printf("Hello C\n"); if(flags & PRINT_D) printf("Hello D\n"); if(flags == PRINT_DFL) printf("hello Default\n");}int main(){ printf("hello Default\n"); Show(PRINT_DFL); printf("Hello A\n"); Show(PRINT_A); printf("Hello B\n"); Show(PRINT_B); printf("PRINT_A和PRINT_B\n"); Show(PRINT_A | PRINT_B); printf("PRINT_C和PRINT_D\n"); Show(PRINT_C | PRINT_D); printf("PRINT_A和PRINT_B和PRINT_C和PRINT_D\n"); Show(PRINT_A | PRINT_B | PRINT_C | PRINT_D); return 0;}通过这个例子我们就更好的理解了open的第二个参数,介绍这两个参数后,我们就可以使用open系统函数了,我们也用C语言来实现一下
#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(){ int fd = open("log.txt",O_WRONLY | O_CREAT); if(fd < 0 ) { perror("open"); return 1; } printf("fd: %d\n",fd); return 0;}第三个参数:
通过打印值我们发现fd返回值确实是一个int型。而且我们发现log.txt文件的权限部分是一对乱码,原因是,当我们新建一个文件的时候,我们要设置文件的权限。因此我们得出一个结论,如果我们要创建一个不存在的文件,不能使用两个参数的open接口,而是要使用带有权限参数的open接口。我们重新创建一下log.txt,并设置权限位0666
int main(){ int fd = open("log.txt",O_WRONLY | O_CREAT,0666); if(fd < 0 ) { perror("open"); return 1; } printf("fd: %d\n",fd); return 0;}此时我们发现权限比刚才的正常多了,确实不在是乱码现象了。但是0664,而不是0666。这是因为权限掩码是umask。默认的umask是0002.所以如果我们就想要权限是0666.我们手动设置umask为0即可。我们再来看看
1.1.2 close
我们把文件打开了,总得关闭文件吧。因此我们来看看关闭文件的系统接口close.因此这个接口非常简单好用。
1.1.3 write
我们把文件打开和关闭了解了之后,接下来我们要对文件进行操作了。第一个我们要了解的是write。像文件内写多西。
第一个参数(int fd):
fd,特定的文件描述符。也就是向那个文件写。
第二个参数(const void* buf):
写入缓冲区的起始地址
第三个参数(count):
写入缓冲区的大小
我们也用C语言来练一练
#include <stdio.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(){ umask(0); //打开文件 int fd = open("log.txt",O_WRONLY | O_CREAT,0666); if(fd < 0 ) { perror("open"); return 1; } printf("fd: %d\n",fd); //对文件操作 int cnt = 0; const char *str = "hello file\n"; while(cnt<5) { write(fd,str,strlen(str)); cnt++; } //关闭文件 close(fd); return 0;}我们看到成功的向log.txt文件写入了5条hello file
至此我们已经见过了最基本的系统接口,后续我们还要对系统调用接口详细介绍使用
(本篇完)