【Linux】系统文件IO·文件描述符fd

前言

 C语言文件接口

 C 语言读写文件

1.C语言写入文件

2.C语言读取文件

stdin/stdout/stderr

系统文件IO

文件描述符fd:

文件描述符分配规则:

文件描述符fd:

前言

我们早在C语言中学习关于如何用代码来管理文件,比如文件的输入和文件的输出,一些文件的接口,如何深入学习文件的知识,在Linux下一切皆文件,今天我们探讨Linux的基础I/O。

1>文件=内容+属性

2>访问文件之前,都需要先打开文件,并且对文件的修改都是通过执行代码的方式完成的,文件必须加载到内存中

文件被访问被修改必须得在内存中完成,因为CPU只能访问内存,所以文件必须加载到内存中。 打开文件->文件被加载到内存中

3>文件由谁打开? ->  由进程打开文件

4> 一个进程可以打开多个文件,由操作系统管理多个被打开的文件,那么这些文件是怎样被管理的:  先描述,再组织,内核中一定要有描述被打开文件的结构体,使用其定义对象

5>系统中不是所有的文件都被进程打开了, 没有被打开的文件就在磁盘中

 C语言文件接口

 C 语言读写文件

🗡文件操作:

    首先要打开文件:打开成功,返回文件指针;打开失败,返回NULL
    最后要关闭文件

🗡代码操作:

    FILE *fopen(const char *path, const char *mode);
    int fclose(FILE *fp);

fwritesize_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream )
fcloseint fclose ( FILE * stream );

1.C语言写入文件

int fputs(const char *s, FILE *stream); 
int fprintf(FILE *stream, const char *format, ...);

1. 如下,我们以"w"的方式打开文件,以"w"方式打开文件会先清空文件的内容然后再向文件写入内容。

#include <stdio.h>

int main()
{
    FILE *fp=fopen("./log.txt","w");
    if(fp==NULL)
    {
        perror("fopen");
        return 1;
    }
    fclose(fp);    
    return 0;
}

 

2. 我们使用"a"方式(append) 打开文件  "a"方式 是向文件的末尾追加内容

#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
 
int main()
{
	FILE *fp = fopen("log.txt","a");
	if(fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	const char* s = "hello linux\n";
	fwrite(s,strlen(s),1,fp);
	return 0;	
}

2.C语言读取文件

char *fgets(char *s, int size, FILE *stream); 
int fscanf(FILE *stream, const char *format, ...);
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
	FILE *fp = fopen("./log.txt","r");
	if(fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	char buffer[64];
	while(fgets(buffer,sizeof(buffer),fp))
	{
		printf("%s",buffer);
	}
	return 0;	
}

stdin/stdout/stderr

C默认会打开三个输入输出流,分别是

stdin       标准输入  键盘设备

stdout     标准输出  显示器设备

stderr      标准错误  显示器设备

仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针

 stdin、stdout、stderr 都可以直接使用,例如:

系统文件IO

上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

访问文件不仅仅要有C语言上的文件接口,OS必须提供对应的访问文件的系统调用

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

来看下面的例子

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

 

此时我们可以观察到 创建出来的文件的权限是乱的

这是因为,没有这个文件,要创建它,系统层面就必须指定权限是多少!我们采用权限设置的八进制方案

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

 此时的权限就正常了。

其中我们发现,我们传入的flag为 O_WRONLY|O_CREAT,中间为什么要用|连接起来呢:

这是一种用户层给内核传递标志位的常用做法。int有32个bit位,一个bit代表一个标志,就可以传递多个标志位且位运算效率较高。这些O_RDONLY、O_WRONLY、O_RDWR 都是只有一个比特位是1的数据,并且相互不重复,这样 |在一起,就能传递多个标志位。

看看下面这个例子

#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)

void Print(int flag)
{
  if(flag & ONE ) printf("1\n");
  if(flag & TWO) printf("2\n");
  if(flag & THREE) printf("3\n");
  if(flag & FOUR) printf("4\n");
  if(flag & FIVE) printf("5\n");
}

int main()
{
  Print(ONE);
  printf("------------\n");
  Print(TWO);
  printf("------------\n");
  Print(ONE|TWO);
  printf("------------\n");
  Print(THREE|FOUR|FIVE);
  printf("------------\n");
  Print(ONE|TWO|THREE|FOUR|FIVE);
  printf("------------\n");
  return 0;
}

 

文件描述符fd:

open函数的返回值是就是文件描述符,类型为int,下面我们来看看fd的值

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
 
int main()
{
    int fd1 = open("./log1.txt",O_WRONLY | O_CREAT, 0644);
    int fd2 = open("./log2.txt",O_WRONLY | O_CREAT, 0644);
	int fd3 = open("./log3.txt",O_WRONLY | O_CREAT, 0644);
	int fd4 = open("./log4.txt",O_WRONLY | O_CREAT, 0644);
	
	printf("fd:%d\n",fd1);
	printf("fd:%d\n",fd2);
	printf("fd:%d\n",fd3);
	printf("fd:%d\n",fd4);
 
	close(fd1);
	close(fd2);
	close(fd3);
	close(fd4);
	return 0;	
}

我们可以看到fd的值是从3开始的,一次打印出了3、4、5、6 那么前面的0,1,2去了哪里?

这时候我们想到了stdin,stdout,strerr ,当我们的程序运行起来变成进程,默认情况下,OS会帮助我们打开三个标准输入输出,012其实分别对应的就是标准输入stdin、标准输出stdout、标准错误stderr。对应硬件设备也是键盘、显示器、显示器。

0代表标准输入

1代表标准输出

2代表标准错误

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0
}

