13 Linux下的基础IO_typedef struct _io_file file

pathname:要打开文件的名字

flags:打开文件的方式
O_RDONLY :只读方式
O_WRONLY :只写方式
O_RDWR :读写方式
可选项:
O_TRUNC :截断文件(清空文件内容)
O_CREAT :文件不存在则创建文件
O_APPEND :追加的方式
O_EXCL | O_CREAT :如果文件存在,则打开文件失败

mode:创建文件时的权限(八进制,比如0664等)

返回值:
成功:新打开的文件描述符
失败:-1


可选项的原理:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/560fff3ff5354ee183e4340365beda5b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_11,color_FFFFFF,t_70,g_se,x_16)


所有**选项对应的数在转成二进制后只有一个比特位为1且为1的二进制位是不同的**,所以传多个选项的时候实际上是对传入的数进行按位或处理。最后传给flags的是一个数,这个数有一个或多个比特位为1。


以下面的代码为例:


![在这里插入图片描述](https://img-blog.csdnimg.cn/4cb683d976194d0ea096bebdf551d324.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_14,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/a87a614338844a6694bdc2ec2c6b727c.png)


### 2.2.文件描述符fd(file descriptor)


可以看到打开成功后各自的返回值是3 4 5。。。  
 打开失败返回值则为-1  
 这些返回值叫做**文件描述符**,在系统层面是一个整数,从0开始。  
 其中:0为标准输入,1为标准输出,2为标准错误,这三个默认被打开


文件描述符的本质是数组下标,操作系统为每一个进程维护了一个文件描述符表,该表的索引值都从从0开始的,索引值都有一个指针指向对应的文件:


![在这里插入图片描述](https://img-blog.csdnimg.cn/c2bbbf4a849a4bea9b26303d1a5a10ad.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5q-P5aSp6YO95Zyo5YaZYnVn44CB,size_20,color_FFFFFF,t_70,g_se,x_16)


一个进程如何通过文件描述符找到文件:当进程执行`write(4,"hello",5)`时,进程先找到自己的PCB,PCB中包含文件描述符表指针,通过这个指针找到文件描述符表(struct files\_struct),然后通过索引下标4找到对应的文件。


所以如果将文件描述符为0(标准输入)的关掉,则分配的下标为0:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/659e834b26cc48b883299fb0f5542ffe.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_14,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/cf6a5686d0ba487a9eff586f311be505.png)


可以看出文件描述符的分配规则为:在files\_struct数组当中,找到**当前没有被使用的最小的一个下标,作为新的文件描述符**。


另外,**要记得关闭文件描述符,否则会造成文件描述符泄漏,因为文件描述符表(数组)是有上限的。**


### 2.3.补充内容–函数指针访问硬件


不同的硬件访问方式是不一样的,对于外设访问的方式一般是读和写,但是实现代码是不一样的,此时进程就可以通过**函数指针的方式**来指向它们各自的实现方法:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/fdfeb983cfc44cc5adfd1d888523a937.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAcXFfNTI2NzA0Nzc=,size_20,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/d34f1bc889a04046a4a84d964eb708ed.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAcXFfNTI2NzA0Nzc=,size_19,color_FFFFFF,t_70,g_se,x_16)


### 2.4.重定向的实现原理


重定向有两个操作符分别是`>`(格式化重定向)和`>>`(追加重定向),其实现原理就是操作系统内核把标准输出(1)关掉(**注意:标准错误没有关闭**),然后打开对应的文件,此时该文件的下标就是1,应用层默认输入到下标为1的文件中,所以就输入到该文件中了。至于追加重定向则是在打开的时候加上选项`O_APPEND`。


如果想把标准输出和标准错误都重定向,可以这样写`./文件名 > 文件 2>&1`,`2>&1`的作用是把文件描述符1的内容拷贝到文件描述符2里面,1已经指向了新文件,所以2也指向这个新文件。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/8c4cc46ce4af4108b2a223bb895b5cd6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_15,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/754f8e5cf0644784abf1ad3d7bd1d2a7.png)


![在这里插入图片描述](https://img-blog.csdnimg.cn/cf955b54f8764b31acf2a0dd1c172fb7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_15,color_FFFFFF,t_70,g_se,x_16)




---


## 三、FILE


文件指针中那个`FILE`本质是一个结构体,这个结构体中一定是包含文件描述符(fd)的,因为**访问文件都是通过fd访问的**。


下面是FILE的代码:  
 `typedef struct _IO_FILE FILE; 在/usr/include/stdio.h`



struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it’s too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};


