Linux文件

news2024/10/7 16:18:37

目录

系统级I/O

简介

接口

文件描述符fd

重定向 

缓冲区

文件系统

软硬链接

动静态库

静态函数库

动态库


系统级I/O

简介

输入/输出(I/O)是在主存和外部设备(磁盘驱动器、终端和网络)之间复制数据的过程。输入操作是从I/O设备复制数据到主存,而输出操作是从主存复制数据到I/O设备。

所有语言的运行时系统都提供执行I/O的较高级的工具。例如,ANSI C提供标准I/O库,比较常用的有:fopen()、fclose()、fread()、fwrite()、printf()、fprintf()等。

语言所提供的对操作系统I/O操作的封装,具有跨平台性,工作良好。而直接使用系统的文件调用接口会比较困难,并且不具有跨平台性。

但我们还是要学习系统的I/O操作。

首先,了解系统级I/O将帮助我们理解其他的系统概念。I/O在进程的创建和执行中扮演着关键的角色,进程创建又在不同进程间的文件共享中扮演着关键角色,因此,要真正理解I/O,必须先理解进程。

其次,有时只能使用系统级I/O。例如,标准I/O库中没有提供读取文件元数据的方式,例如文件大小或文件创建时间。另外,I/O库还存在一些问题,使得用它来进行网络编程非常冒险。

接口

open、close

#include<sys/types.h>
#include<sys/sttat.h>
#include<fcatl.h>

int open(char *filename, int flags);
int open(char *filename, int flags,  mode_t mode);

open函数将filename转换成一个文件描述符,并且返回描述符数字。

flags参数指明了进程打算如何访问这个文件

O_RDONLY:只读,相当于fopen里mode参数中的r。

O_WRONLY:只写,相当于mode参数中的w。

O_RDWR,可读可写,相当于mode参数中的r+。

flags参数也可以是一个或者更多位掩码的或,为写提供给一些额外的提示

O_CREAT:如果文件不存在,就创建它的一个截断的文件,相当于open里mode参数中的w+与r+之间的区别。

O_TRUNC:如果文件已存在,就截断它。

O_APPEND:在每次写操作前,设置文件位置到文件的结尾处,相当于open里mode参数中的a。

mode参数指定了新文件的访问权限位,同时,也需要注意有权限掩码umask的存在

#include<unistd.h>

int close(int id);

进程通过调用close函数关闭一个打开的文件,而关闭一个已关闭的描述符会出错

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

int main(){
  int fd = open("file.txt", O_RDWR|O_CREAT, 0777);
  close(fd);
  return 0;
}

read、write

#include<unistd.h>

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

ssize_t write(int fd, const void *buf, size_t n);

read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值为0表示EOF。否则,返回值表示的是实际传送的字节数量。 

write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。返回值-1表示一个错误,否则,返回值表示的是写的字节数。

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

int main(){
  int fd = open("file.txt", O_RDWR|O_CREAT, 0777);
  char str[10];
  memset(str, '\0', sizeof str);
  write(fd, "abcdef\n", 10);
  close(fd);
  fd = open("file.txt", O_RDWR);
  read(fd, str, 4);
  printf("%s\n", str);
  close(fd);
  return 0;
}

文件描述符fd

在open函数中,返回的是文件操作符

Linux进程默认情况下会有三个缺省打开的文件操作符,分别是标准输入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(){
  char str[100];
  int sz=read(0, str, 100);
  write(1, str, sz);
  return 0;
}

 在新建文件时,其文件描述符总是未被使用的最小值

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

int main(){
  int id1=open("file1.txt", O_RDWR|O_CREAT);
  int id2=open("file2.txt", O_RDWR|O_CREAT);
  close(0);
  close(id1);
  int id3=open("file3.txt", O_RDWR|O_CREAT);
  int id4=open("file4.txt", O_RDWR|O_CREAT);
  printf("%d %d %d %d\n", id1, id2, id3, id4);
  return 0;
}

当我们进行输出重定向时,我们只会将标准输出重定向到文件内,而标准错误依旧会输出到显示器上。

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

int main(){
  printf("stdout\n");
  perror("stderror");
  return 0;
}

我们可以通过 2> 来将标准错误重定向到另一个文件 

也可以通过 2>&1 将标准错误重定向到标准输出所重定向的文件。

