[Linux] 基础IO

这篇具有很好参考价值的文章主要介绍了[Linux] 基础IO。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🥁作者华丞臧.
📕​​​​专栏:【LINUX】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站



一、文件I/O

C语言当中fopen和fclose通过文件的路径来打开和关闭文件,fread可以读取文件的内容,fwrite可以向文件中写入数据,在C语言当中还有各种入fputc、fputs、fget等用于读写文件的函数。

操作文件出了上述C接口,我们还可以使用系统调用接口来进行文件访问。

1.1 文件描述符fd

  • 文件存放在磁盘上的,也是数据,即便创建一个空文件也要占据磁盘空间;磁盘上的文件等于文件的内容加上文件的属性,即使内容为空描述该文件的属性也是不为空的。
  • 那么操作文件是在做什么呢?文件包括两个部分文件内容、文件属性,那么对文件的操作无疑就是对内容或者属性的操作。
  • 冯诺依曼体系结构规定,CPU只能从内存中读取数据,所以打开文件实质上是将磁盘中的文件加载到内存当中,并不是所有文件都是打开状态,而是需要使用时再打开。

[Linux] 基础IO

open是一个系统调用接口,其原型如下:

int open(const char *pathname, int flags, mode_t mode);

C语言中fopen打开文件后返回的是一个文件指针,而这里返回的是一个整型,这个整型代表什么呢?

这个整型就是文件描述符,首先我们要知道在内存中的一个进程中可以打开多个文件,当一个程序运行时会默认打开三个输出流分别是stdin、stdout、stderr,并且在Linux操作系统下一切皆文件,底层的一些硬件对于操作系统而言都是文件,操作系统启动肯定会打开某一些文件如屏幕、磁盘等。

  • 对于加载到内存中的文件,操作系统需要管理他们,因此需要先描述内存中的文件,再组织这些描述文件的数据结构,实现对文件的管理。
  • 而在Linux操作系统中使用结构体来描述文件,结构体中描述了文件的各种属性,然后再将这些结构体的地址用一个数组组织起来管理,这也就是为什么文件描述符是整型,它相当于是数组的下标。
  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2。0、1、2对应的物理设备一般是:键盘,显示器,显示器。
  • 每个进程的PCB都有一个指向file结构体数组的指针,指向自己打开的file结构体数组。

[Linux] 基础IO
文件描述符分配规则:在files_struct数组中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。一般默认从3开始,因为0、1、2通常默认是标准输入stdin, 标准输出stdout, 标准错误stderr。

1.2 重定向

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

int main()
{
 	close(1);
 	int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
 	if(fd < 0){
 		perror("open");
 		return 1;
 	}
 	printf("fd: %d\n", fd);
 	fflush(stdout);
 
 	close(fd);
 	exit(0);
}

[Linux] 基础IO
此时,我们发现本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, < 。
重定向本质就是改变file结构体数组中文件描述符位置上指针的指向。
[Linux] 基础IO

dup2系统调用

函数原型如下:

#include <unistd.h>
//将oldfd重定向到newfd中
int dup2(int oldfd, int newfd);

oldfd:源文件描述符
newfd:目的文件描述符
返回值:
	成功,返回newfd
	失败,返回-1

使用方式如下:

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

int main() 
{
 	int fd = open("./log", O_CREAT | O_RDWR);
 	if (fd < 0) {
 		perror("open");
 		return 1;
	}
 	close(1);
 	dup2(fd, 1);
 	for (;;) 
 	{
 		char buf[1024] = {0};
 		ssize_t read_size = read(0, buf, sizeof(buf) - 1);
 		if (read_size < 0) {
 			perror("read");
 			break;
 		}
 		printf("%s", buf);
 		fflush(stdout);
 	}
 	return 0;
}

上述代码的功能就是将log文件重定向到文件描述符1的位置上,重定向完成后fd:1表示log文件的地址。
[Linux] 基础IO

1.3 接口介绍

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。

