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 }
执行上面的代码之后,就创建了log.txt,运行上面的代码之后,进程就跑起来了,此时进程所在的工作路径是当前的工作路径,所以文件也别创建再来当前路径,打开进程的本质其实是进程打开文件。 文件虽然存在,但没有被打开时就存在于磁盘当中 进程可以打开很多文件,系统中可以有很多进程
文件=内容+属性 创建文件时, 没有内容,虽然大小显示的是0kb,但它还是占一定空间的,因为他有各种属性。
这里的pathname表示要打开或者要创建的目标文件 flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算 参数: O_RDONLY: 只读打开 O_WRONLY: 只写打开 O_RDWR : 读,写打开 上面这三个常量,必须指定一个且只能指定一个 O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限 O_APPEND: 追加写 O_TRUNC: 如果文件已经存在,而且是个常规文件,以写的方式打开,传入这个选项后,他就会把文件清空。 返回值: 成功:新打开的文件描述符 失败:-1 mode_t:表示起始权限,int类型
open函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open
在认识返回值之前,先来认识一下两个概念:系统调用和库函数 fopen fclose fread fwrite都是C标准库当中的函数,我们称之为库函数(libc)。 open close read write lseek 都属于系统提供的接口,称之为系统调用接口
可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发
文件描述符就是一个小整数 open的返回值fd是从3开始的。因为C语言默认会打开三个输入输出流, 标准输入stdin 标准输出stdout 标准错误stderr
文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件 。 Linux中一切皆文件,所以0,1,2可以代表键盘,显示器。
在OS内,系统在访问文件的时候,只认文件描述符fd。
struct stat是一个内核结构体,可以直接用。stat的参数2是一个输出型参数,我们把参数传进去后,它会把参数填满然后再传出来
运行后,我们就可以读取文件里的内容了,如下图:
read的参数1指读取的文件fd,参数2是将读取到的内容放到该缓冲区中,参数3是要读取的字节数。 read的返回值:>0 :读取到的字节数 =0:已经读取到文件末尾。
因为文件描述符的0、1、2默认是打开的,所以这里结果是3。如果我们先把描述符0关了再打开新文件会怎样呢?
如果我们把2先关了 ,这时候结果就是2了。 从上面的结果可以得出结论, 文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
如果我们先把1关闭,发现结果什么也不打印。这是因为文件描述符1是标准输出流,关闭后,就不会在显示器打印了。
运行上面代码,发现什么也没打印,但确实创建了新的文件log.txt。打印该文件,发现内容写在了文件里面。 然后就发现log.txt里面存着这些内容。
log.txt存在磁盘中,当进程启动打开时,就会被加载到内存中。由于我们先关闭了文件描述符1,所以此时log.txt的文件描述符就是1。上层的printf和fprintf都是向stdout打印,而stdout的描述符是1,OS只认文件描述符,所以最终就向log,txt打印了内容。 重定向的本质:是在内核中改变文件描述符表特定下标的内容,与上层无关! 每个文件对象都有对应的内核文件缓冲区,我们写数据都是从上层通过文件描述符1,写到对应的文件缓冲区,然后OS再把内容刷新到磁盘的文件中。 stdin、stdout、stderr都是FILE*结构体,里面除了封装着fd,还有语言级别的文件缓冲区。所以我们通过printf/fprintf不是直接写到OS的内部的缓冲区,而是直接写到语言级别的缓冲区中,然后C语言再通过1号文件描述符把内容刷新到OS的内核文件缓冲区中。 所以fflush()里面是stdout,这是因为我们是刷新语言级别缓冲区的内容到OS的内核缓冲区中,内核缓冲区的内容由OS进行刷新。
由上可知,之所以注释掉fflush后,log.txt里面啥也没有,是因为内容在语言级别的缓冲区中,还没执行到return语句,冲刷内容到内核缓冲区中,log.txt就被关闭了。
如果我们把close也注释掉,结果如下:
return的时候,语言级别缓冲区的内容就被冲刷到内核文件缓冲区中,此时log.txt就有内容了。
dup2可以在底层帮我们做两个文件描述符对应的数组内容之间的值拷贝 。 本质是文件描述符下标对应内容的拷贝。
原本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。
运行上面代码,发现不在显示器上打印,而是在log.txt里打印。
缓冲区就是一段内存空间。 缓冲区由C语言维护就叫语言级缓冲区,由OS维护就叫内核缓冲区。 缓冲区存在的意义:OS为语言考虑,语言为用户考虑。给上层提供高效的IO体验,间接提高整体效率。
5.进程退出的时候,也会自动刷新(例如退出stdin,stdout,stderr) 6.close没有退出进程,只有关闭某个文件 这个刷新策略在内核和用户级别的缓冲区都能用。这里介绍用户级别的。
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 512
13 #define ZERO '\0'
14 #define SEP " "
15 #define NUM 32
16 #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 0
28 #define In_Redir 1
29 #define Out_Redir 2
30 #define App_Redir 3
31
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 : output
73 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 4
struct _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);
else
fd = 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);
}
stdout和stderr分别对应文件描述符1和2,他们都指向显示器文件。>是标准输出重定向,只更改1号fd里面的内容,所以重定向后,1号的打印到了log,txt,而2号还是没变,依旧打印在显示器上。
直接运行代码,会全部打印在显示器上。我们可以重定向到不同文件,这样就可以将正确信息和错误信息分出来。这也是fd1,fd2的意义。上面是完整的重定向的写法。
如果我们想把1和2都重定向到同一个文件中,可以通过上面的写法实现
十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:
1.一个冷知识: 屏蔽力是一个人最顶级的能力,任何消耗你的人和事,多看一眼都是你的不对。
2.你不用变得很外向,内向挺好的,但需要你发言的时候,一定要勇敢。 正所谓:君子可内敛不可懦弱,面不公可起而论之。
3.成年人的世界,只筛选,不教育。
4.自律不是6点起床,7点准时学习,而是不管别人怎么说怎么看,你也会坚持去做,绝不打乱自己的节奏,是一种自我的恒心。
5.你开始炫耀自己,往往都是灾难的开始,就像老子在《道德经》里写到:光而不耀,静水流深。
最后如果觉得我写的还不错,请不要忘记点赞✌,收藏✌,加关注✌哦(。・ω・。)
愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油,为自己点赞!