Linux文件编程(系统API调用)

news2024/12/23 6:55:35

文章目录

  • Linux文件编程
    • 标注C的IO缓存类型
        • 代码示例--缓存区的存在
    • 文件I/O系统调用
      • 标准C库关于文件的输入输出函数
      • FILE结构体
      • 文件描述符
      • 文件描述符与文件指针的相互转换
    • 系统调用常用函数
      • open函数(打开或者创建文件)
      • creat函数(创建一个现有文件)
      • close函数(关闭文件)
      • read函数(从打开文件中读取数据)
      • write函数(向打开的文件中写入数据)
      • lseek函数(偏移文件当前的位置)
        • 代码示例:实现cp操作
    • 空洞文件
        • 代码示例:空洞文件
    • 缓存buffer大小设置

Linux文件编程

对于Linux的输入输出而言,它并不是直接输出到屏幕上或者直接从键盘中获取输入。这中间经过了一个缓存。

标注C的IO缓存类型

全缓存

  • 要求填满整个缓存区后才进行I/O系统调用操作。对于磁盘文件通常要求使用全缓存访问。

行缓存

  • 涉及一个终端时(例如标准输入和标准输出),使用行缓存。

    行缓存的特点:

    • 行缓存满自动输出
    • 碰到换行符自动输出

无缓存

  • 标准错误流stderr通常是不带缓存区的,这使得错误信息能够尽快的显示出来。

当写入的字节数不能够满足要填满的最大缓存区这一条件,可以调用fflush函数强制清空缓存,将缓存中的数据输出到指定的流中。

当程序结束之前会自动将缓存里边的内容全部清空。

代码示例–缓存区的存在
#include <stdio.h>

int main()
{
	printf("haha");

	while(1);

	return 0;
}

image-20240826174107738

上边的这行代码由于因为有缓存的缘故,所以当去调用printf函数去向屏幕输出东西不加换行的时候,没有任何东西输出,这就说明在代码和屏幕之间确实是存在一个缓存区。此时若想要输出,可以在printf函数后边加一个换行符或者使用fflush函数强制清空缓存

image-20240826174550752

image-20240826174610755

文件I/O系统调用

下列有关文件的函数都是内核提供的系统调用,它们都是不带缓存功能的。

标准C库关于文件的输入输出函数

char *fgets(char *restrict s, int n, FILE *restrict stream);
int fputs(const char *restrict s, FILE *restrict stream);
int fprintf(FILE *restrict stream, const char *restrict format, ...);
int fscanf(FILE *restrict stream, const char *restrict format, ...);
size_t fread(void *restrict ptr, size_t size, size_t nitems,FILE *restrict stream);
size_t fwrite(const void *restrict ptr, size_t size, size_t nitems,FILE *restrict stream);

int scanf(const char *restrict format, ...);
int printf(const char *restrict format, ...);

FILE结构体

typedef struct iobuf
{
    int cnt;		//剩余的字节数
    char *ptr;		//下一个字符的位置
    char *base;		//缓冲区的位置
    int flag;		//文件访问模式
    int fd;			//文件描述符
}FILE;

由上边的结构体可知,在标准C库里边定义一个FILE类型的指针,实际上是定义了一个结构体指针

实际上在标准C库中定义的FILE类型的结构体,它内部也是通过系统调用来是实现它的文件的打开、关闭、读写等操作。系统调用的最重要的是通过一个文件描述符来进行一系列的操作,在FILE类型的结构体中也能看到这个成员变量,说明fopen底层确实是通过调用open函数来实现它的功能,其他C库函数也是一样。

关于标准C库和系统调用函数的使用场景的话:由于标准C库是面向应用层的,所以它的级别会更高一点,而对于系统调用函数它由于更靠近内核,所以它的级别更低一点。所以如果在实际应用中要求响应更快,可以使用系统调用函数,因为它没有缓存,而且更靠近内核,所以它的实现速度会更快。

文件描述符

  • 在系统调用中,每一个文件都对应一个唯一的文件描述符,文件描述符是一个非负整数。当打开一个现有文件或者创建一个新的文件时,内核向进程返回一个文件描述符。后续的读写、关闭等操作都是基于这个文件描述符。

  • POSIX应用程序中,整数0、1、2被宏定义为STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,这些宏定义被定义在unistd.h头文件中

    image-20240812114934468

  • 文件描述符的范围是0-OPEN_MAX,不同的操作系统所对应的OPEN_MAX的值不相同。在终端输入getconf OPEN_MAX可以获取OPEN_MAX的值 。也就是说一个进程最多能够打开或者创建1024个文件。

    image-20240812141133454

