Linux最新【Linux从青铜到王者】第八篇 Linux基础IO(2),轻松拿到了阿里Linux运维高级开发工程师的offer

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

3.fread写文件

在这里插入图片描述

     1	#include<stdio.h>
     2	#include<string.h>
     3	#include<stdlib.h>
     4	int main()
     5	{
     6	    FILE\* fp=fopen("test.txt","w+");
     7	    if(fp==NULL)
     8	    {
     9	        printf("文件打开失败!\n");
    10	        exit(1);
    11	    }
    12	    fwrite("bit person!",sizeof(char),11,fp);
    13	    fseek(fp,0,SEEK\_CUR);
    14	    char arr[100];
    15	    fread(arr,sizeof(char),strlen(arr),fp);
            printf("%s\n",arr);
    16	    fclose(fp);
    17	    return 0;
    18	}

在这里插入图片描述

4.fwrite写文件

在这里插入图片描述

     1	#include<stdio.h>
     2	#include<string.h>
     3	#include<stdlib.h>
     4	int main()
     5	{
     6	    FILE\* fp=fopen("test.txt","a+");
     7	    if(fp==NULL)
     8	    {
     9	        printf("打开文件失败!\n");
    10	        exit(1);
    11	    }
    12	    char \*arr="bit education!\n";
    13	    fwrite(arr,sizeof(char),strlen(arr),fp);
    14	    fclose(fp);
    15	    return 0;
    16	}

在这里插入图片描述

5.fseek移动文件指针

在这里插入图片描述
在这里插入图片描述

6.ftell获取文件指针当前位置

在这里插入图片描述

7.rewind让文件指针回到文件起始位置

在这里插入图片描述

8.fcloes关闭文件

在这里插入图片描述

9.输出信息到显示器方法

fwrite(msg,strlen(msg),sizeof(char),fp);//往log.txt文件里面写
在这里插入图片描述

     1	#include<stdio.h>
     2	#include<string.h>
     3	int main()
     4	{
     5	    FILE \*fp=fopen("log.txt","w");
     6	    if(NULL==fp)
     7	    {
     8	        printf("打开文件失败!\n");
     9	    }
    10	    else 
    11	    {
    12	       /\* char c='A';
 13 for(;c<'Z';c++)
 14 {
 15 fputc(c,fp);
 16 }\*/
    17	        const char\* msg="Hello bit!\n";
    18	       // fwrite(msg,strlen(msg),sizeof(char),fp);//往log.txt文件里面写
    19	        fwrite(msg,strlen(msg),sizeof(char),stdout);//输出,往显示屏上输出
    20	
    21	    }
    22	    fclose(fp);
    23	    return 0;
    24	}

fwrite(msg,strlen(msg),sizeof(char),stdout);//输出,往显示屏上输出
在这里插入图片描述

10.stdin & stdout & stderr
  • C默认会打开三个输入输出流,分别是stdin, stdout, stderr
  • 仔细观察发现,这三个流的类型都是FILE, fopen返回值类型,文件指针
    在这里插入图片描述

二、系统文件I/O

1.read和write的初次使用

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:

写文件
在这里插入图片描述

     1	#include<stdio.h>
     2	#include<stdlib.h>
     3	#include<sys/stat.h>
     4	#include<fcntl.h>
     5	#include<unistd.h>
     6	#include<string.h>
     
     7	int main()
     8	{
     9	   int fd=open("myfile",O_WRONLY|O_CREAT,0644);
    10	   if(fd<0)
    11	   {
    12	       printf("文件打开失败!\n");
    13	       exit(1);
    14	   }
    15	   char\* arr="bit education!";
    16	   write(fd,arr,strlen(arr)-1);
    17	   close(fd);
    18	   return 0;
    19	}

在这里插入图片描述
读文件
在这里插入图片描述

     1	#include<stdio.h>
     2	#include<stdlib.h>
     3	#include<string.h>
     4	#include<fcntl.h>
     5	#include<sys/stat.h>
     6	#include<unistd.h>
     7	int main()
     8	{
     9	    int fd=open("myfile.txt",O_RDONLY);
    10	    if(fd<0)
    11	    {
    12	        printf("文件打开失败!\n");
    13	        exit(1);
    14	    }
    15	    char\* arr="hello bit!";
    16	    char buff[100];
    17	    read(fd,buff,strlen(arr)-1);
    18	    printf("%s\n",buff);
    19	    close(fd);
    20	    return 0;
    21	}

