初步了解Linux中文件描述符-fd

一、共识知识准备

在正式了解文件描述符之前我们首先回忆一下我们之前了解的一些知识

1.文件 = 文件内容+文件属

2.文件有被打开的文件和没有被打开的文件

3.打开的文件是由进程打开的,所以我们研究的本质是进程和文件的关系

4.没有被打开的文件,是存储在磁盘上的,所以我们一个关注在存储诸多文件的磁盘中,它是如果存储方便我们快速找到文件的。

5.进程要打开文件,首先需要将文件加载到内存,那么os一定是需要管理这些文件的,怎么管理呢?依然是,先描述,在组织的方式进行管理,所以在内核中,一个被打开的文件,都必须有自己的文件打开对象,包含文件的诸多属性,如:struct XXX(文件属性:struct XXX*next)

以上是我们需要具备的一些基础知识。

二、复习C语言的文件IO操作

在C语言中我们可以有诸多接口可以完成我们的IO操作,下面我们用fwrite 和fread为例会议一下我们的基础文件操作

如以下代码:

C语言中,我们需要FILE* 类型来接收我们的fopen的返回值。

利用fwrite函数像文件内写入内容: 注意:由于我们是向文件中写入字符串,C语言的字符串需要以'\0'结尾,但是文件不是C语言,他有自己的格式,所以我们在写入时strlen不需要+1。

利用以写的方式打开"w",会将文件的内容清空,并从头开始写入,如果没有该文件则新建文件

利用追加的方式打开"a",则只是在文件的尾部进行写入,不会清空该文件的内容,如果没有该文件则新建文件

cpp 复制代码
#include <stdio.h>
#include <string.h>
int main()
{
 FILE *fp = fopen("myfile", "w");
 if(!fp){
 printf("fopen error!\n");
 }
 const char *msg = "hello bit!\n";
 int count = 5;
 while(count--){
 fwrite(msg, strlen(msg), 1, fp);
 }
 fclose(fp);
 return 0;
}

这样有没有唤起你对C语言的回忆呢?那么为什么介绍"w"和"a"这两种打开方式呢,一是因为他们很常用,而是因为我们在之前学习的内容里,学到过关于重定向的知识。

我们可以利用重定向向文件中写入内容,重定向有>和>>两种方式,>的方式对应着我们的"w"方式,而>>方式则对应这我们"a"打开文件的方式,下面给大家演示一下:

我们初始化log.txt

在利用> 向log.txt文件中写入hello linux ,内容被清空,和"w"打开的方式类似

利用 >> 向 log.txt写入 hello Linux Linux ,可以发现,就和"a"方式打开一样。

以上,你是不是就能发现,我们的重定向的本质其实就是我们打开文件。后面我们会详细解释

C语打开文件的方式还有很多,下面是常见的一些方式

r Open text file for reading.

The stream is positioned at the beginning of the file.

r+ Open for reading and writing.

The stream is positioned at the beginning of the file.

w Truncate(缩短) file to zero length or create text file for writing.

The stream is positioned at the beginning of the file.

w+ Open for reading and writing.

The file is created if it does not exist, otherwise it is truncated.

The stream is positioned at the beginning of the file.

a Open for appending (writing at end of file).

The file is created if it does not exist.

The stream is positioned at the end of the file.

a+ Open for reading and appending (writing at end of file).

The file is created if it does not exist. The initial file position

for reading is at the beginning of the file,

but output is always appended to the end of the file.

我们可能还听说过,在C语言中,C程序会默认给我们打三个文件流

FILE* stdin :键盘文件

FILE* stdout :显示器文件

FILE* stderr :显示器文件(本质有区别的)

他们的类型和我们打开文件时的返回值类型都是FILE*,那么我们能不能向这些 流里面写入呢?当然是可以的,下面我们来分别验证一下;

首先是stdout为例:我们没有打开任何和文件,因为这三个流是默认打开的

cpp 复制代码
1 #include<stdio.h>
  2 #include<string.h>
  3 
  4 
  5 int main()
  6 {
  7                                                                                                                                                                                          
  8     const char*mes ="hello Linux\n";
  9     fwrite(mes,strlen(mes),1,stdout);
 10 
 11 
 12   return 0;
 13 }

输出结果:发现和我们想的是一样的

向stderr内写入内容

cpp 复制代码
1 #include<stdio.h>
  2 #include<string.h>
  3 
  4 
  5 int main()
  6 {
  7                                                                                                                                                                                          
  8     const char*mes ="hello Linux\n";
  9     fwrite(mes,strlen(mes),1,stdout);
 10 
 11 
 12   return 0;
 13 }

结果还是一样的:

用stdin来读取内容:

cpp 复制代码
 1 #include<stdio.h>
    2 #include<string.h>
    3 
    4 
    5 int main()
    6 {
    7 
    8   char*buffer[1024];
    9  fread(buffer,1,4,stdin);
    10  printf("buffer : %s\n",buffer);                                                                                                                                                       
   11  return 0;
   12 }
  ~