文件描述符与文件指针的相互转换

文件描述符–>文件指针(fdopen)

FILE *fdopen(int fd, const char *mode);

//参数1:文件描述符
//参数2:权限(r 只读 w 只写)
//返回值:文件指针

文件指针–>文件描述符

int fileno(FILE *stream);

//参数:文件流指针
//返回值:文件描述符

系统调用常用函数

open函数(打开或者创建文件)

#include <sys/stat.h>
#include <fcntl.h>

int open(const char *path, int oflag);
int open(const char *path, int oflag, mode_t mode);

//功能:打开或者创建一个文件
//参数1:要打开或者创建文件的路径;
//参数2:打开文件的选项,例如(O_RDONLY 只读) (O_WRONLY 只写) (O_RDWR 读写)

//第二个函数是只有文件不存在,需要创建新的文件的时候才使用
//参数1:要打开或者创建文件的路径;
//参数2:打开文件的选项,例如(O_RDONLY 只读) (O_WRONLY 只写) (O_RDWR 读写)
//参数3:创建文件要赋予的权限

//返回值是一个文件描述符

open函数的flag参数

O_RDONLY:以只读的方式打开文件

O_WRONLY:以只写的方式打开文件

O_RDWR:以读写方式打开文件

O_APPEND:以追加模式打开文件,每次写时都加到文件的尾端

O_CREAT:如果指定的文件不存在,则按mode参数指定的权限来创建文件

O_EXCL:如果同时指定了O_CREAT,而文件已经存在,则出错。可以用来测试一个文件是否存在。

O_DIRECTORY:如果参数pathname不是一个目录,则open出错。

O_TRUNC:如果此文件存在,而且为只读或者只写成功打开,则将其长度截断为0

o_NONBLOCK:如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为此文件的本粗打开操作和后续的I/O操作设置非阻塞方式。

creat函数(创建一个现有文件)

#include <sys/stat.h>
#include <fcntl.h>

int creat(const char *path, mode_t mode);

//参数1:要创建文件的路径
//参数2:创建文件富裕的权限
//返回值:若创建成功返回一个为只写打开的文件描述符,若出错返回-1
  • 此函数等价于open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode)

  • ceate的一个不足之处时它打开的文件是为只写打开的

close函数(关闭文件)

#include <unistd.h>

int close(int fildes);

//参数:一个已经打开的文件描述符
//返回:若成功为0,若出错为-1

read函数(从打开文件中读取数据)

#include <unist.h>

ssize_t read(int fildes, void *buf, size_t nbyte);

//参数1:一个已经打开的文件描述符
//参数2:用来存放读取到的一个缓存
//参数3:要求一次读取的字节数
//返回值:读取到的字节数,若已到达文件尾为0,若出错为-1

注意:

read函数的返回值可能会少于要求读取的字节数的原因:

  • 读取普通文件时,再读到要求字节数之前已经达到了文件尾端;
  • 当从终端设备读时,通常一次最多读一行;
  • 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数;
  • 某些面向记录的设备,例如磁带,一次最多返回一个记录;
  • 进程由于信号造成中断。

read函数从文件的当前的位移量处开始,在成功返回之前,该位移量增加实际读得的字节数。

write函数(向打开的文件中写入数据)

#include <unistd.h>

ssize_t write(int fildes, const void *buf, size_t nbyte);

//参数1:一个已经打开的文件描述符
//参数2:存放要写入文件的数据的一个缓存
//参数3:要求一次写入文件的字节数

//返回值:若成功为已写入的字节数,若出错为-1

需要注意的是:当使用write函数的时候需要特别注意的是要清楚是追加写入还是从头写入,如果想要从头写入那么就直接使用O_TRUNC标志位将文件清空,如果想要追加写入那么就使用O_APPEND标志位在文件的末尾追加写入。

lseek函数(偏移文件当前的位置)

#include <unistd.h>

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

//参数1:已经打开的文件描述符
//参数2:位移量
//参数3:定位的位置
//返回值:若成功则返回新的文件位移量(绝对偏移量(距文件开头的字节数)),若出错返回-1

参数3:whence

  • SEEK_SET:将该文件的位移量设置为距文件开始处offset个字节
  • SEEK_CUR:将该文件的位移量设置为其当前值加offsetoffset 可正可负
  • SEEK_END:将该文件的位移量设置为文件长度(文件末尾)加offsetoffset可正可负

