基础I/O--文件系统

文章目录

回顾C文件接口

C代码:

c 复制代码
#include<stdio.h>    
    
int main()    
{    
  FILE *fp=fopen("log.txt","w");    
  if(NULL==fp)    
  {    
    perror("fopen");    
    return 1;    
  }    
    
  fclose(fp);                                                                                                                            
  return 0;    
}  

我们要进行文件操作,前提是程序运行起来了,所谓的文件的打开和关闭是CPU执行我们的代码才被打开或者关闭的。
fopen()函数:

初步理解文件

打开文件:本质上是进程打开文件

文件没有被打开的时候,在哪里?在磁盘上

一个进程能打开很多文件吗? 可以

系统中可不可以存在很多进程? 可以

因此,在很对情况下,操作系统内部一定存在大量的被打开的文件

操作系统要不要对这些被打开的文件进行管理? 要

如何管理?先描述,再组织

如果在磁盘新建一个文件,里面什么内容都没有,大小为0KB,那么这个文件占用空间吗?占,因为文件对应的创建时间、文件类型等对应的文件属性都存在。

因此,文件=内容+属性

理解文件

操作文件,本质上是进程在操作文件

文件没有被打开时,文件在磁盘上,磁盘属于外部设备,磁盘本质是一个硬件,向文件写入,本质是向硬件中写入,但是用户没有权利直接向硬件写入,硬件的管理者是操系统,因此用户不能绕过操作系统直接访问硬件,必须通过操作系统写入。操作系统给用户提供系统调用,我们用的C/C++/其他语言,都是对系统调用接口的封装。

访问文件除了可以使用已经封装好的函数,还可以使用系统调用。

使用和并认识系统调用

open

概述
bash 复制代码
#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);

int creat(const char *pathname, mode_t mode);

pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行"或"运算,构成flags。

参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开

这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写

返回值:

成功:新打开的文件描述符

失败:-1

open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

标记位传参理解
c 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
  umask(0);
  //system call
  int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
  if(fd<0)
  {
    perror("open");
    return 1;
  }
}

操作系统本身就有一个权限掩码,现在在代码中又写了一个权限掩码。这里使用的是代码中的掩码,就近原则。自己设置了权限掩码就用自己设置的,没有就用系统的。因此最终创建的权限掩码就是666


int flag:是32个比特位的,用比特位来进行标志位的传递。flag标志位传递本质上是一个位图。

我们来设计一个传递为图标记位的函数:

c 复制代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define ONE 1            //1: 0000 0001
#define TWO (1<<1)       //2: 0000 0010
#define THREE (1<<2)     //3: 0000 0100
#define FOUR (1<<3)      //4: 0000 1000

void print(int flag)
{
  if(flag&ONE) printf("one\n");
  if(flag&TWO) printf("two\n");
  if(flag&THREE) printf("three\n");
  if(flag&FOUR) printf("four\n");
} 

int main()
{
  print(ONE);
  printf("\n");

  print(TWO);
  printf("\n");

  print(ONE|TWO);
  printf("\n");

  print(ONE|TWO|THREE);
  printf("\n");

  print(ONE|FOUR);
  printf("\n");

  return 0;
}

如果我们将打印替换成其他功能,那么就可以写出一个向指定函数传递标记位的方法。

返回值
c 复制代码
RETURN VALUE
open() and creat() return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).

返回成功,就帮我们创建一个新的文件描述符,就是一个整数,失败返回-1

c 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