系统调用库函数

  • fopen、fclose、fread、fwrite都是C标准库当中的函数,称之为库函数。
  • 而open、close、read、write、lseek都是系统提供的接口,称之为系统调用接口。
  • C语言当中关于文件读写的库函数都是对系统调用接口的封装,方便二次开发。

close

#include <unistd.h>
//关闭文件描述符对应的文件
int close(int fd);

返回值:
	成功,返回0
	失败,返回-1

read

#include <unistd.h>

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

fd:文件描述符
buf:存放读取数据的空间
count:读取数据字节数

返回值:
	成功,返回读取到的字节数
	失败,返回-1

write

#include <unistd.h>

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

fd:文件描述符
buf:需要写入数据的地址
count:写入数据的字节数

返回值:
	成功,返回写入数据的字节数
	失败,返回-1

lseek

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

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


fd:文件描述符
offset:偏移量	
whence:
	SEEK_SET:偏移量设置为偏移字节。
	SEEK_CUR:偏移量被设置为其当前位置加上偏移字节。
	SEEK_END:偏移量设置为文件大小加上偏移字节。

返回值:
	成功,返回从文件开始的以字节为单位的结果偏移位置
	失败,返回-1

接口实验

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

int main() 
{
    umask(0);
 	int fd = open("myfile", O_CREAT | O_RDWR | O_TRUC, 0666); //O_TRUC表示打开时清空文件内容
 	if (fd < 0) {
 	    perror("open");
 	    return 1;
	}

 	char buf[] = "hello world!";
 	ssize_t read_size = read(fd, buf, sizeof(buf));
 	if (read_size < 0) {
 		perror("read");
 	}

    int write_size = write(fd, buf, sizeof(buf));
 	if(write_size < 0)
    {
        perror("write");
    }
    return 0;
}

在运行上述代码后,可以看到在当前目录下创建了一个myfile的文件,打开该文件其内容如下图:
[Linux] 基础IO

1.4 缓冲区

什么是缓冲区?

  • 缓冲区本质就是一段内存。

为什么要有缓冲区?

  • 解放使用缓冲区进程的时间,进程不用等待传输数据的时间。
  • 缓冲区的存在可以集中处理数据刷新,减少IO的次数,可以提高整机的效率。
  • 缓冲区类似快递驿站,再快递到来之前我们不需要在驿站等待快递,可以去做其他的事情;快递到达驿站后,我们接收到通知去取快递。

缓冲区的刷新策略

  • 常规策略

    • 无缓冲(立即刷新)
    • 行缓冲(逐行刷新)如:显示器
    • 全缓冲(缓冲区满时刷新)如:块设备对应的文件,磁盘文件
  • 特殊策略

    • 进程退出
    • 用户强制刷新

缓冲区在哪里?

首先来看下面这段代码,其运行结果是什么呢?按照代码执行的顺序应该是先打印出 “printf” 再打印 “write” ,真的是这样吗?

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

int main()
{
    printf("printf"); //printf("printf\n");
    write(1, "write", 5);
    sleep(5);
    //close(stdout->_fileno); //关闭标准输出,stdout是C语言封装的文件指针类型,_fileno表示该文件指针对应的文件描述符
    return 0;
}

[Linux] 基础IO
可以看到结果并不是顺序打印,printf 是封装了 write 系统调用的函数,上述结果也说明了printf函数首先会将数据写入缓冲区中,当数据积累到一定程度才会刷新缓冲区,write会将数据写入文件中。

那么上述缓冲区在哪里呢?修改上述代码在休眠五秒后关闭标准输出,再运行程序结果如下图:
[Linux] 基础IO
当我们将标准输出关闭时,看到程序最后并没有打印出 “printf”,这说明printf并没有将数据写入内核级的缓冲区中,而是将数据写入C语言提供的语言级缓冲区中,而write是直接写入到文件中。再来看看下面的这段代码:

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