lseek函数的注意事项

  1. lseek也可以用来确定所涉及的文件是否可以设置偏移量。如果文件描述符引用的是一个管道或FIFO,则lseek返回-1,并将errno设置为EPIPE
  2. 每个打开文件都有一个与其相关联的“当前文件偏移量”。它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读写操作都是从当前文件偏移量处开始,并使用偏移量增加所读或所写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND追加写入或者使用lseek函数进行偏移,否则该位移量被设置为0。
代码示例:实现cp操作
#include "io.h"

#define BUFFER_SIZE 1024

void copy_function(int fdin, int fdout)
{
	ssize_t nbytes = 0;
	char buffer[BUFFER_SIZE];

	memset(buffer,'\0',BUFFER_SIZE);

	//从源文件中读取到buffer中
	while((nbytes = read(fdin, buffer, BUFFER_SIZE)) > 0)
	{
		printf("the size of read bytes is %ld\n",lseek(fdin,0,SEEK_CUR));
		//将buffer里边的数据写入到目标文件中去
		if(write(fdout, buffer, nbytes) != nbytes)
		{
			fprintf(stderr,"write error: %s\n",strerror(errno));

			exit(EXIT_FAILURE);
		}
	}

	if(nbytes < 0)
	{
		fprintf(stderr,"read error: %s\n",strerror(errno));

		exit(EXIT_FAILURE);
	}
}
//cp.c

#include "io.h"

int main(int argc, char **argv)
{
	long size;

	if(argc != 3)
	{
		fprintf(stderr,"usage: %s srcfile destfile\n",argv[0]);

		exit(EXIT_FAILURE);
	}

	int fdin, fdout;

	fdin = open(argv[1],O_RDONLY);
	if(fdin < 0)
	{
		fprintf(stderr,"open file error: %s\n",strerror(errno));

		exit(EXIT_FAILURE);
	}

	size = lseek(fdin,0,SEEK_END);

	printf("the size of file is %ld\n",size);

	fdout = open(argv[2],O_WRONLY | O_CREAT | O_TRUNC, 0777);
	if(fdout < 0)
	{
		fprintf(stderr,"open file error: %s\n",strerror(errno));

		exit(EXIT_FAILURE);
	}

	lseek(fdin,-size,SEEK_END);
	copy_function(fdin,fdout);

	close(fdin);
	close(fdout);

	return 0;
}
//io.h

#ifndef _IO_H
#define _IO_H

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

void copy_function(int fdin, int fdout);

#endif 

需要注意的是lseek函数定位的位置如果设置为SEEK_END标志位,它的偏移量是可正可负的。负方向就是从文件末尾开始向前偏移offset个字节,但如果向正方向偏移就从文件尾开始向后偏移offset个字节。如果在偏移若干个文件后再次写入数据,那么这就是一个空洞文件。

空洞文件

空洞文件是指文件中未写入数据的部分,不占用磁盘空间,直到写入数据时才会分配磁盘块。空洞文件的存在意味着文件名义上的大小可能比其占用的磁盘存储总量要大。在UNIX文件操作中,文件位移量可以大于文件的当前长度,这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞。创建空洞文件可以让多线程在不同的seek上面开始写入文件,如果不是空洞文件就只能串行写入了。例如常常在网络传输中会使用到空洞文件。当从网络上下载的时候会首先创建一个和目标文件相同大小的空洞文件,然后使用多线程在不同的地址上进行写入文件,这样能大大加快传输速度。

代码示例:空洞文件
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>

char *buffer = "1234567890";

int main(int argc, char **argv)
{
	if(argc != 2)
	{
		fprintf(stderr,"usage: %s [file]\n",argv[0]);

		exit(EXIT_FAILURE);
	}

	int fd;

	fd = open(argv[1],O_WRONLY | O_CREAT | O_TRUNC, 0777);
	if(fd < 0)
	{
		perror("open error");
		exit(EXIT_FAILURE);
	}

	if(write(fd, buffer, strlen(buffer)) != strlen(buffer))
	{
		fprintf(stderr,"write error: %s\n",strerror(errno));

		exit(EXIT_FAILURE);
	}

	
	lseek(fd, 10, SEEK_END);
	

	if(write(fd, buffer, strlen(buffer)) != strlen(buffer))
	{
		fprintf(stderr,"write error: %s\n",strerror(errno));

		exit(EXIT_FAILURE);
	}

	close(fd);

	return 0;
}

image-20240826173407214

image-20240826173451504

