Linux系统下的文件操作

news2024/11/28 9:40:06

目录

一、文件理解

二、系统级的文件操作

1、打开open

2、关闭close

3、写入write

4、读取read

三、文件描述符

四、重定向

1、dup2函数

2、追加重定向

3、输入重定向

五、缓冲区

1、缓冲区存在的意义

2、缓冲区的刷新策略


一、文件理解

在Linux中,有一句经典的话叫做:一切皆文件。这句话是站在内核的角度说的,因为在内核中所有的设备(除了网络接口)都一律使用 Linux 独有的虚拟文件系统(VFS)。我们还需要清楚知道这些文件如何和硬件进行交互的。

语音层面的文件操作就是直接使用库函数,而事实上,文件操作是系统层面的问题,像进程管理一样,系统也会通过先描述再组织的方式对文件进行管理和操作。

文件由内容加属性构成。未使用的文件位于磁盘,而使用中的文件属性会被加载到内存。

文件可以同时被多次进行使用,OS为了更好的管理文件,会像进程task_struct 管理进程一样,通过struct file存储文件属性进行管理。struct file结构体包含了文件的各种属性与链接关系。

所以,总的来说,Linux下系统级的文件IO的操作,而C语言中的文件操作是OS与进程之间的交互。

二、系统级的文件操作

1、打开open

首先学习如何直接调用调用系统级函数 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); //可以修改权限

参数解读:

1、open接口的pathname参数表示要打开的文件名,和C语言的fopen一样的,是要带路径的。

2 、flags 参数是打开文件要传递的选项,即 系统传递标记位

3、mode 参数,就是 "文件操作模式" 了。

我们再来看看这个接口的返回值,居然是个 int,而不是我们 fopen 的 FILE* 。

open的返回值是int,返回-1表示error。

1.1 参数二的解读

 O_RDONLY   //只读
 O_WRONLY   //只写
 O_APPEND   //追加
 O_CREAT    //新建
 O_TRUNC    //清空

使用方法:

//三种参数组合,就构成了 fopen 中的 w
  int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);   //权限最好设置
  if(fd == -1)
  {
    perror("open file fail1");
    exit(-1);
  }
  .....
 // int fd=open(LOG,O_WRONLY|O_CREAT|O_APPEND) //对应的是fopen中的a
 // int fd=open(LOG,O_RDONLY)  //对应的是fopen中的r

注意:假若文件不存在,open 中的参数3最好进行设置,否则创建出来的文件权限为随机值

2、关闭close

#include <unistd.h>
​
int close(int fildes);

3、写入write

#include <unistd.h>
ssize_t write(int fildes, const void *buf, size_t nbyte);

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h> //write 的头文件
​
#define LOG "log.txt" //日志文件
#define SIZE 32
                                       
int main()                                                              
{             
  //三种参数组合,就构成了 fopen 中的 w
  int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666); //权限最好设置
  if(fd == -1)
  {                                                         perror("open file fail1");
    exit(-1);                       
  }           
              
  const char* ps = "Hello World!\n";                    
  int cnt = 5;
  while(cnt--) {
    write(fd, ps, strlen(ps));  //不能将 '\0' 写入文件中
 }
  close(fd);
  return 0;
}

4、读取read

#include <unistd.h>
ssize_t read(int fildes, void *buf, size_t nbyte);

代码如下:

#include <stdio.h>                                                                                #include <stdlib.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <string.h>    
#include <unistd.h> //write 的头文件    
    
    
#define LOG "log.txt" //日志文件    
#define SIZE 1024    
    
int main()    
{    
  int fd = open("file_open.c", O_RDONLY);    
  if(fd == -1)    
  {    
    perror("open file fail1");    
    exit(-1);    
  }    
  int n=100; //读取100个字符    
  char buff[SIZE];    
  int pos=0;    
  while(n--){    
    read(fd,(char*)buff+pos,1);    
    pos++;    
  }    
  printf("%s\n",buff);    
  close(fd);    
  return 0;    
}    

补充:C语言中fseek函数的用法:

功能:根据文件指针的位置和偏移量来定位文件指针。

