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

linux下讲解基础IO

回顾C文件接口

在学习C语言时我们了解了一些C语言的对于文件操作的接口。
其中有fopen、fclose、fputc、fgetc、fputs、fgets、fprintf、fscanf、fread、fwrite等...

code.c写文件

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • fread()函数从 stream指向的流中读取 nmemb个数据元素,每个元素大小为 size字节,并将它们存储到 ptr指定的位置。

  • fwrite()函数将 ptr指定位置的 nmemb个数据元素(每个元素大小为 size字节)写入到 stream指向的流中。

#include<stdio.h>
#include<string.h>int main()
{FILE* fd = fopen("log.txt", "w");if(!fd){printf("fopen error!");}const char* str = "hello world!";fwrite(str, 1, strlen(str), fd);fclose(fd);
}

code.c读文件

#include<stdio.h>
#include<string.h>int main()
{FILE* fd = fopen("log.txt", "r");char ch[1024];const char* str = "hello world!";fread(ch, 1, strlen(str), fd);for(int i = 0; i <  strlen(str); i++){printf("%c",ch[i]);}return 0;
}

输出信息到显示器

#include<stdio.h>
#include<string.h>int main()
{char* str = "hello world!\n";printf("%s", str);fprintf(stdout, "%s", str);fwrite(str, 1, strlen(str), stdout);return 0;
}

这样我们可以了解,显示器也可以看作文件,也可以用fwrite()函数来写入。

任何进程在运行时,都会默认打开三个输入输出流。
分别是:

  • 标准输入(键盘)stdin
  • 标准输出(显示器)stdout
  • 标准错误(显示器)stderr

这三个流的类型都是FILE*,文件指针。

系统文件IO

操作系统是不允许用户直接访问到硬件的,我么必须要由系统调用接口才能完成对硬件的访问。故我们所用的C文件接口,底层一定要封装对应文件类系统调用!

  • 之前的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
  • 而 open close read write lseek 都属于系统提供的接口,称之为系统调用接口

系统调用接口和库函数的关系,C库函数就是封装的系统调用接口。

所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

接口介绍

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: 追加写

mode:创建文件时的文件权限
返回值:
成功:新打开的文件描述符
失败:-1

上面列出来的flags参数本是上都是宏(只有一个比特位为1的宏)

#include<stdio.h>#define ONE (1<<0)//1左移0位
#define TWO (1<<1)
#define THREE (1<<2)void testprint(int flags)
{if(flags & ONE){printf("ONE\n");}	if(flags & TWO){printf("TWO\n");}if(flags & THREE){printf("THREE\n");}
}int main()
{testprint(ONE);printf("######################\n");testprint(TWO|THREE);printf("######################\n");testprint(ONE|TWO|THREE);return 0;
}

open使用

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main()
{open("log.txt", O_WRONLY|O_CREAT , 0666);//写权限|创建权限return 0;
}

这里权限与我们代码设置不同是因为umask

我们可以在代码中调用umask设置为0

open函数的返回值

  • open()和 creat()函数成功时返回​新的文件描述符​(一个非负整数)。

  • 如果发生错误,则返回 ​​-1​​,并相应地设置 errno以指示具体错误原因。

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main(int argc, char* argv[])
{int fd = open("argv[0]", O_WRONLY|O_CREAT|O_RDONLY , 0666);printf("fd = %d\n", fd);return 0;
}

write

ssize_t write(int fd, const void *buf, size_t count);
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
int main()
{int fd = open("log.txt", O_WRONLY|O_CREAT|O_RDONLY , 0666);printf("fd = %d\n", fd);const char* str = "hello world\n";write(fd, str, strlen(str));write(fd, str, strlen(str));write(fd, str, strlen(str));return 0;
}

文件描述符fd

通过对open函数的学习,我们知道了文件描述符就是一个小整数。

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器

如下我们进行write操作证明了标准输出的文件描述符为1.

read操作像标准输入读取字符到buffer中

通过上面的代码执行效果来看,这就是一个数组的下标,其中数组的0、1、2下标分被键盘(标准输入)、显示器(标准输出)、显示器(标准错误)给占了,所以分接下来的是3、4、5、6.

当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。
于是就有了file结构体。表示一个已经打开的文件对象。
而进程执行 open 系统调用,所以必须让进程和文件关联起来。
每个进程都有一个指针*files, 指向一张表 files_struct, 该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。
所以,只要拿着文件描述符,就可以找到对应的文件。

文件描述符的分配规则

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>int main()
{close(1);int fd1 = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC , 0666);printf("fd1 = %d\n", fd1);return 0;
}

我们发现fd1替代了标准输入的文件描述符变成了1,将原本要打印到显示屏的内容打印到了文件描述符1的文件当中.

可见,文件描述符的分配规则:在 files_struct 数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

重定向

此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 log.txt 当中,其中,fd1=1。这种现象叫做输出重定向。常见的重定向有:>(输出重定向), >>(追加重定向), <(输入重定向)

FILE

在系统层面,fd是访问文件的唯一方式。那FILE是什么呢?

  • 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
  • 所以C库当中的FILE结构体内部,必定封装了fd。
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>int main()
{printf("stdin:%d\n", stdin->_fileno);printf("stdout:%d\n", stdout->_fileno);printf("stderr:%d\n", stderr->_fileno);return 0;
}