fd本质上是文件描述符表指针数组fd_array的下标,其中存储着file对象的指针,file对象中保存了文件相关的inode元信息。而该数组存储在files_struct结构体中,而该结构体的指针存储在task_struct(PCB)中。

重定向 

 当我们将文件描述符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(){
  close(1);
  open("file.txt", O_RDWR|O_CREAT, 0777);
  printf("Hello world\n");
  return 0;
}

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

char str[100];

int main(){
  close(0);
  int fd=open("file.txt", O_RDWR);
  fgets(str, sizeof str, stdin);
  printf("%s", str);
  close(fd);
  return 0;
}

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

char str[100];

int main(){
  close(2);
  int fd=open("file.txt", O_RDWR|O_CREAT, 0777);
  perror("open");
  close(fd);
  return 0;
}

 我们也可以通过dup2函数实现重定向

#include<unistd.h>

int dup2(int oldfd, int newfd);

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

int main(){
  int fd=open("file.txt", O_RDWR|O_CREAT, 0777);
  dup2(fd, 1);
  printf("Hello world\n");
  close(fd);
  return 0;
}

缓冲区

缓冲区是一段内存空间,用于缓冲输入输出的数据。

当输入输出的数据缓冲在缓冲区中,之后将缓冲区中的数据一并进行输入输出,这样会减少读写的次数,提高整机效率,叫做写回模式(WB)。而直接将输入输出数据进行读写的写透模式(WT)。

缓冲区分为用户级缓冲区(语言提供)和内核级缓冲区(操作系统提供),在我们编写程序时所接触到的缓冲区为用户级缓冲区。C语言提供的缓冲区就存储在struct FILE结构体中

 缓冲区的刷新策略有三种:立即刷新(不带缓冲)、行刷新(行缓冲)、全刷新(全缓冲)。

当我们使用fllush函数或是当进程退出时,缓冲会被强制刷新。

绝大多数的设备都倾向于全缓冲,这样会最大程度的减少读写的次数,进而提高效率,当然也会有特殊情况,例如在我们进行显示器的输入时,人会倾向于一行一行的阅读,因此标准输出的缓冲策略为行缓冲。

例如这样一段代码

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

int main(){
  //c语言提供的函数
  printf("printf\n");
  fprintf(stdout, "fprintf\n");
  const char *s="fputs\n";
  fputs(s, stdout);

  //操作系统提供的
  const char *ss="write\n";
  write(1, ss, strlen(ss));
  
  fork();
  return 0;
}

当我们输出到显示器上时,进行的是行刷新,当创建子进程时,缓冲区中没有内容

 而当我们将输出内容重定向到一个文件中时

会发现我们首先输出的是操作系统提供的函数write,之后将c语言提供的函数输出了两次

这是由于在与文件进行I/O时,进行的是全缓冲,fork创建子进程时缓冲区中的内容还没有被刷新,因此会将缓冲区进行写时拷贝,而当缓冲区的内容被写进磁盘时,无论是父子进程,子进程都会拷贝一份副本,因此父子进程都会进行输出,因此最终会在文件中输出两份。


文件系统

没有被打开的文件处于磁盘中,被称为磁盘级文件。

整块磁盘的组成主要有:

碟片,用于记录数据

机械手臂(包括磁头),用于擦写碟片上的数据

主轴马达,转动碟片进而让磁头读写数据

每个碟片被划分为一个个磁道,每个磁道又划分为一个个扇区。

扇区是存储数据的基本单位,大小为512个字节(外围磁道的扇区和内围的扇区由于密度不同,大小均为512字节)。而操作系统与磁盘进行I/O的基本单位是4kb,一是为了提高效率,而是为了将其与扇区的基本单位区分开,实现磁盘与操作系统的解耦合。

而我们想要找到一个指定的扇区,就需要找到在哪一个盘面(H)、在哪一个磁道(C)、在哪一个扇区(S),称为CHS寻址。

我们可以把磁盘的空间想象成线性的结构。

首先,基于分治的原则,磁盘的整体空间首先被分区,而每个分区分为启动块(Boot Block)和一个个的group block 。

而每个group block又被划分为不同的区域

super block:存放文件系统本身的结构信息,主要包括block和inode的总量、未使用的inode的数量、一个block和inode的大小、最后一次挂载的时间、最后一次写入数据的时间、最后一次检验磁盘的时间等。