此时输出结果为 fd:3

再关闭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("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}

得到的结果会是fd:0 或者fd:2

文件描述符分配规则:

这样文件描述符被分配为01234678… 这样从0开始,连续的整数。 并且会优先分配 最小的,未被使用过的。每次给新文件分配的fd,是从fd_array[]中找一个最小的、未被使用的作为新的fd。

    所有的文件操作都是进程执行对应的函数,即本质上是进程对文件的操作。

    如果一个文件没有被打开,这个文件是在磁盘上。如果我创建一个空文件,该文件也是要占用磁盘空间的,因为文件的属性早就存在了(包括名称、时间、类型、大小、权限、用户名所属组等等),属性也是数据,所谓“空文件”是指文件内容为空。

即磁盘文件 = 文件内容 + 文件属性。事实上,我们之前所学的所有文件操作都可以分为两类:对文件内容的操作 + 对文件属性的操作(fseek、ftell、rewind、chmod、chgrp等等).

    要操作文件,必须打开文件(C语言fopen、C++打开流、系统上open),本质上,就是文件相关的属性信息从磁盘加载到内存。

操作系统中存在大量进程,进程可以打开多个文件,即进程 : 文件 = 1 : n ,系统中可能存在着更多的打开的文件(暂时不考虑一个文件被多个进程打开的特殊情况)。那么,OS要不要把打开的文件在内存中(系统中)管理起来呢?那么就要上管理的六字真言:先描述,再组织!

    打开的这么多文件,怎么知道哪些是我们进程的呢?操作系统为了让进程和文件之间产生关联,进程在内核创建struct files_struct 的结构,这个结构包含了一个数组 struct file* fd_array[] ,也就是一个指针数组,把表述文件的结构体地址填入到特定下标中。

文件描述符fd:

此时我们cat命令查看log.txt文件,内容为空

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

我们这段代码旨在打开log.txt文件,并且向显示器上打印fd的值。

但是我们执行程序之后,显示器上并没有出现我们期望的fd的值

反而我们cat 一下log.txt,发现fd的值竟然打印在了log.txt文件中

 首先分析一下fd的值, 我们关闭了"1" 此时1 就是最小的且未被使用的,所以此时open的返回值是1;

对于本应打印在显示器上的值打印在文件中这件事情,printf底层封装了一些write,stdout等

此时传给printf的fd为1,那么将 文件描述符1 传递给进程后,进程就开始向log.txt文件中打印信息

同时我们也知道了 printf底层是在向标准输出(stdout)打印

int fprintf(FILE *stream, const char *format, ...);
stdout -> FIEL{fileno = 1} -> log.txt// stdout只认识1,只对1输入输出

extern  :  dup2

#include <unistd.h>
int dup2(int oldfd, int newfd); //oldfd->newfd
dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:
*  If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.
*  If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.

拷贝的是fd对应内容,最终相当于全部变成old

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

对于C++ 程序员来说,35岁魔咒是否存在?

大家常说程序员职业生涯会在35岁左右遇到所谓的“35岁魔咒”。这意味着在这个年龄段&#xff0c;程序员可能会面临就业不稳定或职业发展的挑战。对于C程序员来说&#xff0c;这个问题更加引人关注。 随着时间的推移&#xff0c;技术行业不断演进&#xff0c;新的编程语言层出不…

编程精粹—— Microsoft 编写优质无错 C 程序秘诀 01:假想的编译器

这是一本老书&#xff0c;作者 Steve Maguire 在微软工作期间写了这本书&#xff0c;英文版于 1993 年发布。2013 年推出了 20 周年纪念第二版。我们看到的标题是中译版名字&#xff0c;英文版的名字是《Writing Clean Code ─── Microsoft’s Techniques for Developing》&a…

34、shell数组+正则表达式命令

0、课前补充 jiafa () { result$(echo " $1 $2 " | bc ) print "%.2f\n" "$result" } ##保留小数点两位 薄弱加强点 a$(df -h | awk NR>1 {print $5} | tr -d %) echo "$a"一、数组 1.1、定义 数组的定义&am…

Native开发工具之应用开发编辑器打包发布(一)

Nuclide 是基于 Atom 之上构建的单独的一个包&#xff0c;其提供可编程性且社区非常活跃。它为 React Native、Hack 和 Flow 项目提供一流的开发环境。 2. Atom 官网&#xff1a;https://atom.io/ Github 项目地址&#xff1a;atom(https://github.com/atom) 文档&#xff1…

SpringBoot-注解@PropertiySource读取外部属性文件

ConfigurationProperties和Value两个注解能从配置文件中获取数据&#xff0c;但是前面讲了他们是从全局配置文件中获取&#xff0c;且只能从全局配置文件中获取&#xff0c;那么如果是一些数值类的数据放在全局配置文件里&#xff0c;是不怎么合适的&#xff0c;我们往往会把他…

gitlab 获取指定分支下指定路径文件夹的解决方案

第一步&#xff1a; 获取 accessToken 及你的 项目 id &#xff1a; 获取 accessToken ,点击用户头像进入setting 按图示操作&#xff0c;第 3 步 填写你发起请求的域名。 获取项目 id , 简单粗暴方案 进入 你项目仓库页面后 直接 源码搜索 project_id&#xff0c; value 就…

QT自定义标题栏窗口其一:实现拖动及可拉伸效果

1、效果 2、核心代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent<

Android intent 打开链接跳转到外部浏览

前言: 各位同学大家好, 最近接到一个比较诡异的需求 ,不是通常的webview 加URL显示网页 是需要跳转到外部浏览器 ,我这边处理好了就分享给大家 效果图 : 点几就跳转到外部浏览器 如图 具体代码实现: 点击打开链接并跳转外部浏览器方法 public void openBrowser(Con…

算法刷题总结

1. 排序算法 1.1 快速排序算法 public abstract class Sort<T extends Comparable<T>> {public abstract void sort(T[] array);protected boolean less(T first, T two) {return first.compareTo(two) < 0;}protected void swap(T[] array, int i, int j) {T…

《人生苦短,我用python·四》pybind11多场景使用

引言 Pybind11作为一个强大的工具&#xff0c;不仅可以轻松地将简单的C函数和类暴露给Python&#xff0c;还可以处理更复杂的场景&#xff0c;比如支持C标准库容器、处理C异常、以及自定义数据结构的转换。本文将深入介绍Pybind11的一些高级用法&#xff0c;帮助你在实际项目中…

修复 pprof ---node_exproter访问漏洞(go-pprof-leak)

前言&#xff1a; ** 在Go语言中&#xff0c;pprof和debug包是用来检测和避免goroutine泄漏&#xff0c;避免导致goroutine泄漏&#xff0c;进而消耗大量系统资源。不过对于安全而言确又存在一定风险&#xff0c;** 风险&#xff1a; 通过node_exporter web发现 190.168.46.1…

Unity Meta Quest 开发:关闭 MR 应用的安全边界

社区链接&#xff1a; SpatialXR社区&#xff1a;完整课程、项目下载、项目孵化宣发、答疑、投融资、专属圈子 &#x1f4d5;教程说明 这期教程我将介绍如何在应用中关闭 Quest 系统的安全边界。 视频讲解&#xff1a; https://www.bilibili.com/video/BV1Gm42157Zi 在 Unity…

DVWA 靶场 Weak Session IDs 通关解析

前言 DVWA代表Damn Vulnerable Web Application&#xff0c;是一个用于学习和练习Web应用程序漏洞的开源漏洞应用程序。它被设计成一个易于安装和配置的漏洞应用程序&#xff0c;旨在帮助安全专业人员和爱好者了解和熟悉不同类型的Web应用程序漏洞。 DVWA提供了一系列的漏洞场…

VirtualBox虚拟机声音设置

最近发现VirtualBox创建的Windows 10和11虚拟机没有声音&#xff0c;但是另外一个Windows 7的虚拟机确有声音&#xff0c;检查对比了一下虚拟机的声音设置&#xff0c;发现是Host Audio Driver的设置不一样&#xff0c;Windows10和11的是Default&#xff0c;而Windows7的是Puls…

【C++】初始化列表、匿名对象、static成员、友元、内部类

文章目录 一、初始化列表构造函数体赋值初始化列表explicit关键字 二、匿名对象三、static成员四、友元友元函数友元类 五、内部类六、练习题 一、初始化列表 构造函数体赋值 实际上&#xff0c;构造函数的函数体内&#xff0c;并不是对 对象 初始化的地方&#xff0c;而是对…

html做一个雷达图的软件

要实现一个在线输入数据并生成雷达图的功能&#xff0c;可以使用HTML表单和JavaScript来处理用户输入的数据。以下是一个示例代码&#xff0c;演示了如何实现这个功能&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"…

C++初学者指南第一步---13.聚合类型

C初学者指南第一步—13.聚合类型 文章目录 C初学者指南第一步---13.聚合类型1. 类型分类&#xff08;简化&#xff09;2. 如何定义和使用3. 为什么选择自定义类型/数据聚合&#xff1f;4. 聚合类型初始化5.混合6. 复制7. 值和引用的语义8.聚合的向量(std::vector)9.最令人烦恼的…

文件创建与查看

touch touch命令用于创建一个新的文件。 语法&#xff1a;touch Linux路径 其中路径可以是相对路径、绝对路径或者特殊路径符都可以。 改图展示了通过 touch test.txt 命令创建了一个 test.txt文件&#xff0c;其中深色的代表文件夹&#xff0c;白色的代表文件。 使用 ls -lh…

React学习(二)——状态(数据)与状态修改

useState 在React中&#xff0c;useState 是一个非常重要的Hook&#xff0c;它允许你在函数组件中添加“状态”&#xff08;state&#xff09;。在传统的React类组件中&#xff0c;我们使用this.state来管理和更新组件的状态。然而&#xff0c;在函数组件中&#xff0c;由于它们…

一个关于空格的Sql Server面试题

引子 先上题目&#xff1a; 回答下面sql 的输出结果 declare s1 varchar(10) declare s2 varchar(10) set s1a b set s2a b if s1s2 select true 答案是 true 那么上面的 s1 和 s2 是否相等的呢&#xff1f; 我们再看看下面的sql declare s1 varchar(10) declare s2 …