目录
一、文件操作
1.1 C语言文件操作
1.2 文件 系统调用接口
1.2.1 open/close函数
1.2.2 write/read函数
二、进程与文件
2.1 0&1&2 文件描述符
2.2 C语言FILE
2.3 (OS管理&进程找到) 被打开文件方法
2.3.1 struct file 描述文件属性(OS管理文件)
2.3.2 PCB成员 struct files_struct * files(进程找到文件)
三、重定向
3.1 文件描述符分配原则
3.2 重定向(close关闭法)
3.3 dup2()函数重定向
3.4 重定向指令(> < >>)
3.5 自我实现shell_pro 实现文件重定向指令功能
1. 文件=内容 + 属性
2. 空文件在磁盘也占空间!
3. 文件操作对象: 内容和属性
4.标定一个文件,必须使用:文件路径 + 文件名
5.一个文件如果没有被打开,那么我们不能对文件进行操作!文件可以由进程代码需要+OS 打开文件!所以文件操作的本质是进程和被打开文件的关系。
打开文件方式:
当用除带a的写入方式,C语言都会先清空文件!
文件函数:
另一篇C文件函数博客:C语言文件重点知识总结(冲冲冲)_不到满级不改名的博客-CSDN博客_c语言文件知识点总结
OS完成文件的打开、写入、关闭!所有的函数都避免不开操作系统的系统调用!我们之前的C或其他编程语言文件函数都封装了系统调用接口!
int main()
{
//设置掩码
umask(0);
//打开文件,返回值是文件标识符
int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fd=%d\n",fd);
//关闭文件
close(fd);
return 0;
}
第一个参数是文件名,默认创建在当前路径下,也可以指定!
第二个参数是文件的操作形式!
我们注意到第二个参数形式是以一种大写的英文字母组合形成的变量按位与传过去的!这些变量本质是宏定义的整数!它们如何判别操作? 本质是通过控制位图的0/1 来判断是否拥有某项功能!
第三个参数是文件的权限设置。掩码为0,0666代表文件权限为 rw-rw-rw!
返回值是文件描述符,一个文件一个文件描述符区分彼此!
文件有文本类和二进制类,语言决定了一个文本的内容类型! OS系统不管你的类型,它默认都是二进制类,他只关心你要写入的字符个数!
#include
#include
#include
#include
#include
#include
int main()
{
//close(1);
//设置掩码
umask(0);
//打开文件,返回文件标识符
int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fd=%d\n",fd);
//向文件标识符文件写入
char buffer[64];
int cnt=5;
while(cnt)
{
//格式化向指定流中写入
sprintf(buffer,"%s%d\n","hello Linux! cnt=",cnt--);
//这里强调一下\0是C语言字符串结束的标识并不是文件!所以写入文件的字符个数不要带上\0!
write(fd,buffer,strlen(buffer));
}
//关闭文件
close(fd);
return 0;
}
读文件内容:
#include
#include
#include
#include
#include
#include
int main()
{
//close(1);
//设置掩码
umask(0);
int fd=open("log.txt",O_RDONLY);
printf("fd=%d\n",fd);
//读取文件内容
char get_buffer[1024];
int num=read(fd,get_buffer,sizeof(get_buffer)-1);
printf("num=%d\n",num);
//文件结尾不是以\0结束,读完文件后,我们要在字符串末尾加上\0!
if(num>0) get_buffer[num]=0;
printf("%s",get_buffer);
//关闭文件
close(fd);
return 0;
}
Linux进程默认情况下会有3个默认打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误
0,1,2对应的物理设备一般是:键盘,显示器,显示器!
#include
#include
#include
#include
#include
int main()
{
char buf[1024];
//从键盘读
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
//写入显示器
write(1, buf, strlen(buf));
//写入显示器
write(2, buf, strlen(buf));
}
return 0;
}
我们的系统调用只认识文件描述服,任何程序语言文件操作都离不开系统调用!C语言的FILE是一个结构体,它的结构体成员一定包含了文件描述符!
C语言FILE 类型的三种stdin(标准输入键盘) stdout(标准输出显示器) stderr(标准错误->显示器) 分别包含了0,1,2文件描述符!
下面我们来测试一下:
//#...
int main()
{
printf("stdin->fd=%d\n",stdin->_fileno);
printf("stdout->fd=%d\n",stdout->_fileno);
printf("stderr->fd=%d\n",stderr->_fileno);
return 0;
}
键盘、显示器也是文件!在我们眼中,我们通过文件的描述符来表示键盘、显示器!
我们可以初步认识到Linux一切皆文件!
文件操作本质:进程和被打开文件的关系!
我们可以同时打开多个文件,文件如何知道自己的身份?进程如何知道哪些文件是自己打开的?
file结构体中包含了大部分文件属性!其中里面包含了该文件的文件描述符!所以OS通过管理结构体file管理文件!
每一个进程PCB中都有一个指向结构体files的指针!files内有一个struct file* 类型的数组,数组元素指向一个个描述文件属性的结构体file,这样进程就能找到自己打开的文件!
文件描述符会遍历struct file*数组,循序寻找数组中下标小且没有被占用的fd!
下面我来测试一下:
//test
#include
#include
#include
#include
int main()
{
int fd = open("log.txt", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
//myfile
int main()
{
//关闭描述符为0的文件
close(0);
//打开一个文件
int fd = open("log.txt", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);//分配原则,最小且没被占用的下标为0!
close(fd);
return 0;
}
#include
#include
#include
#include
#include
#include
int main()
{
//关闭标准输出(显示器)
close(1);
//打开新文件
int fd=open("myfile.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
//printf:向标准输出打印内容
printf("fd=%d\n",fd);
//向标准输出打印
printf("hello Linux!");
return 0;
}
代码结果是内容没有写到显示器上而是写入了新打开的文件!这种改变标准输入\输出的现象我们称文件重定向!
重定向图片描述:
图片描述:
代码测试输出重定向:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd=open("myfile.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
//打开失败
if(fd<0)
{
perror("open");
exit(-1);
}
//重定向
dup2(fd,1);
printf("hello Linux!\n");
fprintf(stdout,"fa=%d\n",fd);
fflush(stdout);
return 0;
}
1. > 和 < 分别代表重定向的方向,是从左到右还是从右到左
2. >> 和 << 代表追加,也就是不改变重定向目标文件原有的内容,追加在后面。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define NUM_SIEZ 1024
char Command_Line[NUM_SIEZ];
#define OPT_NUM 64
char* myargv[OPT_NUM];
int last_sigcode=0;
int last_exit_code=0;
#define NO_REDIR 0
#define IN_REDIR 1
#define OUT_REDIR 2
#define APPEND_REDIR 3
//宏函数判断字符是否为空格
#define check_space(start) do{while((*start)==' ') start++;}while(0)
int redirType=0;
char* redir_file=NULL;
void check_command()
{
char* start=Command_Line;
char* end=Command_Line+strlen(Command_Line);
while(start < end)
{
//输入重定向
if(*start=='<')
{
redirType=IN_REDIR;
*start='\0';
start++;
check_space(start);
redir_file=start;
}
//输出重定向
else if(*start=='>')
{
*start='\0';
start++;
//追加
if(*start=='>'){
redirType=APPEND_REDIR;
start++;
check_space(start);
redir_file=start;
}
//非追加
else{
redirType=OUT_REDIR;
start++;
check_space(start);
redir_file=start;
}
}
else
{
start++;
}
}
}
int main()
{
while(1)
{
printf("[用户名@主机名 当前路径]$ ");
fflush(stdout);
char* str=fgets(Command_Line,sizeof(Command_Line)-1,stdin);//从标准输入获取字符串
assert(str!=NULL);
//"abcde\n" 让最后一个字符为NULL(0)!为后面命令数组结尾获取0!
Command_Line[strlen(Command_Line)-1]=0;
//检查是否为重定向!
check_command();
//以空格为分隔单位,获取命令与命令选项!
myargv[0]=strtok(Command_Line," ");
int i=1;
if(strcmp(myargv[0],"ls")==0)
{
myargv[i++]="--color=auto";
}
while(myargv[i++]=strtok(NULL," "));
if(myargv[0]!=NULL && myargv[1]!=NULL && strcmp(myargv[0],"cd")==0)
{
chdir(myargv[1]);
continue;
}
if(myargv[0]!=NULL && myargv[1]!=NULL && strcmp(myargv[0],"echo")==0)
{
if(strcmp(myargv[1],"$?")==0)
{
printf("sigcode=%d exit_code=%d\n",last_sigcode,last_exit_code);
continue;
}
else
{
printf("%s\n",myargv[1]);
last_exit_code=0;
last_sigcode=0;
continue;
}
}
//创建子进程
pid_t id=fork();
//重定向
if(id==0)
{
switch(redirType)
{
case NO_REDIR: break;
case IN_REDIR:
{
int fd=open(redir_file,O_RDONLY,0666);
if(id<0)
{
perror("open");
exit(errno);
}
dup2(fd,0);
break;
}
case OUT_REDIR:
case APPEND_REDIR:
{
int flag=O_WRONLY|O_CREAT;
if(redirType==OUT_REDIR)
{
flag|=O_TRUNC;
}
else
{
flag|=O_APPEND;
}
int fd=open(redir_file,flag,0666);
if(id<0)
{
perror("open");
exit(errno);
}
dup2(fd,1);
break;
}
}
execvp(myargv[0],myargv);
exit(1);//进程替换失败
}
else if(id>0)
{
int status=0;
int ret=waitpid(id,&status,0);
assert(ret>0);
//恢复标准输出、输入
redirType=0;
last_sigcode=status&0X7F;
last_exit_code=(status>>8)&0XFF;
}
else
{
printf("creat child process error!\n");
}
}
return 0;
}
子进程创建的时候,子进程会拷贝父进程的文字描述符数组并指向父进程打开的文件!进程具有独立性!子进程文件重定向并不影响父进程!也就是说文件只有一个,指向文件的指针可以有多数!
画图效果:我们改变子进程的重定向并不影响父进程!