GDT:块组描述符,描述块组属性信息

Block Bitmap:通过位图描述block数据块的使用情况

inode Bitmap:通过位图描述inode表的使用情况

inode Table:存储inode,用于记录文件的属性,一个文件占用一个inode,同时记录此文件的数据所在的区块号码。

Data Blocks:记录文件的内容,根据文件的数据大小可以占用多个区块

 当我们创建文件时,首先需要寻找一个空闲的inode节点,并将文件的属性信息存储在其中,之后,根据文件内容的大小,寻找相应数量的block,将数据分别存储到其中,并将block对应的下标存储在inode中,用于读写文件时寻找对应的块。最后将文件名添加到目录中。

而若是删除文件,我们所做的是将对应的block和inode在位图中对应的状态至为未被使用,而不需要去删除原本位置的内容,只需要在重新使用这块空间后将原本数据覆盖即可。因此在我们删除一个文件后,若是未被其他文件覆盖,我们可以做到将其恢复。

软硬链接

当我们在磁盘中寻找文件时,我们找的是文件的inode,而非文件名,而使用硬链接可以让多个inode对应同一个inode。

 目前,我们新建的文件的硬链接数都为1,我们可以通过ln命令来建立硬链接

可以看到,该文件的硬链接数变成了2。

而在我们的目录当中

 可以看到,目录的硬链接数初始为2,这是因为目录中的隐藏文件 . 

可以看到,隐藏文件 . 的inode与目录相同。

而在 目录中创建目录时,外层目录的硬链接数会变成3,这是因为内层目录包含的隐藏文件 ..

而当我们把对应的硬链接文件删除时,只会让该inode文件的硬链接数减一,当减为0时才会真正的去删除。

而软链接创建的文件属于一个独立的文件,具有自己的inode,而该文件内容,就是指向文件的路径。我们可以通过ln -s 命令创建软链接。


动静态库

在很多软件之间都会互相使用彼此提供的函数库来使用其特殊的功能,因此要使用到函数库。函数库依照是否被编译到程序内部而分为动态库与静态库。

静态函数库

这类函数库通常扩展名为libxxx.a,在编译的时候会直接整合到执行程序当中,因此利用静态库编译成的文件会比较大一些。而正因为将其整合到程序中,因此编译成功的可执行文件可以独立运行,不需要向外部要求读取函数库的内容。

若是我们想要形成动态库,首先,我们需要将函数所在的源文件进行预处理、编译、汇编(gcc -c),之后将形成的文件通过ar -rc命令生成静态库。

liboutput.a:myprint_a.o
	ar -rc liboutput.a myprint_a.o

myprint_a.o:myprint.c
	gcc -c myprint.c -o myprint_a.o

.PHONY:output
output:
	mkdir -p output/lib 
	mkdir -p output/include 
	cp -rf *.h output/include
	cp -rf *.a output/lib 

.PHONY:clean
clean:
	rm -rf *.a *.o output

 

若是我们想要使用库函数,我们需要进行库的安装

对于静态库,我们首先需要将头文件拷贝到头文件gcc的默认搜索路径 /usr/include 中,将库文件拷贝到库文件的路径 /usr/lib64 中。

之后,我们可以写一个文件来执行库函数

 而在使用gcc进行编译时,我们需要加一些选项

 gcc -I 头文件路径 -L 库文件路径 -l 库名

 当我们将头文件和库文件删除后,再次执行程序,依然可以运行

动态库

动态库通常扩展名为libxxx.so,与静态库被整个整合到程序中不同的是,动态库在编译的时候,在程序中只有一个指针的位置,当执行文件时,动态库会被加载到内存中并被映射到进程的虚拟地址空间中,当我们要使用到函数库的功能时,程序才会去读取函数库(通过库的起始地址加上所使用的函数的偏移量来寻找对应函数)来使用。

生成动态库首先依然需要形成汇编后的文件,但与静态库不同的是,我们需要通过-fPIC选项来生成与位置无关的目标二进制文件。之后通过gcc -shared 命令来形成库文件。

.PHONY:all
all:liboutput.a liboutput.so 

liboutput.a:myprint_a.o
	ar -rc liboutput.a myprint_a.o

liboutput.so:myprint_so.o
	gcc -shared myprint_so.o -o liboutput.so 

