Linux系统编程(二)文件IO/系统调用IO

一、IO 简介

I/O 是一切实现的基础:

  • 标准 IO(stdio);
  • 系统调用 IO(sysio,文件IO);

不同系统上的系统调用 IO 的使用方式可能不一样,为了隐藏不同系统上的细节,提出了标准 IO 给程序员调用,标准 IO 的实现是依赖于系统调用 IO 的,但是标准 IO 的可移植性更好。

文件 IO 都是不带缓冲的 IO,而标准 IO 是带缓冲的 IO。

二、系统调用 IO

部分系统调用 IO 如下(文件描述符 fd 贯穿始终):

  1. open()、close()
  2. read()、write()
  3. lseek()
  4. dup()、dup2() 
  5. sync()、fsync()、fdatasync()
  6. fcntl()
  7. ioctl()  

2.1 文件描述符 fd

fd 是一个整形数,实际上为一个数组的下标,数组的内容指向一个结构体,结构体中包含了当前打开文件的信息(如文件位置指针 pos),该信息包含指向文件的 inode 节点的指针,每使用 open() 系统调用就会创建一个该结构体,多次对一个文件使用 open() 系统调用会创建多个结构体,这些结构体都指向同一个 inode 节点,如下图所示:

其中,文件描述符 0 与进程的标准输入(stdin)关联,文件描述符 1 与进程的标准输出(stdout)关联,文件描述符 2 与进程的标准错误(stderr)关联。

2.2 open()、close()

以 flag 指定的模式打开文件,返回文件描述符 fd。 

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

参数 flags 必须包含下面三种模式之一:

  • O_RDONLY:以只读打开文件;
  • O_WRONLY:以只写打开文件;
  • O_RDWR:以可读可写打开文件;

另外,flags 还可以包含如下创建选项(creation flags)状态选项(status flags)

  • O_CREAT:若文件不存在则创建它;
  • O_EXCL:该文件必须是新文件,否则报错,一般和 O_CREAT 一起用;
  • O_APPEND:写内容追加到文件末尾;
  • O_ASYNC:使能信号驱动 IO(signal-driven I/O);
  • O_DIRECT:最小化 cache 的影响(cache 一般理解为读的缓冲区,即读加速;buffer 一般理解为写的缓冲区,即加速写);
  • O_NONBLOCK:非阻塞,打开文件和后续的操作不会等待,直接返回(比如请求的锁正在被使用),而阻塞模式则会等待请求的资源,不加此选项则默认为阻塞模式;
  • O_TRUNC:若文件存在,而且以只写或读-写打开,则将文件长度截断为 0;

重载:函数名相同,参数的类型和个数不相同。但上述两个 open 函数不是使用重载实现的,而是使用变参函数实现的。 

另外,还有一种系统调用 openat() 能够使用相对路径名打开目录中的文件。

2.3 read()、write()

read() 系统调用会从文件描述符 fd 中读取 count 个字节的数据到 buf 中,返回读到的字节个数。

#include <unistd.h>

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

write() 系统调用将 buf 中的 count 个字节数据写入文件描述符 fd 中,返回写入的字节数。

#include <unistd.h>

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

下面使用 read 和 write 实现 mycopy,注意在 write 的时候要判断是否写入对应字节的数据,若没有写完,可以报错或者使用 while 循环写完。 

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

#define BUFSIZE 1024

int main(int argc, char *argv[])
{
        if (argc < 3) {
                fprintf(stderr, "Usage:...\n");
                exit(1);
        }

        int fd1, fd2;

        if ((fd1 = open(argv[1], O_RDONLY)) < 0) {
                perror("fopen:");
                exit(1);
        }

        if ((fd2 = open(argv[2], O_CREAT | O_WRONLY | O_TRUNC, 0600)) < 0) {
                close(fd1);
                perror("fopen:");
                exit(1);
        }

        char buf[BUFSIZE];
        int n;

        while ((n = read(fd1, buf, BUFSIZE)) > 0) {
                if (write(fd2, buf, n) != n) {
                        perror("write:");
                        break;
                }
        }

        close(fd1);
        close(fd2);
        exit(0);
}   

2.4 lseek()

