Linux(六)基础I/O

news2024/11/15 5:00:32

引言

      C语言进阶 文件管理

        上一篇文章详细回顾了C语言方面关于文件操作的一些库函数,比如输入输出重定向fscanf、fprintf,对于文件内容以字符形式读取的fgetc、fputc,对于文件内容以字符串形式读取的fgets、fputs,对于二进制文件的数据读取fread、fwrite,前几个函数说的都是关于文件顺序读写的一些操作函数,还有操作于文件指针层面的随机读写:ftell()返回文件指针相对于起始位置的偏移量、frewind()将文件指针置于起始位置、fseek()将文件指针相对于(SEEK_SET | SEEK_CUR | SEEK_END)三种位置offset个偏移量的搬运,以及对于文件权限的了解 w r a w+ r+ a+ wb rb ab wb+ rb+ ab+ 有了上一篇文章关于文件操作库函数的学习,这篇博文引入对于系统调用接口的相关学习。

我们都知道库函数就是相对于系统调用接口进行的进一步封装,而系统调用接口是操作系统为了向上层操作提供的一些用于访问内核指定功能的接口。

正文

目录

一、系统调用接口介绍

1、open(char* pathname, int flag, mode_t mode)

第二个参数:

第三个参数:修改文件权限

文件掩码:

 2、ssize_t write(int fd,char* buf, size_t len)

3、ssize_t read(int fd,char* buf, size_t len)

 4、off_t  lseek(int fd, off_t offset, int whence)

5、close(int fd);

 二、缓冲区的进一步认知

 三、文件描述符

 四、重定向

1、重定向:将原来要写入A的内容不在写入A,而是写入B中

2、重定向的本质

3、加入dup改进minishell 实现可重定向功能


一、系统调用接口介绍

1、open(char* pathname, int flag, mode_t mode)

 它具有俩个函数形式?难道是重载?邦邦就是一拳,这不是c++!

俩种函数都对应这其对应的环境下使用,只有一个open函数,只是第三个参数在有些情况需要写出。

返回值:打开成功返回文件的描述符,失败返回-1

第一个参数就是文件路径或者可以在当前代码文件所在文件内自动索引的文件名。

第二个参数:

        三者必选其一:(O_RDONLY  |  O_WRONLY  |  O_RDWR)

                                        只读(r)        只写(w)    可读可写

        可以或上一些的功能:(O_CREAT  |  O_TRUNC  |  O_APPEND)

                           文件不存在时创建文件       截断文件       尾部追加写入

使用这些宏之前的联合使用,可以表示一些特定功能:

比如 w+  :可读可写、文件不存在则创建、打开文件会截断之前文件

                   那么flag参数就可表示为(O_RDWR | O_CTEATE | O_TAUNC)

    open("1.txt", O_RDWR | O_CREAT | O_TRUNC);
    w+ 格式打开一个文件

比如a+  : 可读可写、文件不存在则创建、尾部追加写入

    open("1.txt", O_RDWR | O_CREAT | O_APPEND);  
    a+ 格式打开一个文件    

第三个参数:修改文件权限

        第三个参数mode只有在当创建文件的时候才需要给出(三位八进制数字,前面加个0)

 看上图,发现刚才打开的文件权限中有s这种未知权限,为了处理我们自己创建出来的文件权限问题,就需要给出第三个参数了。

open("1.txt", O_RDWR | O_CREAT | O_APPEND, 0664);
给出664文件权限  110110100 rw-rw-r-- 
rwx分别代表可读可写可执行

文件掩码:

当涉及文件权限那肯定不能不提系统的默认创建文件掩码

 所有的文件创建出来的权限 = 用户给定的文件权限 & (~umask)

 比如:想要创建一个文件权限为777的文件

open("1.txt", O_RDWR | O_CREAT | O_APPEND, 0777);

                                        111 111 111     777

                                     & 111 111 101    775(002取反)

                                     = 111 111 101  = 775