int main()
{
  int fda=open("loga.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  printf("fda:%d\n",fda);

  int fdb=open("logb.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  printf("fdb:%d\n",fdb);

  int fdc=open("logc.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  printf("fdc:%d\n",fdc);

  int fdd=open("logd.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  printf("fdd:%d\n",fdd);

  return 0;
  
}

运行结果:

返回值是3 4 5 6,怎么不见0 1 2呢?

因为 0 1 2分别对应标准输入(键盘)、标准输出(显示器)、标准错误(显示器)

下文有具体介绍

close

c 复制代码
#include <unistd.h>

int close(int fd);

write

man 2 write可查看write函数

第一个参数 fd:表示待写入文件的文件描述符。

第二个参数 buf:指向待写入的文件内容。

第三个参数 count:待写入内容的大小,单位是字节。

返回值:实际上写入的字节数。

c 复制代码
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
c 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main()
{
  umask(0);
  //system call
  int fd=open("log.txt",O_WRONLY|O_CREAT,0666);
  if(fd<0)
  {
    perror("open");
    return 1;
  }

  const char *message="hello linux file!\n";
  write(fd,message,strlen(message));

  close(fd);
  return 0;
}

会发现确实是写入了hello linux file!

将上述代码中,写入hello linux file!,修改成aaa

c 复制代码
  const char *message="aaa";
  write(fd,message,strlen(message));

运行结果:

可见,默认不清空文件

那么,如何再次写入文件时,之前文件会被清空呢?

需要添加一个O_TRUNC

c 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main()
{
  umask(0);
  //system call
  int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
  if(fd<0)
  {
    perror("open");
    return 1;
  }

  const char *message="aaa";
  write(fd,message,strlen(message));

  close(fd);
  return 0;
}

运行结果:


追加写入:

c 复制代码
int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);

read

c 复制代码
#include <unistd.h>

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

第一个参数 fd:要读取文件的文件描述符。

第二个参数 buf:指向一段空间,该空间用来存储读取到的内容。

第三个参数 count:参数二指向空间的大小。

总结

c 复制代码
intfd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);

写方式打开,不存在就创建,存在就先清空


c 复制代码
int fd=open("log.txt",O_WRONLY|O_CREAT|O_APPEND,0666);

追加形式写入

文件描述符fd

0&1&2

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器

理解

在一个操作系统中有很多文件,操作系统需要对被打开文件进行管理,也就是需要创建对应的数据结构(struct_file),对文件的管理转换成对链表的增删查改。

任何一个文件=内容+属性,磁盘中文件的属性来初始化struct_file,将文件内容写入文件内核缓存里。

一个进程可以打开多个文件,对于进程来说怎么知道被打开文件与自己有关系,因此对应的task_struct存在一个属性struct files_struct *files,指向该类型的一个对象,该类型对象记录了当前进程所打开的所有文件新信息,操作系统中就存在一个结构体:struct file_struct,里面存在一个指针数组,数组的内容指向当前进程所打开的文件结构对象,也就是指向当前进程打开的文件。我们称这个数组为文件描述符表 ,数组下标称为文件描述符

文件描述符fd的本质是什么?

内核进程:文件映射关系的数组下标。

无论读写,都必须在合适的时候,让操作系统把文件内容读到文件缓冲区中。

open在干什么??

  1. 创建file
  2. 开辟文件缓冲区的空间,加载文件数据(延后)
  3. 查进程的文件描述符表
  4. file地址填入对应的下标中
  5. 返回下标

读写函数本质是拷贝函数

相关推荐
梅见十柒13 分钟前
wsl2中kali linux下的docker使用教程(教程总结)
linux·经验分享·docker·云原生
Koi慢热16 分钟前
路由基础(全)
linux·网络·网络协议·安全
传而习乎26 分钟前
Linux:CentOS 7 解压 7zip 压缩的文件
linux·运维·centos
我们的五年36 分钟前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
IT果果日记1 小时前
ubuntu 安装 conda
linux·ubuntu·conda
Python私教1 小时前
ubuntu搭建k8s环境详细教程
linux·ubuntu·kubernetes
羑悻的小杀马特1 小时前
环境变量简介
linux
小陈phd2 小时前
Vscode LinuxC++环境配置
linux·c++·vscode
是阿建吖!2 小时前
【Linux】进程状态
linux·运维
明明跟你说过2 小时前
Linux中的【tcpdump】:深入介绍与实战使用
linux·运维·测试工具·tcpdump