将文件的位置指针设置为相对于 whence 偏移 offset 的位置,并且返回当前文件位置(相当于 fseek 和 ftell 的结合体)。

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

其中 whence 有如下几种:

  • SEEK_SET:代表将文件位置指针设置为 offset 处;
  • SEEK_CUR:将文件位置指针设置为当前位置加上 offset 处;
  • SEEK_END:将文件位置指针设置为文件末尾加上 offset 处;

lseek 只修改文件表项(中间那层结构体)中的当前文件偏移量(pos),不进行任何 IO 操作(可以参考下面的删除文件中任意行的代码)。

2.5 dup()、dup2()  

dup() 系统调用可以复制一个文件描述符,新的文件描述符会使用当前可用的最小的文件描述符(新和旧的文件描述符指向的内容是一样的,)。

dup2() 系统调用的功能和 dup() 一样,区别就是 dup2() 会指定新的文件描述符,当新文件描述符已经被打开,则先关闭新的文件描述符,再执行拷贝操作。如果 oldfd 和 newfd 相同,则什么都不做,直接返回,并且 dup2() 是一个原子操作。

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);

以开头那张图为例,其中 fd1为使用 open() 系统调用打开文件得到的文件描述符,fd2 为对 fd1 使用 dup() 系统调用得到的文件描述符,fd3 则是对与 fd1 同一个文件使用 open() 系统调用得到的文件描述符:

上述操作转换成代码如下: 

char *filepath = "<file_path>"
int fd1 = open(filepath, O_RDONLY);
int fd2 = dup(fd1);
int fd3 = open(filepath, O_RDONLY);

使用 dup() 系统调用可用实现很多功能,比如将标准输出重定向到文件,实现方式为,打开一个文件得到 fd,然后使用 close(1) 关闭标准输出,再使用 dup(fd) 拷贝 fd 到标准输出,之后在代码中对标准输出的写操作将会被重定向到文件中(现象就是命令窗口不会打印 hello,而文件 /tmp/out 中会出现 hello)。

上述的做法有一点问题就是,在多线程并发执行的情况下,关闭和拷贝不是原子的,这使得当一个进程关闭了标准输出后,另外一个进程可能会执行拷贝,这样就违背了最开始的目的。可用使用 dup2() 系统调用来解决这个问题:

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

/* The dup() system call creates a copy of the file descriptor oldfd, using
 * the lowest-numbered unused file descriptor for the new descriptor.
 */

#define FILE_NAME "/tmp/out"

int main()
{
        int fd = open(FILE_NAME, O_CREAT|O_WRONLY|O_TRUNC, 0600);
        if (fd < 0) {
                perror("open");
                exit(1);
        }

        // wrong!! it is not Atomic, we should use dup2()!
        //close(1);
        dup2(fd, 1);

        // 为了避免 fd = 1 导致把自己关闭了
        if (fd != 1)
                close(fd);

        puts("hello!");
        exit(0);
}

编译执行上述代码后,查看 /tmp/out 文件即可发现文件的内容出现 hello!

2.6 sync()、fsync()、fdatasync() 

在操作系统中,大多数磁盘 IO 都通过缓冲区进行,内核会先把数据写入到缓冲区中,然后晚些时候再一起写入磁盘,这种方式被称为延迟写(delayed write)。

为了保证磁盘上实际文件系统与缓冲区的内容的一致性,操作系统提供了 sync、fsync、fdatasync 三个函数:

  • sync 将所有修改过的缓冲区排入写队列,然后返回,不等待实际的写磁盘操作结束;
  • fsync 只对由文件描述符 fd 指定的一个文件起作用,并且等待写磁盘操作结束才返回;
  • fdatasync 类似于 fsync,它只影响文件的数据部分,但不同步更新文件的属性(文件的修改时间等等);
#include <unistd.h>

void sync(void);

int fsync(int fd);

int fdatasync(int fd);

2.7 fcntl()

该系统调用对文件描述符执行对应的操作 cmd,cmd 后面跟着操作需要的参数。

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

该函数功能十分强大,比如 cmd 为 F_DUPFD 时可用实现 dup() 系统调用的功能(fcntl(fd, F_DUPFD, 0)),即复制一个文件描述符。不同的 cmd 可以实现不同的功能,后面跟着需要传入的参数。