### 3.1. 行缓冲和全缓冲


对于下面的代码:



#include<stdio.h>
#include<string.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
const char *msg0=“hello printf\n”;
const char *msg1=“hello fwrite\n”;
const char *msg2=“hello write\n”;
printf(“%s”, msg0);
fwrite(msg1, strlen(msg0), 1, stdout);
write(1, msg2, strlen(msg2));
fork();
return 0;
}


![在这里插入图片描述](https://img-blog.csdnimg.cn/911dc0b2130246cdaa53a08d7c961e43.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_16,color_FFFFFF,t_70,g_se,x_16)


如果对结果重定向,`printf` 和 `fwrite`(库函数)都输出了2次,而`write` 只输出了一次(系统调用)。


刷新一般有三种方式: 第一个是对应系统调用而言,后两个是对应库函数而言


* 无缓冲: 对数据进行操作时,不需要经过缓冲区,直接刷新在文件或显示器上。系统调用(wirte)没有附带缓冲区。
* 行缓冲: 遇到\n就对缓冲区的数据进行刷新,这是对显示器而言。
* 全缓冲: **缓冲区满了**或者**进程退出**时才对数据进行刷新,这是对文件采取的刷新策略。


**重定向影响了缓冲方式**:  
 显示器采用的是行缓冲(遇到\n就刷新)  
 文件采用的则是全缓冲(缓冲区数据写满才刷新)


没有重定向之前是往显示器写,而重定向则是往文件中写。


* 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
* printf、fwrite库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式**由行缓冲变成了全缓冲**。
* **全缓冲进程退出之后,会统一刷新,写入文件当中**。
* fork的时候,父子数据会发生写时拷贝,数据被暂存在用户级缓冲区中,因此子进程也就有了同样的一份数据,即产生两份数据。
* 由于父子进程的缓冲区中有两份一样的数据,所以会刷新两份。
* write 没有刷新两份,说明没有所谓的用户级缓冲区。


我们这里所说的缓冲区,都是C标准库提供的**用户级缓冲区**,`printf`和`fwrite`是库函数, `write`是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,因此`write` 没有用户级缓冲区,而 `printf`和`fwrite`有。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/d25542d4e1ed4c7aa715a81b6f5ccf58.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5q-P5aSp6YO95Zyo5YaZYnVn44CB,size_17,color_FFFFFF,t_70,g_se,x_16)


另外`fflush`函数也在C标准库中,将用户级的缓冲区往系统中刷新:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/321bd0e8337c4a11aefb75a53ecba2a5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_20,color_FFFFFF,t_70,g_se,x_16)


### 3.2. 在刷新前关闭文件描述符


前面说过,文件采用的是全缓冲,全缓冲进程退出之后,会统一刷新,写入文件当中。所以,在程序退出之前,数据一直都在用户级缓冲区中,如果在文件退出前关闭文件描述符,则不会写入文件中。



#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
close(1);
int fd = open(“log.txt”, O_CREAT|O_WRONLY);

if (fd < 0){
	perror("open file fail");
	exit(-1);
}

const char\* msg3 = "hello fwrite\n";
printf("hello printf\n");
fprintf(stdout,"hello fprintf\n");
fwrite(msg3, strlen(msg3), 1, stdout);

close(fd);

return 0;

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/82c05c0ae2364e39a93550ccb6ea716c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5q-P5aSp6YO95Zyo5YaZYnVn44CB,size_20,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/997102b32d1b41c0a9d92214bcd49f10.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5q-P5aSp6YO95Zyo5YaZYnVn44CB,size_18,color_FFFFFF,t_70,g_se,x_16)




---


## 四、dup 重定向


上面的重定向是将标准输出的fd关掉,然后打开重定向的文件。  
 而使用dup2函数就不需要关掉标准输出:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/e44f437a33fa443ea0ffeef51d1d7b2f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_20,color_FFFFFF,t_70,g_se,x_16)