FILE结构体中_fileno指的是文件描述符.

    #include<stdio.h>
    #include<string.h>
    #include<unistd.h>int main()
    {const char* str0 = "hello printf\n";const char* str1 = "hello fwrite\n";	const char* str2 = "hello write\n";printf("%s", str0);fwrite(str1, 1, strlen(str1), stdout);write(1, str2, strlen(str2));fork();return 0;
    }

    通过上述代码我们发现:进行输出重定向时,C语言接口所打印的内容在文件打印了两次。

    • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
    • printf fwrite 库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
    • 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
    • 但是进程退出之后,会统一刷新,写入文件当中。
    • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
    • write 没有变化,说明没有所谓的缓冲区。

    综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。
    那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

    为什么要有缓冲区?

    • 因为系统调用是存在时间消耗的,进行系统调用时,其他IO操作或者其他功能很可能要暂停;所以,为了效率,避免频繁使用系统调用;语言层就会设置缓冲区,当需要写入的数据到了一定条件再调用,这样可以提高IO效率。

    简单设计一下stdio.h

    如果想从底层实现上了解缓冲区的工作原理,可以简单用C语言模拟一下,思路就是模拟C语言封装一下系统调用,并设置缓冲区。

    头文件.h

    #pragma once#define FLUSH_LINE 1#define SIZE 1024struct myFILE
    {int flag;//刷新方式int fileno;//文件描述符char outbuffer[SIZE];//缓冲区int cap;int size;
    };typedef struct myFILE mFILE;mFILE *mfopen(const char *filename, const char *mode);
    int mfwrite(const void *ptr, int num, mFILE *stream);
    void mfflush(mFILE *stream);
    void mfclose(mFILE *stream);
    

    函数实现文件:

    #include"mystdio.h"
    #include<string.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<stdio.h>mFILE* mfopen(const char* filename, const char* mode)
    {int fd = -1;if(strcmp(mode, "w") == 0){fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);}else if(strcmp(mode, "r") == 0){	fd = open(filename, O_RDONLY, 0666);}else if(strcmp(mode, "a") == 0){	fd = open(filename, O_CREAT|O_WRONLY|O_APPEND, 0666);}if(fd < 0) return NULL;mFILE* mf = (mFILE*)malloc(sizeof(mFILE));if(!mf){close(fd);return NULL;}mf->fileno = fd;mf->flag = FLUSH_LINE;mf->size = 0;mf->cap = mf->size;return mf;
    }void mfflush(mFILE* stream)
    {if(stream->size > 0){write(stream->fileno, stream->outbuffer, stream->size);fsync(stream->fileno);//刷新内核级缓冲区stream->size = 0;}
    }int mfwrite(const void* ptr, int num, mFILE* stream)
    {memcpy(stream->outbuffer + stream->size, ptr, num);stream->size += num;if(stream->flag == FLUSH_LINE && stream->size!=0 && stream->outbuffer[stream->size - 1] == '\n'){mfflush(stream);}return num;
    }void mfclose(mFILE* stream)
    {if(stream->size > 0){mfflush(stream);}close(stream->fileno);	free(stream);}
    

    测试文件:

    #include "mystdio.h"
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    int main()
    {mFILE *fp = mfopen("./log.txt", "a");if(fp == NULL){return 1;}int cnt = 10;while(cnt){printf("write %d\n", cnt);char buffer[64];snprintf(buffer, sizeof(buffer),"hello message, number is : %d\n", cnt);cnt--;mfwrite(buffer, strlen(buffer), fp);mfflush(fp);sleep(1);}mfclose(fp);
    }
    

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

    相关文章:

  • 乌兰察布网站建设桂林漓江图片高清
  • Docker革命:软件开发的集装箱时代
  • 北京移动官网网站建设商务网站建设注意事项
  • 某网站的安全建设方案纪念平台网站建设
  • 定州网站制作潍坊网站制作人才招聘
  • 【C语言基础案例】经典C语言程序设计100例附源码解析(21-30例)
  • 网站建设需要缴纳印花税么邢台瑞光网络科技有限公司
  • 2025 年山西省职业院校技能大赛(高职教师组)移动应用设计与开发赛项样题
  • 证券投资网站做哪些内容做网站简单的软件
  • 网站建设费的分录怎么写济南知名网站建设平台
  • 『 数据库 』MySQL复习 - 查询进阶指南:基于经典测试表的复合查询实践
  • openpi π 0.5复现 实战
  • git命令和markdown语法参考
  • 域名如何跟网站绑定网站托管怎做
  • 怎样可以快速增加网站的反链寮步网站建设哪家好
  • 四.docker容器数据卷
  • Sora 2 引爆后,AI 视频赛道正进入「超级加速」
  • 二叉树最小深度解题思路
  • 网站建设与开发 期末作品公司网站更换域名流程
  • 佛山网站建设在哪班级优化大师手机版下载
  • 如何在VScode环境下使用git进行版本控制,并上传到gitee远程仓库
  • 个人网站开发项目报告数据库营销
  • 自适应网站有哪些标签在线设计平台
  • 达梦数据库配置SSL通信加密
  • 【STL】set、multiset、unordered_set、unordered_multiset 的区别
  • HTTP 协议和 MQTT 协议的区别
  • 景区门户网站建设ui设计可以在ipad上面做嘛?
  • 2025年江西省职业院校技能大赛高职组“区块链技术应用”竞赛第六套任务书解析答案
  • 巴中房产网站建设推广网站发布文章
  • 北京网站建设价钱莱芜金点子信息港招聘