而得到的结果确实一个 775权限文件  111 111 101 这就是文件默认掩码的原因

 使用umask接口更改文件掩码  umask(0);将当前文件创建文件掩码设置为0,这样用户创建的文件权限就是实际创建的文件权限了。

#include<stdio.h>
#include<fcntl.h>  // open接口的头文件
#include<sys/stat.h> // umask接口头文件

int main()
{
    umask(0);// 将当前文件默认掩码改为0
    open("1.txt", O_RDWR | O_CREAT | O_APPEND, 0777);
    return 0;
}

 2、ssize_t write(int fd,char* buf, size_t len)

        fd 之前打开文件返回的操作句柄——文件描述符

        buf 待写入文件的数据的存储位置

        len 要写入的数据长度(以字节为单位)

        成功返回实际写入的数据长度,失败返回-1;

3、ssize_t read(int fd,char* buf, size_t len)

        fd 打开文件返回的操作句柄——文件描述符

        buf 从文件中读取出来数据的存放位置

        len 要读取的数据长度

        成功返回实际读取的数据长度,0表示读取到了文件末尾; 读取失败返回-1;

 4、off_t  lseek(int fd, off_t offset, int whence)

        fd:文件描述符

        offset:偏移量

        whence:偏移起始位置  SEEK_SET、SEEK_CUR、SEEK_END

        返回值:当前跳转后,读写位置相对于文件起始位置的偏移量(接口的一种另类用法,跳转到末尾,通过返回值确定文件大小)

5、close(int fd);

完成一段简单的程序,将一段数据写入文件,然后将文件指针移动到起始位置之后,在讲文件中的内容读入到一个新的数组中去

#include<stdio.h>
#include<unistd.h> // write、read、close、lseek头文件
#include<fcntl.h> // open头文件
#include<string.h>
#include<sys/stat.h> // umask头文件

int main()
{
      umask(0);//设置为0,那么当前文件下创建文件权限掩码即为0,即用户所给应用权限即为实际权限
      int fd = open("1.txt",O_RDWR | O_CREAT | O_APPEND, 0777);
      if(fd < 0){
          perror("open error");
          close(fd);
          return -1;
      }
      //将数据data写入文件中
      char* data = "流浪地球2上映了,我还没有看!!!\n";
      ssize_t ret = write(fd, data, strlen(data));
      if(ret < 0){
          perror("write error");
          close(fd);
          return -1;
      }
      
      //将文件中的数据读取到buf数组中
      //可是这时的文件指针已经移动到末尾位置了,所以需要将文件指针移动到起始位置
      lseek(fd, 0, SEEK_SET);
      char buf[1024]={0};
      ret = read(fd, buf, 1023);
      if(ret < 0){
        perror("read error");
        close(fd);
        return -1;
      }
      printf("%s",buf);
      close(fd);
}

 二、缓冲区的进一步认知

之前一直说库函数就是对系统调用接口的进一步封装,现在对于文件操作的系统调用接口我也学会了,上一篇博文的对于库函数的描述我也看的差不多了,现在就来对比一下这二者之间的关系把

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

int main()
{
    printf("first");
    fwrite("second.", 1, 7, stdout);
    fprintf(stdout, "third.");
    write(1, "write", 5);
    return 0;
}

上面几处打印全部都是为了给标准输出(也就是显示屏终端)上进行打印的代码,前三个都是库函数,而最后一个write是系统调用接口,然而打印结果为:

发现最后一个执行的write却第一个进行打印

 改动一下代码,给每一次打印后面加入一个\n来刷新缓冲区,

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

int main()
{
    printf("first\n");
    fwrite("second.\n", 1, 8, stdout);
    fprintf(stdout, "third.\n");
    write(1, "write\n", 6);
    return 0;
}

结果发现就是按照次序依次进行打印!!!

 原因就是系统调用接口没有缓冲区,当系统调用接口获取到一段数据时,他会不假思索的直接将数据从内核发送到磁盘上,然而资源的处理速度从内核到磁盘一路都是呈现递减状态,就好比内核那边已经开始造火箭了,而磁盘这边还在开着学步车走路,俩者之间的数据传输效率根本没法比,那我为了你这一小块数据我就大费周折的把数据从内核开始运输,这显然降低了效率。