fcntl 函数有如下 5 种功能:

  1. 复制一个已有的文件描述符(cmd=F_DUPFD 或 F_DUPFD_CLOEXEC);
  2. 获取/设置文件描述符标志(cmd=F_GETFD 或 F_SETFD);
  3. 获取/设置文件状态标志(cmd=F_GETFL 或 F_SETFL);
  4. 获取/设置异步 IO 所有权(cmd=F_GETOWN 或 F_SETOWN);
  5. 获取/设置记录锁(cmd=F_GETLK、F_SETLK 或 F_SETLKW);

2.8 ioctl()

设备相关的内容,可用于控制设备。每个设备驱动程序都可以定义它自己专用的一组 ioctl 命令。

#include <sys/ioctl.h>

int ioctl(int fd, unsigned long request, ...);

/dev/fd/ 目录(ubuntu 下链接至 /proc/self/fd/):虚目录,显示当前进程的文件描述符信息。比如我用 ls 进程查看 /dev/fd 目录的内容,则其中的文件描述符则为 ls 进程的文件描述符信息。

三、文件 IO(系统调用 IO)和标准 IO

3.1 区别

举例:传达室老大爷跑邮局。

多个人来找老大爷寄信(多次标准 IO),老大爷累计到一定数量后再一起寄出(一次系统调用),如果出现加急信件(fflush 强制刷新),那么会立刻送出现有的信件。

标准 IO 有缓冲,而系统调用 IO 无缓冲。这样的好处是能将多次标准 IO 整合到一个系统调用 IO(合并系统调用),节省用户态到内核态的切换时间。所以系统调用 IO 的响应速度快,而标准 IO 的吞吐量大。尽量使用标准 IO:可移植性好、吞吐量大。

注意:标准 IO 和文件 IO 不可混用!(文件位置指针不一致,一个在 FILE 中,一个在 fd 指向的结构体中,由于 FILE 标准 IO 有缓冲区的机制,所以写入的内容不一定写到了对应位置,所以实际 pos 和 FILE 中的 pos 可能不一样)

举例,下面代码打印的内容是什么:

#include <stdio.h>
#include <unistd.h>

int main()
{
        putchar('a');
        write(1, "b", 1);

        putchar('a');
        write(1, "b", 1);

        putchar('a');
        write(1, "b", 1);

        exti(0);
}

运行结果是 bbbaaa,标准 IO putchar 函数由于缓冲区的存在,所以没有立刻将数据写入标准输出,而系统调用 IO write 无缓冲区,立刻写入标准输出, 直到程序结束,刷新缓冲区才把标准 IO 的内容输出:

 

使用 strace 命令可以跟踪系统调用的执行,可以看到三个标准 IO putchar 被合并为了一个 write 系统调用: 

转换:fileno() 能将 FILE 转换成文件描述符 fd;fdopen() 能将文件描述符 fd 转成 FILE。

3.2 性能

其中 user 为用户态执行时间,sys 为内核态执行时间,real 为真实时间,包括调度等待、切换时间。 

上述 mycopy 将 buffersize 一直增大会出现的性能拐点?

四、文件共享

面试:删除文件的第 10 行。

可以把文件想象成一个数组,用后面的数据覆盖第 10 行的数据,并且将文件多余的空内容截断。

可以使用多个任务共同操作一个文件,协同完成任务。 

一个进程中操作步骤如下,把一个文件使用 open 系统调用打开两次,一个用于读,一个用于写,多进程需要进程间通信: 

truncate 系统调用将一个未打开的文件截断到 length 长度,而 ftruncate 将已打开的文件截断。利用这个函数将移动后产生的空洞删除:

#include <unistd.h>
#include <sys/types.h>

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

 实现代码如下:

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

#define BUFSIZE 1024

char buf[BUFSIZE];

