Linux基础IO

基础IO

        • 1.C语言操作文件接口(回顾)
          • 1.1 FILE *fopen(const char *path,const char *mode)
          • 1.2 size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream)
          • 1.3 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
          • 1.4 int fseek(FILE *stream, long offset, int whence)
          • 1.5 int fclose(FILE *stream)
        • 2. 系统文件IO
          • 2.1 int open(const char *pathname, int flags, mode_t mode)
          • 2.2 ssize_t write(int fd, const void *buf, size_t count)
          • 2.3 ssize_t read(int fd, void *buf, size_t count)
          • 2.4 off_t lseek(int fd, off_t offset, int whence)
          • 2.5 int close(int fd)
        • 3. 文件描述符
          • 0&1&2
          • 文件描述符的分配规则
          • 文件描述符与文件流指针的区别
          • 文件描述符(文件句柄)泄露的问题
          • 一个进程打开文件的最大数量
        • 4. 重定向
          • 命令行感受
          • 重定向接口 int dup2(int oldfd,int newfd)

1.C语言操作文件接口(回顾)

fopen,fwrite,fread,fseek,fclose这些函数都是库函数,是c库当中提供能给程序员调用的函数。

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

函数描述:

path:待打开的文件(文件路径+文件名称)
mode:以何种方式打开

  • r —— 以只读方式打开,当文件不存在的时候,就会打开失败
  • r+ —— 以读写方式打开,当文件不存在的时候,就会打开失败
  • w —— 以只写方式打开,如果文件不存在,则创建文件。如果文件存在,则会截断(清空)文件
  • w+ —— 以读写方式打开,如果文件不存在,则创建文件。如果文件存在,则会截断(清空)文件
  • a —— 以追加方式打开,只支持写,如果文件不存在,则创建文件。当前的文件流指针指向了文件的末尾
  • a+ —— 以追加方式打开,支持读和写。如果文件不存在,则创建文件。当前的文件流指针指向了文件的末尾

返回值:打开成功返回文件流指针,打开失败返回NULL。

示例:

  • 示例1:以 r,r+ 方式打开
#include 
#include 
#include 

int main()
{
     
  //以只读方式打开文件linux
  FILE *fp = fopen("./linux","r"); // 不管是r还是r+,如果文件不存在都会打开失败
  if(!fp)
  {
     
    perror("fopen");
    return -1;
  }
  printf("open success\n");
  return 0;
}
//输出结果:
fopen: No such file or directory

创建linux文件,再次以只读方式打开,结果如下:

[test@localhost c_file]$ touch linux
[test@localhost c_file]$ ./test_file 
open success
  • 示例2:以 w,w+方式打开
    首先删掉前面创建的linux文件,然后以只写方式打开:
#include 
#include 
#include 

int main()
{
     
  FILE *fp = fopen("./linux","w"); //不管是w还是w+,如果文件不存在都会创建文件
  								   //如果存在,则清空文件
  if(!fp)
  {
     
    perror("fopen");
    return -1;
  }
  printf("open success\n");
  return 0;
}
//输出结果:
open success

此时查看文件:

[test@localhost c_file]$ make
gcc test.c -o test_file
[test@localhost c_file]$ ./test_file 
open success
[test@localhost c_file]$ ls
linux  makefile  test.c  test_file

现在linux文件存在,往这个文件中随便写入数据,然后再次以只写方式打开文件:

[test@localhost c_file]$ cat linux
Hello World
[test@localhost c_file]$ ./test_file 
open success
[test@localhost c_file]$ cat linux 
[test@localhost c_file]$ #文件内容已经被清空
  • 示例3:以a,a+方式打开
    首先删掉前面创建的linux文件,然后以追加方式打开:
#include 
#include 
#include 

int main()
{
     
  FILE *fp = fopen("./linux","a"); //不管是a还是a+,如果文件不存在都会创建文件
  							 //如果存在,当前文件流指针指向文件末尾,并不会清空文件内容
  if(!fp)
  {
     
    perror("fopen");
    return -1;
  }
  printf("open success\n");
  return 0;
}
//输出结果:
open success

