Linux —— 文件操作

news2024/11/26 12:19:58

目录

1.内核提供的文件系统调用

1.1open和close

1.2write和read

2.文件描述

2.1文件描述符

 2.2文件描述符分配规则

3.重定向

3.1最“挫”的重定向

3.2使用系统调用

3.3重定向原理

3.4让我们的"shell"支持重定向操作

4.一切皆文件

1.内核提供的文件系统调用

1.1open和close

通过[man]指令浏览其描述,这里截取片段。 

第一个open是文件存在的情况下打开文件,第一个参数为文件名,若不指定文件路径,则默认为父进程的工作路径。第二个参数为标记位,int类型的每个比特位的0 和 1代表了不同的标记。Linux提供了多种标记。

第二个open是文件不存在的情况下打开文件,第三个参数为文件创建的初始权限。

close即关闭文件。

 下面给出代码实例以供参考:

umask(0);       //将umask置0
int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);     //相当于C语言的fopen的"w",只写、自动创建、自动覆盖
//文件的权限受umask的影响
close(fd);

下面列举一些常用的标记:

  • O_WRONLY        ->只写
  • O_CREAT           ->创建
  • O_TRUNC          ->覆盖
  • O_RDONLY        ->只读
  • O_APPEND        ->追加

需要多个标记组合在一起时,使用 '|'(按位或运算符) 连接即可。

1.2write和read

write是一个系统调用,其声明为([man]指令查看):

需要注意的是,write()是从文件的起始位置开始写的,如果在open()中没有O_TRUNC或者O_APPEND标记,那么为将文件以前的内容的一部分覆盖掉。也就是说,要想像C语言一样自动清空文件的内的数据必须加上O_TRUNC标记 。

下面给出write的实际使用案例以供参考:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
    umask(0);       //将umask置0
    int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);     //相当于C语言的fopen的"w",只写、自动创建、自动覆盖
    if(fd < 0) return 1;
        
    char* msg = "hello Linux\n";
    write(fd,msg,strlen(msg));      //像fd描述的文件写入msg指向的字符串
    close(fd);
    return 0;
}

编译运行,查看生成的"log.txt"文件:

read也是一个系统调用,其声明为([man]指令查看):

这里以上面write生成的文件给出read的实际使用案例以供参考:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
    int fd = open("log.txt",O_RDONLY);

    char buffer[64];
    read(fd,buffer,sizeof(buffer)-1);       //留一个位置补'\0'
    buffer[strlen(buffer)]=0;       //文件的字符串不以'\0'结尾
    printf("%s",buffer);

    close(fd);
    return 0;
}

2.文件描述

2.1文件描述符

进程可以打开多个文件,所以操作系统会有大量的文件。操作系统为了管理被打开的文件,使用了"先描述,再组织"的方法将被打开的文件描述为一个struct file的内核结构体,其包含了文件的大部分属性,多个内核结构体之前以特定的数据结构组织起来。

这些struct file结构体并不是文件描述符,而是操作系统为了管理被打开的文件而创建的。事实上,文件操作研究的是进程和被打开文件的关系,也就是说文件是被进程打开的。那么进程和struct file结构体中间还有一层结构体,名为struct files_struct(文件描述符表),在task_struct中有一个struct files_struct* files指针指向文件描述符表。文件描述表有一个专门用来存储struct file结构体的地址的指针数组(struct file* fd_array[]),这个数组的下标即为文件描述符

在操作系统启动时,会默认生成打开三个文件,即:

  • stdin    ->标准输入
  • stdout    ->标准输出
  • strerr    ->标准输入

这三个文件的struct file结构体的地址依次按顺序存储在struct file* fd_array数组对应的下标0、1、2位置。所以我们用户的进程第一次创建的文件的描述符为3。

下面给出进程与文件的假象模型图:

 2.2文件描述符分配规则

 在struct file* fd_array[]数组中,按下标从小到大的顺序,寻找最小、且没有被占用下标作为文件描述符。

下面给出一段代码供加深理解:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
    close(0);
    close(2);       //关闭掉标准输入和标准错误文件,即清空数组的占用
    int fd = open("log.txt",O_RDONLY);
    printf("%d\n",fd);
    close(fd);
    return 0;
}

3.重定向

3.1最“挫”的重定向

观察下面这段代码以及现象:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
    close(1);       //将fd=1的数组位置清空
    int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);     //文件打开时就占用fd=1的位置
    printf("hello world\n");
    fflush(stdout);
    close(fd);
    return 0;
}

