🎬 胖咕噜的稞达鸭 :个人主页
🔥 个人专栏 : 《数据结构》《C++初阶高阶》
《Linux系统学习》
《算法入门》
⛺️技术的杠杆,撬动整个世界!

文件大小0还是会在磁盘上占用空间,文件=内容+属性
系统角度
对文件的操作本质是进程对文件的操作;
磁盘的管理者是操作系统;
文件的读写本质不是通过C语言/C++的库函数来操作的(这些库函数只是为用户提供方便),而是通过文件相关的系统调用接口来实现的。
fclose.fopen这些库函数,封装了底层OS的文件系统调用。
访问文件,就需要先打开文件,对文件的操作,本质上是进程对文件的操作!
当我们向显示器打印,本质就是向显示器写入,因为Linux中一切都是文件。
stdin & stdout & stderr
C默认会打开三个输入输出流:分别是stdin,stdout,stderr;
stdin:标准输入:键盘文件
sdtout:标准输出:显示器文件
stderr:标准错误:显示器文件
w:文件会先被清空;
a:追加重定向
简单实现cat命令:
cpp
#include<stdio.h>
#include<string.h>
int main(int argc,char* argv[])
{
if(argc != 2)//如果命令参数不为2,规定是
{
printf("USAGE:myfile+cd\n");
return 1;
}
FILE* fp = fopen(argv[1],"r");
if(!fp)
{
printf("fopen error!\n");
return 2;
}
char buf[1024];
while(1)
{
int s = fread(buf,1,sizeof(buf),fp);
if(s > 0)
{
buf[s] = 0;
printf("%s",buf);
}
if(feof(fp))
{
break;
}
}
fclose(fp);
return 0;
}
打开文件第一种操作方式:
-
新建文件并且清空
int fd =open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);
cpp
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);
int fd =open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);
if(fd <0)
{
perror("open");
return 1;
}
printf("fd: %d\n",fd);
const char* msg ="abcd";
int cnt =1;
while(cnt)
{
//当作字符来写:
write(fd,msg,strlen(msg));
cnt--;
}
close(fd);
return 0;
}
代码细节:
umask(0):设置进程的文件创建掩码 为 0。
作用:Linux 中创建文件时,最终权限 = open指定的权限 & (~umask)。默认 umask 通常是 0022(拒绝同组 / 其他用户的写权限),这里设为 0,保证open的0666权限能完全生效(否则最终权限会是 0666 & ~0022 = 0644)。
注意:umask是进程级别的设置,只影响当前进程创建的文件,不影响系统全局。
open:Linux 系统调用,用于打开 / 创建文件,返回文件描述符(fd) (失败返回 - 1)。- 参数 1:
"log.txt"→ 要操作的文件名(当前目录下)。 - 参数 2:
O_CREAT | O_WRONLY | O_TRUNC→ 文件打开标志(多个标志用|拼接):O_CREAT:如果文件不存在则创建;如果存在则不影响(需配合第三个参数指定权限)。O_WRONLY:以只写模式打开文件(只能向文件写数据,不能读)。O_TRUNC:如果文件已存在且有内容,清空文件内容(截断为 0 长度)。
- 参数 3:
0666→ 文件创建权限(八进制数,前缀 0 不能少):6= 4(读) + 2(写),所以0666表示:所有者、同组用户、其他用户都有读 + 写权限(无执行权限)。- 最终权限受
umask影响,这里umask(0),所以最终权限就是 0666。
判断open是否成功(文件描述符 fd < 0 表示失败)
- 参数 1:
- 文件新建追加
cpp
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);
int fd =open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);
if(fd <0)
{
perror("open");
return 1;
}
printf("fd: %d\n",fd);
const char* msg ="abcd";
int cnt =1;
while(cnt)
{
//当作字符来写:
write(fd,msg,strlen(msg));
cnt--;
}
close(fd);
return 0;
}

