今天这个小结节,我来大家来了解Linux下的文件操作。首先我们来复习一下C语言的文件操作,基于C语言的文件操作我们对Linux的学习就会方便很多了!我带大家首先来了解文件相关系统的接口和文件描述符,并且理解重定向!
最后在基于重定向,在下一小节将我上节写的myshell完善一下!
正文开始!
当我们以w方式打开文件,准备写入的时候,其实文件已经先被清空了!
#include
#include
int main()
{
FILE* fp=fopen("log.txt","w");
if(fp==NULL)
{
perror("fopen");
return 1;
}
const char* msg="hello rose!";
int cnt=0;
while(cnt<10)
{
fprintf(fp,"%s %d\n",msg,cnt);
cnt++;
}
fclose(fp);
return 0;
}
默认这个"log.txt"文件会在哪里形成呢?—>当前路径
那么什么是当前路径呢?–>进程当前的路径
接下来带大家查看进程的信息
#include
#include
int main()
{
FILE* fp=fopen("log.txt","w");
if(fp==NULL)
{
perror("fopen");
return 1;
}
printf("%d\n",getpid());
while(1)
{
sleep(1);
}//在这里会一直休眠下去,直到我们杀掉这个进程
const char* msg="hello rose!";
int cnt=0;
while(cnt<10)
{
fprintf(fp,"%s %d\n",msg,cnt);
cnt++;
}
fclose(fp);
return 0;
}
ll /proc/进程id
在这里我们就可以看到"log.txt"就在当前cwd,也就是进程所处的路径了。
接下来我们有意识的更改当前路径,这里需要用到系统接口chdir();
#include
#include
int main()
{
chdir("/home/hulu");
FILE* fp=fopen("log.txt","w");
if(fp==NULL)
{
perror("fopen");
return 1;
}
printf("%d\n",getpid());
while(1)
{
sleep(1);
}
}
#include
#include
int main()
{
FILE* fp=fopen("log.txt","a");
if(fp==NULL)
{
perror("fopen");
return 1;
}
const char* msg="hello rose!";
int cnt=0;
while(cnt<5)
{
fprintf(fp,"%s %d\n",msg,cnt);
cnt++;
}
fclose(fp);
return 0;
}
追加写入,不断的往文件中新增内容—>追加重定向!
#include
#include
int main()
{
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;
}
#include
#include
//myfile filename
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);
}
}
当我们向文件写入的时候,最终是不是向磁盘写入?
因为磁盘是硬件,所以只有OS有资格向硬件写入!
那么能绕开操作系统吗?
答:不能!那么所有上层的访问文件的操作,都必须贯穿操作系统!
操作系统是如何被上层使用的呢?
因为操作系统不相信任何人,所以必须使用操作系统提供的相关系统调用!
那么为什么要进行封装文件操作接口呢?
那么封装是如何解决跨平台性的问题呢?
C库提供的文件访问接口是来自于系统调用!
那么就能解释不同的语言有不同的文件访问接口!!
所以无论什么语言的底层的接口是不变的!
所以这就要求我们必须学习文件级别的系统接口!
返回值
打开文件的选项
对于O_RDONLY,O_WRONLY,O_RDWR,O_APPEND,O_CREAT!这些都是宏!
系统传递标记位,使用位图结构来进行传递的!
每一个宏标记,一般只需要有一个比特位为1,并且和其他宏对于的值不能重叠。
代码模拟实现
#include
#define PRINT_A 0x1
#define PRINT_B 0x2
#define PRINT_C 0x4
#define PRINT_D 0x8
#define PRINT_DFL 0x0
void 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()
{
Show(PRINT_DFL);
Show(PRINT_A);
Show(PRINT_B);
Show(PRINT_A|PRINT_B);
Show(PRINT_C|PRINT_D);
Show(PRINT_A|PRINT_B|PRINT_C|PRINT_D);
return 0;
}
我们通过传入不同的选项,打印出不同的语句。
#include
#include
#include
#include
#include
#include
int main()
{
int fd = open("log.txt",O_WRONLY|O_CREAT);
if(fd<0)
{
perror("open error");
return 1;
}
printf("fd=%d\n",fd);
return 0;
}
所以我们要打开曾经不存在的文件,我们要用到第二个open函数,带有权限的设置!
int open(const char *pathname, int flags, mode_t mode);
int fd = open(“log.txt”,O_WRONLY|O_CREAT,0666);
可以看到我们创建文件的权限是0666,可是实际显示的是0664呢?
这就和我们之前学的掩码umask有联系了!
不清楚的话可以去看看这篇博客权限的理解!
#include
#include
#include
#include
#include
#include
int main()
{
int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open error");
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;
}
C在w方式打开文件的时候,会清空的!
int main()
{
int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open error");
return 1;
}
printf("fd=%d\n",fd);
int cnt=0;
const char* str="aaaa";
//const char* str="hello file!\n";
while(cnt<5)
{
write(fd,str,strlen(str));
cnt++;
}
close(fd);
return 0;
}
修改我们的代码后
我们发现此处直接覆盖曾经的数据,但是曾经的数据为什么保留呢了?
因为我们没有带有截断选项O_TRUNC
接下来我们带上这个选项后
int main()
{
int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open error");
return 1;
}
printf("fd=%d\n",fd);
int cnt=0;
const char* str="hello rose!\n";
while(cnt<5)
{
write(fd,str,strlen(str));
cnt++;
}
close(fd);
return 0;
}
现在我们就发现之前的数据被截断了!
所以我们现在可以类比与C语言的fopen,底层的open的选项就是"O_WRONLY|O_CREAT|O_TRUNC"!!!
接下来我们验证追加选项"O_APPEND"
代码如下
int main()
{
int fd = open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
if(fd<0)
{
perror("open error");
return 1;
}
printf("fd=%d\n",fd);
int cnt=0;
const char* str="hello hulu!\n";
while(cnt<5)
{
write(fd,str,strlen(str));
cnt++;
}
close(fd);
return 0;
}
我们打开文件就默认他是存在的,不需要携带"O_CREAT"选项
如果文件不存在会返回-1;
#include
#include
#include
#include
#include
#include
#define NUM 1024
int main()
{
int fd = open("log.txt",O_RDONLY);
if(fd<0)
{
perror("open error");
return 1;
}
printf("fd=%d\n",fd);
char buffer[NUM];
while(1)
{
ssize_t s=read(fd,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]='\0';
printf("%s",buffer);
}
else
break;
}
close(fd);
return 0;
}
在上面的实验中我们了解到打开文件后返回给文件描述符fd=3,这是为什么?
接下来先来看代码,让我们去了解文件描述符
#include
#include
#include
#include
#include
#include
int main()
{
int fda=open("loga.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
int fdb=open("logb.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
int fdc=open("logc.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
int fdd=open("logd.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
int fde=open("loge.txt",O_WRONLY|O_CREAT|O_APPEND,0666);
printf("fda=%d\n",fda);
printf("fdb=%d\n",fdb);
printf("fdc=%d\n",fdc);
printf("fdd=%d\n",fdd);
printf("fde=%d\n",fde);
return 0;
}
0:标准输入,键盘
1:标准输出,显示器
2:标准错误,显示器
这就与我们C语言联系起来了!因为C语言封装的系统接口。
首先我们之前在C语言中学到FILE*–>文件指针—>FILE是什么呢?—>C语言提供的结构体!–>封装了多个成员
因为对于文件操作而言,系统接口只认识fd;(FILE内部必定封装了fd)
0,1,2,3,4…,我们之前见过什么样的数据是这个样子的呢?
这和我们之前学习的C/C++的数组下标相似
进程:内存文件的关系—>内存—>被打开的文件是存在内存里面的!!!
一个进程可不可以打开多个文件?–>当然可以,所以在内核中,进程:打开的文件=1:n–>所以系统在运行中,有可能会存在大量的被打开的文件!—>OS要不要对这些被打开的文件进行管理呢??—>操作系统如何管理这些被打开的文件呢??—>答案是先描述,在组织。
一个文件被打开,在内核中,要创建该被打开的文件的内核数据结构—先描述
那么进程如何和打开的文件建立映射关系呢??
#include
#include
#include
#include
#include
#include
int main()
{
char buffer[1024];
ssize_t s=read(0,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]='\0';
printf("echo:%s",buffer);
}
return 0;
}
int main()
{
const char* s="hello write!\n";
write(1,s,strlen(s));
}
int main()
{
const char* s="hello write!\n";
write(2,s,strlen(s));
}
我们看出标准错误流也打印到了显示器上面。
至于标准输出和标准错误的区别,我们稍后带大家了解。
int main()
{
printf("stdin: %d\n",stdin->_fileno);
printf("stdout: %d\n",stdout->_fileno);
printf("stderr: %d\n",stderr->_fileno);
}
由上面的实验我们可以得出,FILE结构体中的fileno就是封装了文件描述符fd!!!
0,1,2—>stdin,stdout,stderr—>键盘,显示器,显示器(这些都是硬件呀!)也用你上面的struct file来标识对应的文件吗??
如何证明呢?
我来带大家看看LInux下的内核结构!!!
int main()
{
close(0);
//close(1);
//close(2);
int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open");
return 1;
}
printf("fd=%d\n",fd);
}
我们发现把1关掉后什么都没有了!
因为printf->stdout->1虽然不在指向对应的显示器了,但是已经指向了log.txt的底层struct_file对象!
遍历fd_array[],找到最小的没有被使用的下标,分配给新的文件!!
下小节我来给大家讲述关于重定向的本质,和缓冲区的概念,等讲完重定向后,我们再把myshell完善!
(本章完!)