参数解析: fseek第一个参数为流,第二个参数为偏移量,第三个参数为文件指针定位的位置 /SEEK_SET 以文件开头为中心 SEEK_CUR 文件指针的当前位置为中心 SEEK_END 文件结尾为中心

补充:Linux文件lseek函数的用法:

lseek函数头文件及其使用:

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence) ;

lessk()函数的参数说明: 第一个参数是文件描述符;第二个参数是偏移量,int型的数,正数是向后偏移,负数向前偏移;第三个参数是有三个选项: 1.SEEK_SET:将文件指针偏移到传入字节数处(文件头开始偏移) 2.SEEK_CUR:将文件指针偏移到当前位置加上传入字节数处;((当前位置开始偏移) 3.SEEK_END:将文件指针偏移到文件末尾加上传入字节数处(作为拓展作用,必须再执行一次写操作)

补充:文件操作题目详解:

三、文件描述符

咱们都知道在Linux下一切皆文件。固然设备也不例外,若是要对某个设备进行操做,就不得不打开此设备文件,打开文件就会得到该文件的文件描述符fd( file discriptor), 它就是一个很小的整数,每一个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每一个表项都有一个指向已打开文件的指针。

文件描述符0,1,2的理解:

代码如下:

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

如果关闭0或2则会发现:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    close(0);
    //close(2);
    int fd = open("test.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: 0 或者fd:2。可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

那么关闭了1呢,我们会发现输出到显示器上什么都没有,这是因为把stdout关闭了,所以什么都没有了。

四、重定向

使用fflush函数来变出来:

实际上并不是没有,而是没有刷新,用 fflush 刷新缓冲区后,text.txt 内就有内容了。

int main(void)
{
    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);
 
    fflush(stdout);
 
    close(fd);
}

可以发现,它不往显示器打印内容,而是往文件中打印,方向变了。

再往代码中写上一系列的:

fprintf(stdout, "hello world\n");

可以发现它都是重定向到test.txt文件中去了。

1、dup2函数

首先肯定要man一下dup2函数

int dup2(int oldfd, int newfd);

dup2 可以让 newfd 拷贝 oldfd,如果需要可以将 newfd 先关闭。

newfd 是 oldfd 的一份拷贝,将后者 (newfd) 的内容写入前者 (oldfd),最后只保留 oldfd。

至于参数的传递,比如我们要输出重定向 (stdout) 到文件中:

我们要重定向时,本质是将里面的内容做改变,所以是要把 fd 的内容拷贝到 1 中的:

\textrm{oldfd}:\textrm{fd}\leftarrow \textrm{newfd}:1

当我们最后进行输出重定向的时候,所有的内容都和 fd 的内容是一样的了。所以参数在传递时,oldfd 是 fd,所以应该是 dum2(fd, 1);

代码展示:

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h> //perror
#include<unistd.h>
#include<stdlib.h> //exit
int main(){
  int fd = open("./log.txt",O_CREAT | O_WRONLY |O_TRUNC,0644 );
  if(fd < 0){
    perror("open error:");
    exit(1);
  }
  dup2(fd,1); //本应该输出到1的,输出到了fd中
​
  printf("hello 我是printf,我不再输出到屏幕了,我输出到了log.txt\n");
  fprintf(stdout,"hello 我是fprintf,我不在输出到屏幕了,我输出到了log.txt\n");
  fputs("hello 我是fputs,不再输出到屏幕了,我输出到了log.txt\n",stdout);
  close(fd);
  return 0;
}

2、追加重定向

追加重定向只需要将我们 open 的方式改为 O_APPEND 就行了。

3、输入重定向

现在我们使用输入重新的,说白了就是将 "以前从我们键盘上去读" 改为 "在文件中读"。

代码展示:

int main(void)
{
    // 输入重定向
    int fd = open("test.txt", O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 0;
    }
    
    // 将本来从键盘上读 (0),改为从文件里读(3)
    dup2(fd, 0);
    // 读数据
    char line[64];
    while (fgets(line, sizeof(line),stdin) != NULL) {
        printf("%s\n", line);
    }
 
    fflush(stdout);
    close(fd);
 
    return 0;
}

五、缓冲区

1、缓冲区存在的意义

理性理解:

CPU 计算速度非常快!而磁盘的读取速度相对于 CPU 来说是非常非常慢的,因此需要先将数据写入缓冲区中,依据不同的刷新策略,将数据刷新至内核缓冲区中,供 CPU 进行使用,这样做的是目的是尽可能的提高效率,节省调用者的时间

语言级缓冲区:

首先,我们要知道:printf 内部就是封装了 write!

printf 里打印的内容 "Hello printf" 实际上是在缓冲区里的,printf 不显示的原因是没有带 \n,数据没有被立即刷新,所以 sleep 时 printf 的内容没有被显示出来。

所以 printf 不带 \n,数据没有被立即刷新,原因是因为它有缓冲区,此时如果我们想让他刷新,可以手动加上 fflush(stdout) 刷新一下缓冲区。这个缓冲区一定不在 write 内部,因为如果这个缓冲区是函数内部提供的,那么直接刷新出来了。

所以这个缓冲区它只能是 C 语言提供的,该缓冲区是一个 语言级缓冲区 (语言级别的缓冲区) 。

这就意味着我们曾经谈论的缓冲区,不是内核级别的缓冲区,而是一个语言级别的缓冲区。

看如下代码:

#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
 
int main(void)
{
    // 给它们都带上 \n
    printf("Hello printf\n");    // stdout -> 1
    fprintf(stdout, "Hello fprintf!\n");
    fputs("Hello fputs!\n", stdout);
 
    const char* msg = "Hello write\n";
    write(1, msg, strlen(msg));
 
    sleep(5);
 
    return 0;
}

运行结果可以看出可以正常的打印。

再把\0去掉呢?

int main(void)
{
    printf("Hello printf");    // stdout -> 1
    fprintf(stdout, "Hello fprintf!");
    fputs("Hello fputs!", stdout);
 
    const char* msg = "Hello write";
    write(1, msg, strlen(msg));
 
    sleep(5);
 
    return 0;
}

此时结果是只有 write 内容先出,当退出时 printf, fprint, fputs 的东西才显示出来。

然而 write 无论带不带 \n 都会立马刷新,也就是说,只要 printf, fprint, fputs 调了 write 数据就一定显示。

我们继续往下深挖,stdout 的返回值是 FILE,FILE 内部有 struct,封装很多的成员属性,其中就包括 fd,还有该 FILE 对应的语言级缓冲区。

C 库函数 printf, fwrite, fputs... 都会自带缓冲区,但是 write 系统调用没有带缓冲区。

我们现在提及的缓冲区都是用户级别的缓冲区,为提高性能,OS 会提供相关的 内核级缓冲区。

(内核级缓冲区不在本章的探讨范围内,本质我们专注于用户级缓冲区)

库函数在系统调用的上层,是对系统调用做的封装,但是 write 没有缓冲区,这说明了:

该缓冲区是二次加上的,由 C 语言标准库提供,我们来看下 FILE 结构体:

2、缓冲区的刷新策略

缓冲区有多种刷新策略,比如 C语言scanf 的缓冲区刷新策略为:遇到空白字符或换行就刷新,因此在输入时需要按一下回车,缓冲区中的数据才能刷新至内核缓冲区中,而 printf 的刷新策略为 行缓冲,即遇到 \n 才会进行刷新

总体来说,缓冲区的刷新策略分为以下三种:

  1. 无缓冲 -> 没有缓冲区

  2. 行缓冲 -> 遇到 \n 才进行刷新,一次冲刷一行

  3. 全缓冲 -> 缓冲区满了才进行刷新

一般而言,显示器的刷新策略为 行缓冲,而普通文件的刷新策略为 全缓冲

看这段代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
​
using namespace std;
​
int main()
{
    fprintf(stdout, "hello fprintf\n");
    const char* str = "hello write\n";
    write(1, str, strlen(str));
​
    fork(); //创建子进程
    return 0;
}

当我们直接运行程序,结果如下:

当我们进行重定向后,结果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/512899.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux多线程(2)线程安全(同步与互斥)

1.线程安全 概念 在多线程程序中&#xff0c;涉及到了对共享资源的操作&#xff0c;则有可能导致数据的二义性&#xff0c;而线程安全指的是&#xff0c;就算对共享资源进行操作也不会导致数据二义。 总结&#xff1a;多线程中对共享资源的操作不会出现问题。 实现&#xff1a;…

【AUTOSA】

目录 一、概述 二、限制与约束 三、功能描述 3.1 网络通信模式请求的转换 3.2 当前网络通信方式的输出 3.3 外围设备的控制 3.3.1 以太网接口控制器 3.4 多网络 3.5 网络模式状态机 3.5.1 初始化 3.5.2 在亚状态ETHSM_STATE_OFFLINE中的行为 3.5.3 子状态ETHSM_STA…

深入学习MYSQL-数据操纵及视图

前言 本博客中的例子和文字大部分来源于书籍《mysql必会知识》&#xff0c;后续会根据更多的书籍不断完善此笔记。 插入操作 可以这种方式向数据库插入两条数据&#xff0c;mysql和pg都支持这种写法。在实战中我们应该更多的使用这种写法&#xff0c;因为数据库的批量操作会…

TCP之滑动窗口和流量控制

TCP 利用发送字节数和接收字节数&#xff0c;这个二元组的唯一性保证顺序。讨论下保证顺序的具体算法&#xff0c;以及如何在保证顺序的基础上&#xff0c;同时追求更高的吞吐量。——TCP 的滑动窗口算法。 TCP 作为一个传输层协议&#xff0c;最核心的能力是传输。传输需要保证…

前端CSS学习(一)

1、基础认知 CSS概述&#xff1a;CSS:层叠样式表(Cascading style sheets)CSS作用是给页面中的HTML标签设置样式&#xff0c;起到美化修饰网页的作用CSS语法规则&#xff1a;CsS写在style标签中&#xff0c;style标签一般写在head标签里面&#xff0c; title 标签下面<!DOC…

AI类软件项目的应用场景

AI类的APP可以做很多事情&#xff0c;现在在很多业行都有具体的应用&#xff0c;从语音识别、图像识别到智能客服等都是不错的应用方向&#xff0c;在AI项目的时候一定要注意用户隐私&#xff0c;否则可能会带来不少潜在的问题。今天和大家分享一下这方面的内容&#xff0c;希望…

Call for Papers丨第三届GLB@KDD‘23 Workshop

鉴于介绍新数据集和Benchmark研究往往需要不同于常规论文的评审标准&#xff0c;计算机视觉和自然语言处理领域&#xff0c;以及最近的NeurIPS会议&#xff0c;都有专门致力于建立新Benchmark数据集和任务的Conference Track。然而在图机器学习领域&#xff0c;我们还没有类似的…

在 IDEA 中创建 Spring Boot 项目的方式(详细步骤教程)

开发环境 以下是我的开发环境 JDK 1.8Maven 3.6.3IDEA 2019&#xff08;2019 无所畏惧&#xff0c;即使现在已经 2023 年了哈哈哈&#xff09; 使用 Maven 的方式创建 Spring Boot 项目 下面的内容可能会因 IDEA 版本不同&#xff0c;而有些选项不同&#xff0c;但是大同小…

若依定制化改造

若依定制化改造 1 前端1.1 去除开屏加载动画1.2 去除登录时验证码1.3 修改网站标题&#xff08;主要是去除“若依”字样&#xff09; 2 后端2.1 用本地缓存替换Redis&#xff08;未完成&#xff09; 1 前端 1.1 去除开屏加载动画 做法&#xff1a; 效果&#xff1a; 这样子那…

契约锁助力公立医院“电子病历”评级,6大应用场景助力评审过关

2019年以来&#xff0c;公立医院每年绩效考核必须要过“电子病历关”&#xff0c;国家卫健委要求所有三级医院电子病历评级要达到4级以上&#xff0c;二级医院要达到3级以上。《电子病历系统应用水平分级评价管理办法&#xff08;试行&#xff09;及评价标准&#xff08;试行&a…

不可不知的脑网络

前言 人们常说&#xff0c;大脑是人类已知的最复杂的网络。人类大脑由大约1000亿个(1011个)神经元组成&#xff0c;由大约100万亿个(1014个)突触连接&#xff0c;这些神经元在多个空间尺度上进行组织&#xff0c;在多个时间尺度上进行功能交互。这个庞大的系统是我们所有思想、…

Prompt learning 教学[基础篇]:prompt基本原则以及使用场景技巧助力你更好使用chatgpt,得到你想要的答案

Prompt learning 教学[基础篇]&#xff1a;prompt基本原则以及使用场景技巧助力你更好使用chatgpt&#xff0c;得到你想要的答案 如果你想系统学习 如果你对 AI 和 Prompt Engineering 不是很了解&#xff0c;甚至连 ChatGPT 也不是很了解&#xff0c;那我建议你从基础篇开始读…

GPS定位与IP地址定位

在日常生活中&#xff0c;常用的定位技术包括GPS定位、基站定位与IP地址定位​、WIFI定位。 关于GPS定位 GPS(Global Positioning System&#xff0c;全球定位系统)起始于1958年美国军方的一个项目&#xff0c;1964年投入使用。20世纪70年代美国陆海空三军联合研制了新一代卫…

HBase入门 Phoenix使用第三方客户端DataGrip连接 连接包含hbase-site.xml配置(八)

1、因为hbase配置了phoenix HBase入门 Phoenix使用第三方客户端DataGrip连接&#xff08;六&#xff09; <property><name>phoenix.schema.isNamespaceMappingEnabled</name><value>true</value></property><property><name>…

@RequestBody,@RequestParam,@RequestPart应用场景和区别

ReqeustBody 使用此注解接收参数时&#xff0c;适用于请求体格式为 application/json&#xff0c;只能用对象接收 RequestParam 支持application/json&#xff0c;也同样支持multipart/form-data请求 RequestPart RequestPart这个注解用在multipart/form-data表单提交请求的方法…

封装Python脚本:使用钉钉机器人发送消息至钉钉

官方帮助文档&#xff1a;https://open.dingtalk.com/document/robots/custom-robot-access 一、获取自定义机器人webhook 可以通过如下步骤设置钉钉机器人&#xff1a; 首先建立或者进入某个群聊在群聊内部点击“设置>机器人>添加机器人” 添加一个自定义机器人&…

从Facebook到Diem币:社交媒体巨头在加密货币领域的演变

大家都知道Facebook是一个全球知名的社交媒体平台&#xff0c;几乎每个人都在其中与朋友分享照片、发表状态或留言。 然而&#xff0c;随着时间的推移&#xff0c;Facebook不仅仅局限于社交交流&#xff0c;而是逐渐涉足更广阔的领域&#xff0c;其中之一就是加密货币。在本文…

三菱FX5U系列PLC本体自带模拟量输入输出使用方法介绍及示例

三菱FX5U系列PLC本体自带模拟量输入输出使用方法介绍及示例 如下图所示,三菱FX5U本体自带2路模拟量输入和1路模拟量输出,打开CPU左侧的保护盖板即可看到接线端子的位置, 如下图所示,查看手册,可以看到模拟量输入的相关说明: 输入:DC0-10V 软元件:SD6020(通道1)、SD…

杂记(二)2023.5.11

目录 流程图应该如何绘制&#xff1f; coverage 泡鲁达拿铁是什么&#xff1f;泡鲁达英文是什么&#xff1f;什么来历&#xff1f; 为什么我的泡鲁达咖啡会送我一些面包条呢&#xff1f; 介绍一下欧内斯特 梅的《历史的教训》 介绍一下陆奥宗光 介绍一下陆奥宗光的《蹇蹇录…

多处最优服务次序问题——算法设计与分析(C实现)

问题描述&#xff1a;设有n个顾客同时等待一项服务。顾客i需要的服务时间为&#xff0c;共有s处可以提供此项服务。应该如何安排n个顾客的服务次序&#xff0c;才能使平均等待时间达到最小&#xff1f;平均等待时间是n个顾客的等待服务时间的总和除以n。 算法设计&#xff1a;对…