基础IO(上)——Linux

1.储备知识

对文件的操作范畴:
在系统角度理解文件
文件 = 内容 + 属性(也是数据)
创建一个空文件也占空间
对文件的所有操作无外乎两种:对内容和对属性

c、c++程序会默认打开三个文件流:
标准输入:键盘 extern FILE *stdin;
标准输出:显示器 extern FILE *stdout;
标准错误:显示器 extern FILE *stderr;

linux下一切皆文件
感性认识:
曾经理解的文件:read、write
键盘和显示器可以被看做文件吗?可以
我从来没有打开过键盘和显示器文件,但是依旧能够直接使用scanf,fgets,printf,cout…
是因为c、c++程序会默认打开三个文件流

磁盘是硬件,只有操作系统才能真正的访问磁盘
文件在磁盘上放着,我们访问文件,需要先写代码然后编译生成exe最后运行,那么访问文件本质是谁在访问文件呢?
进程
进程访问文件是需要接口的
之前我们学习的接口是语言类的接口

  1. 要向硬件上写入,只有谁才有权利呢?(代码上)
    操作系统
  1. 如果普通用户也想向硬件写入呢?
    必须让OS提供接口
    提供文件类的系统调用接口

为什么文件类的系统调用接口之前从未接触过?
1). 因为之前学的是C语言,文件类的系统调用接口,比较难掌握,语言上对这些接口做一下封装,让接口更好被使用——>语言类接口
每个语言都会做封装,就会导致了不同的语言有不同的语言级别文件访问接口。但是底层用的都是系统接口。
为什么要学习os层面的接口?
linux中这样的接口只有一套
(os只有一个)
 

2). 跨平台:把所有平台的代码都实现一遍,条件编译,动态裁剪
如果语言不提供对文件的系统接口的封装,是不是所有访问文件的操作,都必须直接使用OS的接口?
是的
面对语言的客户,要不要访问文件呢?

一旦使用系统接口,编写所谓的文件代码,无法在其他平台中直接运行了。不具备跨平台性!
显示器和磁盘写入没有区别

什么叫做文件?
站在系统的角度,可以被input读取,或者能够output写出的设备就叫做文件
狭义文件:普通的磁盘文件
广义文件:显示器,键盘,网卡,声卡,显卡,磁盘……几乎所有的外设,都可以称之为文件。

 
 

2. 文件描述符

2.1 c接口

makefile

   myfile:myfile.c
     gcc -o $@ $^
   .PHONY:clean
     rm -f myfile                                  

w:从文件开始写
a:从结尾开始写

  1 #include <stdio.h>
  2 
  3 int main()
  4 {
  5   FILE *fp = fopen("log.txt","w");
  6   if(fp == NULL)
  7   {
  8     perror("fopen");
  9     return 1;
 10   }
 11   //进行文件操作
 12                                                                                
 13 
 14 
 15   fclose(fp);            
 16   return 0;
 17 }

此时在当前路径下,是否存在文件log.txt?——还没有,需要运行程序之后,log.txt才会被创建
log.txt是谁创建的?——os
会在哪里创建?——当前路径
当前路径:当一个进程运行起来的时候,每个进程都会记录自己当前所处的工作路径
在这里插入图片描述
 
写文件的接口

  1 #include <stdio.h>
  2 #include<unistd.h>
  3 #include<string.h>
  4           
  5 int main()
  6 {         
  7   FILE *fp = fopen("log.txt","w");
  8   if(fp == NULL)
  9   {       
 10     perror("fopen");
 11     return 1;
 12   }       
 13   //进行文件操作
 14   const char *s1 = "hello fwrite";
 15   fwrite(s1,strlen(s1),1,fp);
 16           
 17   const char *s2 = "hello fprintf";
 18   fprintf(fp,"%s",s2);
 19           
 20   const char *s3 = "hello fputs";
 21   fputs(s3,fp);
 22                                                                                
 23  
 24              
 25   fclose(fp);
 26   return 0;
 27 }

先删掉之前的log.txt,再运行程序
在这里插入图片描述

 const char *s1 = "hello fwrite\n";
   fwrite(s1,strlen(s1),1,fp);

要不要+1?fwrite(s1,strlen(s1)+1,1,fp);
不要。
\0结尾是C语言的规定,文件不用遵守
如果+1打印出来就会多了一个乱码
文件保存的是有效数据,/n只是标志结尾的标识符