缓存buffer大小设置

在进行读写操作的时候,必然会有一个缓存来存放临时的数据。实际上这个缓存的大小也是有讲究的。在数据读写的过程中,一般使用块作为一个单位进行存放,如果定义缓存大小位一个块的大小,会大大地增加传输的效率。不同系统的块大小是不一样的,可以使用以下指令查看块大小

首先使用df -h指令查看磁盘情况

image-20240813120306589

然后使用指令tune2fs查看文件系统参数(以超级用户权限)

sudo tune2fs -l /dev/sda3

image-20240813120400261

image-20240813120428456

上边的Block siez就是当前系统的块大小,将这个定义为缓存的大小有利用系统进行文件的读写操作。

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

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

相关文章

JAVA-封装

目录 一、封装的概念 二、封装扩展之包 1. 包的概念 2.导入包中的类 3.自定义包 4.常见的包 三、访问限定符 在同一包中&#xff1a; 在不同包中&#xff1a;​编辑 一、封装的概念 面向对象程序三大特性&#xff1a;封装、继承、多态。而类和对象阶段&#xff0c;主…

ESP32修改分区表

修改分区表 官方参考 在工程目录文件夹新建分区表&#xff0c;参考官方的写就行&#xff0c;我这里改成了8M的FLASH&#xff0c;所以新建的分区表为名字是 default_8MB.csv &#xff0c;内容如下&#xff1a; # Name, Type, SubType, Offset, Size, Flags nvs, data, …

后端微服务与分布式系统

编写一篇关于后端微服务和分布式系统的文档&#xff0c;需要详细讨论微服务架构的核心概念、优缺点、关键技术&#xff0c;以及在分布式系统中的应用。以下是文档的大纲和内容概述&#xff1a; 后端微服务与分布式系统 1. 简介 微服务架构是一种将大型应用程序分解为一系列小…

leetCode - - - 二叉树

目录​​​​​​​ 1.前中后序遍历&#xff08;递归&#xff09; 2.前中后序遍历&#xff08;迭代&#xff09; 3.翻转二叉树&#xff08;LeetCode 226&#xff09; 4.最大二叉树&#xff08; LeetCode 654 &#xff09; 5.平衡二叉树&#xff08; LeetCode 110 &#xf…

02- javascript 高阶-构造函数(知识点)呀

目录 1.构造函数 1.1 JS构造函数的实例成员和静态成员 1.1.1实例成员 1.1.2静态成员 1.2构造函数原型prototype 1.3对象原型 1.4 constructor构造函数 1.5原型链 1.6构造函数实例和原型对象三角关系 1.7原型链和成员的查找机制 1.7.1 Object.prototype.hasOwnPropert…

WEB渗透Win提权篇-提权工具合集

提权工具合集包&#xff1a; 夸克网盘分享 往期文章 WEB渗透Win提权篇-RDP&Firewall-CSDN博客 WEB渗透Win提权篇-MSSQL-CSDN博客 WEB渗透Win提权篇-MYSQL-udf-CSDN博客 WEB渗透Win提权篇-AccountSpoofing-CSDN博客 WEB渗透Win提权篇-弱权限提权-CSDN博客 Tools合集 工…

UVa1670/LA5920 Kingdom Roadmap

UVa1670/LA5920 Kingdom Roadmap 题目链接题意分析AC 代码 题目链接 本题是2011年icpc欧洲区域赛东北欧赛区的K题 题意 输入一个n&#xff08;n≤100000&#xff09;个结点的树&#xff0c;添加尽量少的边&#xff0c;使得任意删除一条边之后图仍然连通。如下图所示&#xff0…

如果是你,你会背叛师门吗?看了凌晨一点杭州隧道里睡满的外卖员,我觉得李佳琦被骂一点也不冤——早读(逆天打工人爬取热门微信文章解读)

如果是你&#xff0c;你会背叛师门吗&#xff1f;&#xff1f; 引言Python 代码第一篇 洞见 看了凌晨一点杭州隧道里睡满的外卖员&#xff0c;我觉得李佳琦被骂一点也不冤第二篇 股市风云结尾 (先不论人品如何&#xff0c;这个问题就有点类似董宇辉跟新东方&#xff0c;大伙且看…

Java学习第一天

Java介绍&#xff1a; Java是一种高级编程语言&#xff1b;由sun公司研发&#xff0c;之后被Oracle公司收购&#xff1b;Java之父是詹姆斯.高斯林&#xff1b;Java什么都能做但主要用于企业互联网系统的开发&#xff1b;包括JavaSE&#xff08;标准版&#xff09;、JavaEE&…

