- 练习open/read/write/close等文件相关系统调用接口,纵向对比fd与FILE结构体
- 对之前编写的自主shell进行修改,使其支持输入/输出/追加重定向
<1>.open函数:
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数介绍:
pathname:要打开或创建的目录文件。
flags:打开文件时,可以传入多个参数选项。
参数:
O_RONLY:只读打开。
O_WRONLY:只写打开。
O_RDWR:读写打开
前面三个常量,只能指定一个。
O_CREAT:若文件不存在,则创建它。需要用mode设置权限。
O_APPEND:追加写。
mode:当文件不存在,需要open创建文件,mode表示默认权限 ,否则使用两个参数的open
返回值:
成功:返回新打开的文件描述符。
失败:返回-1。
<2>.write函数:
函数原型:size_t write(int fd,const void *buf,size_t nbyte)
参数:
fd:文件描述符。
buf:指定的缓冲区。即要写入的指针。
nbyte:要写入文件指定的字节数。
返回值:
写入文档成功,返回写的字节数。
错误时返回-1。
1 #include<stdio.h>
2 #include<string.h>
3 #include<fcntl.h>
4 #include<unistd.h>
5 #include<sys/types.h>
6 #include<sys/stat.h>
7
8 int main()
9 {
10 umask(0);
11 int fd=open("myfile1",O_WRONLY|O_CREAT,0644);//open函数,0644代表权限
12 if(fd<0){
13 perror("open");
14 return 1;
15 }
16 int count=5;
17 const char*msg="hello world\n";
18 int len=strlen(msg);
19 while(count--){
20 write(fd,msg,len+1);
21 }
22 close(fd);
23 return 0;
24 }
<3>.read函数:
函数原型:size_t read(int fd,const void *buf,size_t nbyte)
参数:
fd:文件描述符。
buf:指定的缓冲区。
nbyte:要读取文件指定的字节数。
返回值:
成功时返回读取到的字节数(为零表示读到文件描述符),此返回值受文件剩余字节数限制,当返回值小于指定的字节数(比如已经接近文件结尾,或者正在从管道或者终端读取数据,或者read()被信号中断),发生错误时返回-1。
<4>.close函数:
函数原型:
int close(int fd);
该函数只有一个参数,参数表示文件标识符,
返回值:
返回0表示成功
返回-1表示失败
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<fcntl.h>
5 #include<unistd.h>
6 #include<string.h>
7
8 int main()
9 {
10 int fd=open("myfile1",O_RDONLY);
11 if(fd<0){
12 perror("open");
13 return 1;
14 }
15 const char *msg="hello world\n";
16 char buf[1024];//定义缓冲区
17 while(1){
18 ssize_t s=read(fd,buf,strlen(msg)+1);
19 if(s>0){
20 printf("%s",buf);
21 }else
22 {
23 break;
24 }
25 }
26 close(fd);
27 return 0;
28 }
纵向对比fd与FILE结构体 :
文件描述符(fd)
我们都知道在Linux下一切皆文件。当然设备也不例外,如果要对某个设备进行操作,就不得不打开此设备文件,打开文件就会获得该文件的文件描述符fd( file discriptor), 它就是一个很小的整数,每个进程在PCB中都有一个指针*files,指向一张表files_struct,表中包含一个指针数组,每个元素都是指向打开文件的指针。文件描述符就是这个表的索引,即为该数组的下标。
文件描述符分配规则:在 file_struct数组中,找到当前没有被使用的最小下标,作为一个新的文件描述符。
下面来举例证明:
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
int main()
{
//close(0);
//close(1);
int fd=open("myfile",O_RDONLY);
if(fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
close(fd);
return 0;
}
当close(0)时,输出结果为fd:0。
分析图如下:
注意:一般情况下,file_struct数组下标的 0,1,2 分别被标准输入(键盘),标准输出(显示器),标准错误(显示器)占用,只有当调用close()函数,关闭掉某个下标 ,fd才会是0或1,或2.若没有被关闭的,fd会是3。
其次,由于数组下标没有负数,所以fd也不会是负数。
所以,通过上面的介绍,可以知道,fd的本质其实就是一个表示数组下标的小整数。
FILE结构体
1、FILE结构体中的成员
缓冲区基址,缓冲区当前指针,缓冲区大小,缓冲区剩余字节数,文件读写方式等。
struct FILE
{
char *_ptr;//文件输入的下一个位置
int _cnt;//当前缓冲区的相对位置
char *_base;//指基础位置(文件的起始位置)
int _flag;//文件标志
int _file;//文件的有效性验证
int _charbuf;//检查缓冲区状况,如果缓冲区则不读取
int _bufsiz;//文件的大小
char *_tmpfname;//临时文件名
};
2、(FILE*)文件指针
文件指针指向进程用户区中一个被叫做FILE结构的结构数据。FILE结构包括一个缓冲区和一个文件描述符 。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。
通常,任何程序运行起来之后都会默认的打开三个标准输入流(stdin:键盘),标准输出流(stdout:显示器),标准错误流(stderr:显示器)。
简单来说,fd是一个一个表示数组下标的小整数。FILE为一个结构体,FILE* 为文件指针,指向文件。
文件描述符与文件指针(即指向FILE结构体的指针)区别:
fd是一个表示数组下标的整数,在打开文件的过程中,起到一个索引的作用,进程通过PCB中的文件描述符找到所指向的文件指针file*。
open:文件描述符的操作(如:open)返回的是一个文件描述符(int fd),内核会在每个进程空间中维护一个文件描述符表,此表中的文件描述符来引用。
fopen:流(如:fopen)返回的是一个文件指针(即指向FILE结构体的指针),FILE结构是包含有文件描述符的,fopen可以看做是open(fd直接操作的系统调用)的封装,它的优点是带有I/O缓存。
重定向
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<fcntl.h>
5 #include<stdlib.h>
6
7 int main()
8 {
9 close(1);
10 int fd=open("myfile",O_WRONLY|O_CREAT,0644);
11 if(fd<0){
12 perror("open");
13 return 1;
14 }
15 printf("fd:%d\n",fd);
16 fflush(stdout);
17 close(fd);
18 exit(0);
19 }
本该输出到显示器的内容,却输出到了myfile文件中。这种现象就叫做输出重定向。
常见的重定向有
> 会删除原来的内容,写当下输出的内容
>> 直接在原文后面追加新内容
< 输入重定向
上面结果解释:
printf函数是C库当中的IO函数,本该往stdout输出,在执行了close(1),当stdout在底层访问文件时,找到的还是fd:1.但此时fd:1所表示的内容,已经变成了myfile的地址,不再是显示器文件的地址,凡是应该写进标准输出文件的内容,全部被改写到myfile文件中