当前位置: 首页 > news >正文

LINUX(三)文件I/O、对文件打开、读、写、偏移量

系列文章目录


文章目录

  • 系列文章目录
  • 概念介绍
    • 系统调用
  • 文件I/O
    • open
    • write
    • read
    • close
    • lseek
  • 文件解析
    • 文件存储与节点区
    • 进程操作文件原理
    • 返回错误处理与errno
      • strerror
      • perror函数
      • exit、_exit、_Exit


概念介绍

系统调用

system call是linux内核给应用层的API,是进入内核的入口,应用程序用过调用系统接口实现使用内核提供的服务、资源以及各种各样的功能。

驱动开发工程师通过调用 Linux 内核提供的接口完成设备驱动的注册,驱动程序负责底层硬件操作相关逻辑。
Linux 应用编程(系统编程)则指的是基于 Linux 操作系统的应用编程,在应用程序中通过调用系统调用 API 完成应用程序的功能和逻辑,应用程序运行于操作系统之上。

通常在操作系统下有两种不同的状态:内核态和用户态,应用程序运行在用户态、而内核则运行在内核态。

应用编程简单点来说就是:开发 Linux 应用程序,通过调用内核提供的系统调用或使用 C 库函数来开发具有相应功能的应用程序。

文件I/O

文件 I/O(Input、Outout),对文件的读写操作,Linux 下一切皆文件,文件作为 Linux 系统设计思想的核心理念,在 Linux 系统下显得尤为重要

文件描述符:file description,非负整数,比如在open函数成功时会返回的这个值,这个值是内核向进程返回的,指代被打开的文件,失败操作会返回-1。

一个进程可以打开多个文件,在 Linux 系统中,一个进程可以打开的文件数是有限制,打开的文件是需要占用内存资源的,如果超过进程可打开的最大文件数限制,内核将会发送警告信号给对应的进程,然后结束进程;可以通过 ulimit 命令来查看进程可打开的最大文件数,一般是1024

ulimit -n

对于一个进程来说,文件描述符是一种有限资源,文件描述符是从 0 开始分配的,进程中第一个被打开的文件对应的文件描述符是 0、第二个文件是 1、第三个文件是 2、第 4 个文件是 3……文件描述符数字最大值为 1023(0~1023)。每一个被打开的文件在同一个进程中都有一个唯一的文件描述符,不会重复,如果文件被关闭后,它对应的文件描述符将会被释放,那么这个文件描述符将可以再次分配给其它打开的文件绑定起来。

一般012这三个文件描述符被系统占用了,分别分配给了系统标准输入(0)、标准输出(1)以及标准错误(2)

硬件设备也对应的文件,叫做设备文件,应用程序通过对设备文件进行读写等操作、来使用、操控硬件设备,譬如 LCD 显示器、串口、音频、键盘等。

标准输入一般是键盘,标准输出一般是LCD屏幕,标准错误一般也是LCD显示器

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);

man命令可以查看帮助信息,譬如函数功能介绍、函数原型、参数、返回值以及使用该函数所需包含的头文件等信息。
2表示系统调用,1代表Linux命令,3表示标准C库函数

man 2 open

open命令有两种,这是可变参数的写法,需要包含三个头文件

参数:
pathname:字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径)信息,譬如:“./src_file”(当前目录下的 src_file 文件)、"/home/dengtao/hello.c"等;如果 pathname 是一个符号链接,会对其进行解引用。
flags:调用 open 函数时需要提供的标志,包括文件访问模式标志以及其它文件相关标志,都是宏定义常量,可以单独使用某一
个标志,也可以通过位或运算(|)将多个标志进行组合。

在这里插入图片描述
在这里插入图片描述
还有很多的标志位,O_APPEND、O_ASYNC、O_DSYNC、O_NOATIME、O_NONBLOCK、O_SYNC 以及 O_TRUNC 等,不同内核版本可能也不一样。但这些命令只有文件有相关的权限时,才能正常操作。

mode:此参数用于指定新建文件的访问权限,只有当 flags 参数中包含 O_CREAT 或 O_TMPFILE 标志时才有效(O_TMPFILE 标志用于创建一个临时文件)。一般用touch创建文件后,会有默认的权限,经常通过chmod来修改权限

ls -l 查看文件权限

mode参数u32无符号整数,
在这里插入图片描述
O—这 3 个 bit 位用于表示其他用户的权限;
G—这 3 个 bit 位用于表示同组用户(group) 的权限,即与文件所有者有相同组 ID 的所有用户;
U—这 3 个 bit 位用于表示 文件所属用户 的权限,即文件或目录的所属者;
S—这 3 个 bit 位用于表示文件的特殊权限,一般不管