int main(int argc, char *argv[])
{
        if(argc < 3){
                fprintf(stderr, "Usage: del_line <file_path> <line>\n");
                exit(1);
        }
        int line = atoi(argv[2]);
        char* filepath = argv[1];

        int fd1, fd2;

        if((fd1 = open(filepath, O_RDONLY)) < 0){
                perror("fd1: open");
                exit(1);
        }

        if((fd2 = open(filepath, O_WRONLY)) < 0){
                perror("fd2: open");
                exit(1);
        }

        char c;
        int i = line - 1;
        if(i < 0){
                perror("line should not lower than or equal 0");
                exit(1);
        }
        while(i > 0){
                if((read(fd1, &c, sizeof(c))) != sizeof(c)){
                        perror("read");
                        exit(1);
                }
                if(c == '\n'){
                        i--;
                }
        }

        off_t pos1 = lseek(fd1, 0, SEEK_CUR);
        while(1){
                if((read(fd1, &c, sizeof(c))) != sizeof(c)){
                        perror("read");
                        exit(1);
                }
                if(c == '\n'){
                        break;
                }
        }
        off_t pos2 = lseek(fd1, 0, SEEK_CUR);

        lseek(fd2, pos1, SEEK_SET);
        int n;
        while((n = read(fd1, &buf, BUFSIZE)) > 0){
                if(write(fd2, &buf, n) != n){
                        perror("write");
                        exit(1);
                }
        }
        off_t filesize = lseek(fd1, 0, SEEK_END);

        ftruncate(fd2, filesize - (pos2 - pos1));
        exit(0);
} 

假如有一个文件 txt,里面有 9 行内容,现在要删除第六行的内容: 

执行程序后可以看到第 6 行内容成功被删除:

4.1 原子操作

原子操作:不可分割的操作。

作用:解决并发环境下的竞争和冲突。

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

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

相关文章

mysql 为大表新增字段或索引

1 问题 mysql 为大表增加或增加索引等操作时&#xff0c;直接操作原表可能会因为执行超时而导致失败。解决办法如下。 2 解决办法 &#xff08;1&#xff09;建新表-复制表A 的数据结构&#xff0c;不复制数据 create table B like A; &#xff08;2&#xff09;加字段或索…

使用muduo库编写网络server端

muduo库源码编译安装和环境搭建 C muduo网络库知识分享01 - Linux平台下muduo网络库源码编译安装-CSDN博客 #include<iostream> #include<muduo/net/TcpServer.h> #include<muduo/net/EventLoop.h> using namespace std; using namespace muduo; using name…

两道有挑战的问题(算法村第九关黄金挑战)

将有序数组转换为二叉搜索树 108. 将有序数组转换为二叉搜索树 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 高度平衡 二叉搜索树。 高度平衡 二叉树是一棵满足「每个节点的左右两个…

作为班主任如何管理好班级

作为班主任&#xff0c;如何才能把班级管理得井井有条&#xff0c;让每个学生都能够得到全面的发展呢&#xff1f;这个问题一直困扰着许多班主任。接下来&#xff0c;我将从几个方面来分享一下自己的经验和看法。 建立良好的师生关系是班级管理的基石。作为班主任&#xff0c;…

【linux】粘滞位.yum

粘滞位 1.为什么我们普通用户可以删掉别人的文件&#xff08;包括root&#xff09;?合理吗&#xff1f; 2.删除一个文件和目标文件有关系吗&#xff1f; 没关系&#xff0c;和所处的目录有关系。 1.我们先以root身份创建一个目录&#xff0c;接着在这个目录下创建一个文件 2…

如何获取一个德国容器

1.注册discord账号 discord注册网址:https://discord.com/ 下面是容器的discord邀请链接 https://discord.com/Discord邀请链接:https://discord.com/invite/jVMSWrchC4 2.进入discord群聊点击link 在点击网址,这个网址每星期都会变就是图中的② 3.进入容器网址,进入界面…

POKT Network 开启周期性通缩,该计划将持续至 2025 年

POKT Network&#xff08;也被称为 Pocket Network&#xff09;在通证经济模型上完成了重大的改进&#xff0c;不仅将通货膨胀率降至 5% 以下&#xff0c;并使 POKT 通证在 2025 年走向通缩的轨迹上&#xff0c;预计到2024 年年底通货膨胀率将降至 2% 以下。POKT Network 的 “…

JVM工作原理与实战(十九):运行时数据区-方法区

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、运行时数据区 二、方法区 1.方法区介绍 2.方法区在Java虚拟机的实现 3.类的元信息 4.运行时常量池 5.字符串常量池 6.静态变量的存储 总结 前言 JVM作为Java程序的运行环境…