现在linux文件存在,往这个文件中随便写入数据,然后再次以追加方式打开文件:

[test@localhost c_file]$ echo "Hello world" >> linux 
[test@localhost c_file]$ cat linux 
Hello world
[test@localhost c_file]$ ./test_file 
open success
[test@localhost c_file]$ cat linux 
Hello world   #可以看到文件内容没有被清空
1.2 size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream)

函数描述:

ptr:要往文件当中写的内容
size:写入块的大小,单位是字节
nmemb:块的个数,单位是个(写入文件字节的数量:size * nmemb)
注意:一般在程序中使用时,是将size设置为1,则nmemb就表示写入的字节数量。
stream:文件流指针
返回值:返回写入成功块的个数,切记不是写入成功字节的数量。

示例:

#include 
#include 
#include 
#include 
#include 

int main()
{
     
  FILE *fp = fopen("./linux","w+"); //以读写方式打开文件
  if(!fp)
  {
     
    perror("fopen");
    return -1;
  }
  printf("open success\n");
  //创建buf数组用于保存需要往文件写入的数据
  char buf[1024] = {
     0};
  const char *ptr = "Hello World!";
  strncpy(buf,ptr,strlen(ptr));
  size_t ret = fwrite(buf,1,strlen(ptr),fp);
  printf("ret:%d\n",ret); // 返回的是写入成功块的个数
  return 0;
}

输出结果:

[test@localhost c_file]$ ./test_file 
open success
ret:12
[test@localhost c_file]$ cat linux 
Hello World![test@localhost c_file]$
1.3 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

函数描述:

ptr:要将读到的内容保存在哪里
size:每次读块的大小
nmemb:块的个数
stream:文件流指针
返回值:成功读到的块的个数,返回0说明读取成功了但是没有读到内容。

示例:

#include 
#include 
#include 
#include 
#include 

int main()
{
     
  FILE *fp = fopen("./linux","r");
  if(!fp)
  {
     
    perror("fopen");
    return -1;
  }
  printf("open success\n");
  // 创建数组用以保存读取的数据	
  char buf[1024] = {
     0};
  size_t ret = fread(buf,1,sizeof(buf)-1,fp);
  printf("buf:%s\n",buf);
  printf("ret:%d\n",ret); // 返回的是读到的块的个数
  return 0;
}

输出结果:

[test@localhost c_file]$ ./test_file 
open success
buf:Hello world

ret:12
1.4 int fseek(FILE *stream, long offset, int whence)

函数描述:

stream:文件流指针
offset:偏移量
whence:

  • SEEK_SET:文件流指针偏移到文件头部
  • SEEK_CUR:文件流指针偏移到当前位置
  • SEEK_END:文件流指针指向文件末尾位置

示例:
在一个程序中在文件写入数据后,想要继续读取数据,此时就用到了fseek函数
不使用fseek:

#include 
#include 
#include 
#include 
#include 

int main()
{
     
  FILE *fp = fopen("./linux","w+");
  if(!fp)
  {
     
    perror("fopen");
    return -1;
  }
  printf("open success\n");

  char buf_w[1024] = {
     0};
  const char *ptr = "Hello World!";
  strncpy(buf_w,ptr,strlen(ptr));
  size_t ret_w = fwrite(buf_w,1,strlen(ptr),fp);
  printf("ret_w:%d\n",ret_w);
  
  //fseek(fp,0,SEEK_SET);   //将文件流指针偏亮到文件头,偏移量为0
  char buf_r[1024] = {
     0};
  size_t ret_r = fread(buf_r,1,sizeof(buf_r)-1,fp);
  printf("buf_r:%s\n",buf_r);
  printf("ret_r:%d\n",ret_r);
  return 0;
}

输出结果:

[test@localhost c_file]$ ./test_file 
open success
ret_w:12
buf_r: #此时文件流指针指在文件末尾,所以没有读取到
ret_r:0 # 返回值为-1,表示fread函数调用错误,为0,则表示读取成功了,但是没有读到内容

使用fseek后:

[test@localhost c_file]$ ./test_file 
open success
ret_w:12
buf_r:Hello World!
ret_r:12
[test@localhost c_file]$ cat linux 
Hello World![test@localhost c_file]$
1.5 int fclose(FILE *stream)

关闭文件流指针

fclose(fp); // fp 文件流指针

2. 系统文件IO

—— 系统调用函数的操作文件接口
open,write,read,lseek,close这些函数都是系统调用,是操作系统内核为程序员提供的函数

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

函数描述:

pathname:要打开的文件名称(路径+名称)
flags:以何种方式打开

  • 必须的宏,三个宏有且只能出现一个

    • O_RDONLY —— 只读方式
    • O_WRONLY —— 只写方式
    • O_RDWR —— 读写方式
  • 可选的宏

    • O_APPEND —— 追加
    • O_TRUNC —— 截断
    • O_CREAT —— 文件不存在则创建

使用方式:必须的宏和可选的宏之间使用按位或的方式(部分)

  • O_RDONLY —— 八进制的0
  • O_WRONLY —— 八进制的1
  • O_RDWR —— 八进制的2
  • O_CREAT —— 八进制的100

例:O_RDWR | O_CREAT (是按照位图的方式来使用的)
Linux基础IO_第1张图片
mode:权限,给新创建出来的文件设置权限,传参的时候,传八进制数字就可以了。
返回值:打开成功,返回大于等于0的数字,是文件描述符,打开失败,返回-1。
示例:

#include 
#include 
#include 
#include 
#include 

int main()
{
     
  int fd = open("./linux",O_RDWR | O_CREAT,0664);
  if(fd<0)
  {
     
    perror("open");
    return -1;
  }
  printf("open success\n");
  printf("fd:%d\n",fd);
  return 0;
}

输出结果:

[test@localhost sys_file]$ ./sys_file 
open success
fd:3
[test@localhost sys_file]$ ls
linux  makefile  sys_file  test.c
2.2 ssize_t write(int fd, const void *buf, size_t count)

函数描述:

fd:文件描述符,open的返回值
buf:往文件里写的内容
count:写的内容的大小
返回值:写成功的字节数量

示例:

#include 
#include 
#include 
#include 
#include 

int main()
{
     
  int fd = open("./linux",O_RDWR | O_CREAT,0664);//以读写方式打开文件,如果文件不存在,则创建
  if(fd<0)
  {
     
    perror("open");
    return -1;
  }
  printf("open success\n");
  printf("fd:%d\n",fd);

  char buf[1024] = {
     0};
  const char *ptr = "Hello World!";
  strncpy(buf,ptr,strlen(ptr));
  write(fd,buf,strlen(ptr));
  return 0;
}

输出结果:

[test@localhost sys_file]$ ./sys_file 
open success
fd:3
[test@localhost sys_file]$ cat linux 
Hello World![test@localhost sys_file]$
2.3 ssize_t read(int fd, void *buf, size_t count)

函数描述:

fd:文件描述符,open的返回值
buf:要将读到的内容放到哪里去
count:最大可以读多少个单位字节
返回值:返回读到的字节数量,返回0说明读取成功了,但是没有读取到内容

示例:

#include 
#include 
#include 
#include 
#include 

int main()
{
      
  int fd = open("./linux",O_RDWR | O_CREAT,0664);
  if(fd<0)
  {
     
    perror("open");
    return -1;
  }
  printf("open success\n");
  printf("fd:%d\n",fd);
  char buf[1024] = {
     0};
  read(fd,buf,sizeof(buf)-1); //-1 是为了给 '\0'留一个位置
  printf("buf:%s\n",buf);
  return 0;
}

输出结果:

[test@localhost sys_file]$ ./sys_file 
open success
fd:3
buf:Hello World!
2.4 off_t lseek(int fd, off_t offset, int whence)

函数描述:

fd:文件描述符
offset:偏移量
whence:

  • SEEK_SET:文件流指针偏移到文件头部
  • SEEK_CUR:文件流指针偏移到当前位置
  • SEEK_END:文件流指针指向文件末尾位置

示例:

#include 
#include 
#include 
#include 
#include 

int main()
{
     
  //打开文件
  int fd = open("./linux",O_RDWR | O_CREAT,0664);
  if(fd<0)
  {
     
    perror("open");
    return -1;
  }
  printf("open success\n");
  printf("fd:%d\n",fd);

  //写
  char buf_w[1024] = {
     0};
  const char *ptr = "Hello World!";
  strncpy(buf_w,ptr,strlen(ptr));
  write(fd,buf_w,strlen(ptr));

  //lseek(fd,0,SEEK_SET); //将文件流指针偏移到文件头
  //读
  char buf_r[1024] = {
     0};
  read(fd,buf_r,sizeof(buf_r)-1); //-1 是为了给 '\0'留一个位置
  printf("buf_r:%s\n",buf_r);
  return 0;
}

不使用lseek输出结果:

[test@localhost sys_file]$ ./sys_file 
open success
fd:3
buf_r:   #此时文件流指针指在文件末尾,所以没有读取到

使用lseek之后:

[test@localhost sys_file]$ ./sys_file 
open success
fd:3
buf_r:Hello World!
2.5 int close(int fd)

关闭文件描述符

close(fd); //fd 文件描述符  
close(0); // 关闭标准输入
close(1); // 关闭标准输出
close(2); // 关闭标准错误

3. 文件描述符

通过对open函数的学习与理解,文件描述符fd就是一个整数。

0&1&2

操作系统会为每一个进程在磁盘上创建一个以进程号命名的文件夹,在该文件夹下有一个fd文件夹,保存的信息即为该进程打开的文件描述符信息。
下面来一段代码演示:

#include 
#include 
#include 
#include 

int main()
{
     
  int fd = open("./linux",O_RDWR | O_CREAT,0644);//打开文件
  if(fd<0)
  {
     
    perror("open");
    return -1;
  }
  printf("fd:%d\n",fd);
  while(1)
  {
     
    sleep(1);
  }
  return 0;
}
//输出结果
fd:3  //然后程序死循环,方便查看信息

查看该进程的文件描述符信息:
Linux基础IO_第2张图片
可以看到,当我们新创建出来一个进程,势必会打开3个文件描述符,分别对应,标准输入 (0),标准输出 (1),标准错误 (2)。

如图所示,当 ./main运行该程序时:
Linux基础IO_第3张图片
可以看到,文件描述符其实就是在内核当中的fd_array数组的下标。

文件描述符的分配规则

通过上面的代码发现,打开新的文件后,fd的值为3,那么我们关闭0或者2再看

示例代码:

#include 
#include 
#include 
#include 

int main()
{
     
  close(0);
  int fd = open("./linux",O_RDWR | O_CREAT,0644);//打开文件
  if(fd<0)
  {
     
    perror("open");
    return -1;
  }
  printf("fd:%d\n",fd);
	
  while(1)
  {
     
    sleep(1);
  }
  return 0;
}
//输出结果:
fd:0  //然后程序死循环,方便查看信息

如图所示:关闭0以后
Linux基础IO_第4张图片
如图所示:关闭2以后
Linux基础IO_第5张图片
结论:文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符即最小未占用原则。

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

如图所示:
Linux基础IO_第6张图片
区别:

  • 文件流指针是fopen函数返回的,文件流指针是属于c库在维护的
  • 文件描述符是open函数返回的,文件描述符是内核在维护的。
  • 不同的文件流指针,在c库当中会创建不同的struct _IO_FILE,在_IO_FILE 结构体当中保存了不同的文件描述符。
  • 文件流指针当中包含文件描述符
  • 操作系统广泛存在缓冲区,但是如果针对文件流指针而言的缓冲区,是c库在维护的。
    • exit函数在退出进程时,会刷新缓冲区,就是因为操作的是文件流指针
    • _exit函数在退出进程时,不会刷新该缓冲区,是因为该缓冲区是c库在维护的,内核并不知道,所以不会刷新。