然后每3bit都是按照rwx来分配的,read/write/execute,读/写/执行,0表示没有,1表示有

最高权限表示方法:111111111(二进制表示)、777(八进制表示)、511(十进制表示)

111000000(二进制表示):表示文件所属者具有读、写、执行权限,而同组用户和其他用户不具有任何权限;
100100100(二进制表示):表示文件所属者、同组用户以及其他用户都具有读权限,但都没有写、执行权限。

除了自己赋值,还可以用linux里面弄好的宏定义
在这里插入图片描述
打开范例:

//可读可写方式打开已经存在的文件
int fd = open("./app.c", O_RDWR)
if (-1 == fd)return fd;//打开指定文件
int fd = open("/home/dengtao/hello", O_RDWR | O_NOFOLLOW);
if (-1 == fd)return fd;

write

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

fd:文件描述符
buf:指定写入数据对应的缓冲区。
count:指定写入的字节数。
返回值:如果成功将返回写入的字节数(0 表示未写入任何字节),如果此数字小于 count 参数,譬如磁盘空间已满,可能会发生这种情况;如果写入出错,则返回-1。

read

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

fd:文件描述符。与 write 函数的 fd 参数意义相同。
buf:指定用于存储读取数据的缓冲区。
count:指定需要读取的字节数。
返回值:如果读取成功将返回读取到的字节数,实际读取到的字节数可能会小于 count 参数指定的字节数,也有可能会为 0,譬如进行读操作时,当前文件位置偏移量已经到了文件末尾。

close

关闭文件

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

fd:文件描述符,需要关闭的文件所对应的文件描述符。
返回值:如果成功返回 0,如果失败则返回-1。
在 Linux 系统中,当一个进程终止时,内核会自动关闭它打开的所有文件,很多程序都利用了这一功能而不显式地用 close 关闭打开的文件。
文件描述符是有限资源,当不再需要时必须将其释放、归还于系统。

lseek

对于每个打开的文件,系统都会记录它的读写位置偏移量,我们也把这个读写位置偏移量称为读写偏移量,记录了文件当前的读写位置

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

当打开文件时,会将读写偏移量设置为指向文件开始位置处,以后每次调用 read()、write()将自动进行累计,指向已读或已写数据后的下一字节

fd:文件描述符。
offset:偏移量,以字节为单位。可以正也可以负
whence:用于定义参数 offset 偏移量对应的参考值
SEEK_SET:读写偏移量将指向 offset 字节位置处
SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处
SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处

//将读写位置移动到文件开头处
off_t off = lseek(fd, 0, SEEK_SET);
if (-1 == off)return -1;//将读写位置移动到文件末尾:
off_t off = lseek(fd, 0, SEEK_END);
if (-1 == off)return -1;//将读写位置移动到偏移文件开头 100 个字节处
off_t off = lseek(fd, 100, SEEK_SET);
if (-1 == off)return -1;//获取当前读写位置偏移量
off_t off = lseek(fd, 0, SEEK_CUR);
if (-1 == off)return -1;

文件解析

文件存储与节点区

文件是存放在磁盘里面,硬盘的最小存储单位叫做“扇区”(Sector),每个扇区储存 512 字节(相当于 0.5KB),操作系统一般会一次性读取多个扇区,这叫做块,是文件存取的最小单位一个块4KB,所以1个块是8个扇区

磁盘在进行分区、格式化时分为两个区域,数据区,用于存储文件中的数据;== inode 节点区,用于存放 inode table(inode 表)==,每一个文件都必须对应一个 inode,inode 实质上是一个结构体,里面元素记录了文件信息如文件字节大小、文件所有者、文件对应的读/写/执行权限、文件时间戳(创建时间、更新时间等)、文件类型、文件数据存储的 block(块)位置等等信息,文件名并不是记录在 inode 中

查看inode编号

ls -istat xxx文件名

在这里插入图片描述
之前买的闪迪的U盘,就有数据恢复软件,原因就是其实删除数据时删掉的是inode表,数据内容还在,只有新的数据在存入时才会覆盖掉。

windows里面的快速格式化就是例子,这种格式化会非常快,也是因为删掉的只是inode table表,数据也是可以找回来的。
在这里插入图片描述
open打开文件时,内核会申请内存拷贝静态文件,叫动态文件,然后对动态文件进行读写操作,之后再同步更新到设备中。