那么库函数在对系统调用接口进行封装的时候就考虑到这一点,于是就引入了缓冲区这个概念,它的作用就是在内存往磁盘传输数据的时候,不会为了你一点点数据来了我就开始传输,而是在内存上开辟一块属于该文件的内存缓冲区,把内存向磁盘方向传输的数据统一先保存到这块缓冲区中,等这块缓冲区满了、或者文件关闭了不在写入数据就把这块缓冲区的数据送往硬盘,这样做就直接减少了内存与磁盘之间的数据传输次数,也就明显的提高了操作系统处理效率。

什么时候会清空缓冲区?

        ① 缓冲区满了

        ② 刷新缓冲区(\n、fflush)

        ③ 文件关闭 或者 程序退出

                                (三种退出方式main中return、exit、_exit(_exit不执行刷新缓冲区操作))

 三、文件描述符

对于这么文件管理的这么多系统调用接口他们之前上下串通的一个重要的线就是这个文件描述符,这个操作句柄,这个int类型的fd到底是个啥呢?

 当操作系统打开一个文件的时候,就会把这个文件的相关信息存放到一个struct file结构体中,每次打开一个文件都会创建其对应的struct file结构体,那么就需要对这么些个结构体进行一个规划整合,那么就用一个数组来保存这些文件描述结构体。

文件描述符就是用来记录存放的数组位置的信息,所以说这个小整数也就是文件描述信息存放在数组中的下标。

操作系统通过这个下标来访问文件描述信息存储的位置,通过访问这个文件描述信息从而掌握文件。

打印一下这个fd:

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

int main()
{
    int fd = open("1.txt",O_RDWR | O_CREAT | O_TRUNC, 0664);
    if(fd < 0){
      perror("open error");
      return -1;
    }
    printf("fd = %d\n",fd);
    close(fd);
    return 0;
}

 结果为一个3,之前说过,当Linux打开一个文件时,会默认打开三个文件分别是

        0--标准输入    1--标准输出   2--标准错误

那么如果在打开一个文件,那么它所对应的文件描述符(数组下标)也就是下一个3了。

并且Linux满足最小下标分配原则,新打开的文件就会在当前file_array中找到一个最小的合适的下标。

int main()
{
    int fd1 = open("1.txt",O_RDWR | O_CREAT | O_TRUNC, 0664);
    int fd2 = open("2.txt",O_RDWR | O_CREAT | O_TRUNC, 0664);
    int fd3 = open("3.txt",O_RDWR | O_CREAT | O_TRUNC, 0664);
    
    printf("fd1 = %d\n",fd1);   fd1 = 3
    printf("fd2 = %d\n",fd2);   fd2 = 4
    printf("fd3 = %d\n",fd3);   fd3 = 5
    
    close(fd1);
    close(fd2);
    close(fd3);
    return 0;
}

 改变一下文件1的关闭时间:

int main()
{
    int fd1 = open("1.txt",O_RDWR | O_CREAT | O_TRUNC, 0664);
    int fd2 = open("2.txt",O_RDWR | O_CREAT | O_TRUNC, 0664);
    printf("fd1 = %d\n",fd1);    fd1 = 3
    close(fd1);                  此时关闭文件1
   
    int fd3 = open("3.txt",O_RDWR | O_CREAT | O_TRUNC, 0664);
    
    printf("fd2 = %d\n",fd2);    fd2 = 4
    printf("fd3 = %d\n",fd3);    fd3 = 3
    
    close(fd2);
    close(fd3);
    return 0;
}

关闭标准输入文件

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

int main()
{
    close(0);
    int fd = open("1.txt",O_RDWR);
    printf("fd = %d\n",fd);    fd = 0
    close(fd);
    return 0;
}

关闭标准输出文件

