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

【Linux】基础IO(文件描述符、缓冲区、重定向)

🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12625432.html

9efbcbc3d25747719da38c01b3fa9b4f.gif

目录

前言

C文件IO相关操作

系统文件I/O

 open

open函数返回值

文件描述符fd

read、stat

 文件描述符的分配规则

 重定向

使用 dup2 系统调用

 缓冲区

缓冲区的刷新策略

自主shell补充

封装简单库

stderr


前言

    💬 hello! 各位铁子们大家好哇。

             今日更新了Linux基础IO的内容
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

C文件IO相关操作

  1 #include<stdio.h>2 3 int main()4 {5   FILE* fp=fopen("log.txt","w");6   if(NULL==fp)7   {8     perror("fopen");9     return 1;10   }11   fclose(fp);                                                                                                                                                                  12   return 0;13 }

e0fec33b5cf14dc28f5496a3d625432f.png

执行上面代码后,创建了log.txt。运行代码时,进程就跑起来了,此时进程所在的路径就是当前进程的工作路径,所以在此创建文件。打开文件的本质其实是进程打开文件。

文件存在,但没有被打开时存在于磁盘中。

进程可以打开很多文件,系统中可以有很多进程。 

cd7e2c0fa2df40f6928fb7919faa0037.png

文件=内容+属性

创建文件时, 没有内容,大小显示的是0kb,但它还是占一定空间的,因为他有各种属性。 

系统文件I/O

 open

1ee5eb421b724dadb00c41c34b036ee3.png

pathname: 要打开或创建的目标文件

flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。

参数:

        O_RDONLY: 只读打开

        O_WRONLY: 只写打开

        O_RDWR : 读,写打开

                           上面这三个常量,必须指定一个且只能指定一个

        O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限         O_APPEND: 追加写 

        O_TRUNC: 如果文件已经存在,而且是个常规文件,以写的方式打开,传入这个选项后,他就会把文件清空。

返回值:

        成功:新打开的文件描述符

        失败:-1

mode_t理解:就是int类型,表示起始权限。

open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。 

 ea4b4ed1485b4d30ae98f2dff77df852.png5365beb7a7df48d9b881024564bacc6a.png

运行上面代码,发现创建了log.txt,但是它的权限是乱码的。 

c73534e3c93a4c27876224b2156c5534.png

2aa4a089a59d4906b6826e18dabca946.png

我们加上666权限后,现在权限不是乱码了,但权限依旧不是666对应的权限,而是664。这是因为权限掩码会与你的设置的权限,进行位运算,结果就不一样了。系统默认权限掩码是0002

1a1e609e2ae44920ace8f8d7d1370c35.png

所以我们可以在打开文件前设置默认的权限掩码为0473078ce23714d21ab340fba5cc7194e.pngad50c626ddf841d5a37943627fab86b6.png

此时权限就对应上了。注意:权限掩码按照就近原则,如果我们有设置默认权限掩码,就用我们设置的,如果没有,就会使用系统默认的。

4cf3b04d13ca40068611fce62659178a.png75fee4e36e6d4b569efc67ad214b2d0c.png3a9c83c019384ff0affda7cfe517e7c0.png

上面是系统调用接口close和write。fd就是open的返回值。94eca7375ddd4833bb71425e8245957e.png

eb4a1a384c044e0f9a0331781ec9c26e.png4b06101b5fe648fb9b183393c0250d63.png

 我们把message里的内容换成abc,然后直接运行代码。发现之前的内容还在,旧的内容没被清空。

e47cdf469b98463ab42d0489e7b22589.png

这是因为这里的open默认不存在就创建,存在就打开,默认不清空文件。

如果我们想像C语言fopen的“w”打开方式一样, 打开就清空文件,就需要再传一个选项O_TRUNC。 

7d3d2f22e9be45b8b873e6d0606c10d1.png

fd0be8f998d84151910480c5536dfea2.png

9a1382edd4d9462c89200d30a28658b7.png

它表示 如果文件已经存在,而且是个常规文件,并以写的方式打开,传入这个选项后,他就会把文件清空。

f4c3face4db34e4a9deec4ba07978a73.png

7be75efe5ae14d1e9fd98b05cfcd9e85.png

O_APPEND就是追加的意思。

open函数返回值

 在认识返回值之前,先来认识一下两个概念:系统调用库函数

fopen fclose fread fwrite都是C标准库当中的函数,我们称之为库函数(libc)。

open close read write lseek 都属于系统提供的接口,称之为系统调用接口