dup函数的作用是,返回一个新的文件描述符(可用文件描述符的最小值)newfd,并且新的文件描述符newfd指向oldfd所指向的文件表项。  
 比如:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/cd66ff3b101b4dc1ac42390c399816a6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_17,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/ef4788821f0446ad861c012d4ff1d8a1.png)


文件描述符为1的文件通过dup重定向到文件描述符1上,那么也就相当于文件描述符3对应的文件也是显示器文件,那么向文件描述符3进行write,最终结果也会打印在显示器上。


### 4.1.使用dup2 完成重定向


dup2有两个参数`oldfd`和`newfd`,`newfd`是`oldfd`的拷贝,将`newfd`重定向到`oldfd`,当整个函数调用成功后,会将`newfd`关掉,然后让`newfd`指向`oldfd`。总之,dup2函数的作用就是让`newfd`重定向到`oldfd`所指的文件表项上,如果出错就返回-1,否则返回的就是`newfd`。所以如果想让原本输出到显示器上的数据重定向到文件中,可以这样写:  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/58c4ce1a2fa74bccb4b0f6c53407e10f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_19,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/9631321de1df4e97ad056581b52ed911.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_14,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/16d362625f9b459782e1887ce200e3f8.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5q-P5aSp6YO95Zyo5YaZYnVn44CB,size_20,color_FFFFFF,t_70,g_se,x_16)


### 4.2.重定向恢复


在进行重定向后,如果想要恢复到重定向之前的状态,可以在重定向之前用dup函数保留该文件描述符对应的文件表项,然后在需要恢复重定向的时候使用dup2重定向到原来的文件表项,以重定向后恢复标准输出为例,如下所示:


![在这里插入图片描述](https://img-blog.csdnimg.cn/e24d601738104e4ba902b4f9947ed261.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_20,color_FFFFFF,t_70,g_se,x_16)


![在这里插入图片描述](https://img-blog.csdnimg.cn/29ea4a015321460faa00e82314bb3b4c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57K-6Ie055qE54GwKD5fPCk=,size_14,color_FFFFFF,t_70,g_se,x_16)


### 4.3.在my\_shell中添加重定向功能


其实现原理是在子进程进行程序替换之前将标准输出(1)重定向到打开的文件中。



#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define SIZE 256
#define NUM 16 //命令行参数的个数

void redirect(char* cmd)
{
int fd=-1;
int redirect_count=0;//记录>的个数
char*file=NULL;
char*ptr=cmd;
while(*ptr)
{
if(*ptr==‘>’)
{
*ptr++=‘\0’;
redirect_count++;
if(*ptr==‘>’)
{
*ptr++=‘\0’;
redirect_count++;
}
while(*ptr!=‘\0’&&isspace(*ptr))//跳过空格
{
ptr++;
}
file=ptr;//找到文件名
while(*ptr!=‘\0’&&!isspace(*ptr))//清空文件名后面的空格
{
ptr++;
}
*ptr=‘\0’;

  if(redirect_count==1){
    //>
    fd=open(file,O_CREAT|O_TRUNC|O_WRONLY,0644);
  }
  else if(redirect_count==2){
    //>>
    fd=open(file,O_CREAT|O_APPEND|O_WRONLY,0644);   
  }
  else{
    //do nothing!
  }
  //文件已经打开,用dup2重定向
  dup2(fd,1);
  close(fd);
}//end if
else if(\*ptr=='<')

{
//和重定向>类似
}
ptr++;
}

}
int main()
{
char cmd[SIZE];
const char* cmd_line="[my_shell@VM-0-16-centos ~]# ";
while(1)
{
cmd[0]=0;

printf("%s",cmd_line);
fgets(cmd,SIZE,stdin);
cmd[strlen(cmd)-1]='\0'; 
pid\_t id=fork();
if(id<0)
{
  perror("fork error!\n");
  continue;
}
if(id==0)//子进程
{
  redirect(cmd);
  char\*args[NUM];//将命令字符串分割
  args[0]=strtok(cmd," ");
  int i=1;
    do    
    {    
      args[i]=strtok(NULL," ");    
      if(args[i]==NULL)    
      {    
        break;    
      }    
      i++;    
    }while(1);   
 execvp(args[0],args);
 exit(1);
 }
int status=0;
pid\_t ret=waitpid(id,&status,0);
if(ret>0)
{
  printf("status code:%d\n",(status>>8)&0xFF);
}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值