【Linux】基础IO1

文章目录

  • C下面的文件接口
    • fopen函数
    • fwrite函数
    • fread函数
    • fseek函数
    • fclose函数
  • 系统调用的文件接口
    • open函数
    • write函数
    • read函数
    • lseek函数
    • close函数
  • 文件描述符
  • 文件描述符和文件流指针的区别

C下面的文件接口

fopen函数

函数原型:

FILE *fopen(const char *path, const char *mode);

参数:

  • path:要打开的文件,带路径
  • mode:以何种方式打开
参数 打开方式
r 只读,文件流指向文件头部
r+ 读写,文件流指向文件的头部
w 只写,如果文件存在,则清空文件开始写,如果文件不存在,则创建文件
w+ 读写,如果文件存在,则清空文件开始写,如果文件不存在,则创建文件
a 追加写,如果文件不存在,则创建文件,从文件末尾开始写
a+ 可读也可追+加写,如果文件不存在,则创建文件,从文件末尾开始写

有+号的,都可以进行读。

返回值:

成功返回文件流指针,失败返回NULL。

fwrite函数

函数原型:

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

功能就是用来写文件的。

参数:

  • ptr:往文件当中写的内容
  • size:定义往文件当中写的时候,一块是多少字节(通常定义为一个字节)。
  • nmemb:准备写多少块
  • stream:文件流指针,准备写到哪个文件当中去

返回值:

返回成功写入到文件当中的块的数量,不是成功写入的字节,而是成功写入的块的个数。
代码验证:

#include <stdio.h>
    2 #include <string.h>
    3 int main(){
    4   FILE *fp = fopen("./b.txt","w+");
    5   if(fp==NULL){
    6     perror("fopen");
    7     return 0;
    8   }
    9   const char* str = "hello world";
   10   ssize_t w_size = fwrite(str,1,strlen(str),fp);                                             
   11   printf("w_size:%d\n",w_size);
   12   return 0;
   13 }

通常设置块的大小为一个字节,那么块的个数参数就可以按照字符串的字节数量进行填充。返回值就相当于是成功写入的字节数量,因为一个块的大小为1。

fread函数

函数原型:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

参数:

  • ptr:将从文件当中读到的内容保存到ptr准备的空间当中去,这个空间需要程序员提前准备,防止非法访问其他空间。
  • size:定义从文件当中读的时候,一个块是多少字节(通常定义为一个字节)
  • nmemb:期望读多少块
  • stream:文件流指针

返回值:

成功读入的文件块的个数

代码验证:

 #include <stdio.h>
    2 int main(){
    3   FILE *fp = fopen("./b.txt","r");
    4   if(fp == NULL){
    5     perror("fopen");
    6     return 0;
    7   }
    8 
    9   char buf[1024] = {0};
   10   ssize_t r_size = fread(buf,1,sizeof(buf)-1,fp);
   11   printf("r_size : %d,buf : %s\n",r_size,buf);                                               
   12   return 0;
   13 }

ssize_t r_size = fread(buf,1,sizeof(buf)-1,fp);

这个地方为什么是“sizeof(buf)-1”呢?因为我们要预留“\0”的位置,假设我们有三个字节来存放读到的内容,我们当然可以把三个空间都放读到的内容,不会出错,那为什么还要预留“\0”的位置呢?因为不访问这块空间还不会出事儿,一旦访问,就很可能会因为没有结束标志导致程序崩溃。为了保证安全性,防止越界访问造成程序的崩溃,我们预留一个\0的位置。

fseek函数

函数原型:

int fseek(FILE *stream, long offset, int whence);

参数:

  • stream:文件流指针
  • offset:偏移量(向后偏移)
  • whence:将文件流指针偏移到什么位置
含义
SEEK_SET 文件头部
SEEK_CUR 当前文件流指针的位置
SEEK_END 文件末尾

返回值:

成功返回0,失败返回-1.

fseek函数是为了移动文件流指针产生的,那在什么情况下需要我们去移动文件流指针呢?我们来看看下面这段代码:

 #include <stdio.h>
    2 #include <string.h>
    3 int main(){
    4   FILE *fp = fopen("./b.txt","w+");
    5   if(fp==NULL){
    6     perror("fopen");
    7     return 0;
    8   }
    9 
   10   const char* str = "hello world";
   11   ssize_t w_size = fwrite(str,1,strlen(str),fp);
W> 12   printf("w_size:%d\n",w_size);
   13 
   14   char buf[1024] = {0};
   15   ssize_t r_size = fread(buf,1,sizeof(buf) - 1,fp);
W> 16   printf("r_size : %d,buf : %s\n",r_size,buf);                                               
   17   return 0;
   18 }

按理说,我们先往文件当中写,再从文件当中读,没什么问题,但是上述代码的执行结果并不是我们想要的。

执行结果:

【Linux】基础IO1_第1张图片

这是为什么呢?因为我们往文件当中写完内容之后,文件流指针是指向最后的,我们读取的时候,那从最后去读肯定读不到内容啦。于是我们就需要fseek函数去移动文件流指针。

我们通过fseek函数去修改一下上述代码的文件流指针,然后再次打印试试:

#include <stdio.h>
    2 #include <string.h>
    3 int main(){
    4   FILE *fp = fopen("./b.txt","w+");
    5   if(fp==NULL){
    6     perror("fopen");
    7     return 0;
    8   }
    9 
   10   const char* str = "hello world";
   11   ssize_t w_size = fwrite(str,1,strlen(str),fp);
W> 12   printf("w_size:%d\n",w_size);
   13 
   14   fseek(fp,6,SEEK_SET);                                                                      
   15 
   16   char buf[1024] = {0};
   17   ssize_t r_size = fread(buf,1,sizeof(buf) - 1,fp);
W> 18   printf("r_size : %d,buf : %s\n",r_size,buf);
   19   return 0;
   20 }
  ~