int main()
{
    char *str1 = "hello printf\n";
    char *str2 = "hello fprintf\n";
    char *str3 = "hello fputs\n";
    char *str4 = "hello write\n";

    printf(str1);
    fprintf(stdout, str2);
    fputs(str3, stdout);

    write(stdout->_fileno, str4, strlen(str4));

    fork();
}

[Linux] 基础IO
结果原理:

  • 第一次运行test程序时,是将数据写入到stdout即显示器文件中,显示器属于行缓冲因此会每一行每一行刷新,而我们写入的数据中结尾都带有\n,所以会立即刷新,当fork创建子进程时缓冲区数据已经刷新到显示器上了。
  • 第二次运行test程序并重定向到log.txt文件时,是将数据写入磁盘文件属于全缓冲,C接口缓冲区是自己的FILE内部维护的,属于父进程的数据区域,因此fork创建子进程会写时拷贝父进程的数据,所以父子进程都会刷新一次,write是直接将数据写入到文件中,fork时数据已经写入到文件中去了。

1.5 模拟实现IO函数

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

#define NUM 1024 

#define NONE_FLUSH 0x0
#define LINE_FLUSH 0x1
#define FULL_FLUSH 0x2

typedef struct MyFile
{
    int _fileno;
    char _buffer[NUM];
    int _end;
    int _flags;
}MyFile;

MyFile *my_fopen(const char *filename, const char *method)
{
    assert(filename);
    assert(method);
    int flags = O_RDONLY; 

    if(strcmp(method, "r") == 0)
    {
        flags = O_RDONLY;
    }
    else if(strcmp(method, "w") == 0)
    {
        flags = O_WRONLY | O_CREAT | O_TRUNC;
    }
    else if(strcmp(method, "r+") == 0)
    {
        flags = O_RDWR | O_CREAT;
    }
    else if(strcmp(method, "w+") == 0)
    {
        flags = O_RDWR | O_CREAT;
    }
    else if(strcmp(method, "a") == 0)
    {
        flags = O_WRONLY | O_CREAT | O_APPEND;
    }
    else if(strcmp(method, "a+") == 0)
    {
        flags = O_RDWR | O_CREAT | O_APPEND;
    }
    umask(0);
    int fileno = open(filename, flags, 0666);
    MyFile *fp = (MyFile*)malloc(sizeof(MyFile));
    if(fp == NULL) 
    {
        perror("malloc file\n");
        return NULL;
    }
    memset(fp, 0, sizeof(MyFile));

    fp->_fileno = fileno;
    if(fp->_fileno < 0) return NULL;
    fp->_flags |= LINE_FLUSH;
    fp->_end = 0;

    return fp;
}

void my_fwrite(MyFile *fp, const char * start, int size)
{
    assert(fp);
    assert(start);
    assert(size > 0);

    strncpy(fp->_buffer + fp->_end, start, size); //将数据写到缓冲区
    fp->_end += size;

    if(fp->_flags & NONE_FLUSH)
    {}
    else if(fp->_flags & LINE_FLUSH)
    {
        if(fp->_end > 0 && fp->_buffer[fp->_end - 1] == '\n')
        {
            write(fp->_fileno, fp->_buffer, fp->_end);
            fp->_end = 0;
        }
    }
    else if(fp->_flags & FULL_FLUSH)
    {}
}

void my_fflush(MyFile *fp)
{
    assert(fp);

    if(fp->_end > 0)
    {
        write(fp->_fileno, fp->_buffer, fp->_end);
        fp->_end = 0;
    }
}

void my_fclose(MyFile *fp)
{
    my_fflush(fp);
    close(fp->_fileno);
    free(fp);
}


int main()
{
    MyFile *fp = my_fopen("log.txt", "w");
    if(fp == NULL)
    {
        printf("my_fopen error\n");
        return 1;
    }

    const char *s = "hello myfile\n";
    my_fwrite(fp, s, strlen(s));
    printf("\n");
    sleep(3);

    const char *ss = "hello myfile";
    my_fwrite(fp, ss, strlen(ss));
    sleep(3);

    printf("写入了一个不满足条件的字符串\n");

    my_fclose(fp);
    return 0;
}