在这里插入图片描述

2.接口介绍

在这里插入图片描述

open man open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1

mode_t理解:直接 man 手册,比什么都清楚。
在这里插入图片描述
open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。
在这里插入图片描述
在这里插入图片描述

3.open函数返回值
  • 在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数
  • 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
  • 而open close read write lseek 都属于系统提供的接口,称之为系统调用接口。
  • 回忆一下我们讲操作系统概念时,画的一张图。

在这里插入图片描述
系统调用接口和库函数的关系,一目了然。所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

4.文件描述符fd(file descriptor)
  • 通过对open函数的学习,我们知道了文件描述符就是一个小整数。
    在这里插入图片描述
5.0 & 1 & 2

在这里插入图片描述

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2。
  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器。
     1	#include<stdio.h>
     2	#include<string.h>
     3	#include<stdlib.h>
     4	#include<fcntl.h>
     5	#include<unistd.h>
     6	int main()
     7	{
     8	    char buff[100];
     9	    int fd=read(0,buff,sizeof(buff));
    10	    write(1,buff,strlen(buff)-1);
    11	    write(2,buff,strlen(buff)-1);
    12	    return 0;
    13	}

在这里插入图片描述
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
在这里插入图片描述

6.文件描述符的分配规则

输出发现fd是3:

     1	#include<iostream>
     2	#include<fcntl.h>
     3	#include<sys/stat.h>
     4	#include<sys/types.h>
     5	#include<unistd.h>
     6	int main()
     7	{
     8	   int fd=open("myfile",O_RDONLY|O_CREAT);
     9	   if(fd<0)
    10	   {
    11	       std::cout<<"文件打开失败!"<<std::endl;
    12	   }
    13	   std::cout<<fd<<std::endl;
    14	   close(fd);
    15	   return 0;
    16	}

在这里插入图片描述
关闭0或者2,在看:

     1	#include<iostream>
     2	#include<unistd.h>
     3	#include<fcntl.h>
     4	#include<sys/stat.h>
     5	#include<sys/types.h>
     6	int main()
     7	{
     8	    close(0);
     9	   // close(2);
    10	    int fd=open("myfile",O_RDONLY|O_CREAT,0664);
    11	    if(fd<0)
    12	    {
    13	        std::cout<<"文件打开失败!"<<std::endl;
    14	        return 1;
    15	    }
    16	    std::cout<<"fd:"<<fd<<std::endl;
    17	    close(fd);
    18	    return 0;
    19	}

在这里插入图片描述
发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
----------------------最小未分配原则----------------------
在这里插入图片描述

7.重定向
1.清空重定向

我们发现第一次写的hello world被第二次写的你好呀覆盖了,这就是清空重定向。
在这里插入图片描述

2.追加重定向

我们发现第一次写的hello没有被第二次写的你覆盖,而是追加在hello后面,这就是追加重定向。在这里插入图片描述

3.重定向本质

那如果关闭1呢?看代码:

     1	#include<iostream>
     2	#include<stdlib.h>
     3	#include<unistd.h>
     4	#include<fcntl.h>
     5	#include<sys/types.h>
     6	#include<sys/stat.h>
     7	int main()
     8	{
     9	    close(1);
    10	    int fd=open("myfile",O_WRONLY|O_CREAT,0644);
    11	    if(fd<0)
    12	    {
    13	        std::cout<<"文件打开失败!"<<std::endl;
    14	        exit(1);
    15	    }
    16	    std::cout<<"fd:"<<fd<<std::endl;
    17	    close(fd);
    18	    return 0;
    19	}

在这里插入图片描述
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <

  • 那重定向的本质是什么呢?
    在这里插入图片描述
8.FILE

在这里插入图片描述

  • 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
  • 所以C库当中的FILE结构体内部,必定封装了fd。