f0377bfec69d4e169a51e9d0b23639e0.png

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

文件描述符fd

71d36bcc357f461f925fdfa15891333e.png ab47ed3904874d17a1f942c259ab27d5.png 

文件描述符就是一个小整数

open的返回值fd是从3开始的。因为C语言默认会打开三个输入输出流,

标准输入stdin、

标准输出stdout、

标准错误stderr 。

a528be49dcd34dddb495e1021d9fb8ed.png 71427e4d759b4c30bd13a6e94f22d93e.png


5e41542a73bd40c4b0acecef4e112ef9.png0b079b4c62d74a5a89625b9bcb15c01a.png

我们可以用write配合文件描述符在显示器上打印。

 05d91c45a7354736ba2e10e7ef30e8f9.png

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

Linux中一切皆文件,所以0,1,2可以代表键盘,显示器。

 在OS内,系统在访问文件的时候,只认文件描述符fd。

a528be49dcd34dddb495e1021d9fb8ed.png

FILE* 是C语言提供的结构体类型,里面封装着文件fd。

所有的C语言上的文件操作函数,本质都是对系统调用的封装。

df8abb10feba4f63b63c0fdea10e2335.png

2041d8b32712496c917bac49dca459ff.png

 FILE* 结构体中就封装着文件描述符fd。

学了系统调用,我们可以用系统调用接口,也可以用语言提供的文件方法。但还是推荐使用语言提供的方法。因为系统不同,系统调用的接口可能不一样。


我们打开的文件是当前xshell的终端。

3d1ce4dfcc1f42ea9121bae0740e1af9.png

  系统中有一个proc目录,里面有很多蓝色的文件夹,它是由进程的pid来做的。

c7fc449301fe43cdab5ec4bb381d681e.png

我们查看某个进程的文件夹。cwd就是当前进程的工作路径。exe指向当前可执行程序的二进制文件。里面还有一个目录fd。

c56dd3e92c444b2bbb1f7fd6850d083d.png

进入fd目录,可以看到默认的文件描述符0、1、2是打开的。

打开的设备是dev目录下的pts/3。

d1ad2f793f4146f39fd2430aa55f98c6.png

2d67b72fe0a940e8ba9079731d50b8cd.pngd09eec15e10a49a0ba23075d105fc391.png

/dev/pts/3 指的就是我打开的右边的界面,也就是终端,这个终端文件就叫/dev/pts/3。

 070312fd594d44f898ec413b5ae31d82.png

967581e5a76a43168c92d19293032429.png

运行后如上图,会在另一个终端上打印。 

云服务器下,我们看到的显示器文件一般在 /dev/pts/目录下

b9b44bcc936043a4a6957cbfd00ad23c.png

22479a9c298f4a1d829d62a9255331ad.png

如果再登录一个就多了一个新的文件。 

read、stat

bc58abddb59a47a1bb8d365c9dc6ed35.png5cce20455aaf4d0c9bed87c55920542a.png9f71179c222c4a1ebbe48f367f6e9659.png

51f8821d03564b619295967918eccfc8.png

运行上面代码,结果如下图:d9afa9daff9240f58357bbb1755a1b24.png

struct stat是一个内核结构体,可以直接用。stat的参数2是一个输出型参数,我们把参数传进去后,它会把参数填满然后再传出来。

f52fa933ac4b486f884408c70c7a4d3c.png

1183cc4b808e4f5484c4375d1d60c10d.png

运行后,我们就可以读取文件里的内容了,如下图:c1e0a35a89c2467ea0184fa01d52f615.png

read的参数1指读取的文件fd,参数2是将读取到的内容放到该缓冲区中,参数3是要读取的字节数。

read的返回值:>0 :读取到的字节数     =0:已经读取到文件末尾。 

 文件描述符的分配规则

fa4c5d3ae586464e8b32f958b55b1bb7.png

649ad6dc437b46fe919e6ea9d11930a7.png

因为文件描述符的0、1、2默认是打开的,所以这里结果是3。如果我们先把描述符0关了再打开新文件会怎样呢?6222d81a9d4d4a4196ddbb53f416d397.png

4e5d7d6675c540b6af0d7c828291ef5a.png 结果是0。

cc37ca8023654c988a975d85aca1446c.png

9eb07b0e250b44df984afba07c0cabb2.png

如果我们把2先关了 ,这时候结果就是2了。

从上面的结果可以得出结论,

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

 重定向

f2ae50f8e26845f39bdf8255dcd2be10.png

57f2021c14e945fba5604797fb9ddcd7.png