[Linux] 基础IO文章来源地址https://www.toymoban.com/news/detail-421230.html

到了这里,关于[Linux] 基础IO的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux:Linux基础网络设置

    ifconfig #查看网卡信息  inet---网络接口的ip地址 netmask---子网掩码 broadcast---广播地址 ether----物理地址(mac地址) hostname  #查看主机名 hostnamectl set-hostname + 自定义主机名  # 配置永久主机名 重启之后仍然存在 hostname + 主机名  # 配置临时主机名 重启以后恢复原来    route -n # 查

    2024年02月07日
    浏览(39)
  • Linux基础 - Linux ARM 原子读写

    在Linux Arm kernel实现原子读写64位数据;

    2024年02月03日
    浏览(30)
  • 【Linux基础】Linux环境变量(超详细)

    按 生命周期 分: 永久的 :在环境变量脚本文件中配置,用户每次登录时会自动执行这些脚本,相当于永久生效。 临时的 :用户利用export命令,在当前终端下声明环境变量,关闭Shell终端失效。 按 作用域 分: 系统 环境变量:公共的,对全部的用户都生效。 用户 环境变量

    2024年01月19日
    浏览(28)
  • 【linux基础(二)】Linux基本指令(中)

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:Linux从入门到开通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学更多操作系统知识   🔝🔝 本篇文章紧接着上一节的指令做拓展 建议先看第一篇文章,再看本篇文章: linux基本指令(上) 本章重点: 本篇文章着重讲解以

    2024年02月15日
    浏览(31)
  • Linux基础--Mobaxterm远程连接Linux系统

    当我们安装好Linux系统之后,可以在虚拟机里面进行操作,但是在实际的工作中,我们基本上是无法见到这个系统的,为了解决这个问题,我们可以使用远程工具对我们的服务器进行操作,来达到我们的目的。 在之前的文章里大家应该都看了如何安装配置虚拟机和操作系统,

    2024年01月23日
    浏览(44)
  • Linux基础-01:Linux命令的基本格式

    在CentOS 7操作系统中,Linux命令提示符就像是你与电脑交流的一个小标志,告诉你系统已经准备好接受你的指令了。 它通常会显示在你打开的终端窗口或控制台的最前面。 让我们来看一个示例: 在这个示例中: root: 是当前登录的用户名。 @ :分隔符号,没有特殊含义。 l

    2024年04月22日
    浏览(34)
  • Linux操作系统学习,Linux基础命令大全

    友情提醒 先看文章目录,大致了解文章知识点结构,点击文章目录可直接跳转到文章指定位置。 ①Linux是基于Unix的开源的免免费的一款操作系统,由于系统的稳定性和安全性被成为程序代码运行的最佳操作系统环境。 ②Linux发行版的不同,可以分为 1)乌班图:Ubuntu 2)红帽

    2024年02月14日
    浏览(64)
  • 【Linux基础】Linux主要指令的详解(指令补充)

    语法: cp [选项] 源文件或目录 目标文件或目录 功能: 复制文件或目录 说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到此目录中。若同时指定多个文件或目录,而最后的

    2024年02月03日
    浏览(38)
  • 【Linux 基础篇】Linux 目录结构速查表

    当谈论到Linux系统管理时,了解常见的目录结构是非常重要的。Linux操作系统采用一种层次结构的目录布局,每个目录都有其特定的用途和功能。在本篇博客中,我们将介绍Linux目录的速查表,帮助您更好地理解和导航Linux文件系统。 以下是Linux目录的速查表: 目录 说明 / 根目

    2024年02月12日
    浏览(38)
  • 【Linux】进程基础铺垫(一)硬件基础:冯诺依曼体结构

    我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系 截至目前,我们所认识的计算机,都是有一个个的硬件组件组成 。 输入单元:包括键盘, 鼠标,扫描仪, 写板等 输出单元:显示器,打印机等 存储器 : 内存 【 掉电易失 】 中央处

    2024年02月19日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包