这就是为啥打开大文件很慢,文档忘记保存丢失数据的原因

硬盘这些块设备,读写起来按块单位操作,内存就能按照字节操作,灵活快速效率高。

进程操作文件原理

在 Linux 系统中,内核会为每个进程设置一个专门的数据结构用于管理该进程,譬如用于记录进程的状态信息、运行特征等,我们把这个称为进程控制块(Process control block,缩写PCB)

PCB 数据结构体中有一个指针指向了文件描述符表(File descriptors),文件描述符表中的每一个元素索引到对应的文件表(File table),文件表也是一个数据结构体,其中记录了很多文件相关的信息,譬如文件状态标志、引用计数、当前文件的读写偏移量以及 i-node 指针(指向该文件对应的 inode)等,进程打开的所有文件对应的文件描述符都记录在文件描述符表中每一个文件描述符都会指向一个对应的文件表

返回错误处理与errno

errno是一个全局变量,每种错误都对应一个编号,errno存储函数执行错误编号,也意味会覆盖上一次的错误码。

本质是int型变量,并不是执行所有的系统调用或 C 库函数出错时,操作系统都会设置 errno

man 2 xxx

用man打开时,可以看返回值有没有
在这里插入图片描述
程序当中包含<errno.h>头文件即可,就能获取errno

#include <stdio.h>
#include <errno.h>
int main(void)
{printf("%d\n", errno);return 0;
}

strerror

调用strerror函数可以将errnoo 转换成适合我们查看的字符串信息,C库函数

#include <string.h>
char *strerror(int errnum);
测试:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void)
{int fd;/* 打开文件 */fd = open("./test_file", O_RDONLY);if (-1 == fd) {printf("Error: %s\n", strerror(errno));return -1;}close(fd);return 0;
}

strerror 返回的字符串是"No such file or directory",可以很直观的知道 open 函数执行的错误原因是文件不存在
在这里插入图片描述

perror函数

这个查看错误的函数用的多,不需要传参errno,而且直接打印错误信息,不是返回字符串,还能在打印前添加自己的信息

#include <stdio.h>
void perror(const char *s);

s:在错误提示字符串信息之前,可加入自己的打印信息,也可不加,不加则传入空字符串即可。
例子:

fd = open("./test_file", O_RDONLY);
if (-1 == fd) {perror("open error");return -1;
}

exit、_exit、_Exit

程序出错的时候要停止,在 Linux 系统下,进程(程序)退出可以分为正常退出和异常退出。

异常往往更多的是一种不可预料的系统异常,可能是执行了某个函数时发生的、也有可能是收到了某种信号等。

进程正常退出除了可以使用 return 之外,还可以使用 exit()、_exit()以及_Exit()

http://www.dtcms.com/a/289255.html

相关文章:

  • 什么是 ELK/Grafana
  • Cosmos:构建下一代互联网的“区块链互联网
  • roboflow使用教程
  • GaussDB 数据库架构师修炼(七) 安全规划
  • C51单片机学习笔记——定时器与中断
  • Image Processing 【Normlize和DeNormlize】
  • 【Linux】3. Shell语言
  • Oracle触发器:数据世界的“隐形守护者“
  • EXPLAIN 用法详解(表格)
  • 数据结构-线性表顺序表示
  • 【Linux内核模块】导出符号详解:模块间的“资源共享”机制
  • 子查询转连接查询
  • 30天打牢数模基础-模糊综合评价讲解
  • Vue基础(21)_Vue生命周期
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 用户注册实现
  • 《拆解WebRTC:NAT穿透的探测逻辑与中继方案》
  • 力扣49:字母异形词分组
  • 处理Electron Builder 创建新进程错误 spawn ENOMEM
  • 下载win10的方法
  • 构建一个简单的Java框架来测量并发执行任务的时间
  • Linux安装jdk和maven教程
  • 论文解读:基于时域相干累积的UWB Radar 生命体征检测
  • PyTorch里的张量及张量的操作
  • The FastMCP Client
  • 反欺诈业务 Elasticsearch 分页与导出问题分析及解决方案
  • Kotlin函数式接口
  • 第六章 提炼:萃取本质--创建第二大脑读书笔记
  • 【esp32s3】4 - 从零开始入门 MQTT
  • Selenium 处理动态网页与等待机制详解
  • 谷歌开源项目MCP Toolbox for Databases实操:Docker一键部署与配置指南