以上是最原始的重定向操作。其原因在于:printf()函数是默认向fd=1对应的文件输出的,但是进行close(1)操作后,log.txt文件的struct file结构体地址就占用了fd=1的位置,所以printf就向log.txt文件输出了。 

3.2使用系统调用

dup类接口是系统提供给我们的接口,其中dup2最为常用。我们通过[man]指令查询:

下面给出实际使用案例以供参考:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
    int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    dup2(fd,1);     //重定向
    printf("hello world");
    fflush(stdout);
    close(fd);
    return 0;
}

3.3重定向原理

上层看到的文件描述符是不会变的,例如printf规定了向标准输出输出,即向fd=1对应的文件输出,那么printf找的是文件描述符而不是对应的文件,所以fd的内容无论怎么变,上层找的还是fd。

子进程重定向不会影响父进程,因为进程之间相互独立。即子在进程在创建出来的时候,就拷贝了一份文件描述符表。但是文件不属于进程,是不会拷贝的。

3.4让我们的"shell"支持重定向操作

在上次模拟实现命令行解释器的基础上,再进行升级,以支持重定向操作。

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

#include <sys/wait.h>
#include <assert.h>

#define NUM 1024
#define NON_REDIR 0     //无重定向
#define INPUT_REDIR 1       //输入重定向 '<'
#define OUT_REDIR 2        //输出重定向 '>'
#define APPEND_REDIR 3      //追加重定向 '>>'



char command[NUM];      //c99数组
char* myargv[64];       //存储指令参数
char* file_name=NULL;
int redir_type=0;

void command_check(char* command)
{
    assert(command);

    //首、尾指针
    char* begin = command;
    char* end = command+strlen(command);
    
    while(begin < end)
    {
        if(*begin == '>')
        {
            *begin=0;
            begin++;
            if(*begin == '>')
            {
                redir_type=APPEND_REDIR;
                begin++;
            }
            else redir_type=OUT_REDIR;
            while(*begin == ' ') begin++;
            file_name=begin;
        }
        else if(*begin == '<')
        {
            *begin=0;       //置0
            redir_type=INPUT_REDIR;
            begin++;
            while(*begin == ' ') begin++;
            file_name = begin;
        }
        else ++begin;
    }

}
int main()
{
    while(1)
    {

        redir_type=NON_REDIR;
        file_name=NULL;

        char buffer[1024]={0};
        getcwd(buffer,sizeof(buffer)-1);        //获取shell的工作路径
        buffer[strlen(buffer)]=0;
        printf("[用户名@主机名 %s]",buffer);
        fflush(stdout);     //刷新缓冲区

        char* s = fgets(command,sizeof(command),stdin);     //输入指令
        command[strlen(command)-1]=0;       //清除 \n 
        

        command_check(command);     //检查指令是否有重定向操作
        myargv[0] = strtok(command," ");
        int i = 1;
        while(myargv[i++] = strtok(NULL," "));      //切割空格

        if(myargv[0] != NULL &&  strcmp(myargv[0],"cd") == 0)
        {
           if(myargv[1] != NULL) chdir(myargv[1]);      //cd命令移动shell的工作路径
           continue;
        }

        pid_t id = fork();
        if(id == 0)
        {

            switch(redir_type)      //使用switch语句
            {
                case NON_REDIR:     //不作处理
                    break;
                case INPUT_REDIR:       //输入重定向
                    {
                        int fd = open(file_name,O_RDONLY);
                        if(fd < 0)
                        {
                            perror("open:");
                            exit(1);
                        }
                        dup2(fd,0);
                    }
                    break;

                case OUT_REDIR:
                case APPEND_REDIR:
                    {
                        umask(0);
                       int flag = O_WRONLY | O_CREAT;
                       if(redir_type == APPEND_REDIR) flag |= O_APPEND;
                       else flag |= O_TRUNC;
                       int fd = open(file_name,flag,0666);
                       if(fd < 0)
                       {
                           perror("open:");
                           exit(1);
                       }
                       dup2(fd,1);
                    }
                    break;
                default:
                    break;
            }
           execvp(myargv[0],myargv);        //进程替换
           exit(1);
        }
        waitpid(id,NULL,0);
    }
    return 0;
}

文件描述符表是属于进程的并且是一个内核数据结构,进程替换是不会影响它的,进程替换只是替换数据段和代码段,是不影响内核数据结构的。