什么是网络安全,如何防范?

网络安全&#xff08;Cyber Security&#xff09;是指网络系统的硬件、软件及其系统中的数据受到保护&#xff0c;不因偶然的或者恶意的原因而遭受到破坏、更改、泄露&#xff0c;系统连续可靠正常地运行&#xff0c;网络服务不中断。 网络安全涵盖了网络设备安全、网络信息安全…

canvas绘制不同样式的五角星(图文示例)

查看专栏目录 canvas实例应用100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

如何在MinIO存储服务中通过Buckets实现远程访问管理界面上传文件

文章目录 前言1. 创建Buckets和Access Keys2. Linux 安装Cpolar3. 创建连接MinIO服务公网地址4. 远程调用MinIO服务小结5. 固定连接TCP公网地址6. 固定地址连接测试 前言 MinIO是一款高性能、分布式的对象存储系统&#xff0c;它可以100%的运行在标准硬件上&#xff0c;即X86等…

真假转换之间 tr

文章目录 真假转换之间 tra-z小写全部转换为大写A-Z大写全部转换为小写貌似起名可以用这个移除文件中的所有空格更多信息 真假转换之间 tr Linux tr 命令用于转换或删除字符。 tr 命令可以从标准输入读取数据&#xff0c;经过字符串转译后&#xff0c;将结果输出到标准输出。…

线性回归理论+实战

线性回归 什么是线性回归 3.1. 线性回归 — 动手学深度学习 2.0.0 documentation (d2l.ai) 模型 损失函数 模型拟合&#xff08;fit&#xff09;数据之前&#xff0c;我们需要确定一个拟合程度的度量。 损失函数&#xff08;loss function&#xff09;能够量化目标的实际值…

[go语言]数据类型

目录 知识结构 整型、浮点型 1.整型 2.浮点型 复数、布尔类型 1.复数 2.布尔类型 字符与字符串 1.字符串的格式化 2.字符串的截取 3.格式化好的字符串赋值给量 4.字符串的转换 5.strings包 知识结构 整型、浮点型 1.整型 在Go语言中&#xff0c;整型数据是一种基…

探索设计模式的魅力:抽象工厂模式的艺术

抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;用于在不指定具体类的情况下创建一系列相关或相互依赖的对象。它提供了一个接口&#xff0c;用于创建一系列“家族”或相关依赖对象&#xff0c;而无需指定它们的具体类。 主要参…

Zookeeper启动报错常见问题以及常用zk命令

Zk常规启动的命令如下 sh bin/zkServer.sh start 启动过程如果存在失败&#xff0c;是没办法直接看出什么问题&#xff0c;只会报出来 Starting zookeeper … FAILED TO START 可以用如下命令启动&#xff0c;便于查看zk启动过程中的详细错误 sh bin/zkServer.sh start-for…

页面数据类型为json,后端接受json数据

项目结构 依赖pom.xml <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.8.RELEASE</version></dependency><dependency><groupId>org.springframework…

ES可视化工具--ElasticHD

说明 ElasticHD 是 github 上的一个开源的项目&#xff0c;所以他没有官方网站&#xff0c;但 github 上的项目界面也可称为是它的官方界面了。 在 github 上直接搜索 ElasticHD 即可找到它&#xff0c;下面我将留下它的直接跳转链接。ElasticHD 下载 在 github 上搜索到之后…

【MCAL】ADC模块详解

目录 前言 正文 1.ADC模块介绍 2.关键概念及依赖的模块 2.1 ADC依赖的模块 3.ADC功能示例 3.1 ADC Buffer Access Mode示例 3.1.1配置&#xff08;Configuration&#xff09; 3.1.2 初始化&#xff08;Initialization&#xff09; 3.1.3 Adc_GetStreamLastPointer的使…

漏洞检测和评估【网站子域扫描工具02】

上一篇&#xff1a;爬取目标网站的域名和子域名【网站子域扫描工具01】 在Python中&#xff0c;有一些流行的漏洞扫描库可以对子域进行漏洞扫描和评估&#xff0c;比如Nmap、Sublist3r等。 1.端口扫描 以下是一个简单的示例代码&#xff0c;展示了如何使用Nmap进行基本的端口扫…