文件描述符(文件句柄)泄露的问题

当我们打开一个文件,操作系统会给程序分配一个文件描述符,如果在使用完毕之后,没有及时关闭文件,就会造成文件句柄泄露。

一个进程打开文件的最大数量
  • 命令行查看:
    Linux基础IO_第7张图片
    可以使用ulimit -[选项][值] 修改对应内容,比如打开文件最大数量。

  • 代码查看:

#include 
#include 
#include 

int main()
{
     
  int fd_count=0;
  while(1)
  {
     
    int fd = open("./linux",O_RDWR | O_CREAT,0664); // 一直打开文件
    if(fd<0)
    {
     
      perror("open");
      break;
    }
    printf("fd:%d\n",fd);
    fd_count++;
  }
  printf("fd_count:%d\n",fd_count);
  return 0;
}

输出结果:

[test@localhost fd]$ ./fd_count 
fd:3
fd:4
fd:5
fd:6
fd:7
……
fd:1019
fd:1020
fd:1021
fd:1022
fd:1023
open: Too many open files #到1024就停下来了
fd_count:1021 #从文件描述符3开始到1023
[test@localhost fd]$

由结果可得知:fd从3开始是因为新创建出来一个进程,操作系统势必会打开3个文件描述符,即 0(标准输入)、1(标准输出)、2(标准错误)

4. 重定向

命令行感受
  • 清空重定向
[test@localhost dup]$ echo "Hello" > linux #将Hello重定向到linux文件中
[test@localhost dup]$ cat linux 
Hello
[test@localhost dup]$ echo "linux" > linux #清空linux文件内容,再把linux重定向到linux文件中
[test@localhost dup]$ cat linux 
linux
[test@localhost dup]$ 
  • 追加重定向
[test@localhost dup]$ echo "Hello" >> linux 
[test@localhost dup]$ cat linux 
linux
Hello
[test@localhost dup]$ #并没有清空文件内容而是追加在后面

重定向的本质如图所示:
Linux基础IO_第8张图片

重定向接口 int dup2(int oldfd,int newfd)

流程:

  1. 关闭1号文件描述符
    1.1 关闭成功(返回文件描述符),才能进行第二步
    1.2 关闭失败(返回-1),不能进行第二步,重定向失败
  2. newfd,拷贝oldfd所描述的文件信息

代码演示:

#include 
#include 
#include 
#include 
int main()
{
     
  //打开文件,不存在则创建
  int fd = open("./linux",O_CREAT | O_RDWR,0664);
  if(fd<0)
  {
     
    perror("open");
  }
  printf("fd:%d\n",fd);
  //将标准输出重定向到文件当中
  
  //dup2(int oldfd,int newfd)
  //oldfd --> fd(上面打开文件的文件描述符)  newfd --> 1(标准输出)
 // dup2(fd,1);
  int ret_d = dup2(fd,1);
  printf("ret_d:%d\n",ret_d);//返回的是1这个文件描述符

  //成功以后,下面的代码在往标准输出当中进行输出的时候,就是往文件当中写了
  printf("Hello World\n");
 // close(fd);
  while(1)
  {
     
    sleep(1);
  }
  return 0;
}
//输出结果:
fd:3 //之后陷入死循环,为了方便查看文件描述信息

Linux基础IO_第9张图片
可以发现已经将标准输出重定向到了文件当中,验证一下是否打印到了文件当中

[test@localhost dup]$ cat linux 
ret_d:1
Hello World  #可以看到dup2函数之后的两条打印语句都打印到了linux这个文件中

你可能感兴趣的:(Linux,linux,c)