int main()
{
    close(1); //关闭标准输出文件
    int fd = open("1.txt",O_RDWR);
    printf("fd = %d\n",fd);
    close(fd);
    return 0;
}

 发现程序运行没有结果!!!

想一想终端上的运行结果是怎么来的?当然是靠我们的stdout标准输出来打印到终端显示上的,现在把终端显示文件关闭了,肯定就没有数据了,那么数据去哪儿了呢?

写到了1.txt中去了嘛?查看一手

 仍然没有,哪这个printf的东西打印到哪里了呢?

数据写入磁盘前都是先放入缓冲区中,然后printf的下一步就是关闭文件,那么1.txt文件被关闭了(由于close是系统调用接口,没有缓冲区这个概念,所以它不进行刷新操作),也就写不进去了,最后return退出的时候刷新缓冲区,但是这时候刷新了也没用了,因为printf只与stdout对接,stdout第一步就被关闭了。

所以可以在文件关闭前进行一次刷新即可将缓冲区内容写入到1.txt文件中了。

int main()
{
    close(1); //关闭标准输出文件
    int fd = open("1.txt",O_RDWR);
    printf("fd = %d\n",fd);
    fflush(stdout);
    close(fd);
    return 0;
}


四、重定向

1、重定向:将原来要写入A的内容不在写入A,而是写入B中

        ls > a.txt  标准输出重定向,将原来打印在终端的内容不再打印,而是写入a.txt中

        >  清空重定向,清空原有内容,写入新的内容

        >>  追加重定向   新内容追加到文本原有内容末尾

 &1 就代表着当前标准输出方向

2、重定向的本质

文件描述符本质就是一个数组下标,通过这个下标访问到文件的描述信息存储,从而通过这个文件描述信息来操作文件。

而重定向的本质就是将一个文件描述符对应信息地址,替换为另一个文件的描述

就比如上面的代码,把1号标准输出文件关闭掉,然后重新打开一个新的文件,就将本来要执行在终端输出窗口的操作执行在了新的文件中,这就是重定向。

为了更方便的控制这些文件的处理,引入了更加丝滑的接口

int  dup2 (int oldfd,int newfd);(把new重定向到old)newfd > oldfd

让newfd的位置,保存oldfd对应的位置信息,把本来要进行在newfd上的操作,替换为old文件

具体步骤就是对 在newfd文件内部想要引入oldfd文件,则关闭newfd接着打开oldfd,dup2就是对这一操作的封装实现,它会妥善处理好文件重定向时,文件替换的安全关闭问题。

3、加入dup改进minishell 实现可重定向功能

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

int main()
{
    while(1)
    {
        printf("【minisheill】$ ");
        fflush(stdout);
        char cmd[1024] = {0};
        gets(cmd);
        int argc = 0;
        char *argv[1024] = {0};
        argv[argc++] = strtok(cmd, " ");
        while((argv[argc] = strtok(NULL, " ")) != NULL)
        {
            argc++;
        }
        
        // 进行判断是否命令中存在重定向命令
        int flag_redirect = 0;    
        char *file_redirect = NULL;
        for(int i = 0; i < argc; i++)
        {
          if(strcmp(argv[i], ">")==0)
          {
              flag_redirect = 1;    //如果标志位为1  则为清空重定向
                                  // 因为如果含有重定向命令,则即为这样的结构 
                                            : ls -l -a > text.txt
              argv[i] = NULL;     // 这里置空一是为了保证argv中只包含命令
                                     置空之后后续的重定向符号就没了,文件名也没了
                              // 二是为了后续进行替换的时候参数最后一个必须满足NULL的要求.
              file_redirect = argv[i+1];    // argv[i+1] 为文件名
              break;
          }
          else if(strcmp(argv[i], ">>")==0) // 标志位为2 则为追加重定向
          {
              flag_redirect = 2;
              argv[i] = NULL;
              file_redirect = argv[i+1];
              break;
          }
        }
        pid_t child_pid = fork();
        if(child_pid < 0){
          perror("fork error");
          return -1;
        }else if(child_pid == 0)
        {
          if(flag_redirect == 1)
          {
              int fd = open(file_redirect, O_RDWR | O_TRUNC | O_CREAT, 0664);
              dup2(fd, 1); // 将终端显示的标准输出重定向到fd文件
          }else if(flag_redirect == 2)
          {
              int fd = open(file_redirect, O_RDWR | O_CREAT | O_APPEND, 0664);
              dup2(fd, 1);
          }
          execvp(argv[0], argv);
          perror("execvp error");
          exit(0);
        }
        wait(NULL);
    }
    return 0;
}

 

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

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