来段代码在研究一下:

     1	#include<iostream>
     2	#include<fcntl.h>
     3	#include<sys/stat.h>
     4	#include<sys/types.h>
     5	#include<string.h>
     6	#include<unistd.h>
     7	#include<stdio.h>
     8	int main()
     9	{
    10	    int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0644);
    11	    if(fd<0)
    12	    {
    13	        std::cout<<"文件打开失败!"<<std::endl;
    14	        return 1;
    15	    }
    16	    const char\* str1="hello printf\n";
    17	    const char\* str2="hello fwrite\n";
    18	    const char\* str3="hello write\n";
    19	    write(1,str3,strlen(str3));
    20	    printf("%s\n",str1);
    21	    fprintf(stdout,"%s\n",str2);
    22	    fork();
    23	    close(fd);
    24	    return 0;
    25	
    26	
    27	}

在这里插入图片描述
但如果对进程实现输出重定向呢? ./hello > file , 我们发现结果变成了:
在这里插入图片描述

  • 我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!
  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
  • printf、fwrite库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后。
  • 但是进程退出之后,会统一刷新,写入文件当中。
  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
  • write 没有变化,说明没有所谓的缓冲。

综上: printf、fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

如果有兴趣,可以看看FILE结构体:

typedef struct _IO_FILE FILE; 在/usr/include/stdio.h

在/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. /
#define _IO_file_flags _flags
//缓冲区相关
/
The following pointers correspond to the C++ streambuf protocol. /
/
Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. /
char
_IO_read_ptr; /* Current read pointer /
char
_IO_read_end; /* End of get area. /
char
_IO_read_base; /* Start of putback+get area. /
char
_IO_write_base; /* Start of put area. /
char
_IO_write_ptr; /* Current put pointer. /
char
_IO_write_end; /* End of put area. /
char
_IO_buf_base; /* Start of reserve area. /
char
_IO_buf_end; /* End of reserve area. /
/
The following fields are used to support backing up and undo. */
char _IO_save_base; / Pointer to start of non-current get area. */
char _IO_backup_base; / Pointer to first valid character of backup area */
char _IO_save_end; / Pointer to end of non-current get area. /
struct _IO_marker _markers;
struct _IO_FILE _chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /
This used to be _offset but it’s too small. /
#define __HAVE_COLUMN /
temporary /
/
1+column number of pbase(); 0 is unknown. /
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/
char
_save_gptr; char
_save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

9.使用 dup2 系统调用

在这里插入图片描述
函数原型如下:

#include <unistd.h>
int dup2(int oldfd, int newfd);

在这里插入图片描述

     1	#include<iostream>
     2	#include<fcntl.h>
     3	#include<sys/stat.h>
     4	#include<sys/types.h>
     5	#include<string.h>
     6	#include<unistd.h>
     7	#include<stdio.h>
     8	int main()
     9	{
    10	    int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0644);
    11	    if(fd<0)
    12	    {
    13	        std::cout<<"文件打开失败!"<<std::endl;
    14	        return 1;
    15	    }
    16	   
    17	    dup2(fd,1);
            //close(fd);
    18	    const char\* str1="hello printf\n";
    19	    const char\* str2="hello fwrite\n";
    20	    const char\* str3="hello write\n";
    21	    write(1,str3,strlen(str3));
    22	    printf("%s\n",str1);
    23	    fprintf(stdout,"%s\n",str2);
    24	    fflush(stdout);
    25	    close(fd);
    26	    return 0;
    27	
    28	
    29	}

在这里插入图片描述

10.理解文件系统

我们使用ls -l的时候看到的除了看到文件名,还看到了文件元数据。
在这里插入图片描述

  • 每行包含7列:
  • 模式
  • 硬链接数
  • 文件所有者
  • 大小
  • 最后修改时间
  • 文件名

ls -l读取存储在磁盘上的文件信息,然后显示出来
在这里插入图片描述
其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息
在这里插入图片描述
上面的执行结果有几个信息需要解释清楚:

inode:
为了能解释清楚inode我们先简单了解一下文件系统:
在这里插入图片描述

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 15
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值