如果我们先把1关闭,发现结果什么也不打印。这是因为文件描述符1是标准输出流,关闭后,就不会在显示器打印了。 

25b5b2024e554ae897878c7163b750ea.png

68f88a64cf5646c2a3f51bd4ccfa97f2.png

运行上面代码,发现什么也没打印,但确实创建了新的文件log.txt。打印该文件,发现内容写在了文件里面。         

d4315c45ced446b3b3ccf1aa87b0e005.png

bbeac6efb66d4e4fac88e2218a88fcff.png

如果我们把fflush注释掉,发现log.txt创建出来了,但里面什么东西也没有。

9a3a971d710d4819b58a3eae97f14684.png

log.txt存在磁盘中,当进程启动打开时,就会被加载到内存中。由于我们先关闭了文件描述符1,所以此时log.txt的文件描述符就是1。上层的printf和fprintf都是向stdout打印,而stdout的描述符是1,OS只认文件描述符,所以最终就向log,txt打印了内容。

重定向的本质:是在内核中改变文件描述符表特定下标的内容,与上层无关!

e5a364ac79dd4f589f2aeff84318aa29.png

每个文件对象都有对应的内核文件缓冲区,我们写数据都是从上层通过文件描述符1,写到对应的文件缓冲区,然后OS再把内容刷新到磁盘的文件中。 

stdin、stdout、stderr都是FILE*结构体,里面除了封装着fd,还有语言级别的文件缓冲区。所以我们通过printf/fprintf不是直接写到OS的内部的缓冲区,而是直接写到语言级别的缓冲区中,然后C语言再通过1号文件描述符把内容刷新到OS的内核文件缓冲区中。

所以fflush()里面是stdout,这是因为我们是刷新语言级别缓冲区的内容到OS的内核缓冲区中,内核缓冲区的内容由OS进行刷新。 

 由上可知,之所以注释掉fflush后,log.txt里面啥也没有,是因为内容在语言级别的缓冲区中,还没执行到return语句,冲刷内容到内核缓冲区中,log.txt就被关闭了。

60881ca3999a48108b1d6b8a881b8fa2.png

如果我们把close也注释掉,结果如下:

47a3ac7d495a42f3850b310837db890b.png

return的时候,语言级别缓冲区的内容就被冲刷到内核文件缓冲区中,此时log.txt就有内容了。

使用 dup2 系统调用

 6a4c7940fb2b49ee9c5ce821133c4c02.png6aaed888d43d425885c3c4f7370e9efc.png

dup2可以在底层帮我们做两个文件描述符对应的数组内容之间的值拷贝 。

本质是文件描述符下标对应内容的拷贝。

597a03759b174e2b96aeebcbac4edc61.png

 原本1号文件的内容指向显示器,3号文件内容指向log.txt。重定向的本质是将3号的内容拷贝给1号。所以1号就不会再指向显示器了,而是变成指向log.txt,所以后来往1号里写的内容都会变成往log.txt里写。

struct file里还存在一个引用计数,有几个指针指向就是几。如log.txt由1号和3号指向就是2,显示器就是0。 

如果我们要对标准输出进行重定向,把往显示器打印的内容变成往log,txt打印,根据上面的参数解释,参数的填法应该是dup2(fd,1)。也就是把oldfd留下来,拷贝给newfd。

4fc542332f904519b8a50fe9eb57ec0e.png

7d0f601a71ac43a2acc657347ad9daa7.png

运行上面代码,发现不在显示器上打印,而是在log.txt里打印。

8bf64c7499cb4b4793f945d84c1cba8f.png

326ee4990579450ab9087bf22444b8f7.png

我们把选项换成O_APPEND,它就会进行追加了。所以>和>>的区别就是选项不同而已。 

 缓冲区

缓冲区就是一段内存空间。

缓冲区由C语言维护就叫语言级缓冲区,由OS维护就叫内核缓冲区。

缓冲区存在的意义:OS为语言考虑,语言为用户考虑。给上层提供高效的IO体验,间接提高整体效率。

缓冲区的刷新策略

  1. 立即刷新。(无缓存)  fflush(stdout)、fsync(int fd)
  2. 行刷新。显示器
  3. 全缓冲。缓冲区写满才刷新。普通文件。
  4. 特殊情况:
    1. 进程退出,系统自动刷新。
    2. 强制刷新。

 这个刷新策略在内核和用户级别的缓冲区都能用。这里介绍用户级别的。

37a542c0f3244b758f6438e17e766633.png