相关文章

ieee会议论文从手稿到发表

0. 前言 在创新点得到认可之后就可以准备发论文了&#xff0c;这个一定要早点&#xff0c;可以给自己设置一个明确的deadline&#xff0c;毕竟ddl是第一生产力。 1. 确定发什么期刊、会议 一定要符合学校的毕业要求&#xff0c;有一些水的学校并不认。时间看能不能赶上学校毕…

物联网智慧消防对比传统消防具有哪些优势?

随着科技的进步和城市化进程的加快&#xff0c;传统消防已经满足不了社会发展的需求&#xff0c;智慧消防应运而生&#xff0c;目前智慧消防已经成为消防安全管理的核心&#xff0c;物联网时代的到来&#xff0c;让智慧消防迎来了更大的发展机遇&#xff0c;变得更加智慧化、系…

手机网站建设怎么做?【手机网站制作】

对于很多公司企业来说&#xff0c;做网站建设都是优先考虑PC端的网站建设&#xff0c;但是某些公司企业可能对于PC端网站的需求不高&#xff0c;倒是更有需要做移动端网站&#xff0c;也就是我们常说的手机网站。那么关于手机网站建设又是怎么做的呢&#xff1f;本文给大家做一…

软件测试员年底总结怎么写?所有问题都帮你梳理好了!

临近年底&#xff0c;很多公司都有年终总结环节&#xff0c;核心目的发现今年的不足&#xff0c;进而总结经验&#xff0c;更好地用以指导明年的工作。当然&#xff0c;即使公司没有要求&#xff0c;对于测试岗位来说&#xff0c;一年一度的总结不可或缺。假如你是测试负责人&a…

<使用Python自定义生成简易二维码>——《Python项目实战》

目录 1.问题导引 2.实现步骤 &#xff08;1&#xff09;查找并安装第三方库qrcode &#xff08;2&#xff09;编写代码并嵌入内置信息 &#xff08;3&#xff09;使用扫码工具读取信息 后记&#xff1a;●由于作者水平有限&#xff0c;文章难免存在谬误之处&#xff0c;敬…

【Tkinter】终于把StringVar讲明白了

文章目录简介Label使用StringVarEntry输入简介 初学者在使用tkinter时常犯的一个错误就是 def changeText(evt):evt.text "new Text"毕竟在创建控件时&#xff0c;text是出现频率很高的参数&#xff0c;换言之&#xff0c;我们会默认控件中有text这个属性&#xf…

CDGA/CDGP数据治理认证班将于2/4正式开课,报名从速!

新的一年&#xff0c;从考证开始&#xff0c;为职场竞争增添更多优势&#xff01; 做数据行业的话&#xff0c;当然推荐考个DAMA-CDGA/CDGP数据管理证书啦&#xff01; DAMA是全球唯一数据管理方面权威性认证&#xff0c;帮助数据从业者提升数据管理能力。 DAMA认证为数据管理专…

【c语言进阶】文件操作(下)

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a; c语言学习 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我…

[NOIP2008 提高组] 笨小猴

题目描述 笨小猴的词汇量很小&#xff0c;所以每次做英语选择题的时候都很头疼。但是他找到了一种方法&#xff0c;经试验证明&#xff0c;用这种方法去选择选项的时候选对的几率非常大&#xff01; 这种方法的具体描述如下&#xff1a;假设 maxn 是单词中出现次数最多的字母的…

