Linux操作系统3-文件与IO操作1(从C语言IO操作到系统调用)

上篇文章:Linux操作系统2-进程控制3(进程替换,exec相关函数和系统调用)_execv系统调用-CSDN博客

本篇代码Gitee仓库:myLerningCode · 橘子真甜/linux学习 - 码云 - 开源中国 (gitee.com)

本篇重点:C语言基础IO与系统调用

目录

[一. 文件相关知识](#一. 文件相关知识)

[二. C语言的文件操作](#二. C语言的文件操作)

[2.1 fopen](#2.1 fopen)

[2.2 fclose](#2.2 fclose)

[2.3 fread](#2.3 fread)

[2.4 fwrite](#2.4 fwrite)

[2.5 fprintf](#2.5 fprintf)

[2.6 举例代码](#2.6 举例代码)

[三. 文件相关的IO系统调用](#三. 文件相关的IO系统调用)

[3.1 open](#3.1 open)

[3.2 close](#3.2 close)

[3.3 write](#3.3 write)

[3.4 read](#3.4 read)

[3.5 举例代码操作](#3.5 举例代码操作)

[四. OS是如何管理被打开的文件?](#四. OS是如何管理被打开的文件?)

[4.1 文件fd](#4.1 文件fd)

[五. 下篇重点: 文件fd, Linux下一切皆文件](#五. 下篇重点: 文件fd, Linux下一切皆文件)


一. 文件相关知识

在基础指令这篇文章 Linux基础1-基本指令2(你真的了解文件吗?)-CSDN博客 中,我们提到了文件的相关命令。总结一下

1 一个空文件也需要占用空间

2 文件 = 文件内容 + 文件属性

3 文件操作 = 操作文件内容 + 操作文件属性

4 我们使用文件路径+文件名标记一个文件

5 进程想要访问一个文件必须要先通过OS打开这个文件

C语言为用户提供了文件操作,C++也有相关的文件操作。像这些语言级别的库函数提供的文件操作,**都是对OS提供的文件操作系统调用的封装。**所以,学习系统调用提供的文件操作有利于我们掌握语言级的文件操作

二. C语言的文件操作

C语言中的库函数为我们提供了很多操作文件的函数:fopen,fclose,fwrite,fread,fprintf,fscanf...等。

2.1 fopen

fopen用于打开一个文件,其函数原型如下:

//所需头文件
#include <stdio.h>

//函数原型
FILE* fopen(const char* filename, const char* mode);

//filename,打开文件的路径。直接写名字默认在当前路径下查找

//mode,打开的方式
"r" 只读方式打开
"w" 只写方式打开,默认会清空文件中的内容
"a" 只写,写的方式是追加
"b" 以二进制方式打开,一般配合r和w使用
"W+" 读写,没有文件会创建,写方式是清空文件从头开始写
"r+" 读写,从头开始写文件
"a+" 读写,追加写

2.2 fclose

用于关闭一个打开的文件

//函数原型
int fclose(FILE* stream);

//关闭stream这个文件流(被fopen打开的文件流)

2.3 fread

用于读一个文件中的数据

cpp 复制代码
//函数原型
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

ptr: 读取的文件存放在内存中的位置
size:读取文件中元素大小(以字节为单位)
nmemb:读取文件元素的数量
stream:读取文件的文件流(你要读取的文件)

2.4 fwrite

cpp 复制代码
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

//参数和fread类似

//只是功能是将ptr写入到stream这个文件流中

2.5 fprintf

cpp 复制代码
//函数原型
int fprintf(FILE *stream, const char *format, ...);

//用法和printf一样,不过是将数据写入到stream这个文件流中

其他文件操作都和上述文件操作类似。具体内容可以查找man手册

2.6 举例代码

用一段代码来举例这些操作的用法

test.c

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#define MY_FILENAME "log.txt"

int main()
{
    // 1.写入数据
    FILE *fp = fopen(MY_FILENAME, "w");
    if (fp == NULL)
    {
        // 打开文件失败
        perror("fopen");
    }
    // 2.使用fwrite写数据,写入三行 Hello world
    const char *buffer = "Hello World!\n";
    fwrite(buffer, sizeof(char), strlen(buffer), fp);
    fwrite(buffer, sizeof(char), strlen(buffer), fp);
    fwrite(buffer, sizeof(char), strlen(buffer), fp);


    // 3. 关闭文件
    fclose(fp);
    fp = NULL;
    return 0;
}

Makefile

cpp 复制代码
test:test.c
	gcc -o $@ $^ -std=c99

.PHONY:clean
clean:
	rm -rf test log.txt

测试结果如下:

修改 log.txt 和 test.c 进行读取数据

log.txt

cpp 复制代码
Hello World!
Hello World!
Hello World!
YZC yzc 
abc 123
156 1sg 45qe1r 5h@#@ ^% 56 @# ^re8 5qh qer56h 16 32`7 tr314yt 9bm    v891-3 

test.c

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#define MY_FILENAME "log.txt"

int main()
{
    // 1.读取数据
    FILE *fp = fopen(MY_FILENAME, "r");
    if (fp == NULL)
    {
        // 打开文件失败
        perror("fopen");
    }
    
    // 2.使用fread写数据,写入三行 Hello world
    char buffer[200];
    fread(buffer, sizeof(char), 200, fp);
    buffer[strlen(buffer) - 1] = '\0';  //将最后的'\n'变为'\0'
    printf("%s\n", buffer);

    // 3. 关闭文件
    fclose(fp);
    fp = NULL;
    return 0;
}

测试结果:

注意C语言的字符串默认在结尾有一个'0',而文本文件中末尾并没有'\0'。所以我们使用C语言接口读取文件后,如果是字符串,需要在末尾加上'\0'

三. 文件相关的IO系统调用

3.1 open

打开文件的系统调用

cpp 复制代码
//所需头文件
#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    打开文件的方式
//mode     创建文件时候文件的权限

//常见的flag
O_RDONLY 表示只读
O_WRONLY 只写
O_WRONLY 读写
O_APPEND 追加写
O_CREAT  没有这个文件要创建文件
O_TRUNC  打开文件的时候清空文件内容

3.2 close

关闭文件fd的系统调用

cpp 复制代码
//文件关闭
#include <unistd.h>

//函数原型
int close(int fildes);

3.3 write

向文件写入数据

cpp 复制代码
//头文件
#include <unistd.h>

//函数原型
ssize_t write(int fd, const void *buf, size_t count);

//fd 写入的文件fd

//buf 要写的数据缓冲区来源

//count 写入的字节个数

//返回值,成功写入,返回写入的字符数,失败返回-1

buf是void* 的原因:在系统看来,任何数据都是二进制

3.4 read

从文件中读取数据

cpp 复制代码
//头文件
#include <unistd.h>

//函数原型
ssize_t read(int fd, void *buf, size_t count)

//将fd文件中的count字节数量的数据读取到buf中

//返回0表示读取到文件结尾

3.5 举例代码操作

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MY_FILENAME "log.txt"

int main()
{
    // 1.打开文件,方式是读写,没有文件创建,清空文件从头开始写。创建的文件权限是0666
    umask(0); // 清空系统的umask
    int fd1 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);

    // 如果写入失败
    if (fd1 < 0)
    {
        perror("open");
        return -1;
    }

    // 2.写入数据
    char buffer[64];
    int cnt = 5;
    while (cnt)
    {
        sprintf(buffer, "YZC Hello World [%d]\n", cnt--); // 将数据写入缓冲区
        write(fd1, buffer, strlen(buffer));             // 向文件写入数据不需要添加'\0'
    }

    // 3.关闭文件描述符fd
    close(fd1);

    // 4.读取这些数据
    int fd2 = open(MY_FILENAME, O_RDONLY);

    // 如果文件打开错误
    if (fd2 < 0)
    {
        perror("open");
        return -1;
    }

    // 读取文件的时候,buffer最多读取sizeof(buf)个数据,由于有'\0'。所以要-1
    char *buf[64];
    ssize_t num = read(fd2, buf, sizeof(buf) - 1);
    printf("%s", buf);

    // 关闭文件
    close(fd2);
    return 0;
}

测试结果:

语言级别的IO操作库函数都是对系统调用IO操作的封装

四. OS是如何管理被打开的文件?

我们知道,OS通过PCB来管理进程。在OS中有很多进程,这些进程也会打开很多的文件。那么OS是如何管理这些被打开的文件的?

OS为了管理被打开的文件,创建对应的内核数据结构 struct_file。这个结构体包含了文件的大量属性。

4.1 文件fd

文件fd是什么东西?我们打印出来看看

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MY_FILENAME "log.txt"

int main()
{
  umask(0); //清楚umask码,仅仅修改该进程创建的文件
  int fd1 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd2 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd3 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd4 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd5 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
  

  printf("fd1:%d\n",fd1);  
  printf("fd2:%d\n",fd2);  
  printf("fd3:%d\n",fd3);  
  printf("fd4:%d\n",fd4);  
  printf("fd5:%d\n",fd5);  

  close(fd1);
  close(fd2);
  close(fd3);
  close(fd4);
  close(fd5);
  return 0;
}

fd为什么从3开始?

因为C语言会默认打开三个输入输出流,stdin, stdout, stderr。

即标准输入,标准输出,标准错误。它们占用了0 1 2

通过stdin这个文件的结构体中的 _fileno 即可获取fd

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MY_FILENAME "log.txt"

int main()
{
  umask(0); //清楚umask码,仅仅修改该进程创建的文件
  int fd1 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd2 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd3 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd4 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd5 = open(MY_FILENAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
  
  printf("stdin->fd [%d]\n",stdin->_fileno);  
  printf("stdout->fd [%d]\n",stdout->_fileno);  
  printf("stderr->fd [%d]\n",stderr->_fileno);  
  printf("fd1:%d\n",fd1);  
  printf("fd2:%d\n",fd2);  
  printf("fd3:%d\n",fd3);  
  printf("fd4:%d\n",fd4);  
  printf("fd5:%d\n",fd5);  

  close(fd1);
  close(fd2);
  close(fd3);
  close(fd4);
  close(fd5);
  return 0;
}

测试结果如下:

这些数字其实是一个数字的下标,在PCB中有一个指针数组 (称为文件描述符表)。这个指针数组存放的是指向struct_file这个文件管理的内核数据结构。

进程通过fd这个数组下标就能够访问文件结构体!

具体关系可见下图:

五. 下篇重点: 文件fd, Linux下一切皆文件

相关推荐
AGI学习社1 分钟前
2024中国排名前十AI大模型进展、应用案例与发展趋势
linux·服务器·人工智能·华为·llama
加油,旭杏15 分钟前
【go语言】变量和常量
服务器·开发语言·golang
H.2021 分钟前
centos7执行yum操作时报错Could not retrieve mirrorlist http://mirrorlist.centos.org解决
linux·centos
wanhengidc35 分钟前
网站服务器中的文件被自动删除的原因
运维·服务器
DARLING Zero two♡1 小时前
【初阶数据结构】逆流的回环链桥:双链表
c语言·数据结构·c++·链表·双链表
9毫米的幻想1 小时前
【Linux系统】—— 编译器 gcc/g++ 的使用
linux·运维·服务器·c语言·c++
helloliyh1 小时前
Windows和Linux系统安装东方通
linux·运维·windows
小深ai硬件分享2 小时前
Keras、TensorFlow、PyTorch框架对比及服务器配置揭秘
服务器·人工智能·深度学习
LilySesy2 小时前
【业务案例】F.13——SAP系统标准的清帐程序有BUG?
运维·bug·sap·abap·esb·internet服务
张某人想退休2 小时前
自动化实现的思路变化
运维·自动化