fopen:

  1. 当以写(w)方式打开文件时,会先清空文件。
    用重定向方式写入:echo helloworld > log.txt
    想要清空:>log.txt
    在这里插入图片描述

2.以a方式打开文件是追加重定向,不会清空之前的内容
r
按行读取
fgets是C语言提供的接口 s(string)会自动在字符结尾添加\0

char line[64];
while(fgets(line,sizeof(line),fp) != NULL)
{
    fprintf(stdout,"%s",line);
}

三个标准输入输出流:
stdout
标准输入:键盘 extern FILE *stdin;
标准输出:显示器 extern FILE *stdout;
标准错误:显示器 extern FILE *stderr;
都叫做文件指针
一切皆文件

 
命令行参数:

int main(int argc,char *argv[])

 

2.2 直接使用系统接口

c库函数: fopen fclose fread fwrote
系统调用:open close read write
两者关系:上面的c库函数底层都是系统调用

宏定义:全大写
如何给函数传递标志位

 6 #define ONE 0x1//0000 0001
    7  #define TWO 0x2//0000 0010
    8  #define THREE 0x4//0000 0100
W>  9  
   10  
   11 void show(int flags)
   12 {
   13   if(flags & ONE) printf("hello one\n");
   14   if(flags & TWO) printf("hello two\n");    
   15   if(flags & THREE)  printf("hello three\n");    
   16 }                                                      
   17                                                        
   18 int main()                                             
   19 {                                                      
   20   show(ONE);                                           
   21   show(TWO);                                           
   22   show(ONE | TWO);                                     
   23   show(ONE | TWO | THREE);                             
   24   return 0;                                                                  
   25     

 

2.3 open函数返回值

file descriptor:文件描述符
返回值:成功:新打开的文件描述符失败:-1
 
接口介绍
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_TRUNC:写之前清空文件
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
mode_t理解:直接 man 手册,比什么都清楚。open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open
write read close lseek ,类比C文件相关接口

 

2.4 文件描述符fd

0 & 1 & 2

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;
}
FILE *fopen(const char *path,const char *mode);

FILE是一个struct的结构体,由c标准库提供
结构体内部会有多种成员
而在系统角度只认fd。
所以FILE结构体必定封装了fd
_fileno

 

2.5 周边文件

fd是什么?
之前说过进程要访问文件,必须先打开文件。
那么一个进程可以打开多个文件吗?
一般而言,进程:打开的文件 = 1:n

文件要被访问,前提是要被加载到内存中,才能直接被访问
进程:打开的文件 = 1:n 如果是多个进程都打开直接的文件呢?
系统中就会存在大量的被打开的文件,所以OS要把如此之多的文件管理起来:
先描述,再组织!

文件的属性从哪里来?
一部分在对应的磁盘中
所以在内核中,OS内部要管理每一个被打开的文件,需要构建结构体

struct file
{
    struct file * next;
    struct file * prev;
    //包含了一个被打开的文件的几乎所有的内容(不仅仅包含属性)
}

创建struct file的对象,充当一个被打开的文件。如果有很多再用双链表组织起来
文件对象里面包含了文件的所有内容
fd在内核中,本质是一个数组下标!

文件:
1.被进程在内存中打开的文件(执行你的代码的一定是CPU)
2.没有被打开的文件(在磁盘上,文件=内容+属性)(磁盘文件)
进程控制块:PCB struct task_struct

fopen——open——fd——FLIE——FILE*
fwrite()——FILE*——fd——write——write(fd,…)——自己执行操作系统内部的write方法——能找到进程的task_struct——*fs——file_struct——fd_arry[fd]——structfile——内存文件被找到了——操作

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

 
 

3. 重定向

3.1 输出重定向

 1 myfile:myfile.c
  2   gcc -o $@ $^
  3 .PHONY:clean
  4 clean:                                                                         
  5   rm -f myfile
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n", fd);

close(fd);
return 0;
}

此时fd是3
因为012已结被提前占用了

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n", fd);

close(fd);
return 0;
}
fd:也是3
close(0);

fd:0
close(2)
fd:2
结论:
fd在系统层面的分配规则是:最小的,没有被占用的文件描述符

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
    close(1);
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n", fd);