0e05e5903c4d463b882002ed1526be18.png

运行上面代码,第一次在显示器上打印,第二次重定向到文件打印。发现打印的顺序不同。这是因为在显示器上打印是行刷新策略,write系统调用没有带缓冲区,就按语句顺序打印,所以第一次打印按顺序。库函数print和fprintf最后都是通过write系统调用刷新。

第二次是重定向到普通文件,此时刷新策略变成全缓冲,执行printf和fprintf语句时,内容都在缓冲区中,write直接输出,然后程序结束自动把缓冲区刷新,才打印出printf和fprintf。

 faf994cfa67746299cf6ef5eca92f294.png

d86e4652e1214376a96f555a71248a14.png

直接运行跟上面的第一次一样,因为是行刷新,所以执行到fork()时,缓冲区没内容了。

如果重定向到普通文件,此时是全缓冲,printf和fprintf的内容都在语言级缓冲区中,write是直接写到内核缓冲区中,所以write打印在最前面且只打印一次。到了fork时,父子进程都有了语言及缓冲区的内容,所以程序结束时,父子进程的缓冲区的内容都被刷新,就打印两次printf和fprintf。

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

 printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区, 都是用户级缓冲区。

自主shell补充

补充了重定向的功能

  1 #include <stdio.h>                                                                                                                                                             2 #include <stdlib.h>3 #include <string.h>4 #include <errno.h>5 #include <unistd.h>6 #include <sys/types.h>7 #include <sys/wait.h>8 #include <ctype.h>9 #include <sys/stat.h>10 #include <fcntl.h>11 12 #define SIZE 51213 #define ZERO '\0'14 #define SEP " "15 #define NUM 3216 #define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)17 #define SkipSpace(cmd, pos) do{\18     while(1){\19         if(isspace(cmd[pos]))\20             pos++;\21         else break;\22     }\23 }while(0)24 25 26 // "ls -a -l -n > myfile.txt"27 #define None_Redir 028 #define In_Redir 129 #define Out_Redir 230 #define App_Redir 331 32 int redir_type=None_Redir;33 char* filename=NULL;34 35 // 为了方便,我就直接定义了36 char cwd[SIZE*2];37 char *gArgv[NUM];38 int lastcode = 0;39 40 void Die()41 {42     exit(1);43 }44 45 const char *GetHome()46 {47     const char *home = getenv("HOME");48     if(home == NULL) return "/";                                                                                                                                               49     return home;50 }51 52 const char *GetUserName()53 {54     const char *name = getenv("USER");55     if(name == NULL) return "None";56     return name;57 }58 const char *GetHostName()59 {60     const char *hostname = getenv("HOSTNAME");61     if(hostname == NULL) return "None";62     return hostname;63 }64 // 临时65 const char *GetCwd()66 {67     const char *cwd = getenv("PWD");68     if(cwd == NULL) return "None";69     return cwd;70 }71 72 // commandline : output73 void MakeCommandLineAndPrint()74 {75     char line[SIZE];76     const char *username = GetUserName();77     const char *hostname = GetHostName();78     const char *cwd = GetCwd();79 80     SkipPath(cwd);81     snprintf(line, sizeof(line), "[%s@%s %s]> ", username, hostname, strlen(cwd) == 1 ? "/" : cwd+1);82     printf("%s", line);83     fflush(stdout);84 }85 86 int GetUserCommand(char command[], size_t n)87 {88     char *s = fgets(command, n, stdin);89     if(s == NULL) return -1;90     command[strlen(command)-1] = ZERO;91     return strlen(command); 92 }93                                                                                                                                                                                94 95 void SplitCommand(char command[], size_t n)96 {97     (void)n;98     // "ls -a -l -n" -> "ls" "-a" "-l" "-n"99     gArgv[0] = strtok(command, SEP);
