【linux】:linux下文件的使用以及文件描述符
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
前言
理解文件原理和操作
我们先快速回忆下一C语言的文件操作
首先看一下fopen函数的使用然后我们写一段简单的C语言打开文件的代码如下图
#include <stdio.h>
#define LOG "log.txt"
int main()
{
//默认写方式打开文件如果文件不存在就删除它
FILE* fp = fopen(LOG,"w");
if (fp==NULL)
{
perror("fopen:");
return 1;
}
//正常进行文件操作
const char* msg = "hello friends\n";
int cnt = 1;
while (cnt)
{
fputs(msg,fp);
--cnt;
}
fclose(fp);
return 0;
}
接下来我们运行一下
通过上图我们可以看到成功将我们的字符串写入文件中那么这次我们什么都不写再看看
为什么原先log里的字符串没了呢因为我们打开的方式是w文件内容会先被清空再写入。
下面我们再介绍一个接口snprintf:
普通的printf是默认向显示器打印fprintf是指定文件流向指定文件进行打印下面我们演示一下
首先我们修改一下代码将fputs替换为fprintf函数
int main()
{
//默认写方式打开文件如果文件不存在就删除它
FILE* fp = fopen(LOG,"w");
if (fp==NULL)
{
perror("fopen:");
return 1;
}
//正常进行文件操作
const char* msg = "hello friends\n";
int cnt = 5;
while (cnt)
{
// fputs(msg,fp);
fprintf(fp,"%s:%d:sxy\n",msg,cnt);
--cnt;
}
fclose(fp);
return 0;
}
运行结果如上图所示并且我们不仅可以像文件中打印也可以在输出流中打印
我们都知道一个程序会默认打开以上三个输入输出流下面我们像stdout打印一下
运行后我们发现直接就能显示不用再打开log.txt查看了下面我们再来演示一下snprintf函数
int main()
{
//默认写方式打开文件如果文件不存在就删除它
FILE* fp = fopen(LOG,"w");
if (fp==NULL)
{
perror("fopen:");
return 1;
}
//正常进行文件操作
const char* msg = "hello friends\n";
int cnt = 5;
while (cnt)
{
char buffer[256];
snprintf(buffer,sizeof(buffer),"%s:%d:sxy\n",msg,cnt);
fputs(buffer,fp);
--cnt;
}
fclose(fp);
return 0;
}
snprintf就是将msg格式化到buffer数组里并且以buffer的大小进行打印然后将数组中的数据写入文件中。
后面还有a追加写入等的方式我们就不在演示这些都是我们C语言的时候学过的。
以上都是语言进行文件的操作下面我们进入今天的正题在系统层面使用文件操作。
一、linux系统中的文件操作以及文件接口
首先我们看一下系统中文件的接口open
然后我们再看一下这个接口的返回值是什么
如果成功返回一个新的文件描述符否则返回-1下面我们演示一下这个接口
首先一定要包头文件
这个函数的第一个参数与刚刚一样都是文件名第二个参数其实是用位图实现的就是用一个标志位充当不同的行为如下图所示
在上图中我们就利用位图传不同的flags就能打打印不同的数并且不仅可以打印一个也可以同时打印多个
而第二个参数的使用方式与上面演示的一样都有下面这几种选项
这些都是宏像我们刚刚演示的#define的那样。下面我们演示这个接口
//系统层面
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define LOG "log.txt"
int main()
{
int fd = open(LOG,O_WRONLY);
printf("fd:%d\n",fd);
close(fd);
return 0;
}
0_CREAT是如果没有这个文件就给我们创建一个文件O_WRONLY是只写的意思。
我们先删除原先的log文件运行后发现返回值为-1也就是说出错了那是什么原因呢我们修改一下代码如果出错就打印一下出错原因
因为我们要写这个文件但是这个文件并不存在所以我们没有的话应该先创建一个文件
这次我们发现文件创建成功了错误码为0。我们这里用的是root所以文件的权限没有问题如果这里是普通用户创建文件的话文件权限是乱码有问题的如下图
所以我们一般创建文件用的是三个接口的那个函数我们演示的这个函数没有提供文件权限的接口。
下面我们用三个接口的open创建一下文件
我们先删掉之前的文件然后重新运行一下发现这次文件的权限没有问题是正确的不是666是因为受umask影响。所以一个文件被创建默认权限受umask影响那么如果想不受umask影响呢
我们可以直接将所有的权限掩码设置为0
我们发现这次就不受umask影响了。接下来我们写入文件试试
关于写入的文件接口的函数为write
向文件描述符写入一个缓冲区大小为count返回值是实际写入多少字节写入失败返回-1.
int main()
{
umask(0);
int fd = open(LOG,O_WRONLY | O_CREAT,0666);
if (fd==-1)
{
printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno));
}
else printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno));
printf("fd:%d\n",fd);
const char* msg = "hello friend";
int cnt = 5;
while (cnt)
{
char line[128];
snprintf(line,sizeof(line),"%s,%d\n",msg,cnt);
write(fd,line,strlen(line));
--cnt;
}
close(fd);
return 0;
}
接下来我们运行一下
通过上图我们发现写入成功其实在我们刚刚strlen的时候发现我们并没有带\0为什么没有\0也能成功写入呢因为\0是C语言的标准不是系统的规定并且\0在系统中写入会变成乱码。
下面我们重新写一下
在我们重新运行后发现文件中保留了上一次的数据也就是说O_CREAT | O_WRONLY是不会清空原来的文件再写入的这个时候我们加上O_TRUNC选项就可以每次写入前先清空文件了
搞定了写入那么饿追加就不是问题了追加只需要注意将写入替换为追加并且我们不希望每次打开文件先清空文件所以不需要O_TRUNC选项
为什么我们上面的运行结果不正确呢因为0_APPEND只是追加不是追加写所以才没有写入。下面我们修改一下代码
这样我们就成功的追加了字符串那么如果是只读呢其实只读就很简单了只读只需要调用两个接口的open函数即可因为只读默认认为是有文件的。
读取的返回值就是当前读取了多少字节读取到文件结尾就是0失败就是-1。
int main()
{
umask(0);
int fd = open(LOG,O_RDONLY);
if (fd==-1)
{
printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno));
}
else printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno));
char buffer[1024];
ssize_t n = read(fd,buffer,sizeof(buffer)-1); //使用系统接口来进行IO的时候一定要注意\0问题。
if (n>0)
{
buffer[n] = '\0';
printf("%s\n",buffer);
}
close(fd);
return 0;
}
我们在读取的时候为什么-1呢因为我们不考虑\0的问题只有当读取的时候返回值大于0我们将\0放到n的位置以字符串的形式打印的时候才需要\0所以最后需要将\0放到n的位置再打印。
以上就是系统级的文件接口而入C语言等要进行文件操作都必须调用系统接口。
二、文件描述符
1.0 & 1 & 2
通过对open函数的学习我们知道了文件描述符就是一个小整数。linux进程默认情况下会有3个缺省打开的文件描述符分别是标准输入0标准输出1标准错误2。0 1 2对应的物理设备一般是键盘显示器显示器所以输出还可以有以下方式
下面我们用文件的读写完成简单的输入输出
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf[1024];
ssize_t s = read(0,buf,sizeof(buf));
if (s>0)
{
buf[s] = 0;
write(1,buf,strlen(buf));
write(2,buf,strlen(buf));
}
return 0;
}
1和2代表标准输出和标准错误我们先去读一下缓冲区当返回值是缓冲区的最后一个字符我们在这个位置放入\0然后写入刚刚读取到的字符下面我们运行一下
通过这个小例子我们就知道了文件描述符就是从0开始的小整数当我们打开文件时操作系统在内存中要创建相应的数据结构来描述目标文件于是就有了file结构体表示一个已经打开的文件对象而进程执行open系统调用所以必须让进程和文件关联起来每个进程都有一个指针*files指向一张表files_struct该表最重要的部分就是包含一个指针数组每个元素都是一个指向打开文件的指针所以本质上文件描述符就是该数组的下标。所以只要拿着文件描述符就可以找到对应的文件。
文件描述符的分配规则
我们写一个只读的代码来获取文件的fd
int main()
{
int fd = open("my.txt",O_RDONLY);
if (fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
close(fd);
return 0;
}
我们发现fd是3那么这个时候我们把0关闭了再看看是什么结果
int main()
{
close(0);
int fd = open("my.txt",O_RDONLY);
if (fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
close(fd);
return 0;
}
通过运行结果我们发现当我们将0关闭后文件描述符立马变成了0我们再看看把2关闭了如何
int main()
{
//close(0);
close(2);
int fd = open("my.txt",O_RDONLY);
if (fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
close(fd);
return 0;
}
当我们将2关闭后文件描述符又变成了2这就说明文件描述符的分配规则是在file_struct数组当中找到当前没有被使用的最小的一个下标作为新的文件描述符。
2.重定向
还是刚刚的代码如果我们将1关闭了会出现什么情况呢
int main()
{
//close(0);
close(1);
int fd = open("my.txt",O_WRONLY | O_CREAT,00644);
if (fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
fflush(stdout);
close(fd);
exit(0);
}
通过运行结果我们可以发现本来应该输出到显示器上的内容输出到了文件中其中fd=1这种现象叫做输出重定向常见的重定向有> >> <。那么重定向的本质是什么呢我们看下图
当然对于重定向来说还有一个系统调用接口这个函数叫dup2:
#include <unistd.h>
int dup2(int oldfd, int newfd);
对于这个函数的参数1来说是被换的那个描述符参数2是像0,1,2,这样的默认描述符接下来我们使用这个函数完成以下重定向
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fd = open("my.txt",O_WRONLY | O_CREAT,00644);
if (fd<0)
{
perror("open");
return 1;
}
dup2(fd,1);
printf("fd:%d\n",fd);
close(fd);
}
通过上图我们可以看到成功的重定向到文件中。下面我们看看文件缓冲区的概念
我们先编写一段代码看看现象
int main()
{
//c库中的
fprintf(stdout,"hello fprintf\n");
//系统调用
const char* msg = "hello write\n";
write(1,msg,strlen(msg));
//fork();
return 0;
}
可以正常打印那么我们调用一下子进程会变成什么呢
我们发现运行结果和刚刚是一样的难道没有什么用吗我们接下来重定向到文件中
这个时候我们发现了居然会多打印一个fprintf为什么会出现这样的现象呢
所以
总结
对于linux下的文件操作而言C语言等对于文件操作的函数都是经过linux系统文件接口来封装的我们在用C语言文件操作的时候看着很简单的一句代码在系统调用中会有很多的操作对于文件描述符实际上就是file_struct中的指针数组的下标文件描述符的分配规则就是优先从最小的下标开始分配