myprint_a.o:myprint.c
	gcc -c myprint.c -o myprint_a.o

myprint_so.o:mypirnt.c
	gcc -fPIC -c myprint.c -o myprint_so.o

.PHONY:output
output:
	mkdir -p output/lib 
	mkdir -p output/include 
	cp -rf *.h output/include
	cp -rf *.a output/lib 

.PHONY:clean
clean:
	rm -rf *.a *.o *.so output

除开像静态库一样需要对库进行安装,我们还可以更改环境变量LD_LIBRARY_PATH,在后面加上库文件所在的路径

 

 而这种更改方式只能在本次登录有效

可以通过配置/etc/ld.so.conf.d/ 并用ldconfig命令来将/etc/ld.so.conf 的数据读入缓存当中。

也可以通过ln -s 命令建立软链接(注意要使用绝对路径)

而ldd命令可以判断某个可执行的二进制文件含有什么动态库

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

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

相关文章

opencv-04 像素处理

opencv-04 像素处理 在 OpenCV 中&#xff0c;最小的数据类型是无符号的 8 位数。因此&#xff0c;在 OpenCV 中&#xff0c;实际上并没有二值图像这种数据类型&#xff0c;二值图像经常是通过处理得到的&#xff0c;然后使用0表示黑色&#xff0c;使用 255 表示白色。 可以将…

【基本算法】三分法模板

洛谷三分法https://www.luogu.com.cn/problem/P3382 题目描述 如题&#xff0c;给出一个 N 次函数&#xff0c;保证在范围 [l,r] 内存在一点 x&#xff0c;使得 [l,x] 上单调增&#xff0c;[x,r] 上单调减。试求出 x 的值。 输入格式 第一行一次包含一个正整数 N 和两个实数…