100     int index = 1;
101     while((gArgv[index++] = strtok(NULL, SEP))); // done, 故意写成=,表示先赋值,在判断. 分割之后,strtok会返回NULL,刚好让gArgv最后一个元素是NULL, 并且while判断结束
102 }
103 
104 void ExecuteCommand()
105 {
106     pid_t id = fork();
107     if(id < 0) Die();
108     else if(id == 0)
109     {
110         //重定向设置
111         if(filename != NULL){
112             if(redir_type == In_Redir)
113             {
114                 int fd = open(filename, O_RDONLY);
115                 dup2(fd, 0);
116             }
117             else if(redir_type == Out_Redir)
118             {
119                 int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
120                 dup2(fd, 1);
121             }
122             else if(redir_type == App_Redir)
123             {
124                 int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
125                 dup2(fd, 1);
126             }
127             else
128             {}
129         }
130 
131 
132         // child                                                                                                                                                               
133         execvp(gArgv[0], gArgv);
134         exit(errno);
135     }
136     else
137     {
138         // fahter
139         int status = 0;
140         pid_t rid = waitpid(id, &status, 0);
141         if(rid > 0)
142         {
143             lastcode = WEXITSTATUS(status);
144             if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
145         }
146     }
147 }
148 
149 void Cd()
150 {
151     const char *path = gArgv[1];
152     if(path == NULL) path = GetHome();
153     // path 一定存在
154     chdir(path); //更改当前的工作路径
155 
156     // 刷新环境变量
157     char temp[SIZE*2];//临时缓冲区
158     getcwd(temp, sizeof(temp)); //得到当前进程的绝对路径 
159     snprintf(cwd, sizeof(cwd), "PWD=%s", temp);//
160     putenv(cwd); // 导入新的环境变量
161 }
162 
163 int CheckBuildin()
164 {
165     int yes = 0;
166     const char *enter_cmd = gArgv[0];
167     if(strcmp(enter_cmd, "cd") == 0)
168     {
169         yes = 1;
170         Cd();
171     }
172     else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
173     {
174         yes = 1;                                                                                                                                                               
175         printf("%d\n", lastcode);
176         lastcode = 0;
177     }
178     return yes;
179 }
180 
181 
182 void CheckRedir(char cmd[])
183 { 
184   // > >> <
185   // "ls -a -l -n > myfile.txt"
186   int pos = 0;
187     int end = strlen(cmd);
188 
189     while(pos < end)
190     {
191         if(cmd[pos] == '>')
192         {
193             if(cmd[pos+1] == '>')
194             {
195                 cmd[pos++] = 0;
196                 pos++;
197                 redir_type = App_Redir;
198                 SkipSpace(cmd, pos);
199                 filename = cmd + pos;
200             }
201             else
202             {
203                 cmd[pos++] = 0;
204                 redir_type = Out_Redir;
205                 SkipSpace(cmd, pos);
206                 filename = cmd + pos;
207             }
208         }
209         else if(cmd[pos] == '<')
210         {
211             cmd[pos++] = 0;
212             redir_type = In_Redir;
213             SkipSpace(cmd, pos);
214             filename = cmd + pos;
215         }
216         else
217         {
218             pos++;
219         }                                                                                                                                                                      
220     }
221 }
222 
223 int main()
224 {
225     int quit = 0;
226     while(!quit)
227     {
228         //0.重置
229         redir_type=None_Redir;
230         filename=NULL;
231         // 1. 我们需要自己输出一个命令行
232         MakeCommandLineAndPrint();
233 
234         // 2. 获取用户命令字符串
235         char usercommand[SIZE];
236         int n = GetUserCommand(usercommand, sizeof(usercommand));
237         if(n <= 0) return 1;
238         
239         //2.1checkredir
240         CheckRedir(usercommand);
241 
242         // 3. 命令行字符串分割. 
243         SplitCommand(usercommand, sizeof(usercommand));
244 
245         // 4. 检测命令是否是内建命令
246         n = CheckBuildin();
247         if(n) continue;
248         // 5. 执行命令
249         ExecuteCommand();
250     }
251     return 0;
252 }

封装简单库

 mystdio.h

#pragma once#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define LINE_SIZE 1024
#define FLUSH_NOW  1
#define FLUSH_LINE 2
#define FLUSH_FULL 4struct _myFILE
{unsigned int flags;int fileno;// 缓冲区char cache[LINE_SIZE];int cap;int pos; // 下次写入的位置
};typedef struct  _myFILE myFILE;myFILE* my_fopen(const char *path, const char *flag);
void my_fflush(myFILE *fp);
ssize_t my_fwrite(myFILE *fp, const char *data, int len);
void my_fclose(myFILE *fp);

mystdio.c