对于文件的关闭,在进程退出时自动关闭。其本质还是在于struct file里面的计数器,这个计数器记录有多少个进程正在引用这个struct file,当进程退出时,计数器就会减1,当计数器为0才文件才销毁。

4.一切皆文件

不止是磁盘上的可执行文件被打开才是文件,硬件也是文件。键盘、鼠标、显示器、内存、硬盘等等都是文件。

操作系统能够使用驱动来管理硬件,那么在驱动上,就一定有硬件与操作系统的IO交互方法。那么在内核中,都有唯一的一份struct file内核结构体对应硬件,以便操作系统管理。所以在Linux的视角来看,一切皆文件。

那么在上层想要与硬件互动时,也是通过struct file结构体实现的,其原因在于此结构体有硬件与操作系统IO交互方法的函数指针,通过这些函数指针去调用不同的交互方式。

发现了吗?即使磁盘上的各种可执行文件或者是硬件非常杂乱,但是在操作系统下总是能有序的抽象化成一个struct file结构体来进行管理。也就是说,我们通过统一的struct file结构体(其中描述了文件的共有属性)来操作不同的文件。用官方的话说(参考Linux内核设计与实现原理):我们可以直接使用open()、read()和write()这样的系统调用而无需考虑具体文件系统和实际物理介质。这样的行为就构成了内核的子系统——虚拟文件系统(VFS)。

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

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

相关文章

什么是杜邦分析?杜邦分析法的公式及示例

什么是杜邦分析? 杜邦分析也称为杜邦恒等式、杜邦方程、杜邦框架、杜邦模型或杜邦方法&#xff0c;是一个多步骤的财务方程式&#xff0c;可以深入了解企业的基本绩效。杜邦模型对影响公司股本回报率 (ROE) 的关键指标进行了全面分析。杜邦分析的另一个术语是杜邦模型。这些名…

做app的测试,你大概率会用到这个命令,尤其是做monkey测试

1.普通命令 1.1 devices命令 语法格式 &#xff1a;adb devices [-l] # 作用 &#xff1a;返回已连接设备的信息 # 示例 &#xff1a;adb devices : 返回设备的信息adb devices -l : 返回设备的详细信息1.2 help命令 语法格式 &#xff1a;adb --help # 作用 &#xff1…

人工智能:人工神经网络的应用场景

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉&#xff1a; 数据库领域优质创作者&#x1f3c6;&#x…

android接入微信API相关细节

细节1 想要接入微信&#xff0c;如接入分享微信功能、跳转小程序功能等&#xff1b;首先需要到微信开放平台申请AppId&#xff0c;如何申请在开放平台上的流程很清楚&#xff0c;就不赘述了 但有个细节就是应用包名签名&#xff0c;这个应用包名签名记得是以app有正式签名文件…

WebDAV之葫芦儿·派盘+思源笔记

思源笔记 支持webdav方式连接葫芦儿派盘。 想要一款支持全平台,支持Markdown语法,还支持大纲、块级双向链接、全文搜索、标签分类、数学公式、思维导图 / 流程图、代码片段、跨平台同步等全功能的笔记APP吗?推荐您使用思源笔记与葫芦儿派盘结合方案。 思源笔记是一款本地…

位 运 算

位运算符 在处理整形数值时&#xff0c;可以直接对组成整形数值的各个位进行操作。这意味着可以使用屏蔽技术获得整数中的各个位&#xff08;&#xff1f;&#xff1f;&#xff09; &(与)、|(或)、^(异或)、~(非/取反) “>>” 和 “<<” 运算符将二进制位进行…

06 Job/CronJob: 为什么不直接用Pod来处理业务?

文章目录1. 前言2. 为什么不直接使用pod?2.1 面向对象的设计思想3. 为什么要有Job/CronJob3.1 离线业务的种类4. 如何使用 YAML 描述 临时任务 Job4.1 Job 的 YAML“文件头”4.2 使用kubectl create 生成模板文件4.3 Job 的 YAML body 部分“spec ”4.4 如何在Kubernetes 里操…

【C++ STL容器】:vector存放数据

前言 时不可以苟遇&#xff0c;道不可以虚行。 STL 中最常用的容器为&#xff1a;vector&#xff0c;暂且把它理解为我们之前学过的数组Array。 一、创建一个vector容器&#xff08;数组&#xff09; 添加头文件&#xff1a;#include <vector> vector<int> v;二、…

数商云渠道商协同系统对机械企业的应用价值体现

