在之前的博客当中我们简单的了解过文件的相关操作,之前的属于是
语言方案
,接下来要学习的就是系统方案
查看文件内存多大
du -m 文件名 //MB查看该文件有多大内存
du -k 文件名 //KB
os如何传递标志位的
在之前我们理解的就是
int func(int flag) flag=1、2、3
但是这只能传递一个标志位
如果我们想要将多个标志位传递应该怎么办?
:这里就要借助位图
的方法了,一个整数有32bite位,将它们分为32个,每一个bite就代表一个标志位
#include <stdio.h>
#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
#define FOUR 0x8
#define FIVE 0x10
// 0000 0000 0000 0000 0000 0000 0000 0000
void Print(int flags)
{
if(flags & ONE) printf("hello 1\n"); //充当不同的行为
if(flags & TWO) printf("hello 2\n");
if(flags & THREE) printf("hello 3\n");
if(flags & FOUR) printf("hello 4\n");
if(flags & FIVE) printf("hello 5\n");
}
int main()
{
printf("--------------------------\n");
Print(ONE);
printf("--------------------------\n");
Print(TWO);
printf("--------------------------\n");
Print(FOUR);
printf("--------------------------\n");
Print(ONE|TWO);
printf("--------------------------\n");
Print(ONE|TWO|THREE);
printf("--------------------------\n");
Print(ONE|TWO|THREE|FOUR|FIVE);
printf("--------------------------\n");
return 0;
}
所以在接口
open
当中的标志位也是采用该思想借助位图
,flag
的每个不同的数值都是宏
接口open
open
的第二个参数flag
中O_WRONLY
并不像语言方案那样如果该文件没有创建,就创建文件,系统方案并没有该功能,需要自己进行创建O_CREAT
。int fd=open(log,O_WRONLY|O_CREAT);
但是加上之后发现还是不行,原因是创建文件需要权限,现在并没有权限,所以就要使用open的有三个参数的,就是加上权限。
从下面的图片可以看出文件的权限是乱码,
所以在系统方案当中想要打开文件但是还没有该文件就使用三个参数的接口,有该文件就是用两个参数的接口。int fd=open(log,O_WRONLY|O_CREAT,0666);
但是通过下面的图片可以看到权限并不是0666
而是0664
,原因是umask
,所以为了不影响我们创建文件就需要将umask
进行修改,而我们自己写了一个umask
,系统中也有一个umask0002
,编译器采用就近原则用自己写的。
1 #include<stdio.h>
2 #include<string.h>
3 #include<errno.h>
4 #include<unistd.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7 #include<fcntl.h>
8
9 #define log "log.txt"
10
11
12 int main()
13 {
11 umask(0);//就近原则
14 int fd=open(log,O_WRONLY|O_CREAT,0666);
15 if(fd==-1)
16 {
17 printf("fd=%d,errno=%d,strerror=%s\n",fd,errno,strerror(errno));
18 }
19
20 printf("fd=%d,errno=%d,strerror=%s\n",fd,errno,strerror(errno));
21 close(fd);
22 return 0;
23 }
之前C语言当中用的文件操作是属于库函数,使用的底层是我们今天的所见到(调用)
w(写)
下面的代码是写进文件时的相关操作1是将内容写进文件但是并不会覆盖,并不会将内容清空。
2是将之前的内容进行清空==w
3是追加在文件当中,但是是从顶部开始追加的,并不是从尾部。==a
int fd=open(log,O_WRONLY|O_CREAT,0666);//1
int fd = open(LOG, O_WRONLY | O_CREAT | O_TRUNC, 0666);//2
int fd = open(LOG, O_WRONLY | O_CREAT | O_APPEND, 0666);//3
这里要注意一下在
write
的第三个参数,不要把\0
算进去,我们现在进行的是系统方案,并不认识\0
,它是在C语言当中的。所以这也就导致在使用read
时需要加上\0
在输出字符串时。
while(cnt)
35 {
36 char line[128];
37 snprintf(line, sizeof(line), "%s, %d\n", msg, cnt);
38 write(fd, line, strlen(line)); //这里的strlen不要+1, \0是C语言的规定,不是文件的规定!
39 cnt--;
40 }
r(读)
int fd = open(LOG, O_RDONLY);
并不能进行按行读取,只能进行整体读取。
ssize_t n = read(fd, buffer, sizeof(buffer)-1); //使用系统接口来进行IO的时候,一定要注意,\0问题
29 if(n > 0)
30 {
31 buffer[n] = '\0';
32 printf("%s\n", buffer);
33 }
文件描述符
stdin & stdout & stderr
C默认会打开三个输入输出流,分别是stdin, stdout, stderr
C++默认打开三个输入输出流,分别是cout,cin,cerr
仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针(linux下皆文件)
标准输入–>对应的是键盘文件
标准输出–>对应的是显示器文件
标准错误–>对应的是显示器文件
1 #include<iostream>
2 #include<cstdio>
3
4
5 int main()
6 {
7 printf("hello printf->stdout\n");
8 fprintf(stdout,"hello fprintf->stdout\n");
9 fprintf(stderr,"hello fprintf->stderr\n");
10
11 std::cout<<"hello ->cout"<<std::endl;
12 std::cerr<<"hello ->cerr"<<std::endl;
13 }
通过上面的图片可以知道使用输出重定向并不能改变标准错误的输出方向,虽然标准输出和标准错误都是对应的显示器文件。
下面该图的具体流程就是文件被打开到返回文件描述符的过程。
先是从磁盘当中获取文件信息加载到内存当中,之后再将文件的信息组织起来使用数据结构,将每个文件以数组下标的形式放到指定数组,按顺序排列下去,之后在将该数组传到打开该文件的进程当中。
Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2.
0,1,2对应的物理设备一般是:键盘,显示器,显示器。
所以文件描述符都是从三开始的,是因为被标准输入、标准输出、标准错误占去了。
Linux下理解一切皆文件,使用OS的本质是通过进程的方式进行OS的访问,进程直接对应的就是文件,所以一切皆文件
在操作系统层面,我们必须要使用文件描述符才能找到文件
任何语言层访问外设或文件,都需要经过OS
重定向
通过上面的图片了解到当将标准输入和标准错误文件进行关闭,在进行打开文件会优先将标准输入和标准错误进行打开,将两个打开的文件替换之前的键盘文件和显示器文件。
在上面提到过为什么标准输出和标准错误都是对应的显示器文件,为什么在输出重定向的时候,标准错误不会改变输出方向
在这里也就可以解释了,在我们将文件描述符1进行关闭,再次打开的文件就是代替标准输出的文件,而那个文件的文件描述符也就是1了,这也就是属于重定向。这里只是将输出1进行了重定向,而错误2并没有进行输出重定向,也就导致没有输出到一个文件当中。
将标准输出和标准错误都输出重定向到log.txt
将标准输出和标准错误分别输出重定向到log.txt和err.txt
下面的代码将常规消息和错误消息进行分开打印
close(1);
open(LOG_NORMAL, O_WRONLY | O_CREAT | O_APPEND, 0666);
close(2);
open(LOG_ERROR, O_WRONLY | O_CREAT | O_APPEND, 0666);
//因为Linux下一切皆文件,所以,向显示器打印,本质就是向文件中写入, 如何理解?TODO
//C
printf("hello printf->stdout\n");
printf("hello printf->stdout\n");
printf("hello printf->stdout\n");
printf("hello printf->stdout\n");
fprintf(stdout, "hello fprintf->stdout\n");
fprintf(stdout, "hello fprintf->stdout\n");
fprintf(stderr, "hello fprintf->stderr\n");
fprintf(stderr, "hello fprintf->stderr\n");
dup和dup2
在上面的重定向当中可以看出是非常的麻烦,
dup2
就是在简化重定向的函数。下面着重的介绍一下dup2
。
dup2的用法就是将将oldfd文件的内容拷贝给newfd,怕(下面用红色圈住的意思是,newfd是oldfd的一份拷贝)使它们指向同一个文件,这样就完成重定向了。
所以要是想要使用fd重定向标准输出的话,dup2(fd,1)
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
返回值:
成功:dup函数返回当前系统可用的最小整数值。
dup2函数返回第一个不小于newfd的整数值,分两种情况:
1. 如果newfd已经打开,则先将其关闭,再复制文件描述符;
2. 如果newfd等于oldfd,则dup2函数返回newfd,而不关闭它。
失败:dup和dup2函数均返回-1,并设置errno。
dup例子
/* 例子:复制文件描述符,并向文件写数据 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
void main()
{
int oldfd,newfd;
char *oldstr = "Write by oldfd.\n";
char *newstr = "Write by newfd.\n";
oldfd = open("test.txt", O_RDWR|O_CREAT, 0644);
printf("oldfd = %d\n", oldfd);
if(oldfd == -1) {
printf("File open error\n");
exit(-1);
}
/* 开始复制 */
newfd = dup(oldfd);
printf("newfd = %d\n", newfd);
/* 使用oldfd写 */
write(oldfd, oldstr, strlen(oldstr));
if(close(oldfd) == -1) {
printf("Close oldfd error.\n");
exit(-1);
}
/* 使用newfd写 */
write(newfd, newstr, strlen(newstr));
if(close(newfd) == -1) {
printf("Close newfd error.\n");
exit(-1);
}
exit(0);
}
终端输出:
oldfd = 3
newfd = 4
查看test.txt:
Write by oldfd.
Write by newfd.
缓冲区
语言当中的缓冲区在C库当中,OS中也是有缓冲区的,在文件结构体当中(位置在内存)。
C库结合一定的刷新策略,将缓冲区的数据写入OS当中(write(fd,…))
缓冲策略一共有三种:1.无缓冲,就是直接写到OS当中。
2.行缓冲,遇到\n
就刷新,显示器文件就是行缓冲。
3.全缓冲,就是当缓冲区满时,进行刷新,普通文件是全缓冲。这样也是比较节省时间的,也是为什么要有缓冲区的原因,节省调用者的时间(进程在调用C库将文件内容拷贝缓冲区中,之后就不再管它了,进程就去做别的事情去了,节省时间)。行缓冲并不会节省时间。系统调用也是费时间的。
1 #include<stdio.h>
2 #include<string.h>
3 int main()
4 {
5 fprintf(stdout,"hello fprintf\n");
6
7 const char* msg="hello write\n";
E> 8 write(1,msg,strlen(msg));
9
E> 10 fork();
11 return 0;
12 }
在上面的代码和运行结果当众可以看出两次打印的结果是不一样的,首先来说第一次输出:
write
是属于OS,所以并没有缓冲区问题,就是正常输出,而fprintf
是输出到显示器上,所以是行缓冲,也就是直接就输出了。
第二次输出:write
是属于OS,所以并没有缓冲区问题,就是正常输出,而fprintf
是重定向到文件上了,就是是全缓冲,并不能将缓冲区填满,也就不会马上就刷新,在经过fork
时也就会进行拷贝父进程的内容,最后两个进程都需要刷新,就是对缓冲区进行清空,谁先谁就进行写时拷贝,所以就打印了两次。
在上面介绍了C库当中的缓冲区,这里介绍一下内核当中的缓冲区,并且和强制刷新内核缓冲区。
简单的介绍一下下面图片的流程,从用户调用函数,将要输出的数据拷贝到C库的缓冲区,该拷贝是用户到语言层的,在之后通过系统接口将内容拷贝到内核缓冲区,该拷贝是语言到内核层面的,在之后就刷新到磁盘,进行了第三次拷贝。
sync
该函数的作用是将内核缓冲区当中的内容强制的刷新到磁盘当中。
printf最后输出的都是字符串
当我们输出int类型的数据时,最后输出的是字符串并不是整数,过程是获取变量,在定义缓冲区,将要输出的数据转化为字符串,调用系统接口将数据拷贝到缓冲区,在进行刷新。
scanf是先将字符串输入写入缓冲区,之后在将它转换为int ( atoi),再将它的数据保存到整形int变量当中