C程序设计——表达式的值0

表达式 表达式是一组序列&#xff0c;由操作符或操作数组成。 表达式的值 C语言中&#xff0c;所有的表达式&#xff0c;都是有值的&#xff0c;所以本节专门讲讲表达式的值。 算数表达式 算数表达式的值&#xff0c;就是算数运算的结果&#xff0c;比如表达式 1 1的值就…

Threejs三要素及demo

本文目录 前言一、threejs三要素1.1 场景Scene1.2 相机Camera1.3 渲染器Renderer1.4 项目构建准备 二、安装Threejs2.1 编写代码 前言 Three.js是一个在 MIT 许可下的 JavaScript 库&#xff0c;它在 WebGL 之上运行。这个库的目标就是简化处理3D内容的过程。它是一个WebGL引擎…

【Linux】第一次使用linux向gitee上提交代码

1.首先要在gitee上新建一个仓库 2.然后&#xff0c;复制https的仓库链接 3. 三板斧 第一斧 git add . 4.三板斧 第二斧 git commit -m ‘日志’ 5.三板斧 第三斧 git push

QQ官方BOT 机器人Python实现群聊本地图片【base64编码】上传及其发送

参考&#xff1a;实现群聊本地图片【base64编码】上传及其发送 by SlieFamily 拉取请求 #199 腾讯连接/Botpy (github.com) 首先找到api.py&#xff0c;如果你是通过pip安装的botpy。找到包对应的地址&#xff0c;如果是clone的&#xff0c;也直接找到api.py。 打开后&#xff…

io进程----文件io

目录 一丶概念 二丶特点 三丶函数 1.打开文件 open 2.关闭文件 close 3.读取文件 read 4.写入文件 write 5.文件定位操作 标准IO和文件IO区别 四丶获取文件属性 1.stat函数 2.获取文件类型 五丶目录操作 一丶概念 在posix(可移植操作系统接口)中定义的一组输入输出…

STC89C52 定时器浅谈

文章目录 1、定时器1.1 定时器简介1.2 定时器构成1.2.1 系统时钟1.2.2 计数单元1.2.3 中断系统 1.2 定时器0/1的相关寄存器1.2.1 TMOD1.2.2 TCON 1.3 初始化定时器0 1、定时器 1.1 定时器简介 定时器&#xff0c;又称为计数器&#xff0c;是51单片机的内部资源&#xff0c;即…

16、CPU缓存架构

CPU缓存架构 CPU缓存架构详解CPU高速缓存概念CPU多核缓存架构CPU多核缓存架构缓存一致性问题分析 CPU缓存架构缓存一致性的解决方案缓存一致性协议实现原理总线窥探工作原理窥探协议类型 缓存一致性协议MESI协议 伪共享的问题linux下查看Cache Line大小避免伪共享方案 CPU缓存架…

netty编程之实现HTTP服务

写在前面 http是应用层协议&#xff0c;是我们日常开发中直接用到最多的协议了。本文来看下通过netty如何实现。 1&#xff1a;程序 netty不仅仅提供了String相关的编解码器&#xff0c;还贴心的提供了http相关的编码器和解码器&#xff0c;直接拿来用就行了&#xff0c;所以…

【3.3】贪心算法-解分发糖果

一、题目 老师想给孩子们分发糖果&#xff0c;有N 个孩子站成了一条直线&#xff0c;老师会根据每个孩子的表现&#xff0c;预先给他们评分。 你需要按照以下要求&#xff0c;帮助老师给这些孩子分发糖果&#xff1a; 1&#xff09;每个孩子至少分配到1 个糖果。 2&#xff…

apache经典模型和nginx参数配置

Apache 几个经典的 Web 服务端 ①Apache prefork 模型 ● 预派生模式&#xff0c;有一个主控制进程&#xff0c;然后生成多个子进程&#xff0c;使用select模型&#xff0c;最大并发 1024 ● 每个子进程有一个独立的线程响应用户请求 ● 相对比较占用内存&#xf…

Cyberchef实用功能之-模拟沙箱的文件静态分析能力

本文将介绍如何使用cyberchef 提取文件的重要元数据的信息&#xff0c;根据自己的需求实现沙箱中的静态文件的分析能力。 在网络安全日常的运营&#xff0c;护网行动&#xff0c;重保活动的过程中&#xff0c;样本的分析是一个重要的过程&#xff0c;这些可疑或者恶意的样本的…