吐血整理,Jmeter分布式性能压测-常见问题+解决(详细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 安装常见问题 问题…

使用npm和nrm查看源和切换镜像

一、使用npm查看当前源、切换淘宝镜像、切换官方源 &#xff08;1&#xff09;npm查看当前源&#xff1a; npm get registry &#xff08;2&#xff09;npm设置淘宝镜像源&#xff1a; npm config set registry http://registry.npm.taobao.org &#xff08;3&#xff09;n…

注册中心技术Eureka、Nacos

说明&#xff1a;在微服务框架中&#xff0c;各个服务之间都是独立的。理论上来说&#xff0c;各个服务之间是可以直接通信的&#xff0c;但实际上因为服务之间通信需要管理和规划&#xff0c;如请求怎么负载均衡、请求怎么降级处理等等&#xff0c;所以就需要使用一个技术&…

企业绿色发展关键环节:产品碳足迹管理

近年来&#xff0c;产品生产消费对环境造成的影响越来越受到重视。“产品碳足迹”作为衡量产品对环境影响的重要指标&#xff0c;已成为企业在产品生命周期管理中不可忽视的元素。“产品碳足迹”&#xff08;Product Carbon Footprint, PCF&#xff09;是指产品在生产、制造、运…

【剧前爆米花--前端三剑客】JavaScript(WebAPI)中的相关方法和实例

作者&#xff1a;困了电视剧 专栏&#xff1a;《JavaEE初阶》 文章分布&#xff1a;这是一篇关于JavaScript&#xff08;WebAPI&#xff09;的文章&#xff0c;在这篇文章中我会简单介绍一些常用的js方法&#xff0c;并给出他们的应用实例&#xff0c;希望对你有所帮助&#xf…

代码随想录day2 | 977.有序数组的平方 209.长度最小的子数组 59.螺旋矩阵II

文章目录 一、977.有序数组的平方二、209.长度最小的子数组三、59.螺旋矩阵II 一、977.有序数组的平方 977.有序数组的平方 暴力法&#xff1a;O(NlogN) 先所有数字平方&#xff0c;然后再快排&#xff0c;时间复杂度取决于快排 class Solution { public:vector<int> s…

Openlayers实战:加载天地图

国家地理信息公共服务平台“天地图”(以下简称“天地图“)是国家基础地理信息中心建设的网络化地理信息共享与服务门户。 其属于国家队的,有一定的权威性。 在Openlayers中如何加载天地图呢,方法很简单,用XYZ的形式。 它分为底图和标注,可以灵活的做配合使用。 效果图 源…

java学习路程之篇三、知识点、类、模块、项目、操作、下载、安装、IDEA

文章目录 1、IDEA开发工具2、IDEA的下载和安装3、IDEA中的第一个代码4、IDEAZ中的类、模块、项目的操作 1、IDEA开发工具 2、IDEA的下载和安装 3、IDEA中的第一个代码 4、IDEAZ中的类、模块、项目的操作

PX4实战 各种问题的参数调整解决方案

桨叶旋转频率引起的噪声 比较好的震动 不好的震动 一般好的振动特性时&#xff0c;三轴加速度的原始数据值都会在很小的范围内波动&#xff0c;在3范围内就是非常好的振动环境了。 分析飞行器的幅频特性&#xff0c;一方面除了查看飞行器的振动特性好坏&#xff0c;另一方面…

技术架构的演进-八大架构

目录&#xff1a; 常见概念评价指标单机架构应用数据分离架构应用服务集群架构读写分离 / 主从分离架构引入缓存 —— 冷热分离架构垂直分库业务拆分 —— 微服务容器化引入——容器编排架构总结 1.常见概念&#xff1a; 应用&#xff08;Application&#xff09; / 系统&am…

安卓日志~

文章目录 bugreport获取报告设备模拟器adb命令用户 报告目录 阅读错误报告logcat定义日志记录抓取日志过滤优先级输出格式格式修饰符日志缓冲区 ANR触发ANR避免ANR工作线程 ANR日志定位无响应应用查看堆栈跟踪 死锁 activity内存内存不足内存快照 广播显示器争用后台编译叙述电…

linux终端前面显示base和不显示base

问题描述&#xff1a;前提是系统里装了anaconda3&#xff0c;有时候两个服务器之间相连长时间没使用或者访问时候前面没显示base。。 解决&#xff1a;我们在做深度学习时需要一个环境&#xff0c;如果前面没显示base的话&#xff0c;你直接conda activate是激活不了我们需要跑…

QT打开和保存文件对话框的操作笔记

QT打开和保存文件对话框的操作&#xff0c;需要先包含头文件QFileDialog&#xff0c;一般通过按钮实现打开和保存文件对话框的操作。 代码如下&#xff1a; #include <QDebug> #include <QFileDialog>void Form::on_pushButton_clicked() {QString fileName;fileN…

基于Javaweb实现ATM机系统开发实战(八)实时查询余额功能实现

老规矩&#xff0c;先看前端页面&#xff0c;把前端页面上没有的表达式都删掉&#xff1a; 创建servlet接受和处理请求&#xff1a; package com.atm.servlet;import com.atm.pojo.User; import com.atm.service.UserService; import com.atm.service.impl.UserServiceImpl;im…

HarmonyOS元服务开发

一、什么是HarmonyOS系统 HarmonyOS是华为开发的一款面向未来的全场景分布式智慧操作系统&#xff0c;将逐步覆盖18N全场景终端设备&#xff0c;用一个软件系统解决大量智能终端体验割裂的问题 1&#xff1a;智能手机 …

Freertos任务的管理是何物?

Freertos任务的管理 提示&#xff1a;文章来自正点原子和野火 文章目录 Freertos任务的管理前言一、任务的四种状态二、 四种状态的关系图三、函数vTaskSuspend();挂起vTaskResume() ;解挂函数 四、代码示例总结 前言 使用Freertos&#xff0c;创建任务&#xff0c;运行任务&a…

【1】Spring手写模拟-ApplicationContext获取对象

ApplicationContext获取对象 阶段目标 当前阶段任务&#xff0c;实现ApplicationContext 加载配置文件&#xff0c;实现对配置的包扫描&#xff0c;获取其字节码文件&#xff0c;查看是否包含注需要Spring管理&#xff0c;以及实现单例或者多例获取Bean对象 实现配置文件con…

实现虚拟机(VM15.5.0)与本机相互通信

目录 1、如果虚拟机一打开请先关机 2、进入“控制面板”-“网络和Internet”-“网络连接”&#xff0c;可以看到有两个虚拟网口&#xff0c;关闭虚拟网口VMnet1和VMnet8&#xff0c;如图所示&#xff1a; 3、选择需要通信的虚拟机&#xff0c;点击“编辑虚拟机设置”&#x…