那么C语言是怎么实现这些功能的呢?我们直到,我们调用的函数,都是C语言封装的库函数,而这些调用系统硬件的函数接口,一定是利用了系统调用的,所以接下来我们来了解几个Linux系统调用的接口

三、系统文件的IO

接口介绍: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

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

write 写入指定的文件:fd 文件描述符,buf:缓冲区首地址 coun:写入字节大小 返回值:实际写入的字节数据

close 通过文件描述符来关闭文件

补充知识:比特位级别的标志传参方式

我们发现我们open函数的参数flags,是一个整数,但是我们要是传入多个打开模式怎么?例如我们需要O_CREATE 和O_WRNOLY 这该怎么办呢?用过这个函数的知道只需要 将两个参数 或 其来即可,这是因为利用了比特位级别的传参方式。

顾名思义,就是用一个比特位代表一个标志位,int有32个比特位,可以标志32中不同的传入方式,当然是用不了那么多的。其实我们的参数如就是一个宏,每宏对应着一个2^n整数,n不能相同,

下列代码就是一个比特位级别的标志位传参方式:

将宏定义为2^n,以便在或运算时,每个位置上的标记不会重复。然后再通过函数体内的flags与宏进行与运算,判断该标志位是否有效,有效则运行该功能。

cpp 复制代码
#define ONE (1<<0)
 56 #define TWO (1<<1)
 57 #define THREE (1<<2)
 58 #define FOUR (1<<3)                                                                                                                                                                      
 59 
 60 void show(int flags)
 61 {
 62   if(flags & ONE) printf("hello Function1\n");
 63   if(flags & TWO) printf("hello Function2\n");
 64   if(flags & THREE) printf("hello Function3\n");
 65   if(flags & FOUR) printf("hello Function4\n");
 66 }
 67 
 68 int main()
 69 {
 70   printf("-------------------------------\n");
 71     show(ONE);
 72   printf("-------------------------------\n");
 73     show(TWO);
 74   printf("-------------------------------\n");
 75     show(ONE|TWO);
 76   printf("-------------------------------\n");
 77     show(ONE|TWO|THREE);
 78   printf("-------------------------------\n");
 79     show(THREE|FOUR);
 80   printf("-------------------------------\n");
 81   return 0;
 82 }

我们可利用open函数实现和C语言一样的功能,当我们需要创建文件的时候,需要传递文件的权限值,与Linux中八进制设置相同,同时可以利用umask函数,uamsk 可以修改通过进程创建的文件的权限掩码,就近原则。

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
 umask(0);
 int fd = open("myfile", O_WRONLY|O_CREAT, 0666);
 if(fd < 0){
 perror("open");
 return 1;
 }
 int count = 5;
 const char *msg = "hello bit!\n";
 int len = strlen(msg);
 while(count--){
 write(fd, msg, len);//fd: 文件描述符, msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数据。 返回值:实际写了多少字节数据
 }
 close(fd);
 return 0;
}

文件描述符

文件描述符的本质其实就是open函数的返回值

在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数

C语言中的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。而, open close read writ都属于系统提供的接口,称之为系统调用接口回忆一下我们讲操作系统概念时,画的一张图


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

open函数的返回值:我们叫做文件描述符,是一个整数

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

所以我们还可以这样输入函数

cpp 复制代码
#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;
}

结果照样运行成功

访问文件的本质:

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


文件描述符的分配规则
我们用一份代码即可知道其分配规则:

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
 umask(0);
 int fd = open("myfile", O_RDONLY | O_CREATE, 0666);
 if(fd < 0){
 perror("open");
 return 1;
 }
 printf("fd: %d\n", fd);
 close(fd);
 return 0;
}

输出结果为3,因为0,1,2有三个默认打开的占用了


我们尝试关闭stdin,0来试一试:

cpp 复制代码
#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;
}

当我们关闭掉了stdin之后,0位置就空闲出来了,而我们打开的文件恰好填在了0位置


所以文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符

相关推荐
kblj55551 小时前
学习Linux——学习工具——DNS--BIND工具
linux·运维·学习
微风◝1 小时前
AlmaLinux9配置本地镜像仓库
linux·运维·服务器
赖small强1 小时前
【Linux C/C++开发】Linux C/C++ 高效延迟崩溃分析:基于 mprotect 的内存陷阱技术 (Electric Fence)
linux·c语言·c++·mprotect·buffer overflow
保持低旋律节奏1 小时前
linux——make/Makefile自动化工程构建
linux·运维·自动化
云计算练习生1 小时前
渗透测试行业术语—— 网络攻击方式与漏洞利用
服务器·网络·安全·渗透测试术语·网络安全术语
繁华似锦respect1 小时前
C++ & Linux 中 GDB 调试与内存泄漏检测详解
linux·c语言·开发语言·c++·windows·算法
爱潜水的小L1 小时前
自学嵌入式day25,树
linux
周杰伦_Jay1 小时前
【Linux Shell】命令完全指南
linux·运维·服务器
乾元1 小时前
SDN 与 AI 协同:控制面策略自动化与策略一致性校验
运维·网络·人工智能·网络协议·华为·系统架构·ansible