初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror
先要回答的问题
文件IO指的是什么?
本文主要讲述如何调用Linux OS所提供的相关的OS API,实现文件的读写。
如何理解文件IO?
IO就是input output的意思,文件io就是文件输入输出,也就是文件读写。
文件读写,读写的是什么?
是数据。
文件IO(Input Output),也就是输入输出是对什么而言的?参考点是什么?
是CPU
能不能越过OS,直接操作文件呢?
当有OS的时候,应用程序基于OS运行时,必须通过OS API假借OS之手,才能操作底层硬件,无法回避。
文件IO涉及到的OS API
①open函数:打开文件
②close函数:关闭文件
③read函数:从打开的文件读数据
④write函数:向打开的文件写数据
⑤lseek函数:移动在文件中要读写的位置
⑥dup函数:文件读写位置重定位函数,本来是写到这个文件,重定位后可以写到另一个文件里面
⑦fcntl函数:文件描述符设置函数
⑧ioctl函数:一个特殊的函数
文件读写的简单例子
文件操作三步曲
①打开文件 open函数
②读、写等操作文件 read、write函数
③关闭文件 close函数
代码演示
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> int main(void)
{
int fd = ; fd = open("./file.txt", O_RDWR);
if(- == fd)
{
printf("open fail\n");
return ;
}
else
{
printf("open ok\n");
} char buf1[] = "hello world";
write(fd, (void *)buf1, ); lseek(fd, , SEEK_SET); char buf2[] = {};
read(fd, buf2, sizeof(buf2)); printf("buf2 = %s\n", buf2); close(fd); return ;
}
API
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报错。
第二种:如果文件存在就直接打开,如果文件不存在,就按照mode指定的文件权限,创建一个该名字的新文件。也就是说三个参数时,不仅包含打开已存在文件的功能,还多了一个创建文件的功能。
参数
pathname:表示路径名,很简单
flags:
flags的作用?
flags用于指定文件的打开方式,这些宏还可以使用|组合,比如O_RDONLY | O_APPEND,同时指定多个宏。
这些宏对应的就是一些整形数,#define O_RDONLY 2。
这些宏被定义在了那里?
定义在了open所需要的头文件中,使用open函数时,必须要包含对应的头文件,否者,这些宏你就用不了。
宏的含义
(1)O_RDONLY:只读方式打开文件,只能对文件进行读
(2)O_WRONLY:只写方式打开文件,只能对文件记性写
(3)O_RDWR:可读可写方式打开文件,既能读文件,也能写文件
以上这三个在指定时,只能唯一指定,不可以组合,比如O_RDONLY|O_WRONLY。
(4)O_TRUNC:打开时将文件内容全部清零空
(5)O_APPEND:打开文件后,写数据时,原有数据保留,新写的数据追加到文件末尾,此选项很重要。如果不指定这个选项的话,新写入的数据会从文件头上开始写,覆盖原有的数据。
(6)O_CREAT
open两个参数时的缺点?
只能用于打开已经存在的文件,如果文件不存在就返回-1报错。
O_CREAT的作用
可以解决两个参数的缺点,指定O_CREAT时,如果:
文件存在:直接打开
文件不存在:创建该“名字”的文件。
不过指定O_CREAT,需要给open指定第三个参数mode,用于指定新创建文件的原始权限。
(7)O_EXCL
当O_EXCL与O_CREAT同时被指定,打开文件时,如果文件之前就存在的话,就报错。
意义:保证每次open的是一个新的文件,如果文件以前就存在,提醒你open的不是一个新文件。
mode:创建文件时,用于指定文件的原始权限,其实就是rwxrwxr--。
返回值
如果打开成功,返回一个非负整数的文件描述符。
如果打开失败,返回-1,并且设置错误号给系统定义的全局变量errno,用于标记函数到底出了什么错误。
close
原型
#include <unistd.h>
int close(int fd);
功能
关闭打开的文件。
参数
fd:文件描述符
返回值
成功返回零,失败返回-1并设置errno
备注
就算不主动的调用close函数关闭打开的文件,进程结束时,也会自动关闭进程所打开的所有的文件。但是如果因为某种需求,你需要在进程结束之前关闭文件的话,就主动的调用close函数来实现。Linux c库的标准io函数fclose,向下调用时,调用就是close系统函数。
write
原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能
向fd所指向的文件写入数据。
参数
fd:指向打开的文件
buf:保存数据的缓存空间的起始地址
count:从起地址开始算起,把缓存中count个字符,写入fd指向的文件
返回值
调用成功:返回所写的字符个数
调用失败:返回-1,并给errno自动设置错误号
数据中转过程
应用缓存(buf)————>open打开文件时开辟的内核缓存——————>驱动程序的缓存——————>块设备上的文件
read
原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能
从fd指向的文件中,将数据读到应用缓存buf中
参数
fd:指向打开的文件
buf:读取到数据后,用于存放数据的应用缓存的起始地址
count:缓存大小(字节数)
返回值
调用成功:返回所写的字符个数
调用失败:返回-1,并给errno自动设置错误号
数据中转的过程
应用缓存(buf)<————open打开文件时开辟的内核缓存<——————驱动程序的缓存<——————块设备上的文件
lseek
原型
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能
调整读写的位置,就像在纸上写字时,挪动笔尖所指位置是一样的。
C库的标准io函数里面有一个fseek函数,也是用于调整读写位置的,fseek就是对lseek系统函数封装后实现的
参数
fd:文件描述符,指向打开的文件
whence:
粗定位,选项有:
SEEK_SET:调到文件起始位置
SEEK_CUR:调到文件当前读写的位置
SEEK_END:调到文件末尾位置
offset:
精定位:微调位置,从whence指定的位置,向前或者向后移动指定字节数。
为负数:向前移动指定字节数
为正数:向后移动指定字节数
不过当whence被指定为SEEK_SET时,如果offset被指定为负数的话,是没有意义,为什么?
因为已经到文件头上了,在向前移动就越界了,不再当前文件的范围内了,如果非要向前调整,lseek函数会报错。
返回值
返回当前读写位置相对于文件开始位置的偏移量(字节)。
可以使用lseek函数获取文件的大小,怎么获取?
答:将文件读写的位置移动到最末尾,然后获取返回值,这个返回值就是文件头与文件尾之间的字节数,也就是文件大小。
调用失败,返回-1,并给errno设置错误号。
dup
原型
#include <unistd.h>
int dup(int oldfd);
功能
复制某个已经打开的文件描述符,得到一个新的描述符,这个新的描述符,也指向被复制描述符所指向的文件。
比如:4指向了某个文件,从4复制出5,让5也指向4指向的文件。
复制某个已经打开的文件描述符,得到一个新的描述符,这个新的描述符,也指向被复制描述符所指向的文件。
参数
oldfd:会被复制的、已经存在的文件描述符。
返回值
成功:返回复制后的新文件描述符
失败:返回-1,并且errno被设置。
dup2
原型
#include <unistd.h>
int dup2(int oldfd, int newfd);
功能
功能同dup,只不过在dup2里面,我们可以自己指定新文件描述符。如果这个新文件描述符已经被打开了,dup2会把它给关闭后,再使用。
比如:dup(2, 3);
从2复制出3,让3也指向2所指向的文件,如果3之前被打开过了,dup2会关闭它,然后在使用。
dup2和dup的不同之处在于:
dup:自己到文件描述符池中找新文件描述符
dup2:我们可以自己指定新文件描述符
参数
oldfd:会被复制的、已经存在的文件描述符。
newfd:新的文件描述符
返回值
成功:返回复制后的新文件描述符
失败:返回-1,并且errno被设置。
Linux错误号——errno
在前面的代码中,如果open失败了,只是笼统的打印出“打开文件失败了”,但是并没有提示具体出错的原因,没有详细的出错原因提示,遇到比较难排查的错误原因时,很难排查出具体的函数错误。open失败,如何具体打印出详细的出错信息呢?这就不得不提errno的作用了。
什么是ernno?
函数调用出错时,Linux系统使用错误编号(整形数)来标记具体出错的原因,每个函数有很多错误号,每个错误号代表了一种错误,产生这个错误时,会自动的将错误号赋值给errno这个全局变量。errno是Linux系统定义的全局变量,可以直接使用。
错误号和errno全局变量被定义在了哪里?
都被定义在了errno.h头文件,使用errno时需要包含这个头文件。
打印出具体的出错原因
perror
原型
#include <stdio.h>
void perror(const char *s);
功能
perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。
参数
s:在错误原因前面打印的一串字符
返回值
无
工作原理
perror函数可以自动将“错误号”换成对应的文字信息,并打印出来,方便我们理解。perror是一个C库函数,不是一个系统函数。调用perror函数时,它会自动去一张对照表,将errno中保存的错误号,换成具体的文字信息并打印出来,我们就调用perror函数时,它会自动去一张对照表,将errno中保存的错误号,换成具体的文字信息并打印出来,我们就能知道函数的具体错误原因了。
代码演示
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h> int main(void)
{
int fd = ; fd = open("./file.txt", O_RDWR);
//fd = open("./file.txt", O_RDWR|O_CREAT|O_EXCL, 0664);
if(- == fd)
{
printf("open fail: %d\n", errno);
perror("open fail");
return ;
}
else
{
printf("open ok\n");
printf("fd = %d\n", fd);
} char buf1[] = "hello world";
write(fd, (void *)buf1, ); lseek(fd, SEEK_SET, ); char buf2[] = {};
read(fd, buf2, sizeof(buf2)); printf("buf2 = %s\n", buf2);
close(fd); return ;
}
输出结果
open fail:
open fail: No such file or directory
perror会在入参字符串后加:,打印错误原因,在换行
初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror的更多相关文章
- 【APUE】第3章 文件I/O (3) 文件共享、原子操作、函数dup/dup2、函数sync/fsync/fdatasync、函数fcntl、函数ioct1、目录/dev/fd 使用说明
1.文件共享 UNIX系统支持在不同的进程间共享打开文件.为了说明这种共享,以下介绍内核用于所有I/O的数据结构. 内核使用3种数据结构表示打开文件,它们之间的关系决定了在文件共享方面一个进程对另一个 ...
- 泛函编程(35)-泛函Stream IO:IO处理过程-IO Process
IO处理可以说是计算机技术的核心.不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果.所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果.输入数 ...
- 文件读写IO
摘要:本文主要总结了以下有关文件读写的IO,系统调用与库函数. 1.初级IO函数:close,creat,lseek,open,write 文件描述符是一个整型数 1.1close 1.2int cr ...
- node源码详解(七) —— 文件异步io、线程池【互斥锁、条件变量、管道、事件对象】
本作品采用知识共享署名 4.0 国际许可协议进行许可.转载保留声明头部与原文链接https://luzeshu.com/blog/nodesource7 本博客同步在https://cnodejs.o ...
- linux系统编程:IO读写过程的原子性操作实验
所谓原子性操作指的是:内核保证某系统调用中的所有步骤(操作)作为独立操作而一次性加以执行,其间不会被其他进程或线程所中断. 举个通俗点的例子:你和女朋友OOXX的时候,突然来了个电话,势必会打断你们高 ...
- 文件和IO流
摘要:本文主要介绍了Java的文件处理以及常用的IO流操作. 文件操作 概念 File是数据源(保存数据的地方)的一种,可以表示一个文件,也可以表示一个文件目录. File类只能对文件和文件夹进行创建 ...
- KVM虚拟机IO处理过程(二) ----QEMU/KVM I/O 处理过程
接着KVM虚拟机IO处理过程中Guest Vm IO处理过程(http://blog.csdn.net/dashulu/article/details/16820281),本篇文章主要描述IO从gue ...
- STM32CubeMX新建工程+基本IO配置过程
Ⅰ.写在前面 学习本文之前可以查看我前面的文章: STM32CubeMX介绍.下载与安装 STM32CubeMX使用方法及功能介绍 本文接着上一篇文章结合基本IO配置实例,讲述关于STM32CubeM ...
- 【Go】使用压缩文件优化io (二)
原文链接: https://blog.thinkeridea.com/201907/go/compress_file_io_optimization2.html 上一篇文章<使用压缩文件优化io ...
随机推荐
- Mac更新npm和node版本
npm: 查看当前版本: npm --version 更新到最新版: sodu npm install npm@latest -g node: 1.查看当前版本: node -v 2.清除npm当前缓 ...
- js 如何让两个等长的数组产生键值对关系
问题的准确描述:js 将两个长度一样的一维数组 合成一个一维数组,A为键值,B为key值 js 将两个长度一样的一维数组 合成一个一维数组,A为键值,B为key值 如 var arr1=['a','b ...
- Android_7.1.1_r6源码编译
上篇文章讲述了如何下载Android源码,在篇文章就来说一说Android源码编译.其实一般来说如果修改的软件和底层没什么关系,直接提取相应的源代码到Android Studio编译就可以了,如果是与 ...
- springboot和solr结合测试使用
首先新建springboot项目 新建webapp目录 springboot没有webapp目录——手动添加 web.xml <?xml version="1.0" enco ...
- jenkins相关war包下载
1.jenkins的war包下载地址 地址:http://mirrors.jenkins-ci.org/,打开链接后,表格有war列,Releases行是短期更新包.LTS是长期更新包.一般选择Rel ...
- Java面试 - final、finally、finalize的区别?
final:用于声明属性, 方法和类,分别表示属性不可变.方法不可覆盖.被其修饰的类不可继承. finally:异常处理语句结构的一部分,表示总是执行. finalize:Object 类的一个方法, ...
- Hadoop的eclipse的插件是怎么安装的?
[学习笔记] 1)网上下载hadoop-eclipse-plugin-2.7.4.jar,将该jar包拷贝到Eclipse安装目录下的dropins文件夹下,我的目录是C:\Users\test\ec ...
- Qt程序开机自动运行
一.写入注册表需要管理员权限 1.开发中生成并运行程序需要写入注册表时,应该以管理员权限打开项目: 2.点击程序运行需要写入注册表,则应该以管理员权限打开此程序. 二.实现 void MoreSetW ...
- Apache Tomcat 安装与配置教程
JDK的安装与配置 1. 从官网下载JDK https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-213315 ...
- Scratch编程:牛顿的苹果——地心引力
牛顿的苹果 同学们,你们知道牛顿的苹果的故事吗? 传说1665年秋季,牛顿坐在自家院中的苹果树下苦思着行星绕日运动的原因.这时,一只苹果恰巧落下来,它落在牛顿的脚边.就是这个偶尔的瞬间,牛顿发现了苹果 ...