现在就可以正常打印了。

【Linux】基础IO1_第2张图片

fclose函数

函数原型:

int fclose(FILE *fp);

打开文件之后,一定记得关闭文件,否则容易造成文件句柄泄漏,也就是内存的泄漏。

系统调用的文件接口

open函数

函数原型:

int open(const char *pathname, int flags, mode_t mode);

参数:

  • pathname:要打开或者创建的目标文件,需要带路径
  • flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行或运算,构成flags。
含义
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读、写打开

上面这三个常量,必须指定一个且只能指定一个。

含义
O_CREAT 若文件不存在,则创建它,需要使用mode选项,来指明新文件的访问权限
O_APPEND 追加写

上面这两个可以多选,与前三个用或连接。

  • mode:当创建一个新文件的时候,指定新创建文件的权限,传递一个八进制的数字。

返回值:

成功则返回新打开的文件描述符,失败返回-1.
我们说在进程创建之初,就默认打开了三个文件,标准输入,标准输出,标准错误。标准输入在C库当中是stdin,在操作系统内核进程部分就是0号文件描述符。

write函数

函数原型:

ssize_t write(int fd, const void *buf, size_t count);

参数:

  • fd:文件描述符
  • buf:将buf指向的内容写入到文件当中去
  • count:期望写多少字节

返回值:

返回则写入的字节数量

read函数

函数原型:

ssize_t read(int fd, void *buf, size_t count);

参数;

  • fd:文件描述符
  • buf:将从文件中读到的内容写到buf指向的空间当中去
  • count:期望读多少字节

返回值:

成功则返回读到的字节数量

lseek函数

函数原型:

off_t lseek(int fd, off_t offset, int whence);

参数:

  • fd:文件描述符
  • offset:偏移量(向后偏移),单位字节
  • whence:偏移的位置
含义
SEEK_SET 文件头部
SEEK_CUR 当前文件流指针的位置
SEEK_END 文件末尾

返回值:

成功返回偏移的位置,单位是字节。失败返回-1.

close函数

函数原型:

int close(int fd);

函数功能:关闭文件描述符。

代码验证上述函数:

#include <stdio.h>
    2 #include <fcntl.h>
    3 #include <string.h>
    4 #include <unistd.h>
    5 int main(){
    6   //打开一个文件
    7   int fd = open("./c.txt",O_RDWR | O_CREAT,0664);
    8   if(fd == -1){
    9     perror("open");
   10     return 0;
   11   }
   12 
   13   //往文件中写
   14   const char* str = "happyday";
   15   size_t w_size = write(fd,str,strlen(str));
W> 16   printf("w_size : %d\n",w_size);
   17 
   18   //偏移一下
   19   lseek(fd,0,SEEK_SET);
   20 
   21   //从文件中往外读
   22   char buf[1024] = {0};
   23   size_t r_size = read(fd,buf,sizeof(buf) - 1);
W> 24   printf("r_size :%d,buf : %s\n",r_size,buf);
   25 
   26   printf("fd : %d\n",fd);
   27   //关闭文件
   28   close(fd);
   29   return 0;
   30 } 

执行结果:

【Linux】基础IO1_第3张图片

文件描述符

文件描述符的值是一个小正数。

我们循环打印文件描述符:

1 #include <stdio.h>
  2 #include <fcntl.h>
  3 int main(){
  4   while(1){
  5     int fd = open("./c.txt",O_RDWR | O_CREAT,0664);
  6     if(fd<0){
  7       perror("open");
  8       return 0;
  9     }
 10     printf("fd : %d\n",fd);                                                                    
 11   }
 12   return 0;
 13 }

执行结果:
循环了十万次。

【Linux】基础IO1_第4张图片

为什么是十万次呢?这是操作系统的一个软限制,一个进程打开的文件太多了,达到了一个进程打开文件描述符的上限了。

【Linux】基础IO1_第5张图片

软限制是可以修改的,但是因为硬限制的存在,这个打开个数也并不能无限增加,那硬限制是什么呢?硬限制是操作系统的资源,因为打开文件描述符是需要耗费内存资源的。

可以在/proc/[pid]/fd文件夹下查看文件描述符信息,观察到文件描述符的分配规则是最小未使用原则。

文件是在进程当中打开的,所以文件描述符和进程脱不了关系,在进程的结构体中,有一个描述进程打开文件信息的结构体指针,该指针指向一个结构体,这个结构体有一个指针数组,数组里面存储的指针指向描述文件信息的结构体,而我们所说的文件描述符就是该指针数组的数组元素下标。在描述文件信息的结构体里,有描述文件的名称、文件的大小、文件的权限、文件的所有者、文件的属性、文件在磁盘当中存储的位置等等。

文件描述符和文件流指针的区别

typedef struct _IO_FILE FILE;

从源码的角度来说,文件流指针是一个结构体(struct _IO_FILE),文件流指针对应的结构体是C库定义的,该结构体属于C库的内容,它并不是操作系统内核的东西,文件流指针结构体内的读缓冲区和写缓冲区也是属于C库,不属于操作系统内核的,所以我们在进程控制当中说进程结束的时候会刷新缓冲区,需要用那四种方法去刷新缓冲区。当调用_exit函数去终止进程的时候,会比exit函数少做的其中一件事就是刷新缓冲区,因为_exit函数是系统调用函数,在内核中运行,接触不到缓冲区。

文件流指针结构体当中还有一个东西是int _fileno,该整形变量用来保存文件描述符的数值,保存的就是内核的文件描述符。

你可能感兴趣的:(Linux,linux,后端,运维)