当前&#xff0c;国内机械市场环境较复杂&#xff0c;竞争日趋激烈&#xff0c;使用单一营销渠道模式已不能适应多变的环境&#xff0c;而代理商群体作为机械行业主流营销渠道&#xff0c;也在“价格战”环境下生存空间被进一步挤压&#xff0c;因此&#xff0c;如何采用领先的…

小啊呜产品读书笔记001:《邱岳的产品手记-13》第24讲 产品案例分析:PathSource的混乱与直观 25讲 产品世界的暗黑模式:操纵的诱惑

小啊呜产品读书笔记001&#xff1a;《邱岳的产品手记-13》第24讲 产品案例分析&#xff1a;PathSource的混乱与直观 & 第25讲 产品世界的暗黑模式&#xff1a;操纵的诱惑一、今日阅读计划二、泛读&知识摘录1、第24讲 产品案例分析&#xff1a;PathSource的混乱与直观2、…

进程与信号(一)

目录 一、前言 二、What Is a Process 三、Process Structure 1、The Process Table 2、Viewing Processes 3、System Processes 4、Process Scheduling 一、前言 进程和信号是 Linux 操作环境的基本组成部分。它们控制 Linux 和所有其他类 unix 计算机系统执行的几乎所…

全新版互联网大厂面试题,分类65份PDF,累计2000页

全新版互联网大厂面试题题库非常全面 包括 Java 集合、JVM、多线程、并发编程、设计模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、Python、HTML、CSS、Vue、React、JavaS…

github-将本地代码上传到github上

1. 步骤&#xff1a; 准备&#xff1a;因为本地和远程是通过ssh加密的&#xff0c;所以需要生成秘钥和私钥 ssh-keygen -t rsa -C "youremailexample.com" "" 双括号里的是 关联 github的个人邮箱 cmd 里 输入 上述指令&#xff0c;然后 按三次 ent…

MySQL 进阶篇1.0

01-课程介绍 02-存储引擎-MySQL体系结构 03存储引擎-简介 查询建表语句 --默认存储引擎:InnoDBshow create table account; 查询当前数据库支持的存储引擎show engines; 04存储引擎-InnoDB介绍 开关为"ON": 表示每个innodb引擎的表都有一个idb表共享文件 …

2012-04 《信息资源管理 02378》真卷解析,逐题解析+背诵技巧

本系列博客合计 21 篇&#xff0c;每篇都将解析一张《信息资源管理》真卷&#xff0c;并附带答案解析与背诵技巧。 全国 2012 年 4 月自学考试信息资源管理试题&#xff08;02378&#xff09; 单选题 1、作为现代社会的支柱产业&#xff0c;信息产业的主体有&#xff1a;信息…

【雕爷学编程】Arduino动手做(109)---3路电压转换模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

MySQL 数据库 group by 语句怎么优化?

一、一个简单使用示例 我这里创建一张订单表 CREATE TABLE order_info ( id int NOT NULL AUTO_INCREMENT COMMENT 主键, order_no int NOT NULL COMMENT 订单号, goods_id int NOT NULL DEFAULT 0 COMMENT 商品id, goods_name varchar(50) NOT NULL COMMENT 商品名称, …

Kamiya丨Kamiya艾美捷狗CRP ELISA说明书

Kamiya艾美捷狗CRP ELISA预期用途&#xff1a; 狗CRP ELISA是一种高灵敏度的双位点酶联免疫分析&#xff08;ELISA&#xff09;&#xff0c;用于定量测定狗生物样品中的C反应蛋白&#xff08;CRP&#xff09;。仅供研究使用。 引言 急性期蛋白质是血浆蛋白质&#xff0c;其在…

开发者实践|如何实现云开发场景联动(内附结构图和教学视频)

一千个住户有一千种生活习惯&#xff0c;智能家居如何才能根据用户个性化的需求&#xff0c;实现真正的“智能”&#xff1f;这就需要家居产品之间智能排列&#xff0c;组合成多样化的场景联动模式。 下面我们就来说说如何通过Tuya OpenAPI来实现云开发场景联动&#xff0c;满…

使用 qrcode 生成二维码

qrcode 1 安装2 引入3 使用3.1 方法1 &#xff1a;QRCode.toCanvas()3.2 方法2 &#xff1a;QRCode.toDataURL()4 完整示例qrcode 是一个用于生成二维码的 JavaScript 库。主要是通过获取 DOM 的标签,再通过 HTML5 Canvas 绘制而成 1 安装 npm install --save qrcode2 引入 …