#include "mystdio.h"myFILE* my_fopen(const char *path, const char *flag)
{int flag1 = 0;int iscreate = 0;mode_t mode = 0666;if(strcmp(flag, "r") == 0){flag1 = (O_RDONLY);}else if(strcmp(flag, "w") == 0){flag1 = (O_WRONLY | O_CREAT | O_TRUNC);iscreate = 1;}else if(strcmp(flag, "a") == 0){flag1 = (O_WRONLY | O_CREAT | O_APPEND);iscreate = 1;}else{}int fd = 0;if(iscreate)fd = open(path, flag1, mode);elsefd = open(path, flag1);if(fd < 0) return NULL;myFILE *fp = (myFILE*)malloc(sizeof(myFILE));if(!fp) return NULL;fp->fileno = fd;fp->flags = FLUSH_LINE;fp->cap = LINE_SIZE;fp->pos = 0;return fp;
}void my_fflush(myFILE *fp)
{write(fp->fileno, fp->cache, fp->pos);fp->pos = 0;
}ssize_t my_fwrite(myFILE *fp, const char *data, int len)
{// 写入操作本质是拷贝, 如果条件允许,就刷新,否则不做刷新memcpy(fp->cache+fp->pos, data, len); //肯定要考虑越界, 自动扩容fp->pos += len;if((fp->flags&FLUSH_LINE) && fp->cache[fp->pos-1] == '\n'){my_fflush(fp);}return len;
}void my_fclose(myFILE *fp)
{my_fflush(fp);close(fp->fileno);free(fp);
}

stderr

75497d34e8dc490d8257a3cb25c06d2d.png

61afaff5deaa410c919c0d79269a6714.png5ccfd7db747846faad1b42725ee51527.png

stdout和stderr分别对应文件描述符1和2,他们都指向显示器文件。>是标准输出重定向,只更改1号fd里面的内容,所以重定向后,1号的打印到了log,txt,而2号还是没变,依旧打印在显示器上。

f12807567e264f36b6ec32c449d73b08.png

c14e075490014dbea800081cd12a4e78.png

直接运行代码,会全部打印在显示器上。我们可以重定向到不同文件,这样就可以将正确信息和错误信息分出来。这也是fd1,fd2的意义。上面是完整的重定向的写法。

453720fd234343a4b45396b6d67b4182.png

如果我们想把1和2都重定向到同一个文件中,可以通过上面的写法实现。

相关文章:

【Linux】基础IO(文件描述符、缓冲区、重定向)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/qinjh_/category_12625432.html 目录 前言 C文件IO相关操作 系统文件I/O open open函数返回值 文件描述符fd re…...

一篇文章快速学会docker容器技术

目录 一、Docker简介及部署方法 1.1Docker简介 1.1.1什么是docker 1.1.2 docker在企业中的应用场景 1.1.3 docker与虚拟化的对比 1.1.4 docker的优势 二 、部署docker 2.1 容器工作方法 2.2 部署第一个容器 2.2.1 配置软件仓库 2.2.2 安装docker-ce并启动服务 2.2.…...

【MySQL】使用 JDBC 连接数据库

文章目录 前言1. 认识 JDBC1.1 概念1.2 好处 2. 使用 JDBC2.1 安装数据驱动包2.2 把 jar 包导入到项目中2.3 代码编写2.4 测试结果 3. 代码优化4. 源码展示结语 前言 在 MySQL 系列中&#xff0c;我们介绍了很多内容&#xff0c;包括但不限于建库建表&#xff0c;增删查改等等…...

数据结构与算法笔记:概念与leetcode练习题

1、数组Array 时间复杂度 数组访问&#xff1a;O(1) 数组搜索&#xff1a;O(N) 数组插入&#xff1a;O(N) 数组删除&#xff1a;O(N) 特点 适合读&#xff0c;不适合写 数组常用操作 # 1、创建数组 a [] # 2、尾部添加元素 a.append(1) a.append(2) a.append(3) # 3、…...

十大时间序列预测模型

目录 1. 自回归模型 原理 核心公式 推导过程: 完整案例 2. 移动平均模型 原理 核心公式 推导过程: 完整案例 3. 自回归移动平均模型 原理 核心公式 推导过程: 完整案例 4. 自回归积分移动平均模型 原理 核心公式 推导过程 完整案例 5. 季节性自回归积分…...

G2O 通过工厂函数类 OptimizationAlgorithmFactory 来生成固定搭配的优化算法

OptimizationAlgorithmFactory 类位于 optimization_algorithm_factory.h //***g2o源码 g2o/g2o/core/optimization_algorithm_factory.h ***// /*** \brief create solvers based on their short name** Factory to allocate solvers based on their short name.* The Factor…...

手机USB连接不显示内部设备,设备管理器显示“MTP”感叹号,解决方案

进入小米驱动下载界面&#xff0c;等小米驱动下载完成后&#xff0c;解压此驱动文件压缩包。 5、小米USB驱动安装方法&#xff1a;右击“计算机”&#xff0c;从弹出的右键菜单中选择“管理”项进入。 6、在打开的“计算机管理”界面中&#xff0c;展开“设备管理器”项&…...