close(fd);
return 0;
}

不显示了

处理:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
close(1);

int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);

//close(fd);
return 0;
}

printf默认是往stdout
运行./myfile时依旧不显示
但是cat log.txt时有内容,打印了fd:1
确实是之前所说的fd分配原则

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
int main()
{
close(1);

int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
fprintf(stdout, "hello fprintf\n");
const char *s = "hello fwrite\n";
fwrite(s, strlen(s), 1, stdout);

fflush(stdout);

close(fd);
return 0;
}

这些接口都应该是往显示器(标准输出)打印的
但是下面的内容都写入(显示)到了log.txt
这就叫做输出重定向

重定向的本质:
就是在OS内部更改fd对应的内容的指向

 

3.2 输出重定向

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>


int main()
{
int fd = open("log.txt", O_RDONLY);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n", fd);

char buffer[64];
fgets(buffer, sizeof buffer, stdin);

printf("%s\n", buffer);

return 0;
}

fd:3
输入hello
输出hello

close(0);

fd:0
aaaaaaaaaaaaaa
本来应该从键盘读取的内容,却直接读取log.txt中的内容
这是输入重定向

 

3.3 追加重定向

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>


int main()
{
close(1);
int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT);
if (fd < 0)
{
perror("open");
return 1;
}
fprintf(stdout, "you can see me, success\n");

return 0;
}

./myfile
cat log.txt
you can see me,success
再运行,打印也只有一行

int main()
{
close(1);
//int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT);
int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT);
if (fd < 0)
{
perror("open");
return 1;
}
fprintf(stdout, "you can see me, success\n");

return 0;
}

./myfile
./myfile
./myfile
./myfile
you can see me,success
you can see me,success
you can see me,success
you can see me,success
运行几次,追加几次 这就是追加重定向

 

3.4 dup

oldfd copu to newfd
最终要和oldfd一样,那么newfd就没有意义了,就可以关闭了

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>


int main(int argc, char *argv[])
{
if (argc != 2)
{
return 2
}
int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT);
if (fd < 0)
{
perror("open");
return 1;
}

fprintf(stdout, "%s\n", argv[1]);
return 0;
}

退出码:2
./myfile 105
105

int main(int argc, char *argv[])
{
if (argc != 2)
{
return 2
}
int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT);
if (fd < 0)
{
perror("open");
return 1;
}

dup2(fd, 1);
fprintf(stdout, "%s\n", argv[1]);
return 0;
}

./myfile hello
不显示
cat log.txt
hello
想显示的内容都会被打印到文件中而不是在显示器中

int main(int argc, char *argv[])
{
if (argc != 2)
{
return 2
}
//int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT);
int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT);
if (fd < 0)
{
perror("open");
return 1;
}

dup2(fd, 1);
fprintf(stdout, "%s\n", argv[1]);
return 0;
}

./myfile
./myfile aaa
./myfile bbb
cat log.txt
aaa
bbb
追加

int main(int argc, char *argv[])
{
if (argc != 2)
{
return 2
}
//int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT);
int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT);
if (fd < 0)
{
perror("open");
return 1;
}

dup2(fd, 1);
fprintf(stdout, "%s\n", argv[1]);
close(fd);
return 0;
}

依旧可以显示
这是dup2的一种特性,与缓冲区有关

 
 

4. 如何理解一切皆文件?

理性理解:
这是linux的设计哲学,体现在os的软件设计层面
 
linux是用C语言写的,那么如何用C语言实现面向对象,甚至是运行时多态?
类:所有事物与属性的结合
成员属性+成员方法
struct:可以包含成员属性,但是在纯C语言中不包含成员方法
但是我想让struct中包含成员方法呢?
可以定义函数指针,指向函数,去调用就可以了

底层不同的硬件,一定对应的是不同的操作方法
但是上面的设备都是外设,所以每一个设备的核心访问函数都可以是read、write(I、O)
所有的设备都可以有自己的read和write,但是代码的实现一定不一样!
设计一个struct,打开一个磁盘文件的时候,创建一个struct file
各自指向各自的
在这一层上面看,没有任何的硬件差别了,看待所有文件的方式,都统一成为了struct file
所以就有了linux下一切皆文件的说法
VFS虚拟文件技术

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hey pear!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值