操作系统不关心你的写入方式类型。
读权限:
cpp
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
umask(0);
//int fd =open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);
//int fd =open("log.txt",O_CREAT | O_WRONLY | O_APPEND,0666);
int fd =open("log.txt",O_RDONLY );
if(fd <0)
{
perror("open");
return 1;
}
printf("fd: %d\n",fd);
while(1)
{
char buffer[64];
int n=read(fd,buffer,sizeof(buffer)-1);
if(n >0)
{
buffer[n] = 0;
printf("%s",buffer);
}
else if(n ==0)//说明读结束了
{
break;
}
}
close(fd);
return 0;
}
在OS接口层面,只认fd,即文件描述符。
cpp
[keda@VM-0-4-centos lesson19]$ ./myfile
stdin:0
stdout:1
stderr:2
fd: 3
fd: 4
fd: 5
fd: 6
[keda@VM-0-4-centos lesson19]$ cat myfile.c
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
umask(0);
printf("stdin:%d\n",stdin->_fileno);
printf("stdout:%d\n",stdout->_fileno);
printf("stderr:%d\n",stderr->_fileno);
printf("\n\n");
int fd1 =open("log1.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);
int fd2 =open("log2.txt",O_CREAT | O_WRONLY | O_APPEND,0666);
int fd3 =open("log3.txt",O_CREAT | O_WRONLY | O_APPEND,0666);
int fd4 =open("log4.txt",O_CREAT | O_WRONLY | O_APPEND,0666);
if(fd1 <0)
{
perror("open");
return 1;
}
if(fd2 <0 )exit(1);
if(fd3 <0 )exit(1);
if(fd4 <0 )exit(1);
printf("fd: %d\n",fd1);
printf("fd: %d\n",fd2);
printf("fd: %d\n",fd3);
printf("fd: %d\n",fd4);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
0,1,2叫做标准输入,标准输出,标准错误
cpp
FILE* fopen(const char* path,const char* mode);
FILE是C语言提供的结构体,是typedef(xxx;;;)FILE;(定义为FILE),这个FILE一定封装了文件fd。
操作系统访问文件,只认识文件描述符。
文件操作:
- 在类型层面文件对象FILE封装了文件描述符;
- 在接口层面:封装了选项
语言为什么要增加自己的可移植性?-->让更多人使用增加市场占有率。
在内核中是怎么管理文件的?

READ :用户输入文件描述符,假如是read,操作系统拿着fd文件描述符,找到文件描述符表中的数组,对应数组下标找到用户想要打开的文件,找到指针对应的文件,每个文件都有自己的struct file,其内部有一个文件缓冲区,然后磁盘将文件内容预加载到文件缓冲区,然后将文件缓冲区中的数据拷贝到用户层面的buffer缓冲区中,所以read函数本质是内核到用户空间的拷贝函数。
WRITE :根据文件描述符找到当前进程,根据进程找到当前的文件描述符表,根据文件描述符表找到数组下标,再找到对应的文件,把数据从buffer缓冲区拷贝到对应的文件缓冲区,然后操作系统再将数据从文件缓冲区中刷到磁盘中,这个本质也是拷贝。
EXECUTE :CPU不会和外设打交道,先把文件读到struct file的文件缓冲区,在内存中做修改,然后修改完再写回磁盘。
对文件内容做任何操作本质上都是先把文件加载到内核对应的文件缓冲区,加载的含义就是磁盘到内存的拷贝。
什么是重定向?

文件描述符的分配原则:最小的,没有被使用的作为新的fd给用户。
- 已经关闭了1,新打开
log.txt,根据文件描述符的分配原则,会分配给最小的,没有被使用的,作为新的fd给用户打开的新文件,所以此时log.txt对应的文件描述符就是1,在对应的1把新打开的log.txt的文件地址填进去。 printf是用户层面的只认stdout,stdout->1,前两行代码都是在内核操作系统层面操作的,用户层面完全不清楚操作系统内部的操作,这就导致stdout->1追索到的fd=1,此时fd指针指向的空间已经发生了改变,由原来的标准输出转为向新打开的文件中进行写入。- 这也就解释了为什么文件打开没有到显示器上,反而到了新打开的文件,做出了文件的写入。实际上是操作系统内部做出了重定向。
- 再次申明:重定向就是上层不变,用01234这样的数组下标,改的是文件描述符表中对应的数组下标里面的指针指向。
cpp
int dup2(int oldfd,int newfd);
cpp
on success,these system calls return the new descriptor.ono error,-1 is returned,and error is set appropritaely.
dup2()makes newfd be the copy of oldfd,closing newfd first if necessary.
那么在关闭1的条件下dup2是怎么传参的?
dup2是旧的fd的拷贝,重定向:把3里面的内容覆盖到1中,1的指针指向log.txt,fd是3,所以dup2(fd,1).
在自定义shell中加入重定向:
(重要片段)详情移步我的gitee官网链接:
cpp
void RedirCheck(char cmd[])
{
redir =NONE_REDIR;
filename.clear();
int start = 0;
int end = strlen(cmd)-1;
//"ls -a -l >> file.txt" | <输入重定向; >> 追加重定向; >输出重定向
while(end > start)
{
if(cmd[end] == '<')
{
cmd[end++] = 0;
TrimSpace(cmd,end);
redir = INPUT_REDIR;
filename = cmd+end;
break;
}
else if(cmd[end] == '>')
{
if(cmd[end-1] == '>')
{
//>>追加重定向
cmd[end-1] = 0;
redir =APPEND_REDIR;
}
else{
//>输出重定向
redir =OUTPUT_REDIR;
}
cmd[end++]= 0 ;
TrimSpace(cmd,end);
filename = cmd+end;
break;
}
else
{
end--;
}
}
}
追加问题:当一个进程在进行重定向的时候会不会影响它原本打开的文件?
不会影响。
进程替换会不会影响重定向的结果?
进程有自己的地址空间,页表,物理内存和磁盘,进程在替换的时候替换的是代码和数据,而重定向是在操作系统内部文件描述符表将数组下标对应的指针改变其指向,指向另一个打开的文件,是在操作系统内部进行的,并没有创建新的进程。
为什么我们新建普通文件,它的起始权限是666,因为shell新建文件的时候起始权限就是 666;为什么我们的命令会受到umask影响,因为open函数创建文件,会受到系统umask的影响。
有个问题:我们在改变1的指针指向,让1指向log.txt的时候,原本的标准输出去哪里了?
一个文件可不可以被多个进程打开? (可以)如果一个文件file.txt被多个进程打开,当其中一个进程关闭这个文件file.txt,释放struct file ,不会影响文件file.txt本身,这就涉及到struct file 内部有一个int ref cnt;引用计数,会记录有多少个进程打开了这个文件file.txt,
在做dup2的时候,这个1不再指向标准输出,不会影响系统,反而系统会识别到拷贝,从而将标准输出的引用计数--,本身就打开了一次,--就是变为0次数,直接释放掉关掉。