SpringBootWeb快速入门!详解如何创建一个简单的SpringBoot项目?

在现代Web开发中&#xff0c;SpringBoot以其简化的配置和快速的开发效率而受到广大开发者的青睐。本篇文章将带领你从零开始&#xff0c;搭建一个基于SpringBoot的简单Web应用~ 一、前提准备 想要创建一个SpringBoot项目&#xff0c;需要做如下准备&#xff1a; idea集成开发…...

RabbitMQ 入门到精通指南

RabbitMQ 是一种开源消息代理软件&#xff0c;基于 AMQP&#xff08;高级消息队列协议&#xff09;构建&#xff0c;用于异步传输数据&#xff0c;帮助我们解耦系统、削峰流量、处理高并发。本指南将详细介绍 RabbitMQ 的架构设计、使用场景、安装步骤以及一些高级应用&#xf…...

ARM base instruction -- movz

Move wide with zero moves an optionally-shifted 16-bit immediate value to a register. 用零移动宽值将可选移位的16位即时值移动到寄存器。即把立即数移动寄存器前先把寄存器清零。 32-bit variant MOVZ <Wd>, #<imm>{, LSL #<shift>} 64-bit var…...

安装jdk安装开发环境与maven

1.下载maven 链接: https://pan.baidu.com/s/1gTmIWBFBdIQob0cqGG3E_Q 提取码: 42ck&#xff0c;apache-maven-3.8.4-bin.zip 2.安装java jdk yum install -y java-1.8.0-openjdk-devel 3.在/opt目录下新建目录 mkdir /opt/maven 4.将apache-maven-3.8.4-bin.zip上传到/opt/ma…...

openpnp - 图像传送方向要在高级校正之前设置好

文章目录 openpnp - 图像传送方向要在高级校正之前设置好笔记图像传送方向的确定END openpnp - 图像传送方向要在高级校正之前设置好 笔记 图像传送方向和JOG面板的移动控制和实际设备的顶部摄像头/底部摄像头要一致&#xff0c;这样才能和贴板子时的实际操作方向对应起来。 …...

数据库建表规范【记录】

建表规约 【强制】创建表时必须显式指定表存储引擎类型&#xff0c;如无特殊需求&#xff0c;一律为InnoDB。 【强制】必须有行数据的创建时间字段create_date和最后更新时间字段edit_date。 【强制】自增主键命名必须是id&#xff0c;关联表外键命名xxyyzz_id&#xff1b;业务…...

css的动画属性

CSS动画属性是CSS3的一个重要特性&#xff0c;它允许你创建平滑的过渡效果&#xff0c;增强用户的交互体验。CSS动画可以通过keyframes规则和animation属性来创建。 animation属性 animation属性是一个简写属性&#xff0c;用于设置动画的多个属性&#xff0c;包括动画名称、…...

【Ubuntu】PlantUML工具 | 安装 | 语法 | 使用工具画序列图

🌱 PlantUML是一个通用性很强的工具,可以快速、直接地创建各种图表。 目录 1 安装 2 使用PlantUML画序列图 ① 语法 ②示例和效果 利用简单直观的语言,用户可以毫不费力地绘制各种类型的图表。PlantUML 是一个开源项目,支持快速绘制:• 时序图• 用例图• 类图• 对...

微信步数C++

题目&#xff1a; 样例解释&#xff1a; 【样例 #1 解释】 从 (1,1) 出发将走 2 步&#xff0c;从 (1,2) 出发将走 4 步&#xff0c;从 (1,3) 出发将走 4 步。 从 (2,1) 出发将走 2 步&#xff0c;从 (2,2) 出发将走 3 步&#xff0c;从 (2,3) 出发将走 3 步。 从 (3,1) 出发将…...

AI写作工具大比拼:揭秘Claude的神秘魅力以及如何订阅Claude

AI写作困境与Claude的惊喜表现 最近有很多朋友在吐槽AI写的文章不太行&#xff0c;我一看他的要求写的很清楚&#xff0c;已经把提示词都用到位了&#xff0c;例如&#xff1a;写作背景、写作要求等&#xff0c;都有具体写出来。但文章阅读起来就是欠缺点啥。 你们有没有遇到…...

秋招内推2025-招联金融

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递&#xff09; 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策…...

GOM引擎启动后M2提示Invalid filename报错的解决办法