Python 中当前时间表示方法详解

在 Python 中获取当前时间是许多与时间有关的操作的一个很好的起点。一个非常重要的用例是创建时间戳。在本教程中&#xff0c;你将学习如何用 datetime 模块获取、显示和格式化当前时间。我们将学习如何读取当前时间的属性&#xff0c;如年份、分钟或秒。为了使时间更容易阅读…

函数的求导法则——“高等数学”

今天&#xff0c;小雅兰的内容是函数的求导法则&#xff0c;上篇博客我们知道了导数的定义、导数的几何意义及可导与连续关系&#xff0c;这篇博客我们来仔细学习一下求导法则&#xff0c;下面&#xff0c;就让我们进入导数的世界吧 一、函数的和、差、积、商的求导法则 二、反…

Beryl Li 代表 YGG 出席 2023 年世界经济论坛会议

Yield Guild Games&#xff08;YGG&#xff09;联合创始人 Beryl Li 代表 YGG 参加了 2023 年 1 月 16 日至 20 日在瑞士达沃斯举行的 2023 年世界经济论坛年会 &#xff08;WEF23&#xff09;&#xff0c;在全球舞台上分享区块链、通证化、数字资产监管、治理和价值创造的潜力…

C++语法复习笔记-4. C++基本容器

文章目录1.数组声明与定义数组的开闭区间差一错误左闭右开非对称区间原则数组的增删改查一维数组二维数组面向对象的动态数组-vector自动扩容增删改查2. 字符串字符串变量与常量unicode编码字符串指针表示方法指针的表示方法char[]和char* 的区别数组每个值可改指针指向的字符串…

Yann LeCun 新作!大幅超越 MAE,图像语义表示卷出新高度

文&#xff5c;CV酱计算机视觉中&#xff0c;有两种常见的从图像中进行自我监督学习的方法&#xff1a;基于不变性的方法和生成方法。基于不变性的预训练方法优化编码器&#xff0c;使其产生相似的嵌入&#xff0c;用于同一图像的两个或多个视图&#xff0c;其中图像视图通常使…

基于Java+Spring的图书管理系统详细设计和实现

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

【项目精选】基于Web的机票预订系统

文章目录 1 摘 要2 系统相关技术概述2.1 Java web2.2 三大框架SSM2.3 前端框架AngularJS2.4 数据库MySQL2.5 数据库Redis2.6 开发工具Eclipse 3 需求分析3.1 系统实现目标3.2 系统功能分析3.3 系统用列图 4 系统总体设计4.1 软件架构设计4.2 总体功能模块设计4.3 数据库设计4…

python小游戏——怀念经典坦克大战代码

♥️作者&#xff1a;小刘在这里 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽放&#xff0c;愿所有的美好&#…

14.集合、泛型、List系列

目录 一.集合类的体系结构 1.1 Collection体系 1.2 Map集合体系 1.3 集合的注意事项 二.泛型 2.1 什么是泛型 2.2 泛型的优点 2.3 泛型可以在哪些地方上定义 2.4 泛型类 2.5 泛型方法 2.6 泛型接口 2.7 泛型通配符 三.Collection集合 3.1 什么是Collection集合 3…

CentOS7没有图形化界面,怎么安装图形化界面

我们在安装CentOS7时&#xff0c;如果选择 “最小化” 安装那么系统就只有命令行界面&#xff0c;但是没有图形化界面&#xff0c;如下图&#xff1a; 解决的完整步骤如下&#xff1a; 1&#xff09;开启CentOS7并登录root用户&#xff08;一定要以root用户登录&#xff0c;其…

windows电脑生成ios证书的方法

在uniapp开发进行云打包的时候&#xff0c;打包Ios应用需要p12格式的私钥证书和证书profile文件。无论使用windows电脑&#xff0c;还是mac电脑&#xff0c;生成ios证书&#xff0c;需要苹果开发者账号&#xff0c;假如你还没有苹果开发者账号&#xff0c;你可以参考下文先到苹…