在架设一个GOM引擎版本的时候&#xff0c;启动M2就提示Invalid filename&#xff0c;之后的网关就没有办法再启动了&#xff0c;研究了半天也终于是弄好了&#xff0c;其实也简单&#xff0c;就是路径设置的不对&#xff0c;所以无法完成启动&#xff0c;很多人以为在控制台设置…...

CPU 多级缓存

在多线程并发场景下&#xff0c;普通的累加很可能错的 CPU 多级缓存 Main Memory : 主存Cache : 高速缓存&#xff0c;数据的读取存储都经过此高速缓存CPU Core : CPU 核心Bus : 系统总线 CPU Core 和 Cache 通过快速通道连接&#xff0c;Main menory 和 Cache 都挂载到 Bus 上…...

Chrome浏览器调用ActiveX控件--allWebOffice控件功能介绍

allWebOffice控件概述 allWebOffice控件能够实现在浏览器窗口中在线操作文档的应用&#xff08;阅读、编辑、保存等&#xff09;&#xff0c;支持编辑文档时保留修改痕迹&#xff0c;支持书签位置内容动态填充&#xff0c;支持公文套红&#xff0c;支持文档保护控制等诸多办公功…...

JavaScript-下篇

上篇我们学习了&#xff0c;一些基础语法和函数&#xff0c;现在我们学习下篇&#xff0c;主要包括,对象和事件。而对象又包括&#xff0c;数组Arrays&#xff0c;String字符串&#xff0c;BOM&#xff0c;DOM等 JS对象 Arrays数组 数组是一种特殊的对象&#xff0c;用于存储…...

STM32-HAL库驱动DHT11温湿度传感器 --2024.9.28

目录 一、教程简介 二、驱动原理讲解 &#xff08;一&#xff09;通信4步骤 &#xff08;二&#xff09;传感器数据解析 三、CubeMX生成底层代码 &#xff08;一&#xff09;基础配置 &#xff08;二&#xff09;配置DHT11的驱动引脚 &#xff08;三&#xff09;配置串口 四…...

使用C语言获取iostat中的await值的方法和方案

使用C语言获取iostat中的await值的方法和方案 1. 准备工作2. 调用iostat命令并获取输出3. 解析iostat输出4. 完整实现和错误处理5. 注意事项在Linux系统中,iostat命令是sysstat软件包的一部分,用于监控系统的CPU、网卡、tty设备、磁盘、CD-ROM等设备的活动情况和负载信息。其…...

阿里云域名解析和备案

文章目录 1、域名解析2、新手引导3、ICP备案 1、域名解析 2、新手引导 3、ICP备案...

gitee公钥设置、创建库及使用

简介 一、如何安装git 使用gitee&#xff0c;需要先安装git工具。 工具网站地址&#xff1a;https://git-scm.com/downloads 安装完成后&#xff0c;在terminal命令行输入git --version可以查看到git的版本。 二、登录gitee 我们先在 gitee上注册账号并登录。gitee官网&#x…...

融媒体服务中PBO进行多重采样抗锯齿(MSAA)

如果不理解pbo 那先去了解概念&#xff0c;在此不再解释&#xff0c;这是我为了做融合服务器viewpointserver做的一部分工作&#xff0c;融合服务器的功能是将三维和流媒体&#xff0c;AI融合在一起&#xff0c;viewpointserver会直接读取三维工程的文件&#xff0c;同时融合rt…...

说说BPMN概念及应用

BPMN&#xff08;Business Process Modeling and Notation&#xff09;即业务流程建模与标注&#xff0c;是一种由OMG&#xff08;Object Management Group&#xff0c;对象管理组织&#xff09;制定的业务流程建模语言。以下是对BPMN标准的详细解释&#xff1a; 一、BPMN的起…...

【微服务】初识(day1)

基础概念 集群 集群是将一个系统完整的部署到多个服务器&#xff0c;每个服务器提供系统的所有服务&#xff0c;多个服务器可以通过负载均衡完成任务&#xff0c;每个服务器都可以称为集群的节点。 分布式 分布式是将一个系统拆分为多个子系统&#xff0c;多个子系统部署在…...

15分钟学 Python 第40天:Python 爬虫入门(六)第一篇

Day40 &#xff1a;Python 爬取豆瓣网前一百的电影信息 1. 项目背景 在这个项目中&#xff0c;我们将学习如何利用 Python 爬虫技术从豆瓣网抓取前一百部电影的信息。通过这一练习&#xff0c;您将掌握网页抓取的基本流程&#xff0c;包括发